From 2e40a60e847d6c128af23e24ea7a8efebd2427da Mon Sep 17 00:00:00 2001 From: yorah Date: Thu, 11 Apr 2013 17:29:05 +0200 Subject: [PATCH 001/367] status: fix handling of filenames with special prefixes Fix libgit2/libgit2sharp#379 --- src/attr_file.c | 47 ++++++++++++++++++++++++++++++-------- src/attr_file.h | 8 ++++++- src/ignore.c | 2 +- src/pathspec.c | 2 +- tests-clar/status/ignore.c | 34 +++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/attr_file.c b/src/attr_file.c index 85cd87624..93f6df1d9 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -85,7 +85,7 @@ int git_attr_file__parse_buffer( } /* parse the next "pattern attr attr attr" line */ - if (!(error = git_attr_fnmatch__parse( + if (!(error = git_attr_fnmatch__parse_gitattr_format( &rule->match, attrs->pool, context, &scan)) && !(error = git_attr_assignment__parse( repo, attrs->pool, &rule->assigns, &scan))) @@ -337,23 +337,16 @@ void git_attr_path__free(git_attr_path *info) * GIT_ENOTFOUND if the fnmatch does not require matching, or * another error code there was an actual problem. */ -int git_attr_fnmatch__parse( +int git_attr_fnmatch__parse_gitattr_format( git_attr_fnmatch *spec, git_pool *pool, const char *source, const char **base) { - const char *pattern, *scan; - int slash_count, allow_space; + const char *pattern; assert(spec && base && *base); - if (parse_optimized_patterns(spec, pool, *base)) - return 0; - - spec->flags = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE); - allow_space = (spec->flags != 0); - pattern = *base; while (git__isspace(*pattern)) pattern++; @@ -375,6 +368,39 @@ int git_attr_fnmatch__parse( pattern++; } + if (git_attr_fnmatch__parse_shellglob_format(spec, pool, + source, &pattern) < 0) + return -1; + + *base = pattern; + + return 0; +} + +/* + * Fills a spec for the purpose of pure pathspec matching, not + * related to a gitattribute file parsing. + * + * This will return 0 if the spec was filled out, or + * another error code there was an actual problem. + */ +int git_attr_fnmatch__parse_shellglob_format( + git_attr_fnmatch *spec, + git_pool *pool, + const char *source, + const char **base) +{ + const char *pattern, *scan; + int slash_count, allow_space; + + assert(spec && base && *base); + + if (parse_optimized_patterns(spec, pool, *base)) + return 0; + + allow_space = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0; + pattern = *base; + slash_count = 0; for (scan = pattern; *scan != '\0'; ++scan) { /* scan until (non-escaped) white space */ @@ -609,6 +635,7 @@ static void git_attr_rule__clear(git_attr_rule *rule) /* match.pattern is stored in a git_pool, so no need to free */ rule->match.pattern = NULL; rule->match.length = 0; + rule->match.flags = 0; } void git_attr_rule__free(git_attr_rule *rule) diff --git a/src/attr_file.h b/src/attr_file.h index d8abcda58..8ca7e4eb7 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -115,7 +115,13 @@ extern uint32_t git_attr_file__name_hash(const char *name); * other utilities */ -extern int git_attr_fnmatch__parse( +extern int git_attr_fnmatch__parse_gitattr_format( + git_attr_fnmatch *spec, + git_pool *pool, + const char *source, + const char **base); + +extern int git_attr_fnmatch__parse_shellglob_format( git_attr_fnmatch *spec, git_pool *pool, const char *source, diff --git a/src/ignore.c b/src/ignore.c index 17779522c..dae974b6e 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -49,7 +49,7 @@ static int parse_ignore_file( match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; - if (!(error = git_attr_fnmatch__parse( + if (!(error = git_attr_fnmatch__parse_gitattr_format( match, ignores->pool, context, &scan))) { match->flags |= GIT_ATTR_FNMATCH_IGNORE; diff --git a/src/pathspec.c b/src/pathspec.c index d4eb12582..9dee55ea1 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -78,7 +78,7 @@ int git_pathspec_init( match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; - ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); + ret = git_attr_fnmatch__parse_shellglob_format(match, strpool, NULL, &pattern); if (ret == GIT_ENOTFOUND) { git__free(match); continue; diff --git a/tests-clar/status/ignore.c b/tests-clar/status/ignore.c index 2d3898ba4..6c17d2c39 100644 --- a/tests-clar/status/ignore.c +++ b/tests-clar/status/ignore.c @@ -459,3 +459,37 @@ void test_status_ignore__automatically_ignore_bad_files(void) cl_git_pass(git_status_should_ignore(&ignored, g_repo, "path/whatever.c")); cl_assert(!ignored); } + +void test_status_ignore__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void) +{ + status_entry_single st; + char *test_cases[] = { + "!file", + "#blah", + "[blah]", + "[attr]", + "[attr]blah", + NULL + }; + int i; + + for (i = 0; *(test_cases + i) != NULL; i++) { + git_buf file = GIT_BUF_INIT; + char *file_name = *(test_cases + i); + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_buf_joinpath(&file, "empty_standard_repo", file_name)); + cl_git_mkfile(git_buf_cstr(&file), "Please don't ignore me!"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); + cl_assert(st.count == 1); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&st.status, repo, file_name)); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_sandbox_cleanup(); + git_buf_free(&file); + } +} From 2da72fb21c1719b34a312dba3ebb7691032cfaa9 Mon Sep 17 00:00:00 2001 From: yorah Date: Fri, 14 Jun 2013 12:10:13 +0200 Subject: [PATCH 002/367] fileops: fix invalid read --- src/fileops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fileops.c b/src/fileops.c index 02f48e120..ae240fcd2 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -320,7 +320,7 @@ int git_futils_mkdir( min_root_len = git_path_root(make_path.ptr); if (root < min_root_len) root = min_root_len; - while (make_path.ptr[root] == '/') + while (root >= 0 && make_path.ptr[root] == '/') ++root; /* clip root to make_path length */ From 519072c9bfedeb2b6c4a8ad49739e5563813c3c4 Mon Sep 17 00:00:00 2001 From: yorah Date: Fri, 14 Jun 2013 14:02:00 +0200 Subject: [PATCH 003/367] diff: fix warning --- tests-clar/diff/rename.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index fd31a3859..224945e60 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -825,6 +825,8 @@ int test_names_expected(const git_diff_delta *delta, float progress, void *p) { struct rename_expected *expected = p; + GIT_UNUSED(progress); + cl_assert(expected->idx < expected->len); cl_assert_equal_i(delta->status, GIT_DELTA_RENAMED); From 3425fee63773813a48f596637609efaa36428713 Mon Sep 17 00:00:00 2001 From: yorah Date: Mon, 17 Jun 2013 14:27:34 +0200 Subject: [PATCH 004/367] util: git__memzero() tweaks On Linux: fix a warning message related to the volatile qualifier (cast) On Windows: use SecureZeroMemory() On both, inline the call, so that no entry point can lead back to this "secure" memory zeroing. --- src/util.c | 9 --------- src/util.h | 12 +++++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/util.c b/src/util.c index 1d084daa8..da15a039d 100644 --- a/src/util.c +++ b/src/util.c @@ -722,12 +722,3 @@ void git__insertsort_r( if (freeswap) git__free(swapel); } - -void git__memzero(volatile void *data, size_t size) -{ - volatile uint8_t *scan = data; - uint8_t *end = scan + size; - - while (scan < end) - *scan++ = 0x0; -} diff --git a/src/util.h b/src/util.h index 0de466677..1ef9e65b5 100644 --- a/src/util.h +++ b/src/util.h @@ -325,6 +325,16 @@ extern size_t git__unescape(char *str); * Safely zero-out memory, making sure that the compiler * doesn't optimize away the operation. */ -extern void git__memzero(volatile void *data, size_t size); +GIT_INLINE(void) git__memzero(void *data, size_t size) +{ +#ifdef _MSC_VER + SecureZeroMemory((PVOID)data, size); +#else + volatile uint8_t *scan = (volatile uint8_t *)data; + + while (size--) + *scan++ = 0x0; +#endif +} #endif /* INCLUDE_util_h__ */ From 0525fb7ef3cb347e20db8582dcfc9c4c67bd9267 Mon Sep 17 00:00:00 2001 From: yorah Date: Mon, 17 Jun 2013 14:31:14 +0200 Subject: [PATCH 005/367] cred: deploy git__memzero to clear memory holding a password --- src/transports/cred.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transports/cred.c b/src/transports/cred.c index 4916c6e18..ba5de6e93 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -17,7 +17,7 @@ static void plaintext_free(struct git_cred *cred) git__free(c->username); /* Zero the memory which previously held the password */ - memset(c->password, 0x0, pass_len); + git__memzero(c->password, pass_len); git__free(c->password); memset(c, 0, sizeof(*c)); @@ -73,7 +73,7 @@ static void ssh_keyfile_passphrase_free(struct git_cred *cred) if (c->passphrase) { /* Zero the memory which previously held the passphrase */ - memset(c->passphrase, 0x0, pass_len); + git__memzero(c->passphrase, pass_len); git__free(c->passphrase); } From 2ad7a4dc92a3e604d4ef52899c88c19b4295afa6 Mon Sep 17 00:00:00 2001 From: yorah Date: Mon, 17 Jun 2013 18:25:30 +0200 Subject: [PATCH 006/367] ref: free the last ref when cancelling git_branch_foreach() Also fixed an assert typo on nulltoken's HEAD --- src/branch.c | 6 ++---- tests-clar/refs/branches/foreach.c | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/branch.c b/src/branch.c index de38e3355..590cdc027 100644 --- a/src/branch.c +++ b/src/branch.c @@ -132,18 +132,17 @@ int git_branch_foreach( { git_reference_iterator *iter; git_reference *ref; - int error; + int error = 0; if (git_reference_iterator_new(&iter, repo) < 0) return -1; - while ((error = git_reference_next(&ref, iter)) == 0) { + while (!error && (error = git_reference_next(&ref, iter)) == 0) { if (list_flags & GIT_BRANCH_LOCAL && git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0) { if (callback(ref->name + strlen(GIT_REFS_HEADS_DIR), GIT_BRANCH_LOCAL, payload)) { error = GIT_EUSER; - break; } } @@ -152,7 +151,6 @@ int git_branch_foreach( if (callback(ref->name + strlen(GIT_REFS_REMOTES_DIR), GIT_BRANCH_REMOTE, payload)) { error = GIT_EUSER; - break; } } diff --git a/tests-clar/refs/branches/foreach.c b/tests-clar/refs/branches/foreach.c index 57f9c0e39..433812cb4 100644 --- a/tests-clar/refs/branches/foreach.c +++ b/tests-clar/refs/branches/foreach.c @@ -122,7 +122,7 @@ void test_refs_branches_foreach__retrieve_remote_symbolic_HEAD_when_present(void cl_git_pass(git_branch_foreach(repo, GIT_BRANCH_REMOTE, contains_branch_list_cb, &exp)); assert_branch_has_been_found(exp, "nulltoken/HEAD"); - assert_branch_has_been_found(exp, "nulltoken/HEAD"); + assert_branch_has_been_found(exp, "nulltoken/master"); } static int branch_list_interrupt_cb( From 09c2f91c150a4862c9189d9e08d0dc111d4d706c Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Mon, 17 Jun 2013 18:48:02 +0200 Subject: [PATCH 007/367] branch: More obvious semantics in `foreach` --- src/branch.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/branch.c b/src/branch.c index 590cdc027..7064fa7fc 100644 --- a/src/branch.c +++ b/src/branch.c @@ -137,7 +137,7 @@ int git_branch_foreach( if (git_reference_iterator_new(&iter, repo) < 0) return -1; - while (!error && (error = git_reference_next(&ref, iter)) == 0) { + while ((error = git_reference_next(&ref, iter)) == 0) { if (list_flags & GIT_BRANCH_LOCAL && git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0) { if (callback(ref->name + strlen(GIT_REFS_HEADS_DIR), @@ -155,6 +155,10 @@ int git_branch_foreach( } git_reference_free(ref); + + /* check if the callback has cancelled iteration */ + if (error == GIT_EUSER) + break; } if (error == GIT_ITEROVER) From 1ee2ef87ec4c2c63b7c89849eb9daad9e46e6fe7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 21 May 2013 11:05:21 -0500 Subject: [PATCH 008/367] status access by index, providing more details to callers --- include/git2/status.h | 63 ++++++++++++ include/git2/types.h | 3 + src/status.c | 232 ++++++++++++++++++++++++++++++------------ src/status.h | 23 +++++ 4 files changed, 255 insertions(+), 66 deletions(-) create mode 100644 src/status.h diff --git a/include/git2/status.h b/include/git2/status.h index 38b6fa5bd..ce1d44a06 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -174,9 +174,72 @@ typedef struct { git_strarray pathspec; } git_status_options; +/** + * A status entry, providing the differences between the file as it exists + * in HEAD and the index, and providing the differences between the index + * and the working directory. + * + * The `status` value provides the status flags for this file. + * + * The `head_to_index` value provides detailed information about the + * differences between the file in HEAD and the file in the index. + * + * The `index_to_workdir` value provides detailed information about the + * differences between the file in the index and the file in the + * working directory. + */ +typedef struct { + git_status_t status; + git_diff_delta *head_to_index; + git_diff_delta *index_to_workdir; +} git_status_entry; + #define GIT_STATUS_OPTIONS_VERSION 1 #define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION} +/** + * Gather file status information and populate the `git_status_list`. + * + * @param out Pointer to store the status results in + * @param repo Repository object + * @param opts Status options structure + * @return 0 on success or error code + */ +GIT_EXTERN(int) git_status_list_new( + git_status_list **out, + git_repository *repo, + const git_status_options *opts); + +/** + * Gets the count of status entries in this list. + * + * @param statuslist Existing status list object + * @return the number of status entries + */ +GIT_EXTERN(size_t) git_status_list_entrycount( + git_status_list *statuslist); + +/** + * Get a pointer to one of the entries in the status list. + * + * The entry is not modifiable and should not be freed. + * + * @param statuslist Existing status list object + * @param idx Position of the entry + * @return Pointer to the entry; NULL if out of bounds + */ +GIT_EXTERN(const git_status_entry *) git_status_byindex( + git_status_list *statuslist, + size_t idx); + +/** + * Free an existing status list + * + * @param statuslist Existing status list object + */ +GIT_EXTERN(void) git_status_list_free( + git_status_list *statuslist); + /** * Gather file status information and run callbacks as requested. * diff --git a/include/git2/types.h b/include/git2/types.h index 1bfa73be6..dc344075c 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -174,6 +174,9 @@ typedef struct git_reference_iterator git_reference_iterator; /** Merge heads, the input to merge */ typedef struct git_merge_head git_merge_head; +/** Representation of a status collection */ +typedef struct git_status_list git_status_list; + /** Basic type of any Git reference. */ typedef enum { diff --git a/src/status.c b/src/status.c index 712e0d515..33e67efac 100644 --- a/src/status.c +++ b/src/status.c @@ -11,6 +11,7 @@ #include "hash.h" #include "vector.h" #include "tree.h" +#include "status.h" #include "git2/status.h" #include "repository.h" #include "ignore.h" @@ -77,71 +78,105 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) return st; } -typedef struct { - git_status_cb cb; - void *payload; - const git_status_options *opts; -} status_user_callback; - -static int status_invoke_cb( - git_diff_delta *h2i, git_diff_delta *i2w, void *payload) +static bool status_is_included( + git_status_list *statuslist, + git_diff_delta *head2idx, + git_diff_delta *idx2wd) { - status_user_callback *usercb = payload; - const char *path = NULL; - unsigned int status = 0; - - if (i2w) { - path = i2w->old_file.path; - status |= workdir_delta2status(i2w->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 ((statuslist->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) { + bool in_tree = (head2idx && head2idx->status != GIT_DELTA_ADDED); + bool in_index = (head2idx && head2idx->status != GIT_DELTA_DELETED); + bool in_wd = (idx2wd && idx2wd->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)) + if ((!in_tree || head2idx->old_file.mode == GIT_FILEMODE_COMMIT) && + (!in_index || head2idx->new_file.mode == GIT_FILEMODE_COMMIT) && + (!in_wd || idx2wd->new_file.mode == GIT_FILEMODE_COMMIT)) return 0; } - return usercb->cb(path, status, usercb->payload); + return 1; } -int git_status_foreach_ext( - git_repository *repo, - const git_status_options *opts, - git_status_cb cb, +static git_status_t status_compute( + git_diff_delta *head2idx, + git_diff_delta *idx2wd) +{ + git_status_t status = 0; + + if (head2idx) + status |= index_delta2status(head2idx->status); + + if (idx2wd) + status |= workdir_delta2status(idx2wd->status); + + return status; +} + +static int status_collect( + git_diff_delta *head2idx, + git_diff_delta *idx2wd, void *payload) { - int err = 0; + git_status_list *statuslist = payload; + git_status_entry *status_entry; + + if (!status_is_included(statuslist, head2idx, idx2wd)) + return 0; + + status_entry = git__malloc(sizeof(git_status_entry)); + GITERR_CHECK_ALLOC(status_entry); + + status_entry->status = status_compute(head2idx, idx2wd); + status_entry->head_to_index = head2idx; + status_entry->index_to_workdir = idx2wd; + + git_vector_insert(&statuslist->paired, status_entry); + + return 0; +} + +git_status_list *git_status_list_alloc(void) +{ + git_status_list *statuslist = NULL; + + if ((statuslist = git__calloc(1, sizeof(git_status_list))) == NULL || + git_vector_init(&statuslist->paired, 0, NULL) < 0) + return NULL; + + return statuslist; +} + +int git_status_list_new( + git_status_list **out, + git_repository *repo, + const git_status_options *opts) +{ + git_status_list *statuslist = NULL; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; - 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; - status_user_callback usercb; + int error = 0; assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); + *out = NULL; + GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options"); - if (show != GIT_STATUS_SHOW_INDEX_ONLY && - (err = git_repository__ensure_not_bare(repo, "status")) < 0) - return err; + if ((error = git_repository__ensure_not_bare(repo, "status")) < 0) + return error; /* if there is no HEAD, that's okay - we'll make an empty iterator */ - if (((err = git_repository_head_tree(&head, repo)) < 0) && - !(err == GIT_ENOTFOUND || err == GIT_EORPHANEDHEAD)) - return err; + if (((error = git_repository_head_tree(&head, repo)) < 0) && + !(error == GIT_ENOTFOUND || error == GIT_EORPHANEDHEAD)) + return error; + + statuslist = git_status_list_alloc(); + GITERR_CHECK_ALLOC(statuslist); + + memcpy(&statuslist->opts, opts, sizeof(git_status_options)); memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); @@ -163,41 +198,106 @@ int git_status_foreach_ext( 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; + error = git_diff_tree_to_index(&statuslist->head2idx, repo, head, NULL, &diffopt); + + if (error < 0) + goto on_error; } if (show != GIT_STATUS_SHOW_INDEX_ONLY) { - err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt); - if (err < 0) - goto cleanup; - } + error = git_diff_index_to_workdir(&statuslist->idx2wd, repo, NULL, &diffopt); - usercb.cb = cb; - usercb.payload = payload; - usercb.opts = opts; + if (error < 0) + goto on_error; + } if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { - if ((err = git_diff__paired_foreach( - head2idx, NULL, status_invoke_cb, &usercb)) < 0) - goto cleanup; + if ((error = git_diff__paired_foreach(statuslist->head2idx, NULL, status_collect, statuslist)) < 0) + goto on_error; - git_diff_list_free(head2idx); - head2idx = NULL; + git_diff_list_free(statuslist->head2idx); + statuslist->head2idx = NULL; } - err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb); + if ((error = git_diff__paired_foreach(statuslist->head2idx, statuslist->idx2wd, status_collect, statuslist)) < 0) + goto on_error; -cleanup: + *out = statuslist; + goto done; + +on_error: + git_status_list_free(statuslist); + +done: git_tree_free(head); - git_diff_list_free(head2idx); - git_diff_list_free(idx2wd); - if (err == GIT_EUSER) - giterr_clear(); + return error; +} - return err; +size_t git_status_list_entrycount(git_status_list *statuslist) +{ + assert(statuslist); + + return statuslist->paired.length; +} + +const git_status_entry *git_status_byindex( + git_status_list *statuslist, + size_t i) +{ + assert(statuslist); + + return git_vector_get(&statuslist->paired, i); +} + +void git_status_list_free(git_status_list *statuslist) +{ + git_status_entry *status_entry; + size_t i; + + if (statuslist == NULL) + return; + + git_diff_list_free(statuslist->head2idx); + git_diff_list_free(statuslist->idx2wd); + + git_vector_foreach(&statuslist->paired, i, status_entry) + git__free(status_entry); + + git_vector_free(&statuslist->paired); + + git__free(statuslist); +} + +int git_status_foreach_ext( + git_repository *repo, + const git_status_options *opts, + git_status_cb cb, + void *payload) +{ + git_status_list *statuslist; + const git_status_entry *status_entry; + size_t i; + int error = 0; + + if ((error = git_status_list_new(&statuslist, repo, opts)) < 0) + return error; + + git_vector_foreach(&statuslist->paired, i, status_entry) { + const char *path = status_entry->head_to_index ? + status_entry->head_to_index->old_file.path : + status_entry->index_to_workdir->old_file.path; + + if (cb(path, status_entry->status, payload) != 0) { + error = GIT_EUSER; + giterr_clear(); + break; + } + } + + git_status_list_free(statuslist); + + return error; } int git_status_foreach( diff --git a/src/status.h b/src/status.h new file mode 100644 index 000000000..b58e0ebd6 --- /dev/null +++ b/src/status.h @@ -0,0 +1,23 @@ +/* + * 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_status_h__ +#define INCLUDE_status_h__ + +#include "diff.h" +#include "git2/status.h" +#include "git2/diff.h" + +struct git_status_list { + git_status_options opts; + + git_diff_list *head2idx; + git_diff_list *idx2wd; + + git_vector paired; +}; + +#endif From dfe8c8df3707b2773e376633c5908dc612e59d6a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 22 May 2013 23:19:40 -0500 Subject: [PATCH 009/367] handle renames in status computation --- include/git2/status.h | 21 ++- src/status.c | 108 +++++++++++++-- tests-clar/status/renames.c | 265 ++++++++++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+), 22 deletions(-) create mode 100644 tests-clar/status/renames.c diff --git a/include/git2/status.h b/include/git2/status.h index ce1d44a06..64ce6756c 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -42,6 +42,7 @@ typedef enum { GIT_STATUS_WT_MODIFIED = (1u << 8), GIT_STATUS_WT_DELETED = (1u << 9), GIT_STATUS_WT_TYPECHANGE = (1u << 10), + GIT_STATUS_WT_RENAMED = (1u << 11), GIT_STATUS_IGNORED = (1u << 14), } git_status_t; @@ -130,6 +131,10 @@ typedef enum { * - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of * ignored directories should be included in the status. This is like * doing `git ls-files -o -i --exclude-standard` with core git. + * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that items that are + * renamed in the index will be reported as renames. + * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that items that + * are renamed in the working directory will be reported as renames. * * Calling `git_status_foreach()` is like calling the extended version * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, @@ -137,13 +142,15 @@ typedef enum { * together as `GIT_STATUS_OPT_DEFAULTS` if you want them as a baseline. */ typedef enum { - GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0), - GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1), - GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2), - GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3), - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4), - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5), - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), + GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0), + GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1), + GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2), + GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3), + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4), + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5), + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7), + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), } git_status_opt_t; #define GIT_STATUS_OPT_DEFAULTS \ diff --git a/src/status.c b/src/status.c index 33e67efac..20e45b75f 100644 --- a/src/status.c +++ b/src/status.c @@ -54,7 +54,6 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) switch (workdir_status) { case GIT_DELTA_ADDED: - case GIT_DELTA_RENAMED: case GIT_DELTA_COPIED: case GIT_DELTA_UNTRACKED: st = GIT_STATUS_WT_NEW; @@ -68,6 +67,9 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) case GIT_DELTA_IGNORED: st = GIT_STATUS_IGNORED; break; + case GIT_DELTA_RENAMED: + st = GIT_STATUS_WT_RENAMED; + break; case GIT_DELTA_TYPECHANGE: st = GIT_STATUS_WT_TYPECHANGE; break; @@ -85,9 +87,9 @@ static bool status_is_included( { /* if excluding submodules and this is a submodule everywhere */ if ((statuslist->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) { - bool in_tree = (head2idx && head2idx->status != GIT_DELTA_ADDED); + bool in_tree = (head2idx && head2idx->status != GIT_DELTA_ADDED); bool in_index = (head2idx && head2idx->status != GIT_DELTA_DELETED); - bool in_wd = (idx2wd && idx2wd->status != GIT_DELTA_DELETED); + bool in_wd = (idx2wd && idx2wd->status != GIT_DELTA_DELETED); if ((!in_tree || head2idx->old_file.mode == GIT_FILEMODE_COMMIT) && (!in_index || head2idx->new_file.mode == GIT_FILEMODE_COMMIT) && @@ -136,24 +138,79 @@ static int status_collect( return 0; } -git_status_list *git_status_list_alloc(void) +GIT_INLINE(int) status_entry_cmp_base( + const void *a, + const void *b, + int (*strcomp)(const char *a, const char *b)) +{ + const git_status_entry *entry_a = a; + const git_status_entry *entry_b = b; + const git_diff_delta *delta_a, *delta_b; + + delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir : + entry_a->head_to_index; + delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir : + entry_b->head_to_index; + + if (!delta_a && delta_b) + return -1; + if (delta_a && !delta_b) + return 1; + if (!delta_a && !delta_b) + return 0; + + return strcomp(delta_a->new_file.path, delta_b->new_file.path); +} + +static int status_entry_icmp(const void *a, const void *b) +{ + return status_entry_cmp_base(a, b, git__strcasecmp); +} + +static int status_entry_cmp(const void *a, const void *b) +{ + return status_entry_cmp_base(a, b, git__strcmp); +} + +static git_status_list *git_status_list_alloc(git_index *index) { git_status_list *statuslist = NULL; + int (*entrycmp)(const void *a, const void *b); + + entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp; if ((statuslist = git__calloc(1, sizeof(git_status_list))) == NULL || - git_vector_init(&statuslist->paired, 0, NULL) < 0) + git_vector_init(&statuslist->paired, 0, entrycmp) < 0) return NULL; return statuslist; } +static int newfile_cmp(const void *a, const void *b) +{ + const git_diff_delta *delta_a = a; + const git_diff_delta *delta_b = b; + + return git__strcmp(delta_a->new_file.path, delta_b->new_file.path); +} + +static int newfile_casecmp(const void *a, const void *b) +{ + const git_diff_delta *delta_a = a; + const git_diff_delta *delta_b = b; + + return git__strcasecmp(delta_a->new_file.path, delta_b->new_file.path); +} + int git_status_list_new( git_status_list **out, git_repository *repo, const git_status_options *opts) { + git_index *index = NULL; git_status_list *statuslist = NULL; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts_i2w = GIT_DIFF_FIND_OPTIONS_INIT; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; @@ -165,7 +222,8 @@ int git_status_list_new( GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options"); - if ((error = git_repository__ensure_not_bare(repo, "status")) < 0) + if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 || + (error = git_repository_index(&index, repo)) < 0) return error; /* if there is no HEAD, that's okay - we'll make an empty iterator */ @@ -173,7 +231,7 @@ int git_status_list_new( !(error == GIT_ENOTFOUND || error == GIT_EORPHANEDHEAD)) return error; - statuslist = git_status_list_alloc(); + statuslist = git_status_list_alloc(index); GITERR_CHECK_ALLOC(statuslist); memcpy(&statuslist->opts, opts, sizeof(git_status_options)); @@ -197,17 +255,23 @@ int git_status_list_new( if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; - if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { - error = git_diff_tree_to_index(&statuslist->head2idx, repo, head, NULL, &diffopt); + findopts_i2w.flags |= GIT_DIFF_FIND_FOR_UNTRACKED; - if (error < 0) + if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { + if ((error = git_diff_tree_to_index(&statuslist->head2idx, repo, head, NULL, &diffopt)) < 0) + goto on_error; + + if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && + (error = git_diff_find_similar(statuslist->head2idx, NULL)) < 0) goto on_error; } if (show != GIT_STATUS_SHOW_INDEX_ONLY) { - error = git_diff_index_to_workdir(&statuslist->idx2wd, repo, NULL, &diffopt); + if ((error = git_diff_index_to_workdir(&statuslist->idx2wd, repo, NULL, &diffopt)) < 0) + goto on_error; - if (error < 0) + if ((opts->flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && + (error = git_diff_find_similar(statuslist->idx2wd, &findopts_i2w)) < 0) goto on_error; } @@ -219,9 +283,22 @@ int git_status_list_new( statuslist->head2idx = NULL; } - if ((error = git_diff__paired_foreach(statuslist->head2idx, statuslist->idx2wd, status_collect, statuslist)) < 0) + if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0) { + statuslist->head2idx->deltas._cmp = + (statuslist->head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ? + newfile_casecmp : newfile_cmp; + + git_vector_sort(&statuslist->head2idx->deltas); + } + + if ((error = git_diff__paired_foreach(statuslist->head2idx, statuslist->idx2wd, + status_collect, statuslist)) < 0) goto on_error; + if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 || + (opts->flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0) + git_vector_sort(&statuslist->paired); + *out = statuslist; goto done; @@ -230,6 +307,7 @@ on_error: done: git_tree_free(head); + git_index_free(index); return error; } @@ -307,7 +385,7 @@ int git_status_foreach( { git_status_options opts = GIT_STATUS_OPTIONS_INIT; - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; @@ -364,7 +442,7 @@ int git_status_file( if (index->ignore_case) sfi.fnm_flags = FNM_CASEFOLD; - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS | GIT_STATUS_OPT_INCLUDE_UNTRACKED | diff --git a/tests-clar/status/renames.c b/tests-clar/status/renames.c new file mode 100644 index 000000000..d29c7bfe8 --- /dev/null +++ b/tests-clar/status/renames.c @@ -0,0 +1,265 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "path.h" +#include "posix.h" +#include "status_helpers.h" +#include "util.h" +#include "status.h" + +static git_repository *g_repo = NULL; + +void test_status_renames__initialize(void) +{ + g_repo = cl_git_sandbox_init("renames"); +} + +void test_status_renames__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void rename_file(git_repository *repo, const char *oldname, const char *newname) +{ + git_buf oldpath = GIT_BUF_INIT, newpath = GIT_BUF_INIT; + + git_buf_joinpath(&oldpath, git_repository_workdir(repo), oldname); + git_buf_joinpath(&newpath, git_repository_workdir(repo), newname); + + cl_git_pass(p_rename(oldpath.ptr, newpath.ptr)); + + git_buf_free(&oldpath); + git_buf_free(&newpath); +} + +static void rename_and_edit_file(git_repository *repo, const char *oldname, const char *newname) +{ + git_buf oldpath = GIT_BUF_INIT, newpath = GIT_BUF_INIT; + + git_buf_joinpath(&oldpath, git_repository_workdir(repo), oldname); + git_buf_joinpath(&newpath, git_repository_workdir(repo), newname); + + cl_git_pass(p_rename(oldpath.ptr, newpath.ptr)); + cl_git_append2file(newpath.ptr, "Added at the end to keep similarity!"); + + git_buf_free(&oldpath); + git_buf_free(&newpath); +} + +struct status_entry { + git_status_t status; + const char *oldname; + const char *newname; +}; + +static void test_status( + git_status_list *status_list, + struct status_entry *expected_list, + size_t expected_len) +{ + const git_status_entry *actual; + const struct status_entry *expected; + const char *oldname, *newname; + size_t i; + + cl_assert(expected_len == git_status_list_entrycount(status_list)); + + for (i = 0; i < expected_len; i++) { + actual = git_status_byindex(status_list, i); + expected = &expected_list[i]; + + cl_assert(actual->status == expected->status); + + oldname = actual->head_to_index ? actual->head_to_index->old_file.path : + actual->index_to_workdir ? actual->index_to_workdir->old_file.path : NULL; + + newname = actual->index_to_workdir ? actual->index_to_workdir->new_file.path : + actual->head_to_index ? actual->head_to_index->new_file.path : NULL; + + if (oldname) + cl_assert(git__strcmp(oldname, expected->oldname) == 0); + else + cl_assert(expected->oldname == NULL); + + if (newname) + cl_assert(git__strcmp(newname, expected->newname) == 0); + else + cl_assert(expected->newname == NULL); + } +} + +void test_status_renames__head2index_one(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "newname.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "newname.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "newname.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 1); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__head2index_two(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_INDEX_RENAMED, "untimely.txt", "bbb.txt" }, + { GIT_STATUS_INDEX_RENAMED, "songof7cities.txt", "ccc.txt" }, + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "ddd.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "ddd.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt"); + rename_file(g_repo, "songof7cities.txt", "ccc.txt"); + rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "ddd.txt")); + cl_git_pass(git_index_add_bypath(index, "aaa.txt")); + cl_git_pass(git_index_add_bypath(index, "ccc.txt")); + cl_git_pass(git_index_add_bypath(index, "bbb.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 4); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__index2workdir_one(void) +{ + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + rename_file(g_repo, "ikeepsix.txt", "newname.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 1); + git_status_list_free(statuslist); +} + +void test_status_renames__index2workdir_two(void) +{ + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED, "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_WT_RENAMED, "untimely.txt", "bbb.txt" }, + { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "ccc.txt" }, + { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ddd.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + rename_file(g_repo, "ikeepsix.txt", "ddd.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt"); + rename_file(g_repo, "songof7cities.txt", "ccc.txt"); + rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 4); + git_status_list_free(statuslist); +} + +void test_status_renames__both_one(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname-workdir.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "newname-index.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "newname-index.txt")); + cl_git_pass(git_index_write(index)); + + rename_file(g_repo, "newname-index.txt", "newname-workdir.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 1); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__both_two(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ikeepsix-both.txt" }, + { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "sixserving-index.txt" }, + { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "songof7cities-workdir.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "untimely.txt", "untimely-both.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_and_edit_file(g_repo, "ikeepsix.txt", "ikeepsix-index.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "sixserving-index.txt"); + rename_file(g_repo, "untimely.txt", "untimely-index.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix-index.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving-index.txt")); + cl_git_pass(git_index_add_bypath(index, "untimely-index.txt")); + cl_git_pass(git_index_write(index)); + + rename_and_edit_file(g_repo, "ikeepsix-index.txt", "ikeepsix-both.txt"); + rename_and_edit_file(g_repo, "songof7cities.txt", "songof7cities-workdir.txt"); + rename_file(g_repo, "untimely-index.txt", "untimely-both.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 4); + git_status_list_free(statuslist); + + git_index_free(index); +} From e3b4a47c1ebd55931cb25bf5c2af821df9b0bffa Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 31 May 2013 16:30:09 -0500 Subject: [PATCH 010/367] git__strcasesort_cmp: strcasecmp sorting rules but requires strict equality --- src/util.c | 25 +++++++++++++++++++++++++ src/util.h | 2 ++ tests-clar/core/string.c | 13 +++++++++++++ 3 files changed, 40 insertions(+) diff --git a/src/util.c b/src/util.c index da15a039d..8536c9513 100644 --- a/src/util.c +++ b/src/util.c @@ -279,6 +279,31 @@ int git__strcasecmp(const char *a, const char *b) return (tolower(*a) - tolower(*b)); } +int git__strcasesort_cmp(const char *a, const char *b) +{ + int cmp = 0; + + const char *orig_a = a; + const char *orig_b = b; + + while (*a && *b) { + if (*a == *b) + ; + else if (tolower(*a) == tolower(*b)) { + if (!cmp) + cmp = (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b); + } else + break; + + ++a, ++b; + } + + if (*a || *b) + return tolower(*a) - tolower(*b); + + return cmp; +} + int git__strncmp(const char *a, const char *b, size_t sz) { while (sz && *a && *b && *a == *b) diff --git a/src/util.h b/src/util.h index 1ef9e65b5..e0088399c 100644 --- a/src/util.h +++ b/src/util.h @@ -194,6 +194,8 @@ extern int git__strcasecmp(const char *a, const char *b); extern int git__strncmp(const char *a, const char *b, size_t sz); extern int git__strncasecmp(const char *a, const char *b, size_t sz); +extern int git__strcasesort_cmp(const char *a, const char *b); + #include "thread-utils.h" typedef struct { diff --git a/tests-clar/core/string.c b/tests-clar/core/string.c index bf6ec0a80..ec9575685 100644 --- a/tests-clar/core/string.c +++ b/tests-clar/core/string.c @@ -26,3 +26,16 @@ void test_core_string__1(void) cl_assert(git__suffixcmp("zaz", "ac") > 0); } +/* compare icase sorting with case equality */ +void test_core_string__2(void) +{ + cl_assert(git__strcasesort_cmp("", "") == 0); + cl_assert(git__strcasesort_cmp("foo", "foo") == 0); + cl_assert(git__strcasesort_cmp("foo", "bar") > 0); + cl_assert(git__strcasesort_cmp("bar", "foo") < 0); + cl_assert(git__strcasesort_cmp("foo", "FOO") > 0); + cl_assert(git__strcasesort_cmp("FOO", "foo") < 0); + cl_assert(git__strcasesort_cmp("foo", "BAR") > 0); + cl_assert(git__strcasesort_cmp("BAR", "foo") < 0); + cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0); +} From c9b18018fd537566e76308fd2fec67e483b1d201 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:26:56 -0700 Subject: [PATCH 011/367] Fix some warnings --- src/util.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/util.c b/src/util.c index 8536c9513..c543a3d21 100644 --- a/src/util.c +++ b/src/util.c @@ -283,17 +283,14 @@ int git__strcasesort_cmp(const char *a, const char *b) { int cmp = 0; - const char *orig_a = a; - const char *orig_b = b; - while (*a && *b) { - if (*a == *b) - ; - else if (tolower(*a) == tolower(*b)) { + if (*a != *b) { + if (tolower(*a) != tolower(*b)) + break; + /* use case in sort order even if not in equivalence */ if (!cmp) - cmp = (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b); - } else - break; + cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b); + } ++a, ++b; } From 4e28e638ea016f5a5b35ba952d9f06a1108d6b5e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:27:30 -0700 Subject: [PATCH 012/367] Clarify some docs and minor reordering This simplifies some documentation and hopefully makes a couple of things easier to read. Also, this rearrages the order in this branch so that the overall diff against the trunk will hopefully be a bit cleaner. --- include/git2/status.h | 183 +++++++++++++++++++++--------------------- 1 file changed, 90 insertions(+), 93 deletions(-) diff --git a/include/git2/status.h b/include/git2/status.h index 64ce6756c..282b606cb 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -59,44 +59,20 @@ typedef enum { typedef int (*git_status_cb)( const char *path, unsigned int status_flags, void *payload); -/** - * Gather file statuses and run a callback for each one. - * - * The callback is passed the path of the file, the status (a combination of - * the `git_status_t` values above) and the `payload` data pointer passed - * into this function. - * - * If the callback returns a non-zero value, this function will stop looping - * and return GIT_EUSER. - * - * @param repo A repository object - * @param callback The function to call on each file - * @param payload Pointer to pass through to callback function - * @return 0 on success, GIT_EUSER on non-zero callback, or error code - */ -GIT_EXTERN(int) git_status_foreach( - git_repository *repo, - git_status_cb callback, - void *payload); - /** * For extended status, select the files on which to report status. * - * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This is the - * rough equivalent of `git status --porcelain` where each file - * will receive a callback indicating its status in the index and - * in the workdir. - * - GIT_STATUS_SHOW_INDEX_ONLY will only make callbacks for index - * side of status. The status of the index contents relative to - * the HEAD will be given. - * - GIT_STATUS_SHOW_WORKDIR_ONLY will only make callbacks for the - * workdir side of status, reporting the status of workdir content - * relative to the index. - * - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR behaves like index-only - * followed by workdir-only, causing two callbacks to be issued - * per file (first index then workdir). This is slightly more - * efficient than making separate calls. This makes it easier to - * emulate the output of a plain `git status`. + * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This roughly + * matches `git status --porcelain` where each file gets a callback + * indicating its status in the index and in the working directory. + * - GIT_STATUS_SHOW_INDEX_ONLY only gives status based on HEAD to index + * comparison, not looking at working directory changes. + * - GIT_STATUS_SHOW_WORKDIR_ONLY only gives status based on index to + * working directory comparison, not comparing the index to the HEAD. + * - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR runs index-only then workdir-only, + * issuing (up to) two callbacks per file (first index, then workdir). + * This is slightly more efficient than separate calls and can make it + * easier to emulate plain `git status` text output. */ typedef enum { GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0, @@ -111,30 +87,30 @@ typedef enum { * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should be made * on untracked files. These will only be made if the workdir files are * included in the status "show" option. - * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should get - * callbacks. Again, these callbacks will only be made if the workdir - * files are included in the status "show" option. Right now, there is - * no option to include all files in directories that are ignored - * completely. + * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files get callbacks. + * Again, these callbacks will only be made if the workdir files are + * included in the status "show" option. * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be * made even on unmodified files. - * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories which - * appear to be submodules should just be skipped over. - * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the contents of - * untracked directories should be included in the status. Normally if - * an entire directory is new, then just the top-level directory will be - * included (with a trailing slash on the entry name). Given this flag, - * the directory itself will not be included, but all the files in it - * will. + * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that submodules should be + * skipped. This only applies if there are no pending typechanges to + * the submodule (either from or to another type). + * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that all files in + * untracked directories should be included. Normally if an entire + * directory is new, then just the top-level directory is included (with + * a trailing slash on the entry name). This flag says to include all + * of the individual files in the directory instead. * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path - * will be treated as a literal path, and not as a pathspec. + * should be treated as a literal path, and not as a pathspec pattern. * - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of * ignored directories should be included in the status. This is like * doing `git ls-files -o -i --exclude-standard` with core git. - * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that items that are - * renamed in the index will be reported as renames. - * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that items that - * are renamed in the working directory will be reported as renames. + * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection + * should be processed between the head and the index and enables + * the GIT_STATUS_INDEX_RENAMED as a possible status flag. + * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename + * detection should be run between the index and the working directory + * and enabled GIT_STATUS_WT_RENAMED as a possible status flag. * * Calling `git_status_foreach()` is like calling the extended version * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, @@ -181,6 +157,9 @@ typedef struct { git_strarray pathspec; } git_status_options; +#define GIT_STATUS_OPTIONS_VERSION 1 +#define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION} + /** * A status entry, providing the differences between the file as it exists * in HEAD and the index, and providing the differences between the index @@ -201,8 +180,64 @@ typedef struct { git_diff_delta *index_to_workdir; } git_status_entry; -#define GIT_STATUS_OPTIONS_VERSION 1 -#define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION} + +/** + * Gather file statuses and run a callback for each one. + * + * The callback is passed the path of the file, the status (a combination of + * the `git_status_t` values above) and the `payload` data pointer passed + * into this function. + * + * If the callback returns a non-zero value, this function will stop looping + * and return GIT_EUSER. + * + * @param repo A repository object + * @param callback The function to call on each file + * @param payload Pointer to pass through to callback function + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_status_foreach( + git_repository *repo, + git_status_cb callback, + void *payload); + +/** + * Gather file status information and run callbacks as requested. + * + * This is an extended version of the `git_status_foreach()` API that + * allows for more granular control over which paths will be processed and + * in what order. See the `git_status_options` structure for details + * about the additional controls that this makes available. + * + * @param repo Repository object + * @param opts Status options structure + * @param callback The function to call on each file + * @param payload Pointer to pass through to callback function + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_status_foreach_ext( + git_repository *repo, + const git_status_options *opts, + git_status_cb callback, + void *payload); + +/** + * Get file status for a single file. + * + * This is not quite the same as calling `git_status_foreach_ext()` with + * the pathspec set to the specified path. + * + * @param status_flags The status value for the file + * @param repo A repository object + * @param path The file to retrieve status for, rooted at the repo's workdir + * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD, + * index, and work tree, GIT_EINVALIDPATH if `path` points at a folder, + * GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error. + */ +GIT_EXTERN(int) git_status_file( + unsigned int *status_flags, + git_repository *repo, + const char *path); /** * Gather file status information and populate the `git_status_list`. @@ -247,44 +282,6 @@ GIT_EXTERN(const git_status_entry *) git_status_byindex( GIT_EXTERN(void) git_status_list_free( git_status_list *statuslist); -/** - * Gather file status information and run callbacks as requested. - * - * This is an extended version of the `git_status_foreach()` API that - * allows for more granular control over which paths will be processed and - * in what order. See the `git_status_options` structure for details - * about the additional controls that this makes available. - * - * @param repo Repository object - * @param opts Status options structure - * @param callback The function to call on each file - * @param payload Pointer to pass through to callback function - * @return 0 on success, GIT_EUSER on non-zero callback, or error code - */ -GIT_EXTERN(int) git_status_foreach_ext( - git_repository *repo, - const git_status_options *opts, - git_status_cb callback, - void *payload); - -/** - * Get file status for a single file. - * - * This is not quite the same as calling `git_status_foreach_ext()` with - * the pathspec set to the specified path. - * - * @param status_flags The status value for the file - * @param repo A repository object - * @param path The file to retrieve status for, rooted at the repo's workdir - * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD, - * index, and work tree, GIT_EINVALIDPATH if `path` points at a folder, - * GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error. - */ -GIT_EXTERN(int) git_status_file( - unsigned int *status_flags, - git_repository *repo, - const char *path); - /** * Test if the ignore rules apply to a given file. * From 3a68d7f00289afbaa415c3b34d5eeca183dcfb52 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:30:26 -0700 Subject: [PATCH 013/367] Fix broken status EXCLUDE_SUBMODULES logic The exclude submodules flag was not doing the right thing, in that a file with no diff between the head and the index and just a delete in the workdir could be excluded if submodules were excluded. --- src/status.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/status.c b/src/status.c index 20e45b75f..ba4eef895 100644 --- a/src/status.c +++ b/src/status.c @@ -81,23 +81,33 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) } static bool status_is_included( - git_status_list *statuslist, + git_status_list *status, git_diff_delta *head2idx, git_diff_delta *idx2wd) { - /* if excluding submodules and this is a submodule everywhere */ - if ((statuslist->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) { - bool in_tree = (head2idx && head2idx->status != GIT_DELTA_ADDED); - bool in_index = (head2idx && head2idx->status != GIT_DELTA_DELETED); - bool in_wd = (idx2wd && idx2wd->status != GIT_DELTA_DELETED); + if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES)) + return 1; - if ((!in_tree || head2idx->old_file.mode == GIT_FILEMODE_COMMIT) && - (!in_index || head2idx->new_file.mode == GIT_FILEMODE_COMMIT) && - (!in_wd || idx2wd->new_file.mode == GIT_FILEMODE_COMMIT)) - return 0; + /* if excluding submodules and this is a submodule everywhere */ + if (head2idx) { + if (head2idx->status != GIT_DELTA_ADDED && + head2idx->old_file.mode != GIT_FILEMODE_COMMIT) + return 1; + if (head2idx->status != GIT_DELTA_DELETED && + head2idx->new_file.mode != GIT_FILEMODE_COMMIT) + return 1; + } + if (idx2wd) { + if (idx2wd->status != GIT_DELTA_ADDED && + idx2wd->old_file.mode != GIT_FILEMODE_COMMIT) + return 1; + if (idx2wd->status != GIT_DELTA_DELETED && + idx2wd->new_file.mode != GIT_FILEMODE_COMMIT) + return 1; } - return 1; + /* only get here if every valid mode is GIT_FILEMODE_COMMIT */ + return 0; } static git_status_t status_compute( From a3e8dbb40b87f66e1f5f4d1730f9989805822db0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:32:09 -0700 Subject: [PATCH 014/367] Be more careful about the path with diffs This makes diff more careful about picking the canonical path when generating a delta so that it won't accidentally pick up a case-mismatched path on a case-insensitive file system. This should make sure we use the "most accurate" case correct version of the path (i.e. from the tree if possible, or the index if need be). --- src/diff.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/diff.c b/src/diff.c index 3bfe149e3..feb77b59b 100644 --- a/src/diff.c +++ b/src/diff.c @@ -134,6 +134,7 @@ static int diff_delta__from_two( { git_diff_delta *delta; int notify_res; + const char *canonical_path = old_entry->path; if (status == GIT_DELTA_UNMODIFIED && DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) @@ -153,7 +154,7 @@ static int diff_delta__from_two( new_mode = temp_mode; } - delta = diff_delta__alloc(diff, status, old_entry->path); + delta = diff_delta__alloc(diff, status, canonical_path); GITERR_CHECK_ALLOC(delta); git_oid_cpy(&delta->old_file.oid, &old_entry->oid); From 351888cf3dc3c3525faccb010adcf5c130ac5b16 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:37:06 -0700 Subject: [PATCH 015/367] Improve case handling in git_diff__paired_foreach This commit reinstates some changes to git_diff__paired_foreach that were discarded during the rebase (because the diff_output.c file had gone away), and also adjusts the case insensitively logic slightly to hopefully deal with either mismatched icase diffs and other case insensitivity scenarios. --- src/diff.c | 78 ++++++++++----- src/diff.h | 1 + src/status.c | 173 ++++++++++++++++------------------ tests-clar/status/worktree.c | 48 ++++++++++ tests-clar/submodule/status.c | 7 +- 5 files changed, 193 insertions(+), 114 deletions(-) diff --git a/src/diff.c b/src/diff.c index feb77b59b..87189a49c 100644 --- a/src/diff.c +++ b/src/diff.c @@ -254,6 +254,13 @@ int git_diff_delta__cmp(const void *a, const void *b) return val ? val : ((int)da->status - (int)db->status); } +int git_diff_delta__casecmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcasecmp(diff_delta__path(da), diff_delta__path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta) { @@ -1197,51 +1204,78 @@ size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type) } int git_diff__paired_foreach( - git_diff_list *idx2head, - git_diff_list *wd2idx, - int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), + git_diff_list *head2idx, + git_diff_list *idx2wd, + int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), void *payload) { int cmp; - git_diff_delta *i2h, *w2i; + git_diff_delta *h2i, *i2w; size_t i, j, i_max, j_max; - int (*strcomp)(const char *, const char *); + int (*strcomp)(const char *, const char *) = git__strcmp; + bool icase_mismatch; - i_max = idx2head ? idx2head->deltas.length : 0; - j_max = wd2idx ? wd2idx->deltas.length : 0; + i_max = head2idx ? head2idx->deltas.length : 0; + j_max = idx2wd ? idx2wd->deltas.length : 0; - /* Get appropriate strcmp function */ - strcomp = idx2head ? idx2head->strcomp : wd2idx ? wd2idx->strcomp : NULL; + /* At some point, tree-to-index diffs will probably never ignore case, + * even if that isn't true now. Index-to-workdir diffs may or may not + * ignore case, but the index filename for the idx2wd diff should + * still be using the canonical case-preserving name. + * + * Therefore the main thing we need to do here is make sure the diffs + * are traversed in a compatible order. To do this, we temporarily + * resort a mismatched diff to get the order correct. + */ + icase_mismatch = + (head2idx != NULL && idx2wd != NULL && + ((head2idx->opts.flags ^ idx2wd->opts.flags) & GIT_DIFF_DELTAS_ARE_ICASE)); - /* Assert both iterators use matching ignore-case. If this function ever - * supports merging diffs that are not sorted by the same function, then - * it will need to spool and sort on one of the results before merging - */ - if (idx2head && wd2idx) { - assert(idx2head->strcomp == wd2idx->strcomp); + /* force case-sensitive delta sort */ + if (icase_mismatch) { + if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { + head2idx->deltas._cmp = git_diff_delta__cmp; + git_vector_sort(&head2idx->deltas); + } else { + idx2wd->deltas._cmp = git_diff_delta__cmp; + git_vector_sort(&idx2wd->deltas); + } } + else if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) + strcomp = git__strcasecmp; for (i = 0, j = 0; i < i_max || j < j_max; ) { - i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL; - w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL; + h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; + i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; - cmp = !w2i ? -1 : !i2h ? 1 : - strcomp(i2h->old_file.path, w2i->old_file.path); + cmp = !i2w ? -1 : !h2i ? 1 : + strcomp(h2i->new_file.path, i2w->old_file.path); if (cmp < 0) { - if (cb(i2h, NULL, payload)) + if (cb(h2i, NULL, payload)) return GIT_EUSER; i++; } else if (cmp > 0) { - if (cb(NULL, w2i, payload)) + if (cb(NULL, i2w, payload)) return GIT_EUSER; j++; } else { - if (cb(i2h, w2i, payload)) + if (cb(h2i, i2w, payload)) return GIT_EUSER; i++; j++; } } + /* restore case-insensitive delta sort */ + if (icase_mismatch) { + if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { + head2idx->deltas._cmp = git_diff_delta__casecmp; + git_vector_sort(&head2idx->deltas); + } else { + idx2wd->deltas._cmp = git_diff_delta__casecmp; + git_vector_sort(&idx2wd->deltas); + } + } + return 0; } diff --git a/src/diff.h b/src/diff.h index ad12e7731..1536f79d7 100644 --- a/src/diff.h +++ b/src/diff.h @@ -74,6 +74,7 @@ extern void git_diff__cleanup_modes( extern void git_diff_list_addref(git_diff_list *diff); extern int git_diff_delta__cmp(const void *a, const void *b); +extern int git_diff_delta__casecmp(const void *a, const void *b); extern bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta); diff --git a/src/status.c b/src/status.c index ba4eef895..9c4aead74 100644 --- a/src/status.c +++ b/src/status.c @@ -130,12 +130,12 @@ static int status_collect( git_diff_delta *idx2wd, void *payload) { - git_status_list *statuslist = payload; + git_status_list *status = payload; git_status_entry *status_entry; - - if (!status_is_included(statuslist, head2idx, idx2wd)) + + if (!status_is_included(status, head2idx, idx2wd)) return 0; - + status_entry = git__malloc(sizeof(git_status_entry)); GITERR_CHECK_ALLOC(status_entry); @@ -143,9 +143,7 @@ static int status_collect( status_entry->head_to_index = head2idx; status_entry->index_to_workdir = idx2wd; - git_vector_insert(&statuslist->paired, status_entry); - - return 0; + return git_vector_insert(&status->paired, status_entry); } GIT_INLINE(int) status_entry_cmp_base( @@ -184,18 +182,23 @@ static int status_entry_cmp(const void *a, const void *b) static git_status_list *git_status_list_alloc(git_index *index) { - git_status_list *statuslist = NULL; + git_status_list *status = NULL; int (*entrycmp)(const void *a, const void *b); + if (!(status = git__calloc(1, sizeof(git_status_list)))) + return NULL; + entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp; - if ((statuslist = git__calloc(1, sizeof(git_status_list))) == NULL || - git_vector_init(&statuslist->paired, 0, entrycmp) < 0) + if (git_vector_init(&status->paired, 0, entrycmp) < 0) { + git__free(status); return NULL; + } - return statuslist; + return status; } +/* static int newfile_cmp(const void *a, const void *b) { const git_diff_delta *delta_a = a; @@ -211,6 +214,7 @@ static int newfile_casecmp(const void *a, const void *b) return git__strcasecmp(delta_a->new_file.path, delta_b->new_file.path); } +*/ int git_status_list_new( git_status_list **out, @@ -218,13 +222,14 @@ int git_status_list_new( const git_status_options *opts) { git_index *index = NULL; - git_status_list *statuslist = NULL; + git_status_list *status = NULL; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; git_diff_find_options findopts_i2w = GIT_DIFF_FIND_OPTIONS_INIT; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; int error = 0; + unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS; assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); @@ -238,123 +243,121 @@ int git_status_list_new( /* if there is no HEAD, that's okay - we'll make an empty iterator */ if (((error = git_repository_head_tree(&head, repo)) < 0) && - !(error == GIT_ENOTFOUND || error == GIT_EORPHANEDHEAD)) + error != GIT_ENOTFOUND && error != GIT_EORPHANEDHEAD) { + git_index_free(index); /* release index */ return error; + } - statuslist = git_status_list_alloc(index); - GITERR_CHECK_ALLOC(statuslist); + status = git_status_list_alloc(index); + GITERR_CHECK_ALLOC(status); - memcpy(&statuslist->opts, opts, sizeof(git_status_options)); - - memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); + if (opts) { + memcpy(&status->opts, opts, sizeof(git_status_options)); + memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); + } diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE; - if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) + if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; - if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) + if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED; - if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) + if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; - if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) + if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; - if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) + if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; - if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0) + if ((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) + if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; findopts_i2w.flags |= GIT_DIFF_FIND_FOR_UNTRACKED; if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { - if ((error = git_diff_tree_to_index(&statuslist->head2idx, repo, head, NULL, &diffopt)) < 0) - goto on_error; + if ((error = git_diff_tree_to_index( + &status->head2idx, repo, head, NULL, &diffopt)) < 0) + goto done; - if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && - (error = git_diff_find_similar(statuslist->head2idx, NULL)) < 0) - goto on_error; + if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && + (error = git_diff_find_similar(status->head2idx, NULL)) < 0) + goto done; } if (show != GIT_STATUS_SHOW_INDEX_ONLY) { - if ((error = git_diff_index_to_workdir(&statuslist->idx2wd, repo, NULL, &diffopt)) < 0) - goto on_error; + if ((error = git_diff_index_to_workdir( + &status->idx2wd, repo, NULL, &diffopt)) < 0) + goto done; - if ((opts->flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && - (error = git_diff_find_similar(statuslist->idx2wd, &findopts_i2w)) < 0) - goto on_error; + if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && + (error = git_diff_find_similar(status->idx2wd, &findopts_i2w)) < 0) + goto done; } if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { - if ((error = git_diff__paired_foreach(statuslist->head2idx, NULL, status_collect, statuslist)) < 0) - goto on_error; + if ((error = git_diff__paired_foreach( + status->head2idx, NULL, status_collect, status)) < 0) + goto done; - git_diff_list_free(statuslist->head2idx); - statuslist->head2idx = NULL; + git_diff_list_free(status->head2idx); + status->head2idx = NULL; } - if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0) { - statuslist->head2idx->deltas._cmp = - (statuslist->head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ? - newfile_casecmp : newfile_cmp; + if ((error = git_diff__paired_foreach( + status->head2idx, status->idx2wd, status_collect, status)) < 0) + goto done; - git_vector_sort(&statuslist->head2idx->deltas); - } - - if ((error = git_diff__paired_foreach(statuslist->head2idx, statuslist->idx2wd, - status_collect, statuslist)) < 0) - goto on_error; - - if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 || - (opts->flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0) - git_vector_sort(&statuslist->paired); - - *out = statuslist; - goto done; - -on_error: - git_status_list_free(statuslist); + if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 || + (flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0) + git_vector_sort(&status->paired); done: + if (error < 0) { + git_status_list_free(status); + status = NULL; + } + + *out = status; + git_tree_free(head); git_index_free(index); return error; } -size_t git_status_list_entrycount(git_status_list *statuslist) +size_t git_status_list_entrycount(git_status_list *status) { - assert(statuslist); + assert(status); - return statuslist->paired.length; + return status->paired.length; } -const git_status_entry *git_status_byindex( - git_status_list *statuslist, - size_t i) +const git_status_entry *git_status_byindex(git_status_list *status, size_t i) { - assert(statuslist); + assert(status); - return git_vector_get(&statuslist->paired, i); + return git_vector_get(&status->paired, i); } -void git_status_list_free(git_status_list *statuslist) +void git_status_list_free(git_status_list *status) { git_status_entry *status_entry; size_t i; - if (statuslist == NULL) + if (status == NULL) return; - git_diff_list_free(statuslist->head2idx); - git_diff_list_free(statuslist->idx2wd); + git_diff_list_free(status->head2idx); + git_diff_list_free(status->idx2wd); - git_vector_foreach(&statuslist->paired, i, status_entry) + git_vector_foreach(&status->paired, i, status_entry) git__free(status_entry); - git_vector_free(&statuslist->paired); + git_vector_free(&status->paired); - git__free(statuslist); + git__memzero(status, sizeof(*status)); + git__free(status); } int git_status_foreach_ext( @@ -363,15 +366,15 @@ int git_status_foreach_ext( git_status_cb cb, void *payload) { - git_status_list *statuslist; + git_status_list *status; const git_status_entry *status_entry; size_t i; int error = 0; - if ((error = git_status_list_new(&statuslist, repo, opts)) < 0) + if ((error = git_status_list_new(&status, repo, opts)) < 0) return error; - git_vector_foreach(&statuslist->paired, i, status_entry) { + git_vector_foreach(&status->paired, i, status_entry) { const char *path = status_entry->head_to_index ? status_entry->head_to_index->old_file.path : status_entry->index_to_workdir->old_file.path; @@ -383,24 +386,14 @@ int git_status_foreach_ext( } } - git_status_list_free(statuslist); + git_status_list_free(status); return error; } -int git_status_foreach( - git_repository *repo, - git_status_cb callback, - void *payload) +int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload) { - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - return git_status_foreach_ext(repo, &opts, callback, payload); + return git_status_foreach_ext(repo, NULL, cb, payload); } struct status_file_info { diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 13335843b..7c27ee588 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -695,3 +695,51 @@ void test_status_worktree__file_status_honors_case_ignorecase_regarding_untracke /* Actually returns GIT_STATUS_IGNORED on Windows */ cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND); } + +void test_status_worktree__simple_delete(void) +{ + git_repository *repo = cl_git_sandbox_init("renames"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + int count; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH | + GIT_STATUS_OPT_EXCLUDE_SUBMODULES | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + count = 0; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__count, &count) ); + cl_assert_equal_i(0, count); + + cl_must_pass(p_unlink("renames/untimely.txt")); + + count = 0; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__count, &count) ); + cl_assert_equal_i(1, count); +} + +void test_status_worktree__simple_delete_indexed(void) +{ + git_repository *repo = cl_git_sandbox_init("renames"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *status; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH | + GIT_STATUS_OPT_EXCLUDE_SUBMODULES | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + cl_git_pass(git_status_list_new(&status, repo, &opts)); + cl_assert_equal_sz(0, git_status_list_entrycount(status)); + git_status_list_free(status); + + cl_must_pass(p_unlink("renames/untimely.txt")); + + cl_git_pass(git_status_list_new(&status, repo, &opts)); + cl_assert_equal_sz(1, git_status_list_entrycount(status)); + cl_assert_equal_i( + GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status); + git_status_list_free(status); +} diff --git a/tests-clar/submodule/status.c b/tests-clar/submodule/status.c index 39c83a0b7..68110bdd5 100644 --- a/tests-clar/submodule/status.c +++ b/tests-clar/submodule/status.c @@ -376,9 +376,12 @@ void test_submodule_status__iterator(void) git_iterator_free(iter); - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - cl_git_pass(git_status_foreach_ext(g_repo, &opts, confirm_submodule_status, &exp)); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, confirm_submodule_status, &exp)); } void test_submodule_status__untracked_dirs_containing_ignored_files(void) From 1540b19990fa5c566d607785c7b3651756e706ff Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 31 May 2013 18:12:49 -0500 Subject: [PATCH 016/367] some simple case-sensitive index tests --- tests-clar/diff/rename.c | 32 ++++++++++++++++++++++++++++++++ tests-clar/index/tests.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 224945e60..6227a54e8 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -906,3 +906,35 @@ void test_diff_rename__rejected_match_can_match_others(void) git_buf_free(&one); git_buf_free(&two); } + +void test_diff_rename__case_changes_are_split(void) +{ + git_index *index; + git_tree *tree; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff = NULL; + diff_expects exp; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/IKEEPSIX.txt")); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "IKEEPSIX.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, NULL)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + + git_index_free(index); +} + diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index 88e374e6e..53d45a84e 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -416,3 +416,35 @@ void test_index_tests__remove_directory(void) git_repository_free(repo); cl_fixture_cleanup("index_test"); } + +void test_index_tests__preserves_case(void) +{ + git_repository *repo; + git_index *index; + const git_index_entry *entry; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_rewritefile("myrepo/test.txt", "hey there\n"); + cl_git_pass(git_index_add_bypath(index, "test.txt")); + + cl_git_pass(p_rename("myrepo/test.txt", "myrepo/TEST.txt")); + cl_git_rewritefile("myrepo/TEST.txt", "hello again\n"); + cl_git_pass(git_index_add_bypath(index, "TEST.txt")); + + cl_assert(git_index_entrycount(index) == 1); + + /* Test access by path instead of index */ + cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); + cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL); + + /* The path should *not* have changed without an explicit remove */ + cl_assert(git__strcmp(entry->path, "test.txt") == 0); + + git_index_free(index); + git_repository_free(repo); +} + From 6ea999bb88e1c5d0be17d823c23728d11adfed47 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:52:12 -0700 Subject: [PATCH 017/367] Make index_insert keep existing case In a case insensitive index, if you attempt to add a file from disk with a different case pattern, the old case pattern in the index should be preserved. This fixes that (and a couple of minor warnings). --- src/index.c | 5 +++-- tests-clar/diff/rename.c | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.c b/src/index.c index 4f0c70135..560a257e7 100644 --- a/src/index.c +++ b/src/index.c @@ -734,8 +734,9 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) if (!replace || !existing) return git_vector_insert(&index->entries, entry); - /* exists, replace it */ - git__free((*existing)->path); + /* exists, replace it (preserving name from existing entry) */ + git__free(entry->path); + entry->path = (*existing)->path; git__free(*existing); *existing = entry; diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 6227a54e8..c4b722314 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -911,7 +911,6 @@ void test_diff_rename__case_changes_are_split(void) { git_index *index; git_tree *tree; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff = NULL; diff_expects exp; From eefef642c8c0d9d527633294acdf9d7a0c9e94c0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 16:09:53 -0700 Subject: [PATCH 018/367] Always do tree to index diffs case sensitively Trees are always case sensitive. The index is always case preserving and will be case sensitive when it is turned into a tree. Therefore the tree and the index can and should always be compared to one another case sensitively. This will restore the index to case insensitive order after the diff has been generated. Consider this a short-term fix. The long term fix is to have the index always stored both case sensitively and case insensitively (at least on platforms that sometimes require case insensitivity). --- src/diff.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/diff.c b/src/diff.c index 87189a49c..fa2c5c71d 100644 --- a/src/diff.c +++ b/src/diff.c @@ -364,6 +364,8 @@ static git_diff_list *diff_list_alloc( diff->strncomp = git__strncasecmp; diff->pfxcomp = git__prefixcmp_icase; diff->entrycomp = git_index_entry__cmp_icase; + + diff->deltas._cmp = git_diff_delta__casecmp; } return diff; @@ -1127,17 +1129,40 @@ int git_diff_tree_to_index( const git_diff_options *opts) { int error = 0; + bool reset_index_ignore_case = false; assert(diff && repo); if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) return error; + if (index->ignore_case) { + git_index__set_ignore_case(index, false); + reset_index_ignore_case = true; + } + DIFF_FROM_ITERATORS( git_iterator_for_tree(&a, old_tree, 0, pfx, pfx), git_iterator_for_index(&b, index, 0, pfx, pfx) ); + if (reset_index_ignore_case) { + git_index__set_ignore_case(index, true); + + if (!error) { + git_diff_list *d = *diff; + + d->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; + d->strcomp = git__strcasecmp; + d->strncomp = git__strncasecmp; + d->pfxcomp = git__prefixcmp_icase; + d->entrycomp = git_index_entry__cmp_icase; + + d->deltas._cmp = git_diff_delta__casecmp; + git_vector_sort(&d->deltas); + } + } + return error; } From fb03a223189f418d0767d7d04ff7509dfcfe8394 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 16:31:11 -0700 Subject: [PATCH 019/367] Test has to work on case sensitive systems --- tests-clar/index/tests.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index 53d45a84e..1bc5e6a07 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -422,12 +422,15 @@ void test_index_tests__preserves_case(void) git_repository *repo; git_index *index; const git_index_entry *entry; + int index_caps; cl_set_cleanup(&cleanup_myrepo, NULL); cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); cl_git_pass(git_repository_index(&index, repo)); + index_caps = git_index_caps(index); + cl_git_rewritefile("myrepo/test.txt", "hey there\n"); cl_git_pass(git_index_add_bypath(index, "test.txt")); @@ -435,15 +438,23 @@ void test_index_tests__preserves_case(void) cl_git_rewritefile("myrepo/TEST.txt", "hello again\n"); cl_git_pass(git_index_add_bypath(index, "TEST.txt")); - cl_assert(git_index_entrycount(index) == 1); + if (index_caps & GIT_INDEXCAP_IGNORE_CASE) + cl_assert_equal_i(1, (int)git_index_entrycount(index)); + else + cl_assert_equal_i(2, (int)git_index_entrycount(index)); /* Test access by path instead of index */ cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); - cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL); - /* The path should *not* have changed without an explicit remove */ cl_assert(git__strcmp(entry->path, "test.txt") == 0); + cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL); + if (index_caps & GIT_INDEXCAP_IGNORE_CASE) + /* The path should *not* have changed without an explicit remove */ + cl_assert(git__strcmp(entry->path, "test.txt") == 0); + else + cl_assert(git__strcmp(entry->path, "TEST.txt") == 0); + git_index_free(index); git_repository_free(repo); } From a1683f28ce2709e615490939e4e244046654d0e5 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 14 Jun 2013 16:18:04 -0700 Subject: [PATCH 020/367] More tests and bug fixes for status with rename This changes the behavior of the status RENAMED flags so that they will be combined with the MODIFIED flags if appropriate. If a file is modified in the index and also renamed, then the status code will have both the GIT_STATUS_INDEX_MODIFIED and INDEX_RENAMED bits set. If it is renamed but the OID has not changed, then just the GIT_STATUS_INDEX_RENAMED bit will be set. Similarly, the flags GIT_STATUS_WT_MODIFIED and GIT_STATUS_WT_RENAMED can both be set independently of one another. This fixes a serious bug where the check for unmodified files that was done at data load time could end up erasing the RENAMED state of a file that was renamed with no changes. Lastly, this contains a bunch of new tests for status with renames, including tests where the only rename changes are case changes. The expected results of these tests have to vary by whether the platform uses a case sensitive filesystem or not, so the expected data covers those platform differences separately. --- src/diff.h | 9 +-- src/diff_patch.c | 6 +- src/diff_print.c | 109 ++++++++++++++------------- src/diff_tform.c | 2 +- src/status.c | 49 ++++++++++--- tests-clar/status/renames.c | 142 +++++++++++++++++++++++++++++++++--- 6 files changed, 237 insertions(+), 80 deletions(-) diff --git a/src/diff.h b/src/diff.h index 1536f79d7..6ef03ee7c 100644 --- a/src/diff.h +++ b/src/diff.h @@ -95,17 +95,16 @@ extern int git_diff__paired_foreach( int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), void *payload); -int git_diff_find_similar__hashsig_for_file( +extern int git_diff_find_similar__hashsig_for_file( void **out, const git_diff_file *f, const char *path, void *p); -int git_diff_find_similar__hashsig_for_buf( +extern int git_diff_find_similar__hashsig_for_buf( void **out, const git_diff_file *f, const char *buf, size_t len, void *p); -void git_diff_find_similar__hashsig_free(void *sig, void *payload); +extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); -int git_diff_find_similar__calc_similarity( +extern int git_diff_find_similar__calc_similarity( int *score, void *siga, void *sigb, void *payload); - #endif diff --git a/src/diff_patch.c b/src/diff_patch.c index a1e1fe84c..40cb3371a 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -175,10 +175,11 @@ static int diff_patch_load(git_diff_patch *patch, git_diff_output *output) goto cleanup; } - /* if we were previously missing an oid, reassess UNMODIFIED state */ + /* if we were previously missing an oid, update MODIFIED->UNMODIFIED */ if (incomplete_data && patch->ofile.file.mode == patch->nfile.file.mode && - git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid)) + git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid) && + patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ patch->delta->status = GIT_DELTA_UNMODIFIED; cleanup: @@ -284,6 +285,7 @@ int git_diff_foreach( git_xdiff_init(&xo, &diff->opts); git_vector_foreach(&diff->deltas, idx, patch.delta) { + /* check flags against patch status */ if (git_diff_delta__should_skip(&diff->opts, patch.delta)) continue; diff --git a/src/diff_print.c b/src/diff_print.c index 244aa6e1d..6fc7425eb 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -41,7 +41,7 @@ static int diff_print_info_init( return 0; } -static char pick_suffix(int mode) +static char diff_pick_suffix(int mode) { if (S_ISDIR(mode)) return '/'; @@ -76,10 +76,11 @@ static int callback_error(void) return GIT_EUSER; } -static int print_compact( +static int diff_print_one_compact( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; + git_buf *out = pi->buf; char old_suffix, new_suffix, code = git_diff_status_char(delta->status); GIT_UNUSED(progress); @@ -87,34 +88,35 @@ static int print_compact( if (code == ' ') return 0; - old_suffix = pick_suffix(delta->old_file.mode); - new_suffix = pick_suffix(delta->new_file.mode); + old_suffix = diff_pick_suffix(delta->old_file.mode); + new_suffix = diff_pick_suffix(delta->new_file.mode); - git_buf_clear(pi->buf); + git_buf_clear(out); if (delta->old_file.path != delta->new_file.path && pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0) - git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code, + git_buf_printf(out, "%c\t%s%c -> %s%c\n", code, delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); else if (delta->old_file.mode != delta->new_file.mode && delta->old_file.mode != 0 && delta->new_file.mode != 0) - git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code, + git_buf_printf(out, "%c\t%s%c (%o -> %o)\n", code, delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode); else if (old_suffix != ' ') - git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); + git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); else - git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path); + git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path); - if (git_buf_oom(pi->buf)) + if (git_buf_oom(out)) return -1; if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + git_buf_cstr(out), git_buf_len(out), pi->payload)) return callback_error(); return 0; } +/* print a git_diff_list to a print callback in compact format */ int git_diff_print_compact( git_diff_list *diff, git_diff_data_cb print_cb, @@ -125,17 +127,18 @@ int git_diff_print_compact( diff_print_info pi; if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) - error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi); + error = git_diff_foreach(diff, diff_print_one_compact, NULL, NULL, &pi); git_buf_free(&buf); return error; } -static int print_raw( +static int diff_print_one_raw( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; + git_buf *out = pi->buf; char code = git_diff_status_char(delta->status); char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; @@ -144,36 +147,37 @@ static int print_raw( if (code == ' ') return 0; - git_buf_clear(pi->buf); + git_buf_clear(out); git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid); git_buf_printf( - pi->buf, ":%06o %06o %s... %s... %c", + out, ":%06o %06o %s... %s... %c", delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); if (delta->similarity > 0) - git_buf_printf(pi->buf, "%03u", delta->similarity); + git_buf_printf(out, "%03u", delta->similarity); if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED) git_buf_printf( - pi->buf, "\t%s %s\n", delta->old_file.path, delta->new_file.path); + out, "\t%s %s\n", delta->old_file.path, delta->new_file.path); else git_buf_printf( - pi->buf, "\t%s\n", delta->old_file.path ? + out, "\t%s\n", delta->old_file.path ? delta->old_file.path : delta->new_file.path); - if (git_buf_oom(pi->buf)) + if (git_buf_oom(out)) return -1; if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + git_buf_cstr(out), git_buf_len(out), pi->payload)) return callback_error(); return 0; } +/* print a git_diff_list to a print callback in raw output format */ int git_diff_print_raw( git_diff_list *diff, git_diff_data_cb print_cb, @@ -184,15 +188,16 @@ int git_diff_print_raw( diff_print_info pi; if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) - error = git_diff_foreach(diff, print_raw, NULL, NULL, &pi); + error = git_diff_foreach(diff, diff_print_one_raw, NULL, NULL, &pi); git_buf_free(&buf); return error; } -static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta) +static int diff_print_oid_range(diff_print_info *pi, const git_diff_delta *delta) { + git_buf *out = pi->buf; char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); @@ -200,27 +205,27 @@ static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta) /* TODO: Match git diff more closely */ if (delta->old_file.mode == delta->new_file.mode) { - git_buf_printf(pi->buf, "index %s..%s %o\n", + git_buf_printf(out, "index %s..%s %o\n", start_oid, end_oid, delta->old_file.mode); } else { if (delta->old_file.mode == 0) { - git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode); + git_buf_printf(out, "new file mode %o\n", delta->new_file.mode); } else if (delta->new_file.mode == 0) { - git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode); + git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode); } else { - git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode); - git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode); + git_buf_printf(out, "old mode %o\n", delta->old_file.mode); + git_buf_printf(out, "new mode %o\n", delta->new_file.mode); } - git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid); + git_buf_printf(out, "index %s..%s\n", start_oid, end_oid); } - if (git_buf_oom(pi->buf)) + if (git_buf_oom(out)) return -1; return 0; } -static int print_patch_file( +static int diff_print_patch_file( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; @@ -247,7 +252,7 @@ static int print_patch_file( git_buf_clear(pi->buf); git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path); - if (print_oid_range(pi, delta) < 0) + if (diff_print_oid_range(pi, delta) < 0) return -1; if (git_oid_iszero(&delta->old_file.oid)) { @@ -288,7 +293,7 @@ static int print_patch_file( return 0; } -static int print_patch_hunk( +static int diff_print_patch_hunk( const git_diff_delta *d, const git_diff_range *r, const char *header, @@ -311,7 +316,7 @@ static int print_patch_hunk( return 0; } -static int print_patch_line( +static int diff_print_patch_line( const git_diff_delta *delta, const git_diff_range *range, char line_origin, /* GIT_DIFF_LINE value from above */ @@ -343,6 +348,7 @@ static int print_patch_line( return 0; } +/* print a git_diff_list to an output callback in patch format */ int git_diff_print_patch( git_diff_list *diff, git_diff_data_cb print_cb, @@ -354,27 +360,15 @@ int git_diff_print_patch( if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) error = git_diff_foreach( - diff, print_patch_file, print_patch_hunk, print_patch_line, &pi); + diff, diff_print_patch_file, diff_print_patch_hunk, + diff_print_patch_line, &pi); git_buf_free(&buf); return error; } - -static int print_to_buffer_cb( - const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, - const char *content, - size_t content_len, - void *payload) -{ - git_buf *output = payload; - GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); - return git_buf_put(output, content, content_len); -} - +/* print a git_diff_patch to an output callback */ int git_diff_patch_print( git_diff_patch *patch, git_diff_data_cb print_cb, @@ -389,13 +383,28 @@ int git_diff_patch_print( if (!(error = diff_print_info_init( &pi, &temp, git_diff_patch__diff(patch), print_cb, payload))) error = git_diff_patch__invoke_callbacks( - patch, print_patch_file, print_patch_hunk, print_patch_line, &pi); + patch, diff_print_patch_file, diff_print_patch_hunk, + diff_print_patch_line, &pi); git_buf_free(&temp); return error; } +static int diff_print_to_buffer_cb( + const git_diff_delta *delta, + const git_diff_range *range, + char line_origin, + const char *content, + size_t content_len, + void *payload) +{ + git_buf *output = payload; + GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); + return git_buf_put(output, content, content_len); +} + +/* print a git_diff_patch to a string buffer */ int git_diff_patch_to_str( char **string, git_diff_patch *patch) @@ -403,7 +412,7 @@ int git_diff_patch_to_str( int error; git_buf output = GIT_BUF_INIT; - error = git_diff_patch_print(patch, print_to_buffer_cb, &output); + error = git_diff_patch_print(patch, diff_print_to_buffer_cb, &output); /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, * meaning a memory allocation failure, so just map to -1... diff --git a/src/diff_tform.c b/src/diff_tform.c index 94fa035f2..64746e7dd 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -483,7 +483,7 @@ static int similarity_measure( if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode)) return 0; - /* if exact match is requested, force calculation of missing OIDs */ + /* if exact match is requested, force calculation of missing OIDs now */ if (exact_match) { if (git_oid_iszero(&a_file->oid) && diff->old_src == GIT_ITERATOR_TYPE_WORKDIR && diff --git a/src/status.c b/src/status.c index 9c4aead74..375100a89 100644 --- a/src/status.c +++ b/src/status.c @@ -20,11 +20,11 @@ #include "git2/diff.h" #include "diff.h" -static unsigned int index_delta2status(git_delta_t index_status) +static unsigned int index_delta2status(const git_diff_delta *head2idx) { - unsigned int st = GIT_STATUS_CURRENT; + git_status_t st = GIT_STATUS_CURRENT; - switch (index_status) { + switch (head2idx->status) { case GIT_DELTA_ADDED: case GIT_DELTA_COPIED: st = GIT_STATUS_INDEX_NEW; @@ -37,6 +37,9 @@ static unsigned int index_delta2status(git_delta_t index_status) break; case GIT_DELTA_RENAMED: st = GIT_STATUS_INDEX_RENAMED; + + if (!git_oid_equal(&head2idx->old_file.oid, &head2idx->new_file.oid)) + st |= GIT_STATUS_INDEX_MODIFIED; break; case GIT_DELTA_TYPECHANGE: st = GIT_STATUS_INDEX_TYPECHANGE; @@ -48,11 +51,12 @@ static unsigned int index_delta2status(git_delta_t index_status) return st; } -static unsigned int workdir_delta2status(git_delta_t workdir_status) +static unsigned int workdir_delta2status( + git_diff_list *diff, git_diff_delta *idx2wd) { - unsigned int st = GIT_STATUS_CURRENT; + git_status_t st = GIT_STATUS_CURRENT; - switch (workdir_status) { + switch (idx2wd->status) { case GIT_DELTA_ADDED: case GIT_DELTA_COPIED: case GIT_DELTA_UNTRACKED: @@ -69,6 +73,28 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) break; case GIT_DELTA_RENAMED: st = GIT_STATUS_WT_RENAMED; + + if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid)) { + /* if OIDs don't match, we might need to calculate them now to + * discern between RENAMED vs RENAMED+MODIFED + */ + if (git_oid_iszero(&idx2wd->old_file.oid) && + diff->old_src == GIT_ITERATOR_TYPE_WORKDIR && + !git_diff__oid_for_file( + diff->repo, idx2wd->old_file.path, idx2wd->old_file.mode, + idx2wd->old_file.size, &idx2wd->old_file.oid)) + idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; + + if (git_oid_iszero(&idx2wd->new_file.oid) && + diff->new_src == GIT_ITERATOR_TYPE_WORKDIR && + !git_diff__oid_for_file( + diff->repo, idx2wd->new_file.path, idx2wd->new_file.mode, + idx2wd->new_file.size, &idx2wd->new_file.oid)) + idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; + + if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid)) + st |= GIT_STATUS_WT_MODIFIED; + } break; case GIT_DELTA_TYPECHANGE: st = GIT_STATUS_WT_TYPECHANGE; @@ -111,18 +137,19 @@ static bool status_is_included( } static git_status_t status_compute( + git_status_list *status, git_diff_delta *head2idx, git_diff_delta *idx2wd) { - git_status_t status = 0; + git_status_t st = GIT_STATUS_CURRENT; if (head2idx) - status |= index_delta2status(head2idx->status); + st |= index_delta2status(head2idx); if (idx2wd) - status |= workdir_delta2status(idx2wd->status); + st |= workdir_delta2status(status->idx2wd, idx2wd); - return status; + return st; } static int status_collect( @@ -139,7 +166,7 @@ static int status_collect( status_entry = git__malloc(sizeof(git_status_entry)); GITERR_CHECK_ALLOC(status_entry); - status_entry->status = status_compute(head2idx, idx2wd); + status_entry->status = status_compute(status, head2idx, idx2wd); status_entry->head_to_index = head2idx; status_entry->index_to_workdir = idx2wd; diff --git a/tests-clar/status/renames.c b/tests-clar/status/renames.c index d29c7bfe8..80ff26020 100644 --- a/tests-clar/status/renames.c +++ b/tests-clar/status/renames.c @@ -61,13 +61,13 @@ static void test_status( const char *oldname, *newname; size_t i; - cl_assert(expected_len == git_status_list_entrycount(status_list)); + cl_assert_equal_sz(expected_len, git_status_list_entrycount(status_list)); for (i = 0; i < expected_len; i++) { actual = git_status_byindex(status_list, i); expected = &expected_list[i]; - cl_assert(actual->status == expected->status); + cl_assert_equal_i((int)expected->status, (int)actual->status); oldname = actual->head_to_index ? actual->head_to_index->old_file.path : actual->index_to_workdir ? actual->index_to_workdir->old_file.path : NULL; @@ -119,8 +119,10 @@ void test_status_renames__head2index_two(void) git_status_list *statuslist; git_status_options opts = GIT_STATUS_OPTIONS_INIT; struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "aaa.txt" }, - { GIT_STATUS_INDEX_RENAMED, "untimely.txt", "bbb.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, + "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, + "untimely.txt", "bbb.txt" }, { GIT_STATUS_INDEX_RENAMED, "songof7cities.txt", "ccc.txt" }, { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "ddd.txt" }, }; @@ -174,8 +176,10 @@ void test_status_renames__index2workdir_two(void) git_status_list *statuslist; git_status_options opts = GIT_STATUS_OPTIONS_INIT; struct status_entry expected[] = { - { GIT_STATUS_WT_RENAMED, "sixserving.txt", "aaa.txt" }, - { GIT_STATUS_WT_RENAMED, "untimely.txt", "bbb.txt" }, + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "untimely.txt", "bbb.txt" }, { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "ccc.txt" }, { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ddd.txt" }, }; @@ -199,7 +203,8 @@ void test_status_renames__both_one(void) git_status_list *statuslist; git_status_options opts = GIT_STATUS_OPTIONS_INIT; struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname-workdir.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "ikeepsix.txt", "newname-workdir.txt" }, }; opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; @@ -229,10 +234,15 @@ void test_status_renames__both_two(void) git_status_list *statuslist; git_status_options opts = GIT_STATUS_OPTIONS_INIT; struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ikeepsix-both.txt" }, - { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "sixserving-index.txt" }, - { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "songof7cities-workdir.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "untimely.txt", "untimely-both.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "ikeepsix-both.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, + "sixserving.txt", "sixserving-index.txt" }, + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "songof7cities.txt", "songof7cities-workdir.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "untimely.txt", "untimely-both.txt" }, }; opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; @@ -263,3 +273,113 @@ void test_status_renames__both_two(void) git_index_free(index); } + +void test_status_renames__both_casechange_one(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + int index_caps; + struct status_entry expected_icase[] = { + { GIT_STATUS_INDEX_RENAMED, + "ikeepsix.txt", "IKeepSix.txt" }, + }; + struct status_entry expected_case[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "ikeepsix.txt", "IKEEPSIX.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + index_caps = git_index_caps(index); + + rename_file(g_repo, "ikeepsix.txt", "IKeepSix.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt")); + cl_git_pass(git_index_write(index)); + + /* on a case-insensitive file system, this change won't matter. + * on a case-sensitive one, it will. + */ + rename_file(g_repo, "IKeepSix.txt", "IKEEPSIX.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + + test_status(statuslist, (index_caps & GIT_INDEXCAP_IGNORE_CASE) ? + expected_icase : expected_case, 1); + + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__both_casechange_two(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + int index_caps; + struct status_entry expected_icase[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "IKeepSix.txt" }, + { GIT_STATUS_INDEX_MODIFIED, + "sixserving.txt", "sixserving.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_MODIFIED, + "songof7cities.txt", "songof7.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "untimely.txt", "untimeliest.txt" } + }; + struct status_entry expected_case[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_RENAMED, + "sixserving.txt", "SixServing.txt" }, + { GIT_STATUS_INDEX_RENAMED | + GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_RENAMED, + "songof7cities.txt", "SONGOF7.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "untimely.txt", "untimeliest.txt" } + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + index_caps = git_index_caps(index); + + rename_and_edit_file(g_repo, "ikeepsix.txt", "IKeepSix.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "sixserving.txt"); + rename_file(g_repo, "songof7cities.txt", "songof7.txt"); + rename_file(g_repo, "untimely.txt", "untimelier.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_add_bypath(index, "songof7.txt")); + cl_git_pass(git_index_add_bypath(index, "untimelier.txt")); + cl_git_pass(git_index_write(index)); + + rename_and_edit_file(g_repo, "IKeepSix.txt", "ikeepsix.txt"); + rename_file(g_repo, "sixserving.txt", "SixServing.txt"); + rename_and_edit_file(g_repo, "songof7.txt", "SONGOF7.txt"); + rename_file(g_repo, "untimelier.txt", "untimeliest.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + + test_status(statuslist, (index_caps & GIT_INDEXCAP_IGNORE_CASE) ? + expected_icase : expected_case, 4); + + git_status_list_free(statuslist); + + git_index_free(index); +} From f3b5bc835ae4345a7a03834ffaf54a0aca92387d Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sun, 16 Jun 2013 21:51:43 -0700 Subject: [PATCH 021/367] Add test of rename with no changes A tree to index rename with no changes was getting erased by the iteration routine (if the routine actually loaded the data for the unmodified file). This invokes the code path that was previously messing up the diff and iterates twice to make sure that the iteration process itself doesn't modify the data. --- tests-clar/diff/rename.c | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index c4b722314..8a08da3ef 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -899,6 +899,7 @@ void test_diff_rename__rejected_match_can_match_others(void) cl_git_pass( git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect)); + git_diff_list_free(diff); git_tree_free(tree); git_index_free(index); git_reference_free(head); @@ -913,6 +914,7 @@ void test_diff_rename__case_changes_are_split(void) git_tree *tree; git_diff_list *diff = NULL; diff_expects exp; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; cl_git_pass(git_repository_index(&index, g_repo)); @@ -934,6 +936,61 @@ void test_diff_rename__case_changes_are_split(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_list_free(diff); git_index_free(index); } +void test_diff_rename__unmodified_can_be_renamed(void) +{ + git_index *index; + git_tree *tree; + git_diff_list *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_list_free(diff); +} From de0555a347b4be48f80d0b3bf26ddd81f5ef38aa Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 17 Jun 2013 09:55:29 -0700 Subject: [PATCH 022/367] Fix memory leaks in diff rename tests This fixes a couple objects I forgot to free, and also updates the valgrind suppressions file on the Mac to cover a few more cases that had crept in. --- tests-clar/diff/rename.c | 3 +++ tests-clar/valgrind-supp-mac.txt | 14 ++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 8a08da3ef..2600bd872 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -947,6 +947,7 @@ void test_diff_rename__case_changes_are_split(void) git_diff_list_free(diff); git_index_free(index); + git_tree_free(tree); } void test_diff_rename__unmodified_can_be_renamed(void) @@ -993,4 +994,6 @@ void test_diff_rename__unmodified_can_be_renamed(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); git_diff_list_free(diff); + git_index_free(index); + git_tree_free(tree); } diff --git a/tests-clar/valgrind-supp-mac.txt b/tests-clar/valgrind-supp-mac.txt index 297b11e62..fcc7ede86 100644 --- a/tests-clar/valgrind-supp-mac.txt +++ b/tests-clar/valgrind-supp-mac.txt @@ -113,24 +113,18 @@ { mac-ssl-leak-1 Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc ... fun:ERR_load_strings } { mac-ssl-leak-2 Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc ... fun:SSL_library_init } { mac-ssl-leak-3 Memcheck:Leak - fun:malloc - fun:strdup ... fun:si_module_with_name fun:getaddrinfo @@ -143,6 +137,14 @@ ... fun:ssl3_get_server_certificate } +{ + mac-ssl-leak-5 + Memcheck:Leak + fun:malloc + fun:CRYPTO_malloc + ... + fun:ERR_put_error +} { clar-printf-buf Memcheck:Leak From f4183347607c85d3fbe02e8591d9393a011ecdf2 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 17 Jun 2013 10:23:53 -0700 Subject: [PATCH 023/367] Update clar to latest version --- tests-clar/clar.c | 48 +++++++++++++++++++++++---------------- tests-clar/clar/sandbox.h | 12 ++++++---- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/tests-clar/clar.c b/tests-clar/clar.c index 0eae81bf5..fb10dd397 100644 --- a/tests-clar/clar.c +++ b/tests-clar/clar.c @@ -183,10 +183,10 @@ clar_run_test( } static void -clar_run_suite(const struct clar_suite *suite, const char *name) +clar_run_suite(const struct clar_suite *suite, const char *filter) { const struct clar_func *test = suite->tests; - size_t i, namelen; + size_t i, matchlen; if (!suite->enabled) return; @@ -200,21 +200,21 @@ clar_run_suite(const struct clar_suite *suite, const char *name) _clar.active_suite = suite->name; _clar.suite_errors = 0; - if (name) { + if (filter) { size_t suitelen = strlen(suite->name); - namelen = strlen(name); - if (namelen <= suitelen) { - name = NULL; + matchlen = strlen(filter); + if (matchlen <= suitelen) { + filter = NULL; } else { - name += suitelen; - while (*name == ':') - ++name; - namelen = strlen(name); + filter += suitelen; + while (*filter == ':') + ++filter; + matchlen = strlen(filter); } } for (i = 0; i < suite->test_count; ++i) { - if (name && strncmp(test[i].name, name, namelen)) + if (filter && strncmp(test[i].name, filter, matchlen)) continue; _clar.active_test = test[i].name; @@ -230,7 +230,7 @@ clar_usage(const char *arg) { printf("Usage: %s [options]\n\n", arg); printf("Options:\n"); - printf(" -sname\tRun only the suite with `name`\n"); + printf(" -sname\tRun only the suite with `name` (can go to individual test name)\n"); printf(" -iname\tInclude the suite with `name`\n"); printf(" -xname\tExclude the suite with `name`\n"); printf(" -q \tOnly report tests that had an error\n"); @@ -256,21 +256,20 @@ clar_parse_args(int argc, char **argv) case 'x': { /* given suite name */ int offset = (argument[2] == '=') ? 3 : 2, found = 0; char action = argument[1]; - size_t j, len, cmplen; + size_t j, arglen, suitelen, cmplen; argument += offset; - len = strlen(argument); + arglen = strlen(argument); - if (len == 0) + if (arglen == 0) clar_usage(argv[0]); for (j = 0; j < _clar_suite_count; ++j) { - cmplen = strlen(_clar_suites[j].name); - if (cmplen > len) - cmplen = len; + suitelen = strlen(_clar_suites[j].name); + cmplen = (arglen < suitelen) ? arglen : suitelen; if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { - int exact = !strcmp(argument, _clar_suites[j].name); + int exact = (arglen >= suitelen); ++found; @@ -419,7 +418,16 @@ void clar__assert_equal_s( if (!match) { char buf[4096]; - snprint_eq(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + + if (s1 && s2) { + int pos; + for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) + /* find differing byte offset */; + snprint_eq(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", s1, s2, pos); + } else { + snprint_eq(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + } + clar__fail(file, line, err, buf, should_abort); } } diff --git a/tests-clar/clar/sandbox.h b/tests-clar/clar/sandbox.h index bed3011fe..1ca6fcae8 100644 --- a/tests-clar/clar/sandbox.h +++ b/tests-clar/clar/sandbox.h @@ -18,9 +18,9 @@ static int find_tmp_path(char *buffer, size_t length) { #ifndef _WIN32 - static const size_t var_count = 4; + static const size_t var_count = 5; static const char *env_vars[] = { - "TMPDIR", "TMP", "TEMP", "USERPROFILE" + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" }; size_t i; @@ -43,6 +43,12 @@ find_tmp_path(char *buffer, size_t length) } #else + DWORD env_len; + + if ((env_len = GetEnvironmentVariable("CLAR_TMP", buffer, length)) > 0 && + env_len < length) + return 0; + if (GetTempPath((DWORD)length, buffer)) return 0; #endif @@ -61,9 +67,7 @@ static void clar_unsandbox(void) if (_clar_path[0] == '\0') return; -#ifdef _WIN32 chdir(".."); -#endif fs_rm(_clar_path); } From 74ded024572318a32ff537c5f8dce001e9812e6b Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 17 Jun 2013 17:03:34 -0700 Subject: [PATCH 024/367] Add "as_path" parameters to blob and buffer diffs This adds parameters to the four functions that allow for blob-to- blob and blob-to-buffer differencing (either via callbacks or by making a git_diff_patch object). These parameters let you say that filename we should pretend the blob has while doing the diff. If you pass NULL, there should be no change from the existing behavior, which is to skip using attributes for file type checks and just look at content. With the parameters, you can plug into the new diff driver functionality and get binary or non-binary behavior, plus function context regular expressions, etc. This commit also fixes things so that the git_diff_delta that is generated by these functions will actually be populated with the data that we know about the blobs (or buffers) so you can use it appropriately. It also fixes a bug in generating patches from the git_diff_patch objects created via these functions. Lastly, there is one other behavior change that may matter. If there is no difference between the two blobs, these functions no longer generate any diff callbacks / patches unless you have passed in GIT_DIFF_INCLUDE_UNMODIFIED. This is pretty natural, but could potentially change the behavior of existing usage. --- include/git2/diff.h | 20 +- src/diff_file.c | 160 +++++++------ src/diff_file.h | 9 +- src/diff_patch.c | 159 +++++++++---- src/diff_print.c | 22 +- tests-clar/diff/blob.c | 517 ++++++++++++++++++++++++++++++++--------- 6 files changed, 641 insertions(+), 246 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 8113a56be..f352f2843 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -983,7 +983,9 @@ GIT_EXTERN(int) git_diff_patch_to_str( * `GIT_DIFF_FORCE_TEXT` of course). * * @param old_blob Blob for old side of diff, or NULL for empty blob + * @param old_as_path Treat old blob as if it had this filename; can be NULL * @param new_blob Blob for new side of diff, or NULL for empty blob + * @param new_as_path Treat new blob as if it had this filename; can be NULL * @param options Options for diff, or NULL for default options * @param file_cb Callback for "file"; made once if there is a diff; can be NULL * @param hunk_cb Callback for each hunk in diff; can be NULL @@ -993,7 +995,9 @@ GIT_EXTERN(int) git_diff_patch_to_str( */ GIT_EXTERN(int) git_diff_blobs( const git_blob *old_blob, + const char *old_as_path, const git_blob *new_blob, + const char *new_as_path, const git_diff_options *options, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, @@ -1010,14 +1014,18 @@ GIT_EXTERN(int) git_diff_blobs( * * @param out The generated patch; NULL on error * @param old_blob Blob for old side of diff, or NULL for empty blob + * @param old_as_path Treat old blob as if it had this filename; can be NULL * @param new_blob Blob for new side of diff, or NULL for empty blob + * @param new_as_path Treat new blob as if it had this filename; can be NULL * @param options Options for diff, or NULL for default options * @return 0 on success or error code < 0 */ GIT_EXTERN(int) git_diff_patch_from_blobs( git_diff_patch **out, const git_blob *old_blob, + const char *old_as_path, const git_blob *new_blob, + const char *new_as_path, const git_diff_options *opts); /** @@ -1033,8 +1041,10 @@ GIT_EXTERN(int) git_diff_patch_from_blobs( * the reverse, with GIT_DELTA_REMOVED and blob content removed. * * @param old_blob Blob for old side of diff, or NULL for empty blob + * @param old_as_path Treat old blob as if it had this filename; can be NULL * @param buffer Raw data for new side of diff, or NULL for empty * @param buffer_len Length of raw data for new side of diff + * @param buffer_as_path Treat buffer as if it had this filename; can be NULL * @param options Options for diff, or NULL for default options * @param file_cb Callback for "file"; made once if there is a diff; can be NULL * @param hunk_cb Callback for each hunk in diff; can be NULL @@ -1044,8 +1054,10 @@ GIT_EXTERN(int) git_diff_patch_from_blobs( */ GIT_EXTERN(int) git_diff_blob_to_buffer( const git_blob *old_blob, + const char *old_as_path, const char *buffer, size_t buffer_len, + const char *buffer_as_path, const git_diff_options *options, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, @@ -1062,16 +1074,20 @@ GIT_EXTERN(int) git_diff_blob_to_buffer( * * @param out The generated patch; NULL on error * @param old_blob Blob for old side of diff, or NULL for empty blob + * @param old_as_path Treat old blob as if it had this filename; can be NULL * @param buffer Raw data for new side of diff, or NULL for empty * @param buffer_len Length of raw data for new side of diff + * @param buffer_as_path Treat buffer as if it had this filename; can be NULL * @param options Options for diff, or NULL for default options * @return 0 on success or error code < 0 */ GIT_EXTERN(int) git_diff_patch_from_blob_and_buffer( git_diff_patch **out, const git_blob *old_blob, - const char *buf, - size_t buflen, + const char *old_as_path, + const char *buffer, + size_t buffer_len, + const char *buffer_as_path, const git_diff_options *opts); diff --git a/src/diff_file.c b/src/diff_file.c index 4fd1177ae..9d06daafa 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -18,23 +18,23 @@ static bool diff_file_content_binary_by_size(git_diff_file_content *fc) { /* if we have diff opts, check max_size vs file size */ - if ((fc->file.flags & DIFF_FLAGS_KNOWN_BINARY) == 0 && + if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 && fc->opts_max_size > 0 && - fc->file.size > fc->opts_max_size) - fc->file.flags |= GIT_DIFF_FLAG_BINARY; + fc->file->size > fc->opts_max_size) + fc->file->flags |= GIT_DIFF_FLAG_BINARY; - return ((fc->file.flags & GIT_DIFF_FLAG_BINARY) != 0); + return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0); } static void diff_file_content_binary_by_content(git_diff_file_content *fc) { - if ((fc->file.flags & DIFF_FLAGS_KNOWN_BINARY) != 0) + if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) return; switch (git_diff_driver_content_is_binary( fc->driver, fc->map.data, fc->map.len)) { - case 0: fc->file.flags |= GIT_DIFF_FLAG_NOT_BINARY; break; - case 1: fc->file.flags |= GIT_DIFF_FLAG_BINARY; break; + case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break; + case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break; default: break; } } @@ -48,38 +48,39 @@ static int diff_file_content_init_common( fc->opts_max_size = opts->max_size ? opts->max_size : DIFF_MAX_FILESIZE; - if (!fc->driver) { - if (git_diff_driver_lookup(&fc->driver, fc->repo, "") < 0) - return -1; + if (fc->src == GIT_ITERATOR_TYPE_EMPTY) fc->src = GIT_ITERATOR_TYPE_TREE; - } + + if (!fc->driver && + git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0) + return -1; /* give driver a chance to modify options */ git_diff_driver_update_options(&fc->opts_flags, fc->driver); /* make sure file is conceivable mmap-able */ - if ((git_off_t)((size_t)fc->file.size) != fc->file.size) - fc->file.flags |= GIT_DIFF_FLAG_BINARY; + if ((git_off_t)((size_t)fc->file->size) != fc->file->size) + fc->file->flags |= GIT_DIFF_FLAG_BINARY; /* check if user is forcing text diff the file */ else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) { - fc->file.flags &= ~GIT_DIFF_FLAG_BINARY; - fc->file.flags |= GIT_DIFF_FLAG_NOT_BINARY; + fc->file->flags &= ~GIT_DIFF_FLAG_BINARY; + fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; } /* check if user is forcing binary diff the file */ else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) { - fc->file.flags &= ~GIT_DIFF_FLAG_NOT_BINARY; - fc->file.flags |= GIT_DIFF_FLAG_BINARY; + fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY; + fc->file->flags |= GIT_DIFF_FLAG_BINARY; } diff_file_content_binary_by_size(fc); - if ((fc->file.flags & GIT_DIFF_FLAG__NO_DATA) != 0) { - fc->file.flags |= GIT_DIFF_FLAG__LOADED; + if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) { + fc->flags |= GIT_DIFF_FLAG__LOADED; fc->map.len = 0; fc->map.data = ""; } - if ((fc->file.flags & GIT_DIFF_FLAG__LOADED) != 0) + if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) diff_file_content_binary_by_content(fc); return 0; @@ -92,15 +93,14 @@ int git_diff_file_content__init_from_diff( bool use_old) { git_diff_delta *delta = git_vector_get(&diff->deltas, delta_index); - git_diff_file *file = use_old ? &delta->old_file : &delta->new_file; bool has_data = true; memset(fc, 0, sizeof(*fc)); fc->repo = diff->repo; + fc->file = use_old ? &delta->old_file : &delta->new_file; fc->src = use_old ? diff->old_src : diff->new_src; - memcpy(&fc->file, file, sizeof(fc->file)); - if (git_diff_driver_lookup(&fc->driver, fc->repo, file->path) < 0) + if (git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0) return -1; switch (delta->status) { @@ -122,7 +122,7 @@ int git_diff_file_content__init_from_diff( } if (!has_data) - fc->file.flags |= GIT_DIFF_FLAG__NO_DATA; + fc->flags |= GIT_DIFF_FLAG__NO_DATA; return diff_file_content_init_common(fc, &diff->opts); } @@ -131,21 +131,24 @@ int git_diff_file_content__init_from_blob( git_diff_file_content *fc, git_repository *repo, const git_diff_options *opts, - const git_blob *blob) + const git_blob *blob, + git_diff_file *as_file) { memset(fc, 0, sizeof(*fc)); fc->repo = repo; + fc->file = as_file; fc->blob = blob; if (!blob) { - fc->file.flags |= GIT_DIFF_FLAG__NO_DATA; + fc->flags |= GIT_DIFF_FLAG__NO_DATA; } else { - fc->file.flags |= GIT_DIFF_FLAG__LOADED | GIT_DIFF_FLAG_VALID_OID; - fc->file.size = git_blob_rawsize(blob); - fc->file.mode = 0644; - git_oid_cpy(&fc->file.oid, git_blob_id(blob)); + fc->flags |= GIT_DIFF_FLAG__LOADED; + fc->file->flags |= GIT_DIFF_FLAG_VALID_OID; + fc->file->size = git_blob_rawsize(blob); + fc->file->mode = GIT_FILEMODE_BLOB; + git_oid_cpy(&fc->file->oid, git_blob_id(blob)); - fc->map.len = (size_t)fc->file.size; + fc->map.len = (size_t)fc->file->size; fc->map.data = (char *)git_blob_rawcontent(blob); } @@ -157,18 +160,21 @@ int git_diff_file_content__init_from_raw( git_repository *repo, const git_diff_options *opts, const char *buf, - size_t buflen) + size_t buflen, + git_diff_file *as_file) { memset(fc, 0, sizeof(*fc)); fc->repo = repo; + fc->file = as_file; if (!buf) { - fc->file.flags |= GIT_DIFF_FLAG__NO_DATA; + fc->flags |= GIT_DIFF_FLAG__NO_DATA; } else { - fc->file.flags |= GIT_DIFF_FLAG__LOADED | GIT_DIFF_FLAG_VALID_OID; - fc->file.size = buflen; - fc->file.mode = 0644; - git_odb_hash(&fc->file.oid, buf, buflen, GIT_OBJ_BLOB); + fc->flags |= GIT_DIFF_FLAG__LOADED; + fc->file->flags |= GIT_DIFF_FLAG_VALID_OID; + fc->file->size = buflen; + fc->file->mode = GIT_FILEMODE_BLOB; + git_odb_hash(&fc->file->oid, buf, buflen, GIT_OBJ_BLOB); fc->map.len = buflen; fc->map.data = (char *)buf; @@ -190,7 +196,7 @@ static int diff_file_content_commit_to_str( unsigned int sm_status = 0; const git_oid *sm_head; - if ((error = git_submodule_lookup(&sm, fc->repo, fc->file.path)) < 0 || + if ((error = git_submodule_lookup(&sm, fc->repo, fc->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) @@ -199,25 +205,25 @@ static int diff_file_content_commit_to_str( } /* update OID if we didn't have it previously */ - if ((fc->file.flags & GIT_DIFF_FLAG_VALID_OID) == 0 && + if ((fc->file->flags & GIT_DIFF_FLAG_VALID_OID) == 0 && ((sm_head = git_submodule_wd_id(sm)) != NULL || (sm_head = git_submodule_head_id(sm)) != NULL)) { - git_oid_cpy(&fc->file.oid, sm_head); - fc->file.flags |= GIT_DIFF_FLAG_VALID_OID; + git_oid_cpy(&fc->file->oid, sm_head); + fc->file->flags |= GIT_DIFF_FLAG_VALID_OID; } if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) status = "-dirty"; } - git_oid_tostr(oid, sizeof(oid), &fc->file.oid); + git_oid_tostr(oid, sizeof(oid), &fc->file->oid); if (git_buf_printf(&content, "Subproject commit %s%s\n", oid, status) < 0) return -1; fc->map.len = git_buf_len(&content); fc->map.data = git_buf_detach(&content); - fc->file.flags |= GIT_DIFF_FLAG__FREE_DATA; + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; return 0; } @@ -227,27 +233,27 @@ static int diff_file_content_load_blob(git_diff_file_content *fc) int error = 0; git_odb_object *odb_obj = NULL; - if (git_oid_iszero(&fc->file.oid)) + if (git_oid_iszero(&fc->file->oid)) return 0; - if (fc->file.mode == GIT_FILEMODE_COMMIT) + if (fc->file->mode == GIT_FILEMODE_COMMIT) return diff_file_content_commit_to_str(fc, false); /* if we don't know size, try to peek at object header first */ - if (!fc->file.size) { + if (!fc->file->size) { git_odb *odb; size_t len; git_otype type; if (!(error = git_repository_odb__weakptr(&odb, fc->repo))) { error = git_odb__read_header_or_object( - &odb_obj, &len, &type, odb, &fc->file.oid); + &odb_obj, &len, &type, odb, &fc->file->oid); git_odb_free(odb); } if (error) return error; - fc->file.size = len; + fc->file->size = len; } if (diff_file_content_binary_by_size(fc)) @@ -259,11 +265,11 @@ static int diff_file_content_load_blob(git_diff_file_content *fc) git_odb_object_free(odb_obj); } else { error = git_blob_lookup( - (git_blob **)&fc->blob, fc->repo, &fc->file.oid); + (git_blob **)&fc->blob, fc->repo, &fc->file->oid); } if (!error) { - fc->file.flags |= GIT_DIFF_FLAG__FREE_BLOB; + fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; fc->map.data = (void *)git_blob_rawcontent(fc->blob); fc->map.len = (size_t)git_blob_rawsize(fc->blob); } @@ -279,16 +285,16 @@ static int diff_file_content_load_workdir_symlink( /* link path on disk could be UTF-16, so prepare a buffer that is * big enough to handle some UTF-8 data expansion */ - alloc_len = (ssize_t)(fc->file.size * 2) + 1; + alloc_len = (ssize_t)(fc->file->size * 2) + 1; fc->map.data = git__calloc(alloc_len, sizeof(char)); GITERR_CHECK_ALLOC(fc->map.data); - fc->file.flags |= GIT_DIFF_FLAG__FREE_DATA; + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; read_len = p_readlink(git_buf_cstr(path), fc->map.data, alloc_len); if (read_len < 0) { - giterr_set(GITERR_OS, "Failed to read symlink '%s'", fc->file.path); + giterr_set(GITERR_OS, "Failed to read symlink '%s'", fc->file->path); return -1; } @@ -307,28 +313,28 @@ static int diff_file_content_load_workdir_file( if (fd < 0) return fd; - if (!fc->file.size && - !(fc->file.size = git_futils_filesize(fd))) + if (!fc->file->size && + !(fc->file->size = git_futils_filesize(fd))) goto cleanup; if (diff_file_content_binary_by_size(fc)) goto cleanup; if ((error = git_filters_load( - &filters, fc->repo, fc->file.path, GIT_FILTER_TO_ODB)) < 0) + &filters, fc->repo, fc->file->path, GIT_FILTER_TO_ODB)) < 0) goto cleanup; /* error >= is a filter count */ if (error == 0) { if (!(error = git_futils_mmap_ro( - &fc->map, fd, 0, (size_t)fc->file.size))) - fc->file.flags |= GIT_DIFF_FLAG__UNMAP_DATA; + &fc->map, fd, 0, (size_t)fc->file->size))) + fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA; else /* fall through to try readbuffer below */ giterr_clear(); } if (error != 0) { - error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file.size); + error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size); if (error < 0) goto cleanup; @@ -340,7 +346,7 @@ static int diff_file_content_load_workdir_file( if (!error) { fc->map.len = git_buf_len(&filtered); fc->map.data = git_buf_detach(&filtered); - fc->file.flags |= GIT_DIFF_FLAG__FREE_DATA; + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; } git_buf_free(&raw); @@ -359,26 +365,26 @@ static int diff_file_content_load_workdir(git_diff_file_content *fc) int error = 0; git_buf path = GIT_BUF_INIT; - if (fc->file.mode == GIT_FILEMODE_COMMIT) + if (fc->file->mode == GIT_FILEMODE_COMMIT) return diff_file_content_commit_to_str(fc, true); - if (fc->file.mode == GIT_FILEMODE_TREE) + if (fc->file->mode == GIT_FILEMODE_TREE) return 0; if (git_buf_joinpath( - &path, git_repository_workdir(fc->repo), fc->file.path) < 0) + &path, git_repository_workdir(fc->repo), fc->file->path) < 0) return -1; - if (S_ISLNK(fc->file.mode)) + if (S_ISLNK(fc->file->mode)) error = diff_file_content_load_workdir_symlink(fc, &path); else error = diff_file_content_load_workdir_file(fc, &path); /* once data is loaded, update OID if we didn't have it previously */ - if (!error && (fc->file.flags & GIT_DIFF_FLAG_VALID_OID) == 0) { + if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) { error = git_odb_hash( - &fc->file.oid, fc->map.data, fc->map.len, GIT_OBJ_BLOB); - fc->file.flags |= GIT_DIFF_FLAG_VALID_OID; + &fc->file->oid, fc->map.data, fc->map.len, GIT_OBJ_BLOB); + fc->file->flags |= GIT_DIFF_FLAG_VALID_OID; } git_buf_free(&path); @@ -389,10 +395,10 @@ int git_diff_file_content__load(git_diff_file_content *fc) { int error = 0; - if ((fc->file.flags & GIT_DIFF_FLAG__LOADED) != 0) + if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) return 0; - if (fc->file.flags & GIT_DIFF_FLAG_BINARY) + if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0) return 0; if (fc->src == GIT_ITERATOR_TYPE_WORKDIR) @@ -402,7 +408,7 @@ int git_diff_file_content__load(git_diff_file_content *fc) if (error) return error; - fc->file.flags |= GIT_DIFF_FLAG__LOADED; + fc->flags |= GIT_DIFF_FLAG__LOADED; diff_file_content_binary_by_content(fc); @@ -411,26 +417,26 @@ int git_diff_file_content__load(git_diff_file_content *fc) void git_diff_file_content__unload(git_diff_file_content *fc) { - if (fc->file.flags & GIT_DIFF_FLAG__FREE_DATA) { + if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) { git__free(fc->map.data); fc->map.data = ""; fc->map.len = 0; - fc->file.flags &= ~GIT_DIFF_FLAG__FREE_DATA; + fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA; } - else if (fc->file.flags & GIT_DIFF_FLAG__UNMAP_DATA) { + else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) { git_futils_mmap_free(&fc->map); fc->map.data = ""; fc->map.len = 0; - fc->file.flags &= ~GIT_DIFF_FLAG__UNMAP_DATA; + fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA; } - if (fc->file.flags & GIT_DIFF_FLAG__FREE_BLOB) { + if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) { git_blob_free((git_blob *)fc->blob); fc->blob = NULL; - fc->file.flags &= ~GIT_DIFF_FLAG__FREE_BLOB; + fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB; } - fc->file.flags &= ~GIT_DIFF_FLAG__LOADED; + fc->flags &= ~GIT_DIFF_FLAG__LOADED; } void git_diff_file_content__clear(git_diff_file_content *fc) diff --git a/src/diff_file.h b/src/diff_file.h index afad8510b..fb08cca6a 100644 --- a/src/diff_file.h +++ b/src/diff_file.h @@ -15,8 +15,9 @@ /* expanded information for one side of a delta */ typedef struct { git_repository *repo; - git_diff_file file; + git_diff_file *file; git_diff_driver *driver; + uint32_t flags; uint32_t opts_flags; git_off_t opts_max_size; git_iterator_type_t src; @@ -34,14 +35,16 @@ extern int git_diff_file_content__init_from_blob( git_diff_file_content *fc, git_repository *repo, const git_diff_options *opts, - const git_blob *blob); + const git_blob *blob, + git_diff_file *as_file); extern int git_diff_file_content__init_from_raw( git_diff_file_content *fc, git_repository *repo, const git_diff_options *opts, const char *buf, - size_t buflen); + size_t buflen, + git_diff_file *as_file); /* this loads the blob/file-on-disk as needed */ extern int git_diff_file_content__load(git_diff_file_content *fc); diff --git a/src/diff_patch.c b/src/diff_patch.c index 40cb3371a..9060d0a24 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -64,12 +64,12 @@ static void diff_patch_update_binary(git_diff_patch *patch) if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) return; - if ((patch->ofile.file.flags & GIT_DIFF_FLAG_BINARY) != 0 || - (patch->nfile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) + if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || + (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) patch->delta->flags |= GIT_DIFF_FLAG_BINARY; - else if ((patch->ofile.file.flags & DIFF_FLAGS_NOT_BINARY) != 0 && - (patch->nfile.file.flags & DIFF_FLAGS_NOT_BINARY) != 0) + else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && + (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; } @@ -143,42 +143,42 @@ static int diff_patch_load(git_diff_patch *patch, git_diff_output *output) output && !output->hunk_cb && !output->data_cb) return 0; -#define DIFF_FLAGS_KNOWN_DATA (GIT_DIFF_FLAG__NO_DATA|GIT_DIFF_FLAG_VALID_OID) - incomplete_data = - ((patch->ofile.file.flags & DIFF_FLAGS_KNOWN_DATA) != 0 && - (patch->nfile.file.flags & DIFF_FLAGS_KNOWN_DATA) != 0); + (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || + (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 0) && + ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || + (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 0)); /* always try to load workdir content first because filtering may * need 2x data size and this minimizes peak memory footprint */ if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) { if ((error = git_diff_file_content__load(&patch->ofile)) < 0 || - (patch->ofile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) + (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) goto cleanup; } if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) { if ((error = git_diff_file_content__load(&patch->nfile)) < 0 || - (patch->nfile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) + (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) goto cleanup; } /* once workdir has been tried, load other data as needed */ if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) { if ((error = git_diff_file_content__load(&patch->ofile)) < 0 || - (patch->ofile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) + (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) goto cleanup; } if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) { if ((error = git_diff_file_content__load(&patch->nfile)) < 0 || - (patch->nfile.file.flags & GIT_DIFF_FLAG_BINARY) != 0) + (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) goto cleanup; } /* if we were previously missing an oid, update MODIFIED->UNMODIFIED */ if (incomplete_data && - patch->ofile.file.mode == patch->nfile.file.mode && - git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid) && + patch->ofile.file->mode == patch->nfile.file->mode && + git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid) && patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ patch->delta->status = GIT_DELTA_UNMODIFIED; @@ -193,7 +193,7 @@ cleanup: patch->delta->status != GIT_DELTA_UNMODIFIED && (patch->ofile.map.len || patch->nfile.map.len) && (patch->ofile.map.len != patch->nfile.map.len || - !git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid))) + !git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid))) patch->flags |= GIT_DIFF_PATCH_DIFFABLE; patch->flags |= GIT_DIFF_PATCH_LOADED; @@ -312,26 +312,31 @@ int git_diff_foreach( typedef struct { git_diff_patch patch; git_diff_delta delta; + char paths[GIT_FLEX_ARRAY]; } diff_patch_with_delta; static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo) { int error = 0; git_diff_patch *patch = &pd->patch; - bool has_old = ((patch->ofile.file.flags & GIT_DIFF_FLAG__NO_DATA) == 0); - bool has_new = ((patch->nfile.file.flags & GIT_DIFF_FLAG__NO_DATA) == 0); + bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); + bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); pd->delta.status = has_new ? (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); - if (git_oid_equal(&patch->nfile.file.oid, &patch->ofile.file.oid)) + if (git_oid_equal(&patch->nfile.file->oid, &patch->ofile.file->oid)) pd->delta.status = GIT_DELTA_UNMODIFIED; patch->delta = &pd->delta; diff_patch_init_common(patch); + if (pd->delta.status == GIT_DELTA_UNMODIFIED && + !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) + return error; + error = diff_patch_file_callback(patch, (git_diff_output *)xo); if (!error) @@ -347,7 +352,9 @@ static int diff_patch_from_blobs( diff_patch_with_delta *pd, git_xdiff_output *xo, const git_blob *old_blob, + const char *old_path, const git_blob *new_blob, + const char *new_path, const git_diff_options *opts) { int error = 0; @@ -357,29 +364,61 @@ static int diff_patch_from_blobs( GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); - pd->patch.delta = &pd->delta; - - if (!repo) /* return two NULL items as UNMODIFIED delta */ - return 0; - if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { - const git_blob *swap = old_blob; - old_blob = new_blob; - new_blob = swap; + const git_blob *tmp_blob; + const char *tmp_path; + tmp_blob = old_blob; old_blob = new_blob; new_blob = tmp_blob; + tmp_path = old_path; old_path = new_path; new_path = tmp_path; } + pd->patch.delta = &pd->delta; + + pd->delta.old_file.path = old_path; + pd->delta.new_file.path = new_path; + if ((error = git_diff_file_content__init_from_blob( - &pd->patch.ofile, repo, opts, old_blob)) < 0 || + &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)) < 0 || (error = git_diff_file_content__init_from_blob( - &pd->patch.nfile, repo, opts, new_blob)) < 0) + &pd->patch.nfile, repo, opts, new_blob, &pd->delta.new_file)) < 0) return error; return diff_single_generate(pd, xo); } +static int diff_patch_with_delta_alloc( + diff_patch_with_delta **out, + const char **old_path, + const char **new_path) +{ + diff_patch_with_delta *pd; + size_t old_len = *old_path ? strlen(*old_path) : 0; + size_t new_len = *new_path ? strlen(*new_path) : 0; + + *out = pd = git__calloc(1, sizeof(*pd) + old_len + new_len + 2); + GITERR_CHECK_ALLOC(pd); + + pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED; + + if (*old_path) { + memcpy(&pd->paths[0], *old_path, old_len); + *old_path = &pd->paths[0]; + } else if (*new_path) + *old_path = &pd->paths[old_len + 1]; + + if (*new_path) { + memcpy(&pd->paths[old_len + 1], *new_path, new_len); + *new_path = &pd->paths[old_len + 1]; + } else if (*old_path) + *new_path = &pd->paths[0]; + + return 0; +} + int git_diff_blobs( const git_blob *old_blob, + const char *old_path, const git_blob *new_blob, + const char *new_path, const git_diff_options *opts, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, @@ -397,7 +436,13 @@ int git_diff_blobs( (git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload); git_xdiff_init(&xo, opts); - error = diff_patch_from_blobs(&pd, &xo, old_blob, new_blob, opts); + if (!old_path && new_path) + old_path = new_path; + else if (!new_path && old_path) + new_path = old_path; + + error = diff_patch_from_blobs( + &pd, &xo, old_blob, old_path, new_blob, new_path, opts); git_diff_patch_free((git_diff_patch *)&pd); @@ -407,7 +452,9 @@ int git_diff_blobs( int git_diff_patch_from_blobs( git_diff_patch **out, const git_blob *old_blob, + const char *old_path, const git_blob *new_blob, + const char *new_path, const git_diff_options *opts) { int error = 0; @@ -417,16 +464,18 @@ int git_diff_patch_from_blobs( assert(out); *out = NULL; - pd = git__calloc(1, sizeof(*pd)); - GITERR_CHECK_ALLOC(pd); - pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED; + if (diff_patch_with_delta_alloc(&pd, &old_path, &new_path) < 0) + return -1; memset(&xo, 0, sizeof(xo)); diff_output_to_patch((git_diff_output *)&xo, &pd->patch); git_xdiff_init(&xo, opts); - if (!(error = diff_patch_from_blobs(pd, &xo, old_blob, new_blob, opts))) + error = diff_patch_from_blobs( + pd, &xo, old_blob, old_path, new_blob, new_path, opts); + + if (!error) *out = (git_diff_patch *)pd; else git_diff_patch_free((git_diff_patch *)pd); @@ -438,8 +487,10 @@ static int diff_patch_from_blob_and_buffer( diff_patch_with_delta *pd, git_xdiff_output *xo, const git_blob *old_blob, + const char *old_path, const char *buf, size_t buflen, + const char *buf_path, const git_diff_options *opts) { int error = 0; @@ -450,28 +501,36 @@ static int diff_patch_from_blob_and_buffer( pd->patch.delta = &pd->delta; - if (!repo && !buf) /* return two NULL items as UNMODIFIED delta */ - return 0; - if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { + pd->delta.old_file.path = buf_path; + pd->delta.new_file.path = old_path; + if (!(error = git_diff_file_content__init_from_raw( - &pd->patch.ofile, repo, opts, buf, buflen))) + &pd->patch.ofile, repo, opts, buf, buflen, &pd->delta.old_file))) error = git_diff_file_content__init_from_blob( - &pd->patch.nfile, repo, opts, old_blob); + &pd->patch.nfile, repo, opts, old_blob, &pd->delta.new_file); } else { + pd->delta.old_file.path = old_path; + pd->delta.new_file.path = buf_path; + if (!(error = git_diff_file_content__init_from_blob( - &pd->patch.ofile, repo, opts, old_blob))) + &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file))) error = git_diff_file_content__init_from_raw( - &pd->patch.nfile, repo, opts, buf, buflen); + &pd->patch.nfile, repo, opts, buf, buflen, &pd->delta.new_file); } + if (error < 0) + return error; + return diff_single_generate(pd, xo); } int git_diff_blob_to_buffer( const git_blob *old_blob, + const char *old_path, const char *buf, size_t buflen, + const char *buf_path, const git_diff_options *opts, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, @@ -489,8 +548,13 @@ int git_diff_blob_to_buffer( (git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload); git_xdiff_init(&xo, opts); + if (!old_path && buf_path) + old_path = buf_path; + else if (!buf_path && old_path) + buf_path = old_path; + error = diff_patch_from_blob_and_buffer( - &pd, &xo, old_blob, buf, buflen, opts); + &pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts); git_diff_patch_free((git_diff_patch *)&pd); @@ -500,8 +564,10 @@ int git_diff_blob_to_buffer( int git_diff_patch_from_blob_and_buffer( git_diff_patch **out, const git_blob *old_blob, + const char *old_path, const char *buf, size_t buflen, + const char *buf_path, const git_diff_options *opts) { int error = 0; @@ -511,17 +577,18 @@ int git_diff_patch_from_blob_and_buffer( assert(out); *out = NULL; - pd = git__calloc(1, sizeof(*pd)); - GITERR_CHECK_ALLOC(pd); - pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED; + if (diff_patch_with_delta_alloc(&pd, &old_path, &buf_path) < 0) + return -1; memset(&xo, 0, sizeof(xo)); diff_output_to_patch((git_diff_output *)&xo, &pd->patch); git_xdiff_init(&xo, opts); - if (!(error = diff_patch_from_blob_and_buffer( - pd, &xo, old_blob, buf, buflen, opts))) + error = diff_patch_from_blob_and_buffer( + pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts); + + if (!error) *out = (git_diff_patch *)pd; else git_diff_patch_free((git_diff_patch *)pd); diff --git a/src/diff_print.c b/src/diff_print.c index 6fc7425eb..30f221a62 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -21,14 +21,15 @@ static int diff_print_info_init( diff_print_info *pi, git_buf *out, git_diff_list *diff, git_diff_data_cb cb, void *payload) { - assert(diff && diff->repo); - pi->diff = diff; pi->print_cb = cb; pi->payload = payload; pi->buf = out; - if (git_repository__cvar(&pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0) + if (!diff || !diff->repo) + pi->oid_strlen = GIT_ABBREV_DEFAULT; + else if (git_repository__cvar( + &pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0) return -1; pi->oid_strlen += 1; /* for NUL byte */ @@ -82,6 +83,8 @@ static int diff_print_one_compact( diff_print_info *pi = data; git_buf *out = pi->buf; char old_suffix, new_suffix, code = git_diff_status_char(delta->status); + int (*strcomp)(const char *, const char *) = + pi->diff ? pi->diff->strcomp : git__strcmp; GIT_UNUSED(progress); @@ -94,7 +97,7 @@ static int diff_print_one_compact( git_buf_clear(out); if (delta->old_file.path != delta->new_file.path && - pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0) + strcomp(delta->old_file.path,delta->new_file.path) != 0) git_buf_printf(out, "%c\t%s%c -> %s%c\n", code, delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); else if (delta->old_file.mode != delta->new_file.mode && @@ -229,10 +232,11 @@ static int diff_print_patch_file( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; - const char *oldpfx = pi->diff->opts.old_prefix; + const char *oldpfx = pi->diff ? pi->diff->opts.old_prefix : NULL; const char *oldpath = delta->old_file.path; - const char *newpfx = pi->diff->opts.new_prefix; + const char *newpfx = pi->diff ? pi->diff->opts.new_prefix : NULL; const char *newpath = delta->new_file.path; + uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL; GIT_UNUSED(progress); @@ -240,17 +244,17 @@ static int diff_print_patch_file( delta->status == GIT_DELTA_UNMODIFIED || delta->status == GIT_DELTA_IGNORED || (delta->status == GIT_DELTA_UNTRACKED && - (pi->diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)) + (opts_flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)) return 0; if (!oldpfx) oldpfx = DIFF_OLD_PREFIX_DEFAULT; - if (!newpfx) newpfx = DIFF_NEW_PREFIX_DEFAULT; git_buf_clear(pi->buf); - git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path); + git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", + oldpfx, delta->old_file.path, newpfx, delta->new_file.path); if (diff_print_oid_range(pi, delta) < 0) return -1; diff --git a/tests-clar/diff/blob.c b/tests-clar/diff/blob.c index b12186d98..42b9fcd5f 100644 --- a/tests-clar/diff/blob.c +++ b/tests-clar/diff/blob.c @@ -6,6 +6,20 @@ static diff_expects expected; static git_diff_options opts; static git_blob *d, *alien; +static void quick_diff_blob_to_str( + const git_blob *blob, const char *blob_path, + const char *str, size_t len, const char *str_path) +{ + memset(&expected, 0, sizeof(expected)); + + if (str && !len) + len = strlen(str); + + cl_git_pass(git_diff_blob_to_buffer( + blob, blob_path, str, len, str_path, + &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); +} + void test_diff_blob__initialize(void) { git_oid oid; @@ -59,7 +73,8 @@ void test_diff_blob__can_compare_text_blobs(void) /* diff on tests/resources/attr/root_test1 */ cl_git_pass(git_diff_blobs( - a, b, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + a, NULL, b, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(1, expected.files); cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); @@ -74,7 +89,8 @@ void test_diff_blob__can_compare_text_blobs(void) /* diff on tests/resources/attr/root_test2 */ memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - b, c, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + b, NULL, c, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(1, expected.files); cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); @@ -89,7 +105,8 @@ void test_diff_blob__can_compare_text_blobs(void) /* diff on tests/resources/attr/root_test3 */ memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - a, c, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + a, NULL, c, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(1, expected.files); cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); @@ -103,7 +120,8 @@ void test_diff_blob__can_compare_text_blobs(void) memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - c, d, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + c, NULL, d, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(1, expected.files); cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); @@ -125,6 +143,7 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) git_blob *a, *b, *c; git_oid a_oid, b_oid, c_oid; git_diff_patch *p; + const git_diff_delta *delta; size_t tc, ta, td; /* tests/resources/attr/root_test1 */ @@ -142,10 +161,18 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) /* Doing the equivalent of a `git diff -U1` on these files */ /* diff on tests/resources/attr/root_test1 */ - cl_git_pass(git_diff_patch_from_blobs(&p, a, b, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, a, NULL, b, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); + cl_assert(git_oid_equal(git_blob_id(a), &delta->old_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(a), delta->old_file.size); + cl_assert(git_oid_equal(git_blob_id(b), &delta->new_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(b), delta->new_file.size); + cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); cl_assert_equal_i(6, git_diff_patch_num_lines_in_hunk(p, 0)); @@ -157,10 +184,18 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) git_diff_patch_free(p); /* diff on tests/resources/attr/root_test2 */ - cl_git_pass(git_diff_patch_from_blobs(&p, b, c, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, b, NULL, c, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); + cl_assert(git_oid_equal(git_blob_id(b), &delta->old_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(b), delta->old_file.size); + cl_assert(git_oid_equal(git_blob_id(c), &delta->new_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(c), delta->new_file.size); + cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); cl_assert_equal_i(15, git_diff_patch_num_lines_in_hunk(p, 0)); @@ -172,12 +207,17 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) git_diff_patch_free(p); /* diff on tests/resources/attr/root_test3 */ - cl_git_pass(git_diff_patch_from_blobs(&p, a, c, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, a, NULL, c, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(13, git_diff_patch_num_lines_in_hunk(p, 0)); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); + cl_assert(git_oid_equal(git_blob_id(a), &delta->old_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(a), delta->old_file.size); + cl_assert(git_oid_equal(git_blob_id(c), &delta->new_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(c), delta->new_file.size); cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p)); cl_assert_equal_i(0, (int)tc); @@ -187,10 +227,18 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) git_diff_patch_free(p); /* one more */ - cl_git_pass(git_diff_patch_from_blobs(&p, c, d, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, c, NULL, d, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); + cl_assert(git_oid_equal(git_blob_id(c), &delta->old_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(c), delta->old_file.size); + cl_assert(git_oid_equal(git_blob_id(d), &delta->new_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(d), delta->new_file.size); + cl_assert_equal_i(2, (int)git_diff_patch_num_hunks(p)); cl_assert_equal_i(5, git_diff_patch_num_lines_in_hunk(p, 0)); cl_assert_equal_i(9, git_diff_patch_num_lines_in_hunk(p, 1)); @@ -212,7 +260,8 @@ void test_diff_blob__can_compare_against_null_blobs(void) git_blob *e = NULL; cl_git_pass(git_diff_blobs( - d, e, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + d, NULL, e, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(1, expected.files); cl_assert_equal_i(1, expected.file_status[GIT_DELTA_DELETED]); @@ -227,7 +276,8 @@ void test_diff_blob__can_compare_against_null_blobs(void) memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - d, e, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + d, NULL, e, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(1, expected.files); cl_assert_equal_i(1, expected.file_status[GIT_DELTA_ADDED]); @@ -242,7 +292,8 @@ void test_diff_blob__can_compare_against_null_blobs(void) memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - alien, NULL, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + alien, NULL, NULL, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(1, expected.files); cl_assert_equal_i(1, expected.files_binary); @@ -253,7 +304,8 @@ void test_diff_blob__can_compare_against_null_blobs(void) memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - NULL, alien, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + NULL, NULL, alien, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(1, expected.files); cl_assert_equal_i(1, expected.files_binary); @@ -266,13 +318,22 @@ void test_diff_blob__can_compare_against_null_blobs_with_patch(void) { git_blob *e = NULL; git_diff_patch *p; + const git_diff_delta *delta; int line; char origin; - cl_git_pass(git_diff_patch_from_blobs(&p, d, e, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_DELETED, git_diff_patch_delta(p)->status); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + cl_assert(git_oid_equal(git_blob_id(d), &delta->old_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(d), delta->old_file.size); + cl_assert(git_oid_iszero(&delta->new_file.oid)); + cl_assert_equal_sz(0, delta->new_file.size); + cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); cl_assert_equal_i(14, git_diff_patch_num_lines_in_hunk(p, 0)); @@ -286,10 +347,18 @@ void test_diff_blob__can_compare_against_null_blobs_with_patch(void) opts.flags |= GIT_DIFF_REVERSE; - cl_git_pass(git_diff_patch_from_blobs(&p, d, e, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_ADDED, delta->status); + cl_assert(git_oid_iszero(&delta->old_file.oid)); + cl_assert_equal_sz(0, delta->old_file.size); + cl_assert(git_oid_equal(git_blob_id(d), &delta->new_file.oid)); + cl_assert_equal_sz(git_blob_rawsize(d), delta->new_file.size); + cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); cl_assert_equal_i(14, git_diff_patch_num_lines_in_hunk(p, 0)); @@ -303,20 +372,28 @@ void test_diff_blob__can_compare_against_null_blobs_with_patch(void) opts.flags ^= GIT_DIFF_REVERSE; - cl_git_pass(git_diff_patch_from_blobs(&p, alien, NULL, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, alien, NULL, NULL, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_DELETED, git_diff_patch_delta(p)->status); - cl_assert((git_diff_patch_delta(p)->flags & GIT_DIFF_FLAG_BINARY) != 0); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); + cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); git_diff_patch_free(p); - cl_git_pass(git_diff_patch_from_blobs(&p, NULL, alien, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, NULL, NULL, alien, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status); - cl_assert((git_diff_patch_delta(p)->flags & GIT_DIFF_FLAG_BINARY) != 0); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_ADDED, delta->status); + cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); + cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); git_diff_patch_free(p); @@ -332,44 +409,66 @@ static void assert_identical_blobs_comparison(diff_expects *expected) void test_diff_blob__can_compare_identical_blobs(void) { - cl_git_pass(git_diff_blobs( - d, d, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_blobs( + d, NULL, d, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); - cl_assert_equal_i(0, expected.files_binary); assert_identical_blobs_comparison(&expected); - - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - NULL, NULL, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); - cl_assert_equal_i(0, expected.files_binary); - cl_assert_equal_i(0, expected.files); /* NULLs mean no callbacks, period */ memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - alien, alien, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + NULL, NULL, NULL, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_identical_blobs_comparison(&expected); + cl_assert_equal_i(0, expected.files_binary); + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + alien, NULL, alien, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + + assert_identical_blobs_comparison(&expected); cl_assert(expected.files_binary > 0); - assert_identical_blobs_comparison(&expected); } void test_diff_blob__can_compare_identical_blobs_with_patch(void) { git_diff_patch *p; + const git_diff_delta *delta; - cl_git_pass(git_diff_patch_from_blobs(&p, d, d, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, d, NULL, d, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status); + cl_assert_equal_sz(delta->old_file.size, git_blob_rawsize(d)); + cl_assert(git_oid_equal(git_blob_id(d), &delta->old_file.oid)); + cl_assert_equal_sz(delta->new_file.size, git_blob_rawsize(d)); + cl_assert(git_oid_equal(git_blob_id(d), &delta->new_file.oid)); + cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); git_diff_patch_free(p); - cl_git_pass(git_diff_patch_from_blobs(&p, NULL, NULL, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, NULL, NULL, NULL, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status); + + delta = git_diff_patch_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status); + cl_assert_equal_sz(0, delta->old_file.size); + cl_assert(git_oid_iszero(&delta->old_file.oid)); + cl_assert_equal_sz(0, delta->new_file.size); + cl_assert(git_oid_iszero(&delta->new_file.oid)); + cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); git_diff_patch_free(p); - cl_git_pass(git_diff_patch_from_blobs(&p, alien, alien, &opts)); + cl_git_pass(git_diff_patch_from_blobs(&p, alien, NULL, alien, NULL, &opts)); cl_assert(p != NULL); cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status); cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); @@ -396,14 +495,16 @@ void test_diff_blob__can_compare_two_binary_blobs(void) cl_git_pass(git_blob_lookup_prefix(&heart, g_repo, &h_oid, 4)); cl_git_pass(git_diff_blobs( - alien, heart, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + alien, NULL, heart, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); assert_binary_blobs_comparison(&expected); memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - heart, alien, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + heart, NULL, alien, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); assert_binary_blobs_comparison(&expected); @@ -413,14 +514,16 @@ void test_diff_blob__can_compare_two_binary_blobs(void) void test_diff_blob__can_compare_a_binary_blob_and_a_text_blob(void) { cl_git_pass(git_diff_blobs( - alien, d, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + alien, NULL, d, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); assert_binary_blobs_comparison(&expected); memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - d, alien, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + d, NULL, alien, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); assert_binary_blobs_comparison(&expected); } @@ -461,7 +564,8 @@ void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) /* Test with default inter-hunk-context (not set) => default is 0 */ cl_git_pass(git_diff_blobs( - old_d, d, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + old_d, NULL, d, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(2, expected.hunks); @@ -469,7 +573,8 @@ void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) opts.interhunk_lines = 0; memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - old_d, d, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + old_d, NULL, d, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(2, expected.hunks); @@ -477,7 +582,8 @@ void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) opts.interhunk_lines = 1; memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - old_d, d, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + old_d, NULL, d, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); cl_assert_equal_i(1, expected.hunks); @@ -490,7 +596,8 @@ void test_diff_blob__checks_options_version_too_low(void) opts.version = 0; cl_git_fail(git_diff_blobs( - d, alien, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + d, NULL, alien, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); err = giterr_last(); cl_assert_equal_i(GITERR_INVALID, err->klass); } @@ -501,7 +608,8 @@ void test_diff_blob__checks_options_version_too_high(void) opts.version = 1024; cl_git_fail(git_diff_blobs( - d, alien, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + d, NULL, alien, NULL, &opts, + diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); err = giterr_last(); cl_assert_equal_i(GITERR_INVALID, err->klass); } @@ -548,10 +656,7 @@ void test_diff_blob__can_compare_blob_to_buffer(void) cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4)); /* diff from blob a to content of b */ - cl_git_pass(git_diff_blob_to_buffer( - a, b_content, strlen(b_content), - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); - + quick_diff_blob_to_str(a, NULL, b_content, 0, NULL); cl_assert_equal_i(1, expected.files); cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, expected.files_binary); @@ -562,37 +667,25 @@ void test_diff_blob__can_compare_blob_to_buffer(void) cl_assert_equal_i(0, expected.line_dels); /* diff from blob a to content of a */ - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - a, a_content, strlen(a_content), - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); - + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + quick_diff_blob_to_str(a, NULL, a_content, 0, NULL); assert_identical_blobs_comparison(&expected); /* diff from NULL blob to content of a */ memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - NULL, a_content, strlen(a_content), - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); - + quick_diff_blob_to_str(NULL, NULL, a_content, 0, NULL); assert_changed_single_one_line_file(&expected, GIT_DELTA_ADDED); /* diff from blob a to NULL buffer */ memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - a, NULL, 0, - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); - + quick_diff_blob_to_str(a, NULL, NULL, 0, NULL); assert_changed_single_one_line_file(&expected, GIT_DELTA_DELETED); /* diff with reverse */ opts.flags ^= GIT_DIFF_REVERSE; memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - a, NULL, 0, - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); - + quick_diff_blob_to_str(a, NULL, NULL, 0, NULL); assert_changed_single_one_line_file(&expected, GIT_DELTA_ADDED); git_blob_free(a); @@ -613,7 +706,7 @@ void test_diff_blob__can_compare_blob_to_buffer_with_patch(void) /* diff from blob a to content of b */ cl_git_pass(git_diff_patch_from_blob_and_buffer( - &p, a, b_content, strlen(b_content), &opts)); + &p, a, NULL, b_content, strlen(b_content), NULL, &opts)); cl_assert(p != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status); @@ -628,8 +721,9 @@ void test_diff_blob__can_compare_blob_to_buffer_with_patch(void) git_diff_patch_free(p); /* diff from blob a to content of a */ + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; cl_git_pass(git_diff_patch_from_blob_and_buffer( - &p, a, a_content, strlen(a_content), &opts)); + &p, a, NULL, a_content, strlen(a_content), NULL, &opts)); cl_assert(p != NULL); cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status); cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); @@ -637,7 +731,7 @@ void test_diff_blob__can_compare_blob_to_buffer_with_patch(void) /* diff from NULL blob to content of a */ cl_git_pass(git_diff_patch_from_blob_and_buffer( - &p, NULL, a_content, strlen(a_content), &opts)); + &p, NULL, NULL, a_content, strlen(a_content), NULL, &opts)); cl_assert(p != NULL); cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status); cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); @@ -646,7 +740,7 @@ void test_diff_blob__can_compare_blob_to_buffer_with_patch(void) /* diff from blob a to NULL buffer */ cl_git_pass(git_diff_patch_from_blob_and_buffer( - &p, a, NULL, 0, &opts)); + &p, a, NULL, NULL, 0, NULL, &opts)); cl_assert(p != NULL); cl_assert_equal_i(GIT_DELTA_DELETED, git_diff_patch_delta(p)->status); cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); @@ -657,7 +751,7 @@ void test_diff_blob__can_compare_blob_to_buffer_with_patch(void) opts.flags ^= GIT_DIFF_REVERSE; cl_git_pass(git_diff_patch_from_blob_and_buffer( - &p, a, NULL, 0, &opts)); + &p, a, NULL, NULL, 0, NULL, &opts)); cl_assert(p != NULL); cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status); cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); @@ -684,6 +778,8 @@ void test_diff_blob__binary_data_comparisons(void) const char *bin_content = "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n"; size_t bin_len = 33; + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + cl_git_pass(git_oid_fromstrn(&oid, "45141a79", 8)); cl_git_pass(git_blob_lookup_prefix(&nonbin, g_repo, &oid, 4)); @@ -692,44 +788,32 @@ void test_diff_blob__binary_data_comparisons(void) /* non-binary to reference content */ - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - nonbin, nonbin_content, nonbin_len, - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + quick_diff_blob_to_str(nonbin, NULL, nonbin_content, nonbin_len, NULL); assert_identical_blobs_comparison(&expected); cl_assert_equal_i(0, expected.files_binary); /* binary to reference content */ - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - bin, bin_content, bin_len, - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL); assert_identical_blobs_comparison(&expected); cl_assert_equal_i(1, expected.files_binary); /* non-binary to binary content */ - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - nonbin, bin_content, bin_len, - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + quick_diff_blob_to_str(nonbin, NULL, bin_content, bin_len, NULL); assert_binary_blobs_comparison(&expected); /* binary to non-binary content */ - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - bin, nonbin_content, nonbin_len, - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + quick_diff_blob_to_str(bin, NULL, nonbin_content, nonbin_len, NULL); assert_binary_blobs_comparison(&expected); /* non-binary to binary blob */ memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - bin, nonbin, &opts, + bin, NULL, nonbin, NULL, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); assert_binary_blobs_comparison(&expected); @@ -739,27 +823,18 @@ void test_diff_blob__binary_data_comparisons(void) opts.flags |= GIT_DIFF_FORCE_TEXT; - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - bin, bin_content, bin_len, - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL); assert_identical_blobs_comparison(&expected); - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - nonbin, bin_content, bin_len, - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + quick_diff_blob_to_str(nonbin, NULL, bin_content, bin_len, NULL); assert_one_modified_with_lines(&expected, 4); - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blob_to_buffer( - bin, nonbin_content, nonbin_len, - &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); + quick_diff_blob_to_str(bin, NULL, nonbin_content, nonbin_len, NULL); assert_one_modified_with_lines(&expected, 4); memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( - bin, nonbin, &opts, + bin, NULL, nonbin, NULL, &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected)); assert_one_modified_with_lines(&expected, 4); @@ -767,3 +842,227 @@ void test_diff_blob__binary_data_comparisons(void) git_blob_free(bin); git_blob_free(nonbin); } + +void test_diff_blob__using_path_and_attributes(void) +{ + git_config *cfg; + git_blob *bin, *nonbin; + git_oid oid; + const char *nonbin_content = "Hello from the root\n"; + const char *bin_content = + "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n"; + size_t bin_len = 33; + const char *changed; + git_diff_patch *p; + char *pout; + + /* set up custom diff drivers and 'diff' attribute mappings for them */ + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "diff.iam_binary.binary", 1)); + cl_git_pass(git_config_set_bool(cfg, "diff.iam_text.binary", 0)); + cl_git_pass(git_config_set_string( + cfg, "diff.iam_alphactx.xfuncname", "^[A-Za-z]")); + cl_git_pass(git_config_set_bool(cfg, "diff.iam_textalpha.binary", 0)); + cl_git_pass(git_config_set_string( + cfg, "diff.iam_textalpha.xfuncname", "^[A-Za-z]")); + cl_git_pass(git_config_set_string( + cfg, "diff.iam_numctx.funcname", "^[0-9]")); + cl_git_pass(git_config_set_bool(cfg, "diff.iam_textnum.binary", 0)); + cl_git_pass(git_config_set_string( + cfg, "diff.iam_textnum.funcname", "^[0-9]")); + git_config_free(cfg); + + cl_git_append2file( + "attr/.gitattributes", + "\n\n# test_diff_blob__using_path_and_attributes extra\n\n" + "*.binary diff=iam_binary\n" + "*.textary diff=iam_text\n" + "*.alphary diff=iam_alphactx\n" + "*.textalphary diff=iam_textalpha\n" + "*.textnumary diff=iam_textnum\n" + "*.numary diff=iam_numctx\n\n"); + + opts.context_lines = 0; + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_oid_fromstrn(&oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&nonbin, g_repo, &oid, 4)); + /* 20b: "Hello from the root\n" */ + + cl_git_pass(git_oid_fromstrn(&oid, "b435cd56", 8)); + cl_git_pass(git_blob_lookup_prefix(&bin, g_repo, &oid, 4)); + /* 33b: "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\n0123456789\n" */ + + /* non-binary to reference content */ + + quick_diff_blob_to_str(nonbin, NULL, nonbin_content, 0, NULL); + assert_identical_blobs_comparison(&expected); + cl_assert_equal_i(0, expected.files_binary); + + /* binary to reference content */ + + quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL); + assert_identical_blobs_comparison(&expected); + cl_assert_equal_i(1, expected.files_binary); + + /* add some text */ + + changed = "Hello from the root\nMore lines\nAnd more\nGo here\n"; + + quick_diff_blob_to_str(nonbin, NULL, changed, 0, NULL); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected.files_binary); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(3, expected.lines); + cl_assert_equal_i(0, expected.line_ctxt); + cl_assert_equal_i(3, expected.line_adds); + cl_assert_equal_i(0, expected.line_dels); + + quick_diff_blob_to_str(nonbin, "foo/bar.binary", changed, 0, NULL); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, expected.files_binary); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); + cl_assert_equal_i(0, expected.line_ctxt); + cl_assert_equal_i(0, expected.line_adds); + cl_assert_equal_i(0, expected.line_dels); + + quick_diff_blob_to_str(nonbin, "foo/bar.textary", changed, 0, NULL); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected.files_binary); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(3, expected.lines); + cl_assert_equal_i(0, expected.line_ctxt); + cl_assert_equal_i(3, expected.line_adds); + cl_assert_equal_i(0, expected.line_dels); + + quick_diff_blob_to_str(nonbin, "foo/bar.alphary", changed, 0, NULL); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected.files_binary); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(3, expected.lines); + cl_assert_equal_i(0, expected.line_ctxt); + cl_assert_equal_i(3, expected.line_adds); + cl_assert_equal_i(0, expected.line_dels); + + cl_git_pass(git_diff_patch_from_blob_and_buffer( + &p, nonbin, "zzz.normal", changed, strlen(changed), NULL, &opts)); + cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_assert_equal_s( + "diff --git a/zzz.normal b/zzz.normal\n" + "index 45141a7..75b0dbb 100644\n" + "--- a/zzz.normal\n" + "+++ b/zzz.normal\n" + "@@ -1,0 +2,3 @@ Hello from the root\n" + "+More lines\n" + "+And more\n" + "+Go here\n", pout); + git__free(pout); + git_diff_patch_free(p); + + cl_git_pass(git_diff_patch_from_blob_and_buffer( + &p, nonbin, "zzz.binary", changed, strlen(changed), NULL, &opts)); + cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_assert_equal_s( + "diff --git a/zzz.binary b/zzz.binary\n" + "index 45141a7..75b0dbb 100644\n" + "Binary files a/zzz.binary and b/zzz.binary differ\n", pout); + git__free(pout); + git_diff_patch_free(p); + + cl_git_pass(git_diff_patch_from_blob_and_buffer( + &p, nonbin, "zzz.alphary", changed, strlen(changed), NULL, &opts)); + cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_assert_equal_s( + "diff --git a/zzz.alphary b/zzz.alphary\n" + "index 45141a7..75b0dbb 100644\n" + "--- a/zzz.alphary\n" + "+++ b/zzz.alphary\n" + "@@ -1,0 +2,3 @@ Hello from the root\n" + "+More lines\n" + "+And more\n" + "+Go here\n", pout); + git__free(pout); + git_diff_patch_free(p); + + cl_git_pass(git_diff_patch_from_blob_and_buffer( + &p, nonbin, "zzz.numary", changed, strlen(changed), NULL, &opts)); + cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_assert_equal_s( + "diff --git a/zzz.numary b/zzz.numary\n" + "index 45141a7..75b0dbb 100644\n" + "--- a/zzz.numary\n" + "+++ b/zzz.numary\n" + "@@ -1,0 +2,3 @@\n" + "+More lines\n" + "+And more\n" + "+Go here\n", pout); + git__free(pout); + git_diff_patch_free(p); + + /* "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n" + * 33 bytes + */ + + changed = "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\nreplace a line\n"; + + cl_git_pass(git_diff_patch_from_blob_and_buffer( + &p, bin, "zzz.normal", changed, 37, NULL, &opts)); + cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_assert_equal_s( + "diff --git a/zzz.normal b/zzz.normal\n" + "index b435cd5..1604519 100644\n" + "Binary files a/zzz.normal and b/zzz.normal differ\n", pout); + git__free(pout); + git_diff_patch_free(p); + + cl_git_pass(git_diff_patch_from_blob_and_buffer( + &p, bin, "zzz.textary", changed, 37, NULL, &opts)); + cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_assert_equal_s( + "diff --git a/zzz.textary b/zzz.textary\n" + "index b435cd5..1604519 100644\n" + "--- a/zzz.textary\n" + "+++ b/zzz.textary\n" + "@@ -3 +3 @@\n" + "-0123456789\n" + "+replace a line\n", pout); + git__free(pout); + git_diff_patch_free(p); + + cl_git_pass(git_diff_patch_from_blob_and_buffer( + &p, bin, "zzz.textalphary", changed, 37, NULL, &opts)); + cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_assert_equal_s( + "diff --git a/zzz.textalphary b/zzz.textalphary\n" + "index b435cd5..1604519 100644\n" + "--- a/zzz.textalphary\n" + "+++ b/zzz.textalphary\n" + "@@ -3 +3 @@\n" + "-0123456789\n" + "+replace a line\n", pout); + git__free(pout); + git_diff_patch_free(p); + + cl_git_pass(git_diff_patch_from_blob_and_buffer( + &p, bin, "zzz.textnumary", changed, 37, NULL, &opts)); + cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_assert_equal_s( + "diff --git a/zzz.textnumary b/zzz.textnumary\n" + "index b435cd5..1604519 100644\n" + "--- a/zzz.textnumary\n" + "+++ b/zzz.textnumary\n" + "@@ -3 +3 @@ 0123456789\n" + "-0123456789\n" + "+replace a line\n", pout); + git__free(pout); + git_diff_patch_free(p); + + git_blob_free(nonbin); + git_blob_free(bin); +} From f0f2ff9cacc17dd9d4523e3647990e5db6013f20 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 17 Jun 2013 17:33:40 -0500 Subject: [PATCH 025/367] test failure when renames produce similar similarities --- tests-clar/diff/rename.c | 70 ++++++++++++++++++ .../17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 | 5 ++ .../50/e90273af7d826ff0a95865bcd3ba8412c447d9 | 3 + .../b9/25b224cc91f897001a9993fbce169fdaa8858f | Bin 0 -> 76 bytes .../ea/c43f5195a2cee53b7458d8dad16aedde10711b | Bin 0 -> 118 bytes .../.gitted/refs/heads/renames_similar_two | 1 + 6 files changed, 79 insertions(+) create mode 100644 tests-clar/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 create mode 100644 tests-clar/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9 create mode 100644 tests-clar/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f create mode 100644 tests-clar/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b create mode 100644 tests-clar/resources/renames/.gitted/refs/heads/renames_similar_two diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 2600bd872..103ecd681 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -908,6 +908,76 @@ void test_diff_rename__rejected_match_can_match_others(void) git_buf_free(&two); } +static void write_similarity_file_two(const char *filename, size_t b_lines) +{ + git_buf contents = GIT_BUF_INIT; + size_t i; + + for (i = 0; i < b_lines; i++) + git_buf_printf(&contents, "%0.2d - bbbbb\r\n", (i+1)); + + for (i = b_lines; i < 50; i++) + git_buf_printf(&contents, "%0.2d - aaaaa%s", (i+1), (i == 49 ? "" : "\r\n")); + + cl_git_pass( + git_futils_writebuffer(&contents, filename, O_RDWR|O_CREAT, 0777)); + + git_buf_free(&contents); +} + +void test_diff_rename__rejected_match_can_match_others_two(void) +{ + git_reference *head, *selfsimilar; + git_index *index; + git_tree *tree; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_diff_list *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + const char *sources[] = { "a.txt", "b.txt" }; + const char *targets[] = { "c.txt", "d.txt" }; + struct rename_expected expect = { 2, sources, targets }; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_git_pass(git_reference_symbolic_set_target( + &selfsimilar, head, "refs/heads/renames_similar_two")); + cl_git_pass(git_checkout_head(g_repo, &opts)); + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(p_unlink("renames/a.txt")); + cl_git_pass(p_unlink("renames/b.txt")); + + cl_git_pass(git_index_remove_bypath(index, "a.txt")); + cl_git_pass(git_index_remove_bypath(index, "b.txt")); + + write_similarity_file_two("renames/c.txt", 7); + write_similarity_file_two("renames/d.txt", 8); + + cl_git_pass(git_index_add_bypath(index, "c.txt")); + cl_git_pass(git_index_add_bypath(index, "d.txt")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass( + git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + cl_git_pass( + git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect)); + cl_assert(expect.idx > 0); + + git_diff_list_free(diff); + git_tree_free(tree); + git_index_free(index); + git_reference_free(head); + git_reference_free(selfsimilar); +} + void test_diff_rename__case_changes_are_split(void) { git_index *index; diff --git a/tests-clar/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 b/tests-clar/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 new file mode 100644 index 000000000..01801ed11 --- /dev/null +++ b/tests-clar/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 @@ -0,0 +1,5 @@ +xEͱ Â@QbWq ÿ®—ÃÅHôŸ¡_ž‰&{ëó]ãðy›û¸•¶Y¬X³û`Ÿì‹=¯Ý'í¶=Zo´Þh½Ñz£õFëÖí¿­­Ð +­Ð +­Ð +­Ð +MhBšÐ„&4¡ MhB3šÑŒf4£ÍhF3šÑŽKûxŒŒ \ No newline at end of file diff --git a/tests-clar/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9 b/tests-clar/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9 new file mode 100644 index 000000000..a98d14ee7 --- /dev/null +++ b/tests-clar/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9 @@ -0,0 +1,3 @@ +xmÍM +Â0`×9Å\@ÉOÛd ˆîE¼@š&4hLI"âíî]Ícà}Ïå”bÉÕ®ïaBÙORvΡ5çÂ"¢ +“óbÀ0[kLo³ï¶ä·èpÍ­U˺ÝSŠ®äšC;¸œŽ ”æ8 ÜhØóŽsF_šl¾ÀeþØ2Ã}ɩ挞È-ý!Dg4*IDO;Ûê!Ð~)>m‰í ä®”¨Ï~*ùD‰ \ No newline at end of file diff --git a/tests-clar/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f b/tests-clar/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f new file mode 100644 index 0000000000000000000000000000000000000000..90e107fa23ca0751d98697725732e1cdd0ab5ec9 GIT binary patch literal 76 zcmV-S0JHyi0V^p=O;s>6V=y!@Ff%bxNYpE-C}DVY#6EE9qH|BJOCoOEx|sF$oj$%n{?~}#xW_ZK=B>@CYpwtzWgk$b0V4PS literal 0 HcmV?d00001 diff --git a/tests-clar/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b b/tests-clar/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b new file mode 100644 index 0000000000000000000000000000000000000000..2fb025080a1f4cacbcf85725d87c9670294029f3 GIT binary patch literal 118 zcmbD@P%*WTQIcm3L{m%75XED~Lt;i?^Van`jh%S_j1`esL61~M1&=4Loc-@4Fd24w1P Sx!}YNBer?}X53-X69oV<`8>G* literal 0 HcmV?d00001 diff --git a/tests-clar/resources/renames/.gitted/refs/heads/renames_similar_two b/tests-clar/resources/renames/.gitted/refs/heads/renames_similar_two new file mode 100644 index 000000000..4ee5d04e1 --- /dev/null +++ b/tests-clar/resources/renames/.gitted/refs/heads/renames_similar_two @@ -0,0 +1 @@ +50e90273af7d826ff0a95865bcd3ba8412c447d9 From 3b334075c909f9023f5f704469965cf774efc4a5 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 17 Jun 2013 17:39:59 -0500 Subject: [PATCH 026/367] test illustrating tri-cyclic rename failure --- tests-clar/diff/rename.c | 53 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 103ecd681..e6bf72c17 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -650,6 +650,59 @@ void test_diff_rename__file_exchange(void) git_buf_free(&c2); } +void test_diff_rename__file_exchange_three(void) +{ + git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT, c3 = GIT_BUF_INIT; + git_index *index; + git_tree *tree; + git_diff_list *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt")); + cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt")); + cl_git_pass(git_futils_readbuffer(&c3, "renames/ikeepsix.txt")); + + cl_git_pass(git_futils_writebuffer(&c1, "renames/ikeepsix.txt", 0, 0)); + cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0)); + cl_git_pass(git_futils_writebuffer(&c3, "renames/songof7cities.txt", 0, 0)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_list_free(diff); + git_tree_free(tree); + git_index_free(index); + + git_buf_free(&c1); + git_buf_free(&c2); + git_buf_free(&c3); +} + void test_diff_rename__file_partial_exchange(void) { git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT; From e4acc3ba19838d39123131d4cc7e52f222691445 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 18 Jun 2013 16:14:35 -0700 Subject: [PATCH 027/367] Fix rename looped reference issues This makes the diff rename tracking code more careful about the order in which it processes renames and more thorough in updating the mapping of correct renames when an earlier rename update alters the index of a later matched pair. --- src/diff_print.c | 4 +- src/diff_tform.c | 281 ++++++++++++++++++++++----------------- tests-clar/diff/rename.c | 8 +- 3 files changed, 167 insertions(+), 126 deletions(-) diff --git a/src/diff_print.c b/src/diff_print.c index 30f221a62..0de548813 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -46,7 +46,7 @@ static char diff_pick_suffix(int mode) { if (S_ISDIR(mode)) return '/'; - else if (mode & 0100) //-V536 + else if (mode & 0100) /* -V536 */ /* in git, modes are very regular, so we must have 0100755 mode */ return '*'; else @@ -162,7 +162,7 @@ static int diff_print_one_raw( if (delta->similarity > 0) git_buf_printf(out, "%03u", delta->similarity); - if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED) + if (delta->old_file.path != delta->new_file.path) git_buf_printf( out, "\t%s %s\n", delta->old_file.path, delta->new_file.path); else diff --git a/src/diff_tform.c b/src/diff_tform.c index 64746e7dd..8c4e96ecf 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -673,6 +673,15 @@ GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta) delta->status == GIT_DELTA_IGNORED); } +GIT_INLINE(void) delta_make_rename( + git_diff_delta *to, const git_diff_delta *from, uint32_t similarity) +{ + to->status = GIT_DELTA_RENAMED; + to->similarity = similarity; + memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); + to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; +} + typedef struct { uint32_t idx; uint32_t similarity; @@ -682,13 +691,15 @@ int git_diff_find_similar( git_diff_list *diff, git_diff_find_options *given_opts) { - size_t i, j, cache_size; + size_t i, j, sigcache_size; int error = 0, similarity; git_diff_delta *from, *to; git_diff_find_options opts; - size_t num_rewrites = 0, num_updates = 0; - void **cache; /* cache of similarity metric file signatures */ - diff_find_match *match_sources, *match_targets; /* cache of best matches */ + size_t num_srcs = 0, num_tgts = 0, tried_srcs = 0, tried_tgts = 0; + size_t num_rewrites = 0, num_updates = 0, num_bumped = 0; + void **sigcache; /* cache of similarity metric file signatures */ + diff_find_match *match_srcs = NULL, *match_tgts = NULL, *best_match; + git_diff_file swap; if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0) return error; @@ -697,70 +708,109 @@ int git_diff_find_similar( if (!git__is_uint32(diff->deltas.length)) return 0; - cache_size = diff->deltas.length * 2; /* must store b/c length may change */ - cache = git__calloc(cache_size, sizeof(void *)); - GITERR_CHECK_ALLOC(cache); - - match_sources = git__calloc(diff->deltas.length, sizeof(diff_find_match)); - match_targets = git__calloc(diff->deltas.length, sizeof(diff_find_match)); - GITERR_CHECK_ALLOC(match_sources); - GITERR_CHECK_ALLOC(match_targets); - - /* next find the most similar delta for each rename / copy candidate */ + sigcache_size = diff->deltas.length * 2; /* keep size b/c diff may change */ + sigcache = git__calloc(sigcache_size, sizeof(void *)); + GITERR_CHECK_ALLOC(sigcache); + /* Label rename sources and targets + * + * This will also set self-similarity scores for MODIFIED files and + * mark them for splitting if break-rewrites is enabled + */ git_vector_foreach(&diff->deltas, i, to) { - size_t tried_sources = 0; + if (is_rename_source(diff, &opts, i, sigcache)) + ++num_srcs; - match_targets[i].idx = (uint32_t)i; - match_targets[i].similarity = 0; - - /* skip things that are not rename targets */ - if (!is_rename_target(diff, &opts, i, cache)) - continue; - - git_vector_foreach(&diff->deltas, j, from) { - if (i == j) - continue; - - /* skip things that are not rename sources */ - if (!is_rename_source(diff, &opts, j, cache)) - continue; - - /* cap on maximum targets we'll examine (per "to" file) */ - if (++tried_sources > opts.rename_limit) - break; - - /* calculate similarity for this pair and find best match */ - if ((error = similarity_measure( - &similarity, diff, &opts, cache, 2 * j, 2 * i + 1)) < 0) - goto cleanup; - - if (similarity < 0) { /* not actually comparable */ - --tried_sources; - continue; - } - - if (match_targets[i].similarity < (uint32_t)similarity && - match_sources[j].similarity < (uint32_t)similarity) { - match_targets[i].similarity = (uint32_t)similarity; - match_sources[j].similarity = (uint32_t)similarity; - match_targets[i].idx = (uint32_t)j; - match_sources[j].idx = (uint32_t)i; - } - } + if (is_rename_target(diff, &opts, i, sigcache)) + ++num_tgts; } - /* next rewrite the diffs with renames / copies */ + /* if there are no candidate srcs or tgts, we're done */ + if (!num_srcs || !num_tgts) + goto cleanup; + + match_tgts = git__calloc(diff->deltas.length, sizeof(diff_find_match)); + GITERR_CHECK_ALLOC(match_tgts); + match_srcs = git__calloc(diff->deltas.length, sizeof(diff_find_match)); + GITERR_CHECK_ALLOC(match_srcs); + + /* + * Find best-fit matches for rename / copy candidates + */ + +find_best_matches: + tried_tgts = num_bumped = 0; git_vector_foreach(&diff->deltas, i, to) { - /* check if this delta was the target of a similarity */ - if ((similarity = (int)match_targets[i].similarity) <= 0) + /* skip things that are not rename targets */ + if ((to->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) continue; - assert(to && (to->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) != 0); + tried_srcs = 0; - from = GIT_VECTOR_GET(&diff->deltas, match_targets[i].idx); - assert(from && (from->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) != 0); + git_vector_foreach(&diff->deltas, j, from) { + /* skip things that are not rename sources */ + if ((from->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0) + continue; + + /* calculate similarity for this pair and find best match */ + if (i == j) + similarity = -1; /* don't measure self-similarity here */ + else if ((error = similarity_measure( + &similarity, diff, &opts, sigcache, 2 * j, 2 * i + 1)) < 0) + goto cleanup; + + /* if this pairing is better for the src and the tgt, keep it */ + if (similarity > 0 && + match_tgts[i].similarity < (uint32_t)similarity && + match_srcs[j].similarity < (uint32_t)similarity) + { + if (match_tgts[i].similarity > 0) { + match_tgts[match_srcs[j].idx].similarity = 0; + match_srcs[match_tgts[i].idx].similarity = 0; + ++num_bumped; + } + + match_tgts[i].similarity = (uint32_t)similarity; + match_tgts[i].idx = (uint32_t)j; + + match_srcs[j].similarity = (uint32_t)similarity; + match_srcs[j].idx = (uint32_t)i; + } + + if (++tried_srcs >= num_srcs) + break; + + /* cap on maximum targets we'll examine (per "to" file) */ + if (tried_srcs > opts.rename_limit) + break; + } + + if (++tried_tgts >= num_tgts) + break; + } + + if (num_bumped > 0) /* try again if we bumped some items */ + goto find_best_matches; + + /* + * Rewrite the diffs with renames / copies + */ + + tried_tgts = 0; + + git_vector_foreach(&diff->deltas, i, to) { + /* skip things that are not rename targets */ + if ((to->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) + continue; + + /* check if this delta was the target of a similarity */ + best_match = &match_tgts[i]; + if (!best_match->similarity) + continue; + + j = best_match->idx; + from = GIT_VECTOR_GET(&diff->deltas, j); /* possible scenarios: * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME @@ -774,99 +824,84 @@ int git_diff_find_similar( if (delta_is_new_only(to)) { - if (similarity < (int)opts.rename_threshold) + if (best_match->similarity < opts.rename_threshold) continue; - from->status = GIT_DELTA_RENAMED; - from->similarity = (uint32_t)similarity; - memcpy(&from->new_file, &to->new_file, sizeof(from->new_file)); - - to->flags |= GIT_DIFF_FLAG__TO_DELETE; + delta_make_rename(to, from, best_match->similarity); + from->flags |= GIT_DIFF_FLAG__TO_DELETE; num_rewrites++; } else { assert(delta_is_split(to)); - if (similarity < (int)opts.rename_from_rewrite_threshold) + if (best_match->similarity < opts.rename_from_rewrite_threshold) continue; - from->status = GIT_DELTA_RENAMED; - from->similarity = (uint32_t)similarity; - memcpy(&from->new_file, &to->new_file, sizeof(from->new_file)); + memcpy(&swap, &to->old_file, sizeof(swap)); - to->status = GIT_DELTA_DELETED; - memset(&to->new_file, 0, sizeof(to->new_file)); - to->new_file.path = to->old_file.path; - to->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; - if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) { - to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; - num_rewrites--; - } + delta_make_rename(to, from, best_match->similarity); + num_rewrites--; + + from->status = GIT_DELTA_DELETED; + memcpy(&from->old_file, &swap, sizeof(from->old_file)); + memset(&from->new_file, 0, sizeof(from->new_file)); + from->new_file.path = from->old_file.path; + from->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; num_updates++; } } else if (delta_is_split(from)) { - git_diff_file swap; if (delta_is_new_only(to)) { - if (similarity < (int)opts.rename_threshold) + if (best_match->similarity < opts.rename_threshold) continue; - memcpy(&swap, &from->new_file, sizeof(swap)); + delta_make_rename(to, from, best_match->similarity); - from->status = GIT_DELTA_RENAMED; - from->similarity = (uint32_t)similarity; - memcpy(&from->new_file, &to->new_file, sizeof(from->new_file)); - if ((from->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) { - from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; - num_rewrites--; - } - - to->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ? + from->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ? GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED; - memcpy(&to->new_file, &swap, sizeof(to->new_file)); - to->old_file.path = to->new_file.path; + memset(&from->old_file, 0, sizeof(from->old_file)); + from->old_file.path = from->new_file.path; + from->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; + + from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; + num_rewrites--; num_updates++; } else { assert(delta_is_split(from)); - if (similarity < (int)opts.rename_from_rewrite_threshold) + if (best_match->similarity < opts.rename_from_rewrite_threshold) continue; - memcpy(&swap, &to->new_file, sizeof(swap)); + memcpy(&swap, &to->old_file, sizeof(swap)); - to->status = GIT_DELTA_RENAMED; - to->similarity = (uint32_t)similarity; - memcpy(&to->new_file, &from->new_file, sizeof(to->new_file)); - if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) { - to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; - num_rewrites--; - } + delta_make_rename(to, from, best_match->similarity); + num_rewrites--; + num_updates++; - memcpy(&from->new_file, &swap, sizeof(from->new_file)); - if ((from->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0) { - from->flags |= GIT_DIFF_FLAG__TO_SPLIT; - num_rewrites++; - } + memcpy(&from->old_file, &swap, sizeof(from->old_file)); - /* in the off chance that we've just swapped the new - * element into the correct place, clear the SPLIT flag + /* if we've just swapped the new element into the correct + * place, clear the SPLIT flag */ - if (match_targets[match_targets[i].idx].idx == i && - match_targets[match_targets[i].idx].similarity > + if (match_tgts[j].idx == i && + match_tgts[j].similarity > opts.rename_from_rewrite_threshold) { - from->status = GIT_DELTA_RENAMED; - from->similarity = - (uint32_t)match_targets[match_targets[i].idx].similarity; - match_targets[match_targets[i].idx].similarity = 0; + from->status = GIT_DELTA_RENAMED; + from->similarity = match_tgts[j].similarity; + match_tgts[j].similarity = 0; from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; num_rewrites--; } + /* otherwise, if we just overwrote a source, update mapping */ + else if (j > i && match_srcs[i].similarity > 0) { + match_tgts[match_srcs[i].idx].idx = j; + } num_updates++; } @@ -874,31 +909,35 @@ int git_diff_find_similar( else if (delta_is_new_only(to)) { if (!FLAG_SET(&opts, GIT_DIFF_FIND_COPIES) || - similarity < (int)opts.copy_threshold) + best_match->similarity < opts.copy_threshold) continue; - to->status = GIT_DELTA_COPIED; - to->similarity = (uint32_t)similarity; + to->status = GIT_DELTA_COPIED; + to->similarity = best_match->similarity; memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); num_updates++; } } + /* + * Actually split and delete entries as needed + */ + if (num_rewrites > 0 || num_updates > 0) error = apply_splits_and_deletes( diff, diff->deltas.length - num_rewrites, FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES)); cleanup: - git__free(match_sources); - git__free(match_targets); + git__free(match_srcs); + git__free(match_tgts); - for (i = 0; i < cache_size; ++i) { - if (cache[i] != NULL) - opts.metric->free_signature(cache[i], opts.metric->payload); + for (i = 0; i < sigcache_size; ++i) { + if (sigcache[i] != NULL) + opts.metric->free_signature(sigcache[i], opts.metric->payload); } - git__free(cache); + git__free(sigcache); if (!given_opts || !given_opts->metric) git__free(opts.metric); diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index e6bf72c17..79f407057 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -755,7 +755,7 @@ void test_diff_rename__file_partial_exchange(void) git_buf_free(&c2); } -void test_diff_rename__file_split(void) +void test_diff_rename__rename_and_copy_from_same_source(void) { git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT; git_index *index; @@ -947,6 +947,7 @@ void test_diff_rename__rejected_match_can_match_others(void) cl_git_pass( git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, &findopts)); cl_git_pass( @@ -967,10 +968,10 @@ static void write_similarity_file_two(const char *filename, size_t b_lines) size_t i; for (i = 0; i < b_lines; i++) - git_buf_printf(&contents, "%0.2d - bbbbb\r\n", (i+1)); + git_buf_printf(&contents, "%0.2d - bbbbb\r\n", (int)(i+1)); for (i = b_lines; i < 50; i++) - git_buf_printf(&contents, "%0.2d - aaaaa%s", (i+1), (i == 49 ? "" : "\r\n")); + git_buf_printf(&contents, "%0.2d - aaaaa%s", (int)(i+1), (i == 49 ? "" : "\r\n")); cl_git_pass( git_futils_writebuffer(&contents, filename, O_RDWR|O_CREAT, 0777)); @@ -1018,6 +1019,7 @@ void test_diff_rename__rejected_match_can_match_others_two(void) cl_git_pass( git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, &findopts)); cl_git_pass( From c41281ad3af420bcfe4afae6acdbe95039290525 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Wed, 19 Jun 2013 13:36:59 +0200 Subject: [PATCH 028/367] CMakeLists: fix zlib linker setup b53671a (Search for zlib unconditional, 2012-12-18) changed things around to always (even on windows, that's what the subject refers to) call FIND_PACKAGE(ZLIB). However, it did not correctly handle the case where ZLIB_LIBRARY is cached, either by the user setting it manually or by an earlier search. In that case, the IF(ZLIB_FOUND) would not trigger (that variable is not cached) and we'd instead use the built-in version. 000e689 (CMake: don't try to use bundled zlib when the system's path is in the cache, 2013-05-12) tried to fix that, but it actually made the problem worse: now with ZLIB_LIBRARY cached, _neither_ of the blocks would execute, resulting in a linker error for me when trying to build such a doubly-configured setup. To fix the issue, we just trust CMake to do the right thing. If ZLIB_LIBRARY is set (either from user or cache) then the find_library in FindZLIB.cmake will use that instead of searching again. So we can unconditionally (for real this time) call FIND_PACKAGE(ZLIB), and just check its result. --- CMakeLists.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bdc46d0b3..34d56b3fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,17 +136,15 @@ IF(WIN32 OR AMIGA) ENDIF() # Optional external dependency: zlib -IF(NOT ZLIB_LIBRARY) - # It's optional, but FIND_PACKAGE gives a warning that looks more like an - # error. - FIND_PACKAGE(ZLIB QUIET) -ENDIF() +# It's optional, but FIND_PACKAGE gives a warning that looks more like an +# error. +FIND_PACKAGE(ZLIB QUIET) IF (ZLIB_FOUND) INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) LINK_LIBRARIES(${ZLIB_LIBRARIES}) # Fake the message CMake would have shown MESSAGE("-- Found zlib: ${ZLIB_LIBRARY}") -ELSEIF (NOT ZLIB_LIBRARY) +ELSE() MESSAGE( "zlib was not found; using bundled 3rd-party sources." ) INCLUDE_DIRECTORIES(deps/zlib) ADD_DEFINITIONS(-DNO_VIZ -DSTDC -DNO_GZIP) From e91f9a8f28ca58c5ff0450749a57d233a5512f2d Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 19 Jun 2013 15:20:59 -0700 Subject: [PATCH 029/367] Add higher level pathspec API Right now, setting up a pathspec to be parsed and processed requires several data structures and a couple of API calls. This adds a new high level data structure that contains all the items that you'll need and high-level APIs that do all of the setup and all of the teardown. This will make it easier to use pathspecs in various places with less repeated code. --- src/pathspec.c | 25 +++++++++++++++++++++++++ src/pathspec.h | 14 ++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/pathspec.c b/src/pathspec.c index 35c79ce82..f029836d0 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -166,3 +166,28 @@ bool git_pathspec_match_path( return false; } + +int git_pathspec_context_init( + git_pathspec_context *ctxt, const git_strarray *paths) +{ + int error = 0; + + memset(ctxt, 0, sizeof(*ctxt)); + + ctxt->prefix = git_pathspec_prefix(paths); + + if ((error = git_pool_init(&ctxt->pool, 1, 0)) < 0 || + (error = git_pathspec_init(&ctxt->pathspec, paths, &ctxt->pool)) < 0) + git_pathspec_context_free(ctxt); + + return error; +} + +void git_pathspec_context_free( + git_pathspec_context *ctxt) +{ + git__free(ctxt->prefix); + git_pathspec_free(&ctxt->pathspec); + git_pool_clear(&ctxt->pool); + memset(ctxt, 0, sizeof(*ctxt)); +} diff --git a/src/pathspec.h b/src/pathspec.h index 43a94baad..f6509df4c 100644 --- a/src/pathspec.h +++ b/src/pathspec.h @@ -37,4 +37,18 @@ extern bool git_pathspec_match_path( bool casefold, const char **matched_pathspec); +/* easy pathspec setup */ + +typedef struct { + char *prefix; + git_vector pathspec; + git_pool pool; +} git_pathspec_context; + +extern int git_pathspec_context_init( + git_pathspec_context *ctxt, const git_strarray *paths); + +extern void git_pathspec_context_free( + git_pathspec_context *ctxt); + #endif From 85b8b18b6a07b74dd2631c3a647ca758660bf298 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 19 Jun 2013 15:22:48 -0700 Subject: [PATCH 030/367] Add fn to check pathspec for ignored files Command line Git sometimes generates an error message if given a pathspec that contains an exact match to an ignored file (provided --force isn't also given). This adds an internal function that makes it easy to check it that has happened. Right now, I'm not creating a public API for this because that would get a little more complicated with a need for callbacks for all invalid paths. --- src/ignore.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ignore.h | 9 ++++++++ 2 files changed, 67 insertions(+) diff --git a/src/ignore.c b/src/ignore.c index e150b9585..cc90b0c61 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -340,3 +340,61 @@ cleanup: return error; } + +int git_ignore__check_pathspec_for_exact_ignores( + git_repository *repo, + git_vector *vspec, + bool no_fnmatch) +{ + int error = 0; + size_t i; + git_attr_fnmatch *match; + int ignored; + git_buf path = GIT_BUF_INIT; + const char *wd, *filename; + git_index *idx; + + if ((error = git_repository__ensure_not_bare( + repo, "validate pathspec")) < 0 || + (error = git_repository_index(&idx, repo)) < 0) + return error; + + wd = git_repository_workdir(repo); + + git_vector_foreach(vspec, i, match) { + /* skip wildcard matches (if they are being used) */ + if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 && + !no_fnmatch) + continue; + + filename = match->pattern; + + /* if file is already in the index, it's fine */ + if (git_index_get_bypath(idx, filename, 0) != NULL) + continue; + + if ((error = git_buf_joinpath(&path, wd, filename)) < 0) + break; + + /* is there a file on disk that matches this exactly? */ + if (!git_path_isfile(path.ptr)) + continue; + + /* is that file ignored? */ + if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0) + break; + + if (ignored) { + giterr_set(GITERR_INVALID, "pathspec contains ignored file '%s'", + filename); + error = GIT_EINVALIDSPEC; + break; + } + } + + git_index_free(idx); + git_buf_free(&path); + + return error; +} + diff --git a/src/ignore.h b/src/ignore.h index e00e4a8c8..cc114b001 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -41,4 +41,13 @@ extern void git_ignore__free(git_ignores *ign); extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored); +/* command line Git sometimes generates an error message if given a + * pathspec that contains an exact match to an ignored file (provided + * --force isn't also given). This makes it easy to check it that has + * happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored + * exact matches (that are not already present in the index). + */ +extern int git_ignore__check_pathspec_for_exact_ignores( + git_repository *repo, git_vector *pathspec, bool no_fnmatch); + #endif From f30fff45a752cb0781067ad48c283e49345a5813 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 19 Jun 2013 15:27:25 -0700 Subject: [PATCH 031/367] Add index pathspec-based operations This adds three new public APIs for manipulating the index: 1. `git_index_add_all` is similar to `git add -A` and will add files in the working directory that match a pathspec to the index while honoring ignores, etc. 2. `git_index_remove_all` removes files from the index that match a pathspec. 3. `git_index_update_all` updates entries in the index based on the current contents of the working directory, either added the new information or removing the entry from the index. --- include/git2/index.h | 115 +++++++++++++++++ src/diff.c | 6 +- src/index.c | 215 +++++++++++++++++++++++++++++++- tests-clar/index/addall.c | 254 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 586 insertions(+), 4 deletions(-) create mode 100644 tests-clar/index/addall.c diff --git a/include/git2/index.h b/include/git2/index.h index 58b0243e0..399d7c9a8 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -11,6 +11,7 @@ #include "indexer.h" #include "types.h" #include "oid.h" +#include "strarray.h" /** * @file git2/index.h @@ -125,6 +126,18 @@ typedef enum { GIT_INDEXCAP_FROM_OWNER = ~0u } git_indexcap_t; +/** Callback for APIs that add/remove/update files matching pathspec */ +typedef int (*git_index_matched_path_cb)( + const char *path, const char *matched_pathspec, void *payload); + +/** Flags for APIs that add files matching pathspec */ +typedef enum { + GIT_INDEX_ADD_DEFAULT = 0, + GIT_INDEX_ADD_FORCE = (1u << 0), + GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH = (1u << 1), + GIT_INDEX_ADD_CHECK_PATHSPEC = (1u << 2), +} git_index_add_option_t; + /** @name Index File Functions * * These functions work on the index file itself. @@ -420,6 +433,108 @@ GIT_EXTERN(int) git_index_add_bypath(git_index *index, const char *path); */ GIT_EXTERN(int) git_index_remove_bypath(git_index *index, const char *path); +/** + * Add or update index entries matching files in the working directory. + * + * This method will fail in bare index instances. + * + * The `pathspec` is a list of file names or shell glob patterns that will + * matched against files in the repository's working directory. Each file + * that matches will be added to the index (either updating an existing + * entry or adding a new entry). You can disable glob expansion and force + * exact matching with the `GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH` flag. + * + * Files that are ignored will be skipped (unlike `git_index_add_bypath`). + * If a file is already tracked in the index, then it *will* be updated + * even if it is ignored. Pass the `GIT_INDEX_ADD_FORCE` flag to + * skip the checking of ignore rules. + * + * To emulate `git add -A` and generate an error if the pathspec contains + * the exact path of an ignored file (when not using FORCE), add the + * `GIT_INDEX_ADD_CHECK_PATHSPEC` flag. This checks that each entry + * in the `pathspec` that is an exact match to a filename on disk is + * either not ignored or already in the index. If this check fails, the + * function will return GIT_EINVALIDSPEC. + * + * To emulate `git add -A` with the "dry-run" option, just use a callback + * function that always returns a positive value. See below for details. + * + * If any files are currently the result of a merge conflict, those files + * will no longer be marked as conflicting. The data about the conflicts + * will be moved to the "resolve undo" (REUC) section. + * + * If you provide a callback function, it will be invoked on each matching + * item in the working directory immediately *before* it is added to / + * updated in the index. Returning zero will add the item to the index, + * greater than zero will skip the item, and less than zero will abort the + * scan and cause GIT_EUSER to be returned. + * + * @param index an existing index object + * @param pathspec array of path patterns + * @param flags combination of git_index_add_option_t flags + * @param callback notification callback for each added/updated path (also + * gets index of matching pathspec entry); can be NULL; + * return 0 to add, >0 to skip, <0 to abort scan. + * @param payload payload passed through to callback function + * @return 0 or an error code + */ +GIT_EXTERN(int) git_index_add_all( + git_index *index, + const git_strarray *pathspec, + unsigned int flags, + git_index_matched_path_cb callback, + void *payload); + +/** + * Remove all matching index entries. + * + * If you provide a callback function, it will be invoked on each matching + * item in the index immediately *before* it is removed. Return 0 to + * remove the item, > 0 to skip the item, and < 0 to abort the scan. + * + * @param index An existing index object + * @param pathspec array of path patterns + * @param callback notification callback for each removed path (also + * gets index of matching pathspec entry); can be NULL; + * return 0 to add, >0 to skip, <0 to abort scan. + * @param payload payload passed through to callback function + * @return 0 or an error code + */ +GIT_EXTERN(int) git_index_remove_all( + git_index *index, + const git_strarray *pathspec, + git_index_matched_path_cb callback, + void *payload); + +/** + * Update all index entries to match the working directory + * + * This method will fail in bare index instances. + * + * This scans the existing index entries and synchronizes them with the + * working directory, deleting them if the corresponding working directory + * file no longer exists otherwise updating the information (including + * adding the latest version of file to the ODB if needed). + * + * If you provide a callback function, it will be invoked on each matching + * item in the index immediately *before* it is updated (either refreshed + * or removed depending on working directory state). Return 0 to proceed + * with updating the item, > 0 to skip the item, and < 0 to abort the scan. + * + * @param index An existing index object + * @param pathspec array of path patterns + * @param callback notification callback for each updated path (also + * gets index of matching pathspec entry); can be NULL; + * return 0 to add, >0 to skip, <0 to abort scan. + * @param payload payload passed through to callback function + * @return 0 or an error code + */ +GIT_EXTERN(int) git_index_update_all( + git_index *index, + const git_strarray *pathspec, + git_index_matched_path_cb callback, + void *payload); + /** * Find the first position of any entries which point to given * path in the Git index. diff --git a/src/diff.c b/src/diff.c index fa2c5c71d..633601699 100644 --- a/src/diff.c +++ b/src/diff.c @@ -675,8 +675,10 @@ static int maybe_modified( } } - /* if oids and modes match, then file is unmodified */ - else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode) + /* if oids and modes match (and are valid), then file is unmodified */ + else if (git_oid_equal(&oitem->oid, &nitem->oid) && + omode == nmode && + !git_oid_iszero(&oitem->oid)) status = GIT_DELTA_UNMODIFIED; /* if we have an unknown OID and a workdir iterator, then check some diff --git a/src/index.c b/src/index.c index 560a257e7..e65dc052c 100644 --- a/src/index.c +++ b/src/index.c @@ -15,6 +15,8 @@ #include "hash.h" #include "iterator.h" #include "pathspec.h" +#include "ignore.h" + #include "git2/odb.h" #include "git2/oid.h" #include "git2/blob.h" @@ -997,7 +999,7 @@ static int index_conflict__get_byindex( int stage, len = 0; assert(ancestor_out && our_out && their_out && index); - + *ancestor_out = NULL; *our_out = NULL; *their_out = NULL; @@ -1010,7 +1012,7 @@ static int index_conflict__get_byindex( stage = GIT_IDXENTRY_STAGE(conflict_entry); path = conflict_entry->path; - + switch (stage) { case 3: *their_out = conflict_entry; @@ -2044,3 +2046,212 @@ git_repository *git_index_owner(const git_index *index) { return INDEX_OWNER(index); } + +int git_index_add_all( + git_index *index, + const git_strarray *paths, + unsigned int flags, + git_index_matched_path_cb cb, + void *payload) +{ + int error; + git_repository *repo; + git_iterator *wditer = NULL; + const git_index_entry *wd = NULL; + git_index_entry *entry; + git_pathspec_context ps; + const char *match; + size_t existing; + bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0; + int ignorecase; + git_oid blobid; + + assert(index); + + if (INDEX_OWNER(index) == NULL) + return create_index_error(-1, + "Could not add paths to index. " + "Index is not backed up by an existing repository."); + + repo = INDEX_OWNER(index); + if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0) + return error; + + if (git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE) < 0) + return -1; + + if ((error = git_pathspec_context_init(&ps, paths)) < 0) + return error; + + /* optionally check that pathspec doesn't mention any ignored files */ + if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 && + (flags & GIT_INDEX_ADD_FORCE) == 0 && + (error = git_ignore__check_pathspec_for_exact_ignores( + repo, &ps.pathspec, no_fnmatch)) < 0) + goto cleanup; + + if ((error = git_iterator_for_workdir( + &wditer, repo, 0, ps.prefix, ps.prefix)) < 0) + goto cleanup; + + while (!(error = git_iterator_advance(&wd, wditer))) { + + /* check if path actually matches */ + if (!git_pathspec_match_path( + &ps.pathspec, wd->path, no_fnmatch, ignorecase, &match)) + continue; + + /* skip ignored items that are not already in the index */ + if ((flags & GIT_INDEX_ADD_FORCE) == 0 && + git_iterator_current_is_ignored(wditer) && + index_find(&existing, index, wd->path, 0) < 0) + continue; + + /* issue notification callback if requested */ + if (cb && (error = cb(wd->path, match, payload)) != 0) { + if (error > 0) /* return > 0 means skip this one */ + continue; + if (error < 0) { /* return < 0 means abort */ + giterr_clear(); + error = GIT_EUSER; + break; + } + } + + /* TODO: Should we check if the file on disk is already an exact + * match to the file in the index and skip this work if it is? + */ + + /* write the blob to disk and get the oid */ + if ((error = git_blob_create_fromworkdir(&blobid, repo, wd->path)) < 0) + break; + + /* make the new entry to insert */ + if ((entry = index_entry_dup(wd)) == NULL) { + error = -1; + break; + } + entry->oid = blobid; + + /* add working directory item to index */ + if ((error = index_insert(index, entry, 1)) < 0) { + index_entry_free(entry); + break; + } + + git_tree_cache_invalidate_path(index->tree, wd->path); + + /* add implies conflict resolved, move conflict entries to REUC */ + if ((error = index_conflict_to_reuc(index, wd->path)) < 0) { + if (error != GIT_ENOTFOUND) + break; + giterr_clear(); + } + } + + if (error == GIT_ITEROVER) + error = 0; + +cleanup: + git_iterator_free(wditer); + git_pathspec_context_free(&ps); + + return error; +} + +enum { + INDEX_ACTION_NONE = 0, + INDEX_ACTION_UPDATE = 1, + INDEX_ACTION_REMOVE = 2, +}; + +static int index_apply_to_all( + git_index *index, + int action, + const git_strarray *paths, + git_index_matched_path_cb cb, + void *payload) +{ + int error = 0; + size_t i; + git_pathspec_context ps; + const char *match; + + assert(index); + + if ((error = git_pathspec_context_init(&ps, paths)) < 0) + return error; + + git_vector_sort(&index->entries); + + for (i = 0; !error && i < index->entries.length; ++i) { + git_index_entry *entry = git_vector_get(&index->entries, i); + + /* check if path actually matches */ + if (!git_pathspec_match_path( + &ps.pathspec, entry->path, false, index->ignore_case, &match)) + continue; + + /* issue notification callback if requested */ + if (cb && (error = cb(entry->path, match, payload)) != 0) { + if (error > 0) { /* return > 0 means skip this one */ + error = 0; + continue; + } + if (error < 0) { /* return < 0 means abort */ + giterr_clear(); + error = GIT_EUSER; + break; + } + } + + switch (action) { + case INDEX_ACTION_NONE: + break; + case INDEX_ACTION_UPDATE: + error = git_index_add_bypath(index, entry->path); + + if (error == GIT_ENOTFOUND) { + giterr_clear(); + + error = git_index_remove_bypath(index, entry->path); + + if (!error) /* back up foreach if we removed this */ + i--; + } + break; + case INDEX_ACTION_REMOVE: + if (!(error = git_index_remove_bypath(index, entry->path))) + i--; /* back up foreach if we removed this */ + break; + default: + giterr_set(GITERR_INVALID, "Unknown index action %d", action); + error = -1; + break; + } + } + + git_pathspec_context_free(&ps); + + return error; +} + +int git_index_remove_all( + git_index *index, + const git_strarray *pathspec, + git_index_matched_path_cb cb, + void *payload) +{ + return index_apply_to_all( + index, INDEX_ACTION_REMOVE, pathspec, cb, payload); +} + +int git_index_update_all( + git_index *index, + const git_strarray *pathspec, + git_index_matched_path_cb cb, + void *payload) +{ + return index_apply_to_all( + index, INDEX_ACTION_UPDATE, pathspec, cb, payload); +} diff --git a/tests-clar/index/addall.c b/tests-clar/index/addall.c new file mode 100644 index 000000000..33873cb7a --- /dev/null +++ b/tests-clar/index/addall.c @@ -0,0 +1,254 @@ +#include "clar_libgit2.h" +#include "../status/status_helpers.h" +#include "posix.h" + +git_repository *g_repo = NULL; + +void test_index_addall__initialize(void) +{ +} + +void test_index_addall__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; +} + +#define STATUS_INDEX_FLAGS \ + (GIT_STATUS_INDEX_NEW | GIT_STATUS_INDEX_MODIFIED | \ + GIT_STATUS_INDEX_DELETED | GIT_STATUS_INDEX_RENAMED | \ + GIT_STATUS_INDEX_TYPECHANGE) + +#define STATUS_WT_FLAGS \ + (GIT_STATUS_WT_NEW | GIT_STATUS_WT_MODIFIED | \ + GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE | \ + GIT_STATUS_WT_RENAMED) + +typedef struct { + size_t index_adds; + size_t index_dels; + size_t index_mods; + size_t wt_adds; + size_t wt_dels; + size_t wt_mods; + size_t ignores; +} index_status_counts; + +static int index_status_cb( + const char *path, unsigned int status_flags, void *payload) +{ + index_status_counts *vals = payload; + + /* cb_status__print(path, status_flags, NULL); */ + + GIT_UNUSED(path); + + if (status_flags & GIT_STATUS_INDEX_NEW) + vals->index_adds++; + if (status_flags & GIT_STATUS_INDEX_MODIFIED) + vals->index_mods++; + if (status_flags & GIT_STATUS_INDEX_DELETED) + vals->index_dels++; + if (status_flags & GIT_STATUS_INDEX_TYPECHANGE) + vals->index_mods++; + + if (status_flags & GIT_STATUS_WT_NEW) + vals->wt_adds++; + if (status_flags & GIT_STATUS_WT_MODIFIED) + vals->wt_mods++; + if (status_flags & GIT_STATUS_WT_DELETED) + vals->wt_dels++; + if (status_flags & GIT_STATUS_WT_TYPECHANGE) + vals->wt_mods++; + + if (status_flags & GIT_STATUS_IGNORED) + vals->ignores++; + + return 0; +} + +static void check_status( + git_repository *repo, + size_t index_adds, size_t index_dels, size_t index_mods, + size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores) +{ + index_status_counts vals; + + memset(&vals, 0, sizeof(vals)); + + cl_git_pass(git_status_foreach(repo, index_status_cb, &vals)); + + cl_assert_equal_sz(index_adds, vals.index_adds); + cl_assert_equal_sz(index_dels, vals.index_dels); + cl_assert_equal_sz(index_mods, vals.index_mods); + cl_assert_equal_sz(wt_adds, vals.wt_adds); + cl_assert_equal_sz(wt_dels, vals.wt_dels); + cl_assert_equal_sz(wt_mods, vals.wt_mods); + cl_assert_equal_sz(ignores, vals.ignores); +} + +static void check_stat_data(git_index *index, const char *path, bool match) +{ + const git_index_entry *entry; + struct stat st; + + cl_must_pass(p_lstat(path, &st)); + + /* skip repo base dir name */ + while (*path != '/') + ++path; + ++path; + + entry = git_index_get_bypath(index, path, 0); + cl_assert(entry); + + if (match) { + cl_assert(st.st_ctime == entry->ctime.seconds); + cl_assert(st.st_mtime == entry->mtime.seconds); + cl_assert(st.st_size == entry->file_size); + cl_assert(st.st_uid == entry->uid); + cl_assert(st.st_gid == entry->gid); + cl_assert_equal_b(st.st_mode & ~0777, entry->mode & ~0777); + cl_assert_equal_b(st.st_mode & 0111, entry->mode & 0111); + } else { + /* most things will still match */ + cl_assert(st.st_size != entry->file_size); + /* would check mtime, but with second resolution it won't work :( */ + } +} + +static void commit_index_to_head( + git_repository *repo, + const char *commit_message) +{ + git_index *index; + git_oid tree_id, commit_id; + git_tree *tree; + git_signature *sig; + git_commit *parent = NULL; + + git_revparse_single((git_object **)&parent, repo, "HEAD"); + /* it is okay if looking up the HEAD fails */ + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + git_index_free(index); + + cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); + + cl_git_pass(git_signature_now(&sig, "Testy McTester", "tt@tester.test")); + + cl_git_pass(git_commit_create_v( + &commit_id, repo, "HEAD", sig, sig, + NULL, commit_message, tree, parent ? 1 : 0, parent)); + + git_commit_free(parent); + git_tree_free(tree); + git_signature_free(sig); +} + +void test_index_addall__repo_lifecycle(void) +{ + int error; + git_index *index; + git_strarray paths = { NULL, 0 }; + char *strs[1]; + + cl_git_pass(git_repository_init(&g_repo, "addall", false)); + check_status(g_repo, 0, 0, 0, 0, 0, 0, 0); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_mkfile("addall/file.foo", "a file"); + check_status(g_repo, 0, 0, 0, 1, 0, 0, 0); + + cl_git_mkfile("addall/.gitignore", "*.foo\n"); + check_status(g_repo, 0, 0, 0, 1, 0, 0, 1); + + cl_git_mkfile("addall/file.bar", "another file"); + check_status(g_repo, 0, 0, 0, 2, 0, 0, 1); + + strs[0] = "file.*"; + paths.strings = strs; + paths.count = 1; + + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + check_stat_data(index, "addall/file.bar", true); + check_status(g_repo, 1, 0, 0, 1, 0, 0, 1); + + cl_git_rewritefile("addall/file.bar", "new content for file"); + check_stat_data(index, "addall/file.bar", false); + check_status(g_repo, 1, 0, 0, 1, 0, 1, 1); + + cl_git_mkfile("addall/file.zzz", "yet another one"); + cl_git_mkfile("addall/other.zzz", "yet another one"); + cl_git_mkfile("addall/more.zzz", "yet another one"); + check_status(g_repo, 1, 0, 0, 4, 0, 1, 1); + + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + check_stat_data(index, "addall/file.bar", true); + check_status(g_repo, 1, 0, 0, 4, 0, 0, 1); + + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + check_stat_data(index, "addall/file.zzz", true); + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1); + + commit_index_to_head(g_repo, "first commit"); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1); + + /* attempt to add an ignored file - does nothing */ + strs[0] = "file.foo"; + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1); + + /* add with check - should generate error */ + error = git_index_add_all( + index, &paths, GIT_INDEX_ADD_CHECK_PATHSPEC, NULL, NULL); + cl_assert_equal_i(GIT_EINVALIDSPEC, error); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1); + + /* add with force - should allow */ + cl_git_pass(git_index_add_all( + index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL)); + check_stat_data(index, "addall/file.foo", true); + check_status(g_repo, 1, 0, 0, 3, 0, 0, 0); + + /* now it's in the index, so regular add should work */ + cl_git_rewritefile("addall/file.foo", "new content for file"); + check_stat_data(index, "addall/file.foo", false); + check_status(g_repo, 1, 0, 0, 3, 0, 1, 0); + + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + check_stat_data(index, "addall/file.foo", true); + check_status(g_repo, 1, 0, 0, 3, 0, 0, 0); + + cl_git_pass(git_index_add_bypath(index, "more.zzz")); + check_stat_data(index, "addall/more.zzz", true); + check_status(g_repo, 2, 0, 0, 2, 0, 0, 0); + + cl_git_rewritefile("addall/file.zzz", "new content for file"); + check_status(g_repo, 2, 0, 0, 2, 0, 1, 0); + + cl_git_pass(git_index_add_bypath(index, "file.zzz")); + check_stat_data(index, "addall/file.zzz", true); + check_status(g_repo, 2, 0, 1, 2, 0, 0, 0); + + strs[0] = "*.zzz"; + cl_git_pass(git_index_remove_all(index, &paths, NULL, NULL)); + check_status(g_repo, 1, 1, 0, 4, 0, 0, 0); + + cl_git_pass(git_index_add_bypath(index, "file.zzz")); + check_status(g_repo, 1, 0, 1, 3, 0, 0, 0); + + commit_index_to_head(g_repo, "second commit"); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 0); + + cl_must_pass(p_unlink("addall/file.zzz")); + check_status(g_repo, 0, 0, 0, 3, 1, 0, 0); + + /* update_all should be able to remove entries */ + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + check_status(g_repo, 0, 1, 0, 3, 0, 0, 0); + + git_index_free(index); +} From 7863523a1be51981bafee9d13b3344fb4ff47347 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 19 Jun 2013 15:54:19 -0700 Subject: [PATCH 032/367] Add tests and fix use of freed memory This adds some tests for updating the index and having it remove items to make sure that the iteration over the index still works even as earlier items are removed. In testing with valgrind, this found a path that would use the path string from the index entry after it had been freed. The bug fix is simply to copy the path of the index entry before doing any actual index manipulation. --- src/index.c | 12 +++++++++--- tests-clar/index/addall.c | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/index.c b/src/index.c index e65dc052c..d5568528b 100644 --- a/src/index.c +++ b/src/index.c @@ -2176,6 +2176,7 @@ static int index_apply_to_all( size_t i; git_pathspec_context ps; const char *match; + git_buf path = GIT_BUF_INIT; assert(index); @@ -2205,23 +2206,27 @@ static int index_apply_to_all( } } + /* index manipulation may alter entry, so don't depend on it */ + if ((error = git_buf_sets(&path, entry->path)) < 0) + break; + switch (action) { case INDEX_ACTION_NONE: break; case INDEX_ACTION_UPDATE: - error = git_index_add_bypath(index, entry->path); + error = git_index_add_bypath(index, path.ptr); if (error == GIT_ENOTFOUND) { giterr_clear(); - error = git_index_remove_bypath(index, entry->path); + error = git_index_remove_bypath(index, path.ptr); if (!error) /* back up foreach if we removed this */ i--; } break; case INDEX_ACTION_REMOVE: - if (!(error = git_index_remove_bypath(index, entry->path))) + if (!(error = git_index_remove_bypath(index, path.ptr))) i--; /* back up foreach if we removed this */ break; default: @@ -2231,6 +2236,7 @@ static int index_apply_to_all( } } + git_buf_free(&path); git_pathspec_context_free(&ps); return error; diff --git a/tests-clar/index/addall.c b/tests-clar/index/addall.c index 33873cb7a..fca6e77fa 100644 --- a/tests-clar/index/addall.c +++ b/tests-clar/index/addall.c @@ -132,6 +132,7 @@ static void commit_index_to_head( cl_git_pass(git_repository_index(&index, repo)); cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_index_write(index)); /* not needed, but might as well */ git_index_free(index); cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); @@ -250,5 +251,24 @@ void test_index_addall__repo_lifecycle(void) cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); check_status(g_repo, 0, 1, 0, 3, 0, 0, 0); + strs[0] = "*"; + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + check_status(g_repo, 3, 1, 0, 0, 0, 0, 0); + + /* must be able to remove at any position while still updating other files */ + cl_must_pass(p_unlink("addall/.gitignore")); + cl_git_rewritefile("addall/file.zzz", "reconstructed file"); + cl_git_rewritefile("addall/more.zzz", "altered file reality"); + check_status(g_repo, 3, 1, 0, 1, 1, 1, 0); + + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + check_status(g_repo, 2, 1, 0, 1, 0, 0, 0); + /* this behavior actually matches 'git add -u' where "file.zzz" has + * been removed from the index, so when you go to update, even though + * it exists in the HEAD, it is not re-added to the index, leaving it + * as a DELETE when comparing HEAD to index and as an ADD comparing + * index to worktree + */ + git_index_free(index); } From 852ded96982ae70acb63c3940fae08ea29e40fee Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 20 Jun 2013 11:37:58 -0700 Subject: [PATCH 033/367] Fix bug in diff untracked dir scan When scanning untracked directories looking for non-ignored files there was a bug where an empty directory would generate a false error. --- src/diff.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/diff.c b/src/diff.c index 633601699..3846a5e1b 100644 --- a/src/diff.c +++ b/src/diff.c @@ -786,10 +786,15 @@ static int diff_scan_inside_untracked_dir( /* need to recurse into non-ignored directories */ if (!is_ignored && S_ISDIR(info->nitem->mode)) { - if ((error = git_iterator_advance_into( - &info->nitem, info->new_iter)) < 0) - break; - continue; + error = git_iterator_advance_into(&info->nitem, info->new_iter); + + if (!error) + continue; + else if (error == GIT_ENOTFOUND) { + error = 0; + is_ignored = true; /* treat empty as ignored */ + } else + break; /* real error, must stop */ } /* found a non-ignored item - treat parent dir as untracked */ From cf300bb9e50c65e4140f7e204243b34f2898fa95 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 20 Jun 2013 11:39:31 -0700 Subject: [PATCH 034/367] Initial implementation of status example --- examples/Makefile | 2 +- examples/status.c | 294 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 examples/status.c diff --git a/examples/Makefile b/examples/Makefile index c5d555566..140cc4da9 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -3,7 +3,7 @@ CC = gcc CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers LFLAGS = -L../build -lgit2 -lz -APPS = general showindex diff rev-list cat-file +APPS = general showindex diff rev-list cat-file status all: $(APPS) diff --git a/examples/status.c b/examples/status.c new file mode 100644 index 000000000..2378c78b6 --- /dev/null +++ b/examples/status.c @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2011-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include +#include +#include + +enum { + FORMAT_DEFAULT = 0, + FORMAT_LONG = 1, + FORMAT_SHORT = 2, + FORMAT_PORCELAIN = 3, +}; +#define MAX_PATHSPEC 8 + +/* + * This example demonstrates the use of `git_status_foreach()` to roughly + * simulate the output of running `git status`. It should serve as a simple + * example of how to get basic status information. + * + * This does not have: + * - Robust error handling + * - Any real command line parsing + * - Colorized or paginated output formatting + * + */ + +static void check(int error, const char *message, const char *extra) +{ + const git_error *lg2err; + const char *lg2msg = "", *lg2spacer = ""; + + if (!error) + return; + + if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) { + lg2msg = lg2err->message; + lg2spacer = " - "; + } + + if (extra) + fprintf(stderr, "%s '%s' [%d]%s%s\n", + message, extra, error, lg2spacer, lg2msg); + else + fprintf(stderr, "%s [%d]%s%s\n", + message, error, lg2spacer, lg2msg); + + exit(1); +} + +static void fail(const char *message) +{ + check(-1, message, NULL); +} + +static void show_branch(git_repository *repo, int format) +{ + int error = 0; + const char *branch = NULL; + git_reference *head = NULL; + + error = git_repository_head(&head, repo); + + if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND) + branch = NULL; + else if (!error) { + branch = git_reference_name(head); + if (!strncmp(branch, "refs/heads/", strlen("refs/heads/"))) + branch += strlen("refs/heads/"); + } else + check(error, "failed to get current branch", NULL); + + if (format == FORMAT_LONG) + printf("# %s\n", branch ? branch : "Not currently on any branch."); + else + printf("## %s\n", branch ? branch : "HEAD (no branch)"); + + git_reference_free(head); +} + +static void print_long(git_repository *repo, git_status_list *status) +{ + (void)repo; + (void)status; +} + +static void print_short(git_repository *repo, git_status_list *status) +{ + size_t i, maxi = git_status_list_entrycount(status); + const git_status_entry *s; + char istatus, wstatus; + const char *extra, *a, *b, *c; + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_CURRENT) + continue; + + a = b = c = NULL; + istatus = wstatus = ' '; + extra = ""; + + if (s->status & GIT_STATUS_INDEX_NEW) + istatus = 'A'; + if (s->status & GIT_STATUS_INDEX_MODIFIED) + istatus = 'M'; + if (s->status & GIT_STATUS_INDEX_DELETED) + istatus = 'D'; + if (s->status & GIT_STATUS_INDEX_RENAMED) + istatus = 'R'; + if (s->status & GIT_STATUS_INDEX_TYPECHANGE) + istatus = 'T'; + + if (s->status & GIT_STATUS_WT_NEW) { + if (istatus == ' ') + istatus = '?'; + wstatus = '?'; + } + if (s->status & GIT_STATUS_WT_MODIFIED) + wstatus = 'M'; + if (s->status & GIT_STATUS_WT_DELETED) + wstatus = 'D'; + if (s->status & GIT_STATUS_WT_RENAMED) + wstatus = 'R'; + if (s->status & GIT_STATUS_WT_TYPECHANGE) + wstatus = 'T'; + + if (s->status & GIT_STATUS_IGNORED) { + istatus = '!'; + wstatus = '!'; + } + + if (istatus == '?' && wstatus == '?') + continue; + + if (s->index_to_workdir && + s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT) + { + git_submodule *sm = NULL; + unsigned int smstatus = 0; + + if (!git_submodule_lookup( + &sm, repo, s->index_to_workdir->new_file.path) && + !git_submodule_status(&smstatus, sm)) + { + if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED) + extra = " (new commits)"; + else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) + extra = " (modified content)"; + else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) + extra = " (modified content)"; + else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED) + extra = " (untracked content)"; + } + } + + if (s->head_to_index) { + a = s->head_to_index->old_file.path; + b = s->head_to_index->new_file.path; + } + if (s->index_to_workdir) { + if (!a) + a = s->index_to_workdir->old_file.path; + if (!b) + b = s->index_to_workdir->old_file.path; + c = s->index_to_workdir->new_file.path; + } + + if (istatus == 'R') { + if (wstatus == 'R') + printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra); + else + printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra); + } else { + if (wstatus == 'R') + printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra); + else + printf("%c%c %s%s\n", istatus, wstatus, a, extra); + } + } + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_WT_NEW) + printf("?? %s\n", s->index_to_workdir->old_file.path); + } +} + +int main(int argc, char *argv[]) +{ + git_repository *repo = NULL; + int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0; + git_status_options opt = GIT_STATUS_OPTIONS_INIT; + git_status_list *status; + char *repodir = ".", *pathspec[MAX_PATHSPEC]; + + opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + for (i = 1; i < argc; ++i) { + if (argv[i][0] != '-') { + if (npaths < MAX_PATHSPEC) + pathspec[npaths++] = argv[i]; + else + fail("Example only supports a limited pathspec"); + } + else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short")) + format = FORMAT_SHORT; + else if (!strcmp(argv[i], "--long")) + format = FORMAT_LONG; + else if (!strcmp(argv[i], "--porcelain")) + format = FORMAT_PORCELAIN; + else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch")) + showbranch = 1; + else if (!strcmp(argv[i], "-z")) { + zterm = 1; + if (format == FORMAT_DEFAULT) + format = FORMAT_PORCELAIN; + } + else if (!strcmp(argv[i], "--ignored")) + opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; + else if (!strcmp(argv[i], "-uno") || + !strcmp(argv[i], "--untracked-files=no")) + opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED; + else if (!strcmp(argv[i], "-unormal") || + !strcmp(argv[i], "--untracked-files=normal")) + opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + else if (!strcmp(argv[i], "-uall") || + !strcmp(argv[i], "--untracked-files=all")) + opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + else if (!strcmp(argv[i], "--ignore-submodules=all")) + opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir="))) + repodir = argv[i] + strlen("--git-dir="); + else + check(-1, "Unsupported option", argv[i]); + } + + if (format == FORMAT_DEFAULT) + format = FORMAT_LONG; + if (format == FORMAT_LONG) + showbranch = 1; + if (npaths > 0) { + opt.pathspec.strings = pathspec; + opt.pathspec.count = npaths; + } + + /* + * Try to open the repository at the given path (or at the current + * directory if none was given). + */ + check(git_repository_open_ext(&repo, repodir, 0, NULL), + "Could not open repository", repodir); + + if (git_repository_is_bare(repo)) + fail("Cannot report status on bare repository"); + + /* + * Run status on the repository + * + * Because we want to simluate a full "git status" run and want to + * support some command line options, we use `git_status_foreach_ext()` + * instead of just the plain status call. This allows (a) iterating + * over the index and then the workdir and (b) extra flags that control + * which files are included. If you just want simple status (e.g. to + * enumerate files that are modified) then you probably don't need the + * extended API. + */ + check(git_status_list_new(&status, repo, &opt), + "Could not get status", NULL); + + if (showbranch) + show_branch(repo, format); + + if (format == FORMAT_LONG) + print_long(repo, status); + else + print_short(repo, status); + + git_status_list_free(status); + git_repository_free(repo); + + return 0; +} + From 22b6b82f2c0d95ce7a433394a6c0574a5714cf4c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 20 Jun 2013 12:16:06 -0700 Subject: [PATCH 035/367] Add status flags to force output sort order Files in status will, be default, be sorted according to the case insensitivity of the filesystem that we're running on. However, in some cases, this is not desirable. Even on case insensitive file systems, 'git status' at the command line will generally use a case sensitive sort (like 'ls'). Some GUIs prefer to display a list of file case insensitively even on case-sensitive platforms. This adds two new flags: GIT_STATUS_OPT_SORT_CASE_SENSITIVELY and GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY that will override the default sort order of the status output and give the user control. This includes tests for exercising these new options and makes the examples/status.c program emulate core Git and always use a case sensitive sort. --- examples/status.c | 3 +- include/git2/status.h | 8 +++ src/attr_file.c | 2 +- src/diff.c | 12 ++--- src/index.c | 10 ++-- src/status.c | 12 ++++- src/submodule.c | 2 +- src/vector.h | 9 ++++ tests-clar/status/status_helpers.c | 3 ++ tests-clar/status/status_helpers.h | 1 + tests-clar/status/worktree.c | 80 ++++++++++++++++++++++++++++++ 11 files changed, 126 insertions(+), 16 deletions(-) diff --git a/examples/status.c b/examples/status.c index 2378c78b6..3c82640b2 100644 --- a/examples/status.c +++ b/examples/status.c @@ -203,7 +203,8 @@ int main(int argc, char *argv[]) opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; for (i = 1; i < argc; ++i) { if (argv[i][0] != '-') { diff --git a/include/git2/status.h b/include/git2/status.h index 282b606cb..63aea2f3b 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -111,6 +111,12 @@ typedef enum { * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename * detection should be run between the index and the working directory * and enabled GIT_STATUS_WT_RENAMED as a possible status flag. + * - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case + * sensitivity for the file system and forces the output to be in + * case-sensitive order + * - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case + * sensitivity for the file system and forces the output to be in + * case-insensitive order * * Calling `git_status_foreach()` is like calling the extended version * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, @@ -127,6 +133,8 @@ typedef enum { GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7), GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9), + GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10), } git_status_opt_t; #define GIT_STATUS_OPT_DEFAULTS \ diff --git a/src/attr_file.c b/src/attr_file.c index d059cfec7..d880398e8 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -498,7 +498,7 @@ int git_attr_assignment__parse( assert(assigns && !assigns->length); - assigns->_cmp = sort_by_hash_and_name; + git_vector_set_cmp(assigns, sort_by_hash_and_name); while (*scan && *scan != '\n') { const char *name_start, *value_start; diff --git a/src/diff.c b/src/diff.c index 3846a5e1b..26e117402 100644 --- a/src/diff.c +++ b/src/diff.c @@ -365,7 +365,7 @@ static git_diff_list *diff_list_alloc( diff->pfxcomp = git__prefixcmp_icase; diff->entrycomp = git_index_entry__cmp_icase; - diff->deltas._cmp = git_diff_delta__casecmp; + git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); } return diff; @@ -1165,7 +1165,7 @@ int git_diff_tree_to_index( d->pfxcomp = git__prefixcmp_icase; d->entrycomp = git_index_entry__cmp_icase; - d->deltas._cmp = git_diff_delta__casecmp; + git_vector_set_cmp(&d->deltas, git_diff_delta__casecmp); git_vector_sort(&d->deltas); } } @@ -1266,10 +1266,10 @@ int git_diff__paired_foreach( /* force case-sensitive delta sort */ if (icase_mismatch) { if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { - head2idx->deltas._cmp = git_diff_delta__cmp; + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); git_vector_sort(&head2idx->deltas); } else { - idx2wd->deltas._cmp = git_diff_delta__cmp; + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__cmp); git_vector_sort(&idx2wd->deltas); } } @@ -1301,10 +1301,10 @@ int git_diff__paired_foreach( /* restore case-insensitive delta sort */ if (icase_mismatch) { if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { - head2idx->deltas._cmp = git_diff_delta__casecmp; + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); git_vector_sort(&head2idx->deltas); } else { - idx2wd->deltas._cmp = git_diff_delta__casecmp; + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp); git_vector_sort(&idx2wd->deltas); } } diff --git a/src/index.c b/src/index.c index d5568528b..1d46779bf 100644 --- a/src/index.c +++ b/src/index.c @@ -290,16 +290,16 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case) { index->ignore_case = ignore_case; - index->entries._cmp = ignore_case ? index_icmp : index_cmp; index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path; index->entries_search = ignore_case ? index_isrch : index_srch; index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path; - index->entries.sorted = 0; + + git_vector_set_cmp(&index->entries, ignore_case ? index_icmp : index_cmp); git_vector_sort(&index->entries); - index->reuc._cmp = ignore_case ? reuc_icmp : reuc_cmp; index->reuc_search = ignore_case ? reuc_isrch : reuc_srch; - index->reuc.sorted = 0; + + git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp); git_vector_sort(&index->reuc); } @@ -2024,7 +2024,7 @@ int git_index_read_tree(git_index *index, const git_tree *tree) git_vector_sort(&index->entries); - entries._cmp = index->entries._cmp; + git_vector_set_cmp(&entries, index->entries._cmp); git_vector_swap(&entries, &index->entries); git_index_clear(index); diff --git a/src/status.c b/src/status.c index 375100a89..e520c1017 100644 --- a/src/status.c +++ b/src/status.c @@ -335,8 +335,16 @@ int git_status_list_new( status->head2idx, status->idx2wd, status_collect, status)) < 0) goto done; - if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 || - (flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0) + if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY) + git_vector_set_cmp(&status->paired, status_entry_cmp); + if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY) + git_vector_set_cmp(&status->paired, status_entry_icmp); + + if ((flags & + (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY | + GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0) git_vector_sort(&status->paired); done: diff --git a/src/submodule.c b/src/submodule.c index af488b7f3..89eba2aa4 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -151,7 +151,7 @@ int git_submodule_foreach( int error; git_submodule *sm; git_vector seen = GIT_VECTOR_INIT; - seen._cmp = submodule_cmp; + git_vector_set_cmp(&seen, submodule_cmp); assert(repo && callback); diff --git a/src/vector.h b/src/vector.h index e2f729b83..1bda9c93d 100644 --- a/src/vector.h +++ b/src/vector.h @@ -78,4 +78,13 @@ void git_vector_remove_matching( int git_vector_resize_to(git_vector *v, size_t new_length); int git_vector_set(void **old, git_vector *v, size_t position, void *value); +/** Set the comparison function used for sorting the vector */ +GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp) +{ + if (cmp != v->_cmp) { + v->_cmp = cmp; + v->sorted = 0; + } +} + #endif diff --git a/tests-clar/status/status_helpers.c b/tests-clar/status/status_helpers.c index f073c2491..902b65c4f 100644 --- a/tests-clar/status/status_helpers.c +++ b/tests-clar/status/status_helpers.c @@ -6,6 +6,9 @@ int cb_status__normal( { status_entry_counts *counts = payload; + if (counts->debug) + cb_status__print(path, status_flags, NULL); + if (counts->entry_count >= counts->expected_entry_count) { counts->wrong_status_flags_count++; goto exit; diff --git a/tests-clar/status/status_helpers.h b/tests-clar/status/status_helpers.h index ae1469e79..f1f009e02 100644 --- a/tests-clar/status/status_helpers.h +++ b/tests-clar/status/status_helpers.h @@ -8,6 +8,7 @@ typedef struct { const unsigned int* expected_statuses; const char** expected_paths; int expected_entry_count; + bool debug; } status_entry_counts; /* cb_status__normal takes payload of "status_entry_counts *" */ diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 7c27ee588..920671e13 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -743,3 +743,83 @@ void test_status_worktree__simple_delete_indexed(void) GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status); git_status_list_free(status); } + +static const char *icase_paths[] = { "B", "c", "g", "H" }; +static unsigned int icase_statuses[] = { + GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, +}; + +static const char *case_paths[] = { "B", "H", "c", "g" }; +static unsigned int case_statuses[] = { + GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED, +}; + +void test_status_worktree__sorting_by_case(void) +{ + git_repository *repo = cl_git_sandbox_init("icase"); + git_index *index; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + bool native_ignore_case; + status_entry_counts counts; + + cl_git_pass(git_repository_index(&index, repo)); + native_ignore_case = + (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0; + git_index_free(index); + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 0; + counts.expected_paths = NULL; + counts.expected_statuses = NULL; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + cl_git_rewritefile("icase/B", "new stuff"); + cl_must_pass(p_unlink("icase/c")); + cl_git_rewritefile("icase/g", "new stuff"); + cl_must_pass(p_unlink("icase/H")); + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 4; + if (native_ignore_case) { + counts.expected_paths = icase_paths; + counts.expected_statuses = icase_statuses; + } else { + counts.expected_paths = case_paths; + counts.expected_statuses = case_statuses; + } + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + opts.flags = GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 4; + counts.expected_paths = case_paths; + counts.expected_statuses = case_statuses; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + opts.flags = GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 4; + counts.expected_paths = icase_paths; + counts.expected_statuses = icase_statuses; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} From f18f772a8ec0cdff9315216886383dadce1379b5 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 20 Jun 2013 14:27:14 -0700 Subject: [PATCH 036/367] Add example implementation of long format status --- examples/status.c | 147 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 2 deletions(-) diff --git a/examples/status.c b/examples/status.c index 3c82640b2..10b1ca8e5 100644 --- a/examples/status.c +++ b/examples/status.c @@ -76,7 +76,8 @@ static void show_branch(git_repository *repo, int format) check(error, "failed to get current branch", NULL); if (format == FORMAT_LONG) - printf("# %s\n", branch ? branch : "Not currently on any branch."); + printf("# On branch %s\n", + branch ? branch : "Not currently on any branch."); else printf("## %s\n", branch ? branch : "HEAD (no branch)"); @@ -85,8 +86,150 @@ static void show_branch(git_repository *repo, int format) static void print_long(git_repository *repo, git_status_list *status) { + size_t i, maxi = git_status_list_entrycount(status); + const git_status_entry *s; + int header = 0, changes_in_index = 0; + int changed_in_workdir = 0, rm_in_workdir = 0; + const char *old_path, *new_path; + (void)repo; - (void)status; + + /* print index changes */ + + for (i = 0; i < maxi; ++i) { + char *istatus = NULL; + + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_CURRENT) + continue; + + if (s->status & GIT_STATUS_WT_DELETED) + rm_in_workdir = 1; + + if (s->status & GIT_STATUS_INDEX_NEW) + istatus = "new file: "; + if (s->status & GIT_STATUS_INDEX_MODIFIED) + istatus = "modified: "; + if (s->status & GIT_STATUS_INDEX_DELETED) + istatus = "deleted: "; + if (s->status & GIT_STATUS_INDEX_RENAMED) + istatus = "renamed: "; + if (s->status & GIT_STATUS_INDEX_TYPECHANGE) + istatus = "typechange:"; + + if (istatus == NULL) + continue; + + if (!header) { + printf("# Changes to be committed:\n"); + printf("# (use \"git reset HEAD ...\" to unstage)\n"); + printf("#\n"); + header = 1; + } + + old_path = s->head_to_index->old_file.path; + new_path = s->head_to_index->new_file.path; + + if (old_path && new_path && strcmp(old_path, new_path)) + printf("#\t%s %s -> %s\n", istatus, old_path, new_path); + else + printf("#\t%s %s\n", istatus, old_path ? old_path : new_path); + } + + if (header) { + changes_in_index = 1; + printf("#\n"); + } + header = 0; + + /* print workdir changes to tracked files */ + + for (i = 0; i < maxi; ++i) { + char *wstatus = NULL; + + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL) + continue; + + if (s->status & GIT_STATUS_WT_MODIFIED) + wstatus = "modified: "; + if (s->status & GIT_STATUS_WT_DELETED) + wstatus = "deleted: "; + if (s->status & GIT_STATUS_WT_RENAMED) + wstatus = "renamed: "; + if (s->status & GIT_STATUS_WT_TYPECHANGE) + wstatus = "typechange:"; + + if (wstatus == NULL) + continue; + + if (!header) { + printf("# Changes not staged for commit:\n"); + printf("# (use \"git add%s ...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : ""); + printf("# (use \"git checkout -- ...\" to discard changes in working directory)\n"); + printf("#\n"); + header = 1; + } + + old_path = s->index_to_workdir->old_file.path; + new_path = s->index_to_workdir->new_file.path; + + if (old_path && new_path && strcmp(old_path, new_path)) + printf("#\t%s %s -> %s\n", wstatus, old_path, new_path); + else + printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path); + } + + if (header) { + changed_in_workdir = 1; + printf("#\n"); + } + header = 0; + + /* print untracked files */ + + header = 0; + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_WT_NEW) { + + if (!header) { + printf("# Untracked files:\n"); + printf("# (use \"git add ...\" to include in what will be committed)\n"); + printf("#\n"); + header = 1; + } + + printf("#\t%s\n", s->index_to_workdir->old_file.path); + } + } + + header = 0; + + /* print ignored files */ + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_IGNORED) { + + if (!header) { + printf("# Ignored files:\n"); + printf("# (use \"git add -f ...\" to include in what will be committed)\n"); + printf("#\n"); + header = 1; + } + + printf("#\t%s\n", s->index_to_workdir->old_file.path); + } + } + + if (!changes_in_index && changed_in_workdir) + printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); } static void print_short(git_repository *repo, git_status_list *status) From 9280855787fcac1e333b6e7e66d7ba0cd2120a0c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 20 Jun 2013 15:10:42 -0700 Subject: [PATCH 037/367] Fix comment and copyright in example --- examples/status.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/status.c b/examples/status.c index 10b1ca8e5..689098415 100644 --- a/examples/status.c +++ b/examples/status.c @@ -1,10 +1,9 @@ /* - * Copyright (C) 2011-2012 the libgit2 contributors + * 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 #include #include @@ -19,15 +18,21 @@ enum { #define MAX_PATHSPEC 8 /* - * This example demonstrates the use of `git_status_foreach()` to roughly - * simulate the output of running `git status`. It should serve as a simple - * example of how to get basic status information. + * This example demonstrates the use of the libgit2 status APIs, + * particularly the `git_status_list` object, to roughly simulate the + * output of running `git status`. It serves as a simple example of + * using those APIs to get basic status information. * * This does not have: * - Robust error handling - * - Any real command line parsing * - Colorized or paginated output formatting * + * This does have: + * - Examples of translating command line arguments to the status + * options settings to mimic `git status` results. + * - A sample status formatter that matches the default "long" format + * from `git status` + * - A sample status formatter that matches the "short" format */ static void check(int error, const char *message, const char *extra) From 94ef2a353cdf4c29323c9935026d8435e8f098bd Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 20 Jun 2013 15:15:10 -0700 Subject: [PATCH 038/367] Add test for fixed diff bug Add test for bug fixed in 852ded96982ae70acb63c3940fae08ea29e40fee Sorry, I wrote that bug fix and forgot to check in a test at the same time. Here is one that fails on the old version of the code and now works. --- tests-clar/diff/workdir.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index 18182ea96..6a2504d09 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -1109,6 +1109,26 @@ void test_diff_workdir__untracked_directory_scenarios(void) git_diff_list_free(diff); + /* empty directory in empty directory */ + + cl_git_pass(p_mkdir("status/subdir/directory/empty", 0777)); + + memset(&exp, 0, sizeof(exp)); + exp.names = files1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_list_free(diff); + /* directory with only ignored files */ cl_git_pass(p_mkdir("status/subdir/directory/deeper", 0777)); From dacce80b128501e9821f254c2edb1e93906eac0b Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 20 Jun 2013 19:05:38 -0500 Subject: [PATCH 039/367] test asserting checkout should not recreate deleted files --- tests-clar/checkout/tree.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 462a46c83..7a1c797a2 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -442,6 +442,36 @@ void test_checkout_tree__checking_out_a_conflicting_content_change_returns_EMERG assert_conflict("branch_file.txt", "hello\n", "5b5b025", "c47800c"); } +void test_checkout_tree__donot_update_deleted_file_by_default(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_oid old_id, new_id; + git_commit *old_commit = NULL, *new_commit = NULL; + git_index *index = NULL; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_oid_fromstr(&old_id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_commit_lookup(&old_commit, g_repo, &old_id)); + cl_git_pass(git_reset(g_repo, (git_object *)old_commit, GIT_RESET_HARD)); + + cl_git_pass(p_unlink("testrepo/branch_file.txt")); + cl_git_pass(git_index_remove_bypath(index ,"branch_file.txt")); + cl_git_pass(git_index_write(index)); + + cl_assert(!git_path_exists("testrepo/branch_file.txt")); + + cl_git_pass(git_oid_fromstr(&new_id, "099fabac3a9ea935598528c27f866e34089c2eff")); + cl_git_pass(git_commit_lookup(&new_commit, g_repo, &new_id)); + cl_git_fail(git_checkout_tree(g_repo, (git_object *)new_commit, &opts)); + + git_commit_free(old_commit); + git_commit_free(new_commit); + git_index_free(index); +} + void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void) { git_index *index = NULL; From 36fd9e30651cf0d6b0ef58452ba2974a3544d4d1 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 21 Jun 2013 11:20:54 -0700 Subject: [PATCH 040/367] Fix checkout of modified file when missing from wd This fixes the checkout case when a file is modified between the baseline and the target and yet missing in the working directory. The logic for that case appears to have been wrong. This also adds a useful checkout notify callback to the checkout test helpers that will count notifications and also has a debug mode to visualize what checkout thinks that it's doing. --- include/git2/checkout.h | 2 + src/checkout.c | 4 +- tests-clar/checkout/checkout_helpers.c | 95 ++++++++++++++++++++++++++ tests-clar/checkout/checkout_helpers.h | 17 +++++ tests-clar/checkout/tree.c | 11 +++ 5 files changed, 128 insertions(+), 1 deletion(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 6798bf31c..f49e87566 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -183,6 +183,8 @@ typedef enum { GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2), GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3), GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4), + + GIT_CHECKOUT_NOTIFY_ALL = 0x0FFFFu } git_checkout_notify_t; /** Checkout notification callback function */ diff --git a/src/checkout.c b/src/checkout.c index ede0be8e8..065bb50fd 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -220,9 +220,11 @@ static int checkout_action_no_wd( action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE); break; case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */ - case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); break; + case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ + action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, CONFLICT); + break; case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/ if (delta->new_file.mode == GIT_FILEMODE_TREE) action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); diff --git a/tests-clar/checkout/checkout_helpers.c b/tests-clar/checkout/checkout_helpers.c index ab93a89bd..8da024dda 100644 --- a/tests-clar/checkout/checkout_helpers.c +++ b/tests-clar/checkout/checkout_helpers.c @@ -91,3 +91,98 @@ void check_file_contents_nocr_at_line( { check_file_contents_internal(path, expected, true, file, line, msg); } + +int checkout_count_callback( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + checkout_counts *ct = payload; + + GIT_UNUSED(baseline); GIT_UNUSED(target); GIT_UNUSED(workdir); + + if (why & GIT_CHECKOUT_NOTIFY_CONFLICT) { + ct->n_conflicts++; + + if (ct->debug) { + if (workdir) { + if (baseline) { + if (target) + fprintf(stderr, "M %s (conflicts with M %s)\n", + workdir->path, target->path); + else + fprintf(stderr, "M %s (conflicts with D %s)\n", + workdir->path, baseline->path); + } else { + if (target) + fprintf(stderr, "Existing %s (conflicts with A %s)\n", + workdir->path, target->path); + else + fprintf(stderr, "How can an untracked file be a conflict (%s)\n", workdir->path); + } + } else { + if (baseline) { + if (target) + fprintf(stderr, "D %s (conflicts with M %s)\n", + target->path, baseline->path); + else + fprintf(stderr, "D %s (conflicts with D %s)\n", + baseline->path, baseline->path); + } else { + if (target) + fprintf(stderr, "How can an added file with no workdir be a conflict (%s)\n", target->path); + else + fprintf(stderr, "How can a nonexistent file be a conflict (%s)\n", path); + } + } + } + } + + if (why & GIT_CHECKOUT_NOTIFY_DIRTY) { + ct->n_dirty++; + + if (ct->debug) { + if (workdir) + fprintf(stderr, "M %s\n", workdir->path); + else + fprintf(stderr, "D %s\n", baseline->path); + } + } + + if (why & GIT_CHECKOUT_NOTIFY_UPDATED) { + ct->n_updates++; + + if (ct->debug) { + if (baseline) { + if (target) + fprintf(stderr, "update: M %s\n", path); + else + fprintf(stderr, "update: D %s\n", path); + } else { + if (target) + fprintf(stderr, "update: A %s\n", path); + else + fprintf(stderr, "update: this makes no sense %s\n", path); + } + } + } + + if (why & GIT_CHECKOUT_NOTIFY_UNTRACKED) { + ct->n_untracked++; + + if (ct->debug) + fprintf(stderr, "? %s\n", path); + } + + if (why & GIT_CHECKOUT_NOTIFY_IGNORED) { + ct->n_ignored++; + + if (ct->debug) + fprintf(stderr, "I %s\n", path); + } + + return 0; +} diff --git a/tests-clar/checkout/checkout_helpers.h b/tests-clar/checkout/checkout_helpers.h index 34053809d..0e8da31d1 100644 --- a/tests-clar/checkout/checkout_helpers.h +++ b/tests-clar/checkout/checkout_helpers.h @@ -19,3 +19,20 @@ extern void check_file_contents_nocr_at_line( #define check_file_contents_nocr(PATH,EXP) \ check_file_contents_nocr_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH) + +typedef struct { + int n_conflicts; + int n_dirty; + int n_updates; + int n_untracked; + int n_ignored; + int debug; +} checkout_counts; + +extern int checkout_count_callback( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload); diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 7a1c797a2..5835ab05b 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -448,9 +448,15 @@ void test_checkout_tree__donot_update_deleted_file_by_default(void) git_oid old_id, new_id; git_commit *old_commit = NULL, *new_commit = NULL; git_index *index = NULL; + checkout_counts ct; opts.checkout_strategy = GIT_CHECKOUT_SAFE; + memset(&ct, 0, sizeof(ct)); + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &ct; + cl_git_pass(git_repository_index(&index, g_repo)); cl_git_pass(git_oid_fromstr(&old_id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); @@ -465,8 +471,13 @@ void test_checkout_tree__donot_update_deleted_file_by_default(void) cl_git_pass(git_oid_fromstr(&new_id, "099fabac3a9ea935598528c27f866e34089c2eff")); cl_git_pass(git_commit_lookup(&new_commit, g_repo, &new_id)); + + cl_git_fail(git_checkout_tree(g_repo, (git_object *)new_commit, &opts)); + cl_assert_equal_i(1, ct.n_conflicts); + cl_assert_equal_i(1, ct.n_updates); + git_commit_free(old_commit); git_commit_free(new_commit); git_index_free(index); From 9094ae5a3c12ee99743498cb8e895d18b932e4dd Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 21 Jun 2013 11:51:16 -0700 Subject: [PATCH 041/367] Add target directory to checkout This adds the ability for checkout to write to a target directory instead of having to use the working directory of the repository. This makes it easier to do exports of repository data and the like. This is similar to, but not quite the same as, the --prefix option to `git checkout-index` (this will always be treated as a directory name, not just as a simple text prefix). As part of this, the workdir iterator was extended to take the path to the working directory as a parameter and fallback on the git_repository_workdir result only if it's not specified. Fixes #1332 --- include/git2/checkout.h | 2 ++ src/checkout.c | 25 ++++++++++++++++++------- src/iterator.c | 12 ++++++++---- src/iterator.h | 15 +++++++++++++-- tests-clar/checkout/index.c | 28 ++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 13 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index f49e87566..a086408c7 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -236,6 +236,8 @@ typedef struct git_checkout_opts { git_strarray paths; git_tree *baseline; /** expected content of workdir, defaults to HEAD */ + + const char *target_directory; /** alternative checkout path to workdir */ } git_checkout_opts; #define GIT_CHECKOUT_OPTS_VERSION 1 diff --git a/src/checkout.c b/src/checkout.c index 065bb50fd..e3ae38710 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -858,7 +858,7 @@ static int checkout_submodule( return 0; if ((error = git_futils_mkdir( - file->path, git_repository_workdir(data->repo), + file->path, data->opts.target_directory, data->opts.dir_mode, GIT_MKDIR_PATH)) < 0) return error; @@ -1030,7 +1030,7 @@ static int checkout_deferred_remove(git_repository *repo, const char *path) { #if 0 int error = git_futils_rmdir_r( - path, git_repository_workdir(repo), GIT_RMDIR_EMPTY_PARENTS); + path, data->opts.target_directory, GIT_RMDIR_EMPTY_PARENTS); if (error == GIT_ENOTFOUND) { error = 0; @@ -1163,7 +1163,8 @@ static int checkout_data_init( return -1; } - if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0) + if ((!proposed || !proposed->target_directory) && + (error = git_repository__ensure_not_bare(repo, "checkout")) < 0) return error; data->repo = repo; @@ -1176,6 +1177,13 @@ static int checkout_data_init( else memmove(&data->opts, proposed, sizeof(git_checkout_opts)); + if (!data->opts.target_directory) + data->opts.target_directory = git_repository_workdir(repo); + else if (!git_path_isdir(data->opts.target_directory) && + (error = git_futils_mkdir(data->opts.target_directory, NULL, + GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0) + goto cleanup; + /* refresh config and index content unless NO_REFRESH is given */ if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) { git_config *cfg; @@ -1238,7 +1246,8 @@ static int checkout_data_init( if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || (error = git_pool_init(&data->pool, 1, 0)) < 0 || - (error = git_buf_puts(&data->path, git_repository_workdir(repo))) < 0) + (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 || + (error = git_path_to_dir(&data->path)) < 0) goto cleanup; data->workdir_len = git_buf_len(&data->path); @@ -1286,11 +1295,13 @@ 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( - &workdir, data.repo, iterflags | GIT_ITERATOR_DONT_AUTOEXPAND, + (error = git_iterator_for_workdir_ext( + &workdir, data.repo, data.opts.target_directory, + 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) + &baseline, data.opts.baseline, + iterflags, data.pfx, data.pfx)) < 0) goto cleanup; /* Should not have case insensitivity mismatch */ diff --git a/src/iterator.c b/src/iterator.c index 76b0e41d0..5917f63fd 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -1321,9 +1321,10 @@ static void workdir_iterator__free(git_iterator *self) git_ignore__free(&wi->ignores); } -int git_iterator_for_workdir( +int git_iterator_for_workdir_ext( git_iterator **out, git_repository *repo, + const char *repo_workdir, git_iterator_flag_t flags, const char *start, const char *end) @@ -1331,8 +1332,11 @@ int git_iterator_for_workdir( int error; workdir_iterator *wi; - if (git_repository__ensure_not_bare(repo, "scan working directory") < 0) - return GIT_EBAREREPO; + if (!repo_workdir) { + if (git_repository__ensure_not_bare(repo, "scan working directory") < 0) + return GIT_EBAREREPO; + repo_workdir = git_repository_workdir(repo); + } /* initialize as an fs iterator then do overrides */ wi = git__calloc(1, sizeof(workdir_iterator)); @@ -1352,7 +1356,7 @@ int git_iterator_for_workdir( return error; } - return fs_iterator__initialize(out, &wi->fi, git_repository_workdir(repo)); + return fs_iterator__initialize(out, &wi->fi, repo_workdir); } diff --git a/src/iterator.h b/src/iterator.h index 493ff4b2a..ea88fa6a2 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -79,15 +79,26 @@ extern int git_iterator_for_index( const char *start, const char *end); +extern int git_iterator_for_workdir_ext( + git_iterator **out, + git_repository *repo, + const char *repo_workdir, + git_iterator_flag_t flags, + const char *start, + const char *end); + /* 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( +GIT_INLINE(int) git_iterator_for_workdir( git_iterator **out, git_repository *repo, git_iterator_flag_t flags, const char *start, - const char *end); + const char *end) +{ + return git_iterator_for_workdir_ext(out, repo, NULL, flags, start, end); +} /* for filesystem iterators, you have to explicitly pass in the ignore_case * behavior that you desire diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index a3a0f8fda..16584ce22 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -506,3 +506,31 @@ void test_checkout_index__issue_1397(void) check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf"); } + +void test_checkout_index__target_directory(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + checkout_counts cts; + memset(&cts, 0, sizeof(cts)); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + opts.target_directory = "alternative"; + + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &cts; + + /* create some files that *would* conflict if we were using the wd */ + cl_git_mkfile("testrepo/README", "I'm in the way!\n"); + cl_git_mkfile("testrepo/new.txt", "my new file\n"); + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_assert_equal_i(0, cts.n_untracked); + cl_assert_equal_i(0, cts.n_ignored); + cl_assert_equal_i(4, cts.n_updates); + + check_file_contents("./alternative/README", "hey there\n"); + check_file_contents("./alternative/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./alternative/new.txt", "my new file\n"); +} From 6a15e8d23ad3e8c419c88b98732ca32addd2887c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 21 Jun 2013 12:26:36 -0700 Subject: [PATCH 042/367] Loosen ensure_not_bare rules in checkout With the new target directory option to checkout, the non-bareness of the repository should be checked much later in the parameter validation process - actually that check was already in place, but I was doing it redundantly in the checkout APIs. This removes the now unnecessary early check for bare repos. It also adds some other parameter validation and makes it so that implied parameters can actually be passed as NULL (i.e. if you pass a git_index, you don't have to pass the git_repository - we can get it from index). --- src/checkout.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index e3ae38710..8f9ec64e4 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -1369,8 +1369,19 @@ int git_checkout_index( int error; git_iterator *index_i; - if ((error = git_repository__ensure_not_bare(repo, "checkout index")) < 0) - return error; + if (!index && !repo) { + giterr_set(GITERR_CHECKOUT, + "Must provide either repository or index to checkout"); + return -1; + } + if (index && repo && git_index_owner(index) != repo) { + giterr_set(GITERR_CHECKOUT, + "Index to checkout does not match repository"); + return -1; + } + + if (!repo) + repo = git_index_owner(index); if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) return error; @@ -1394,8 +1405,19 @@ int git_checkout_tree( git_tree *tree = NULL; git_iterator *tree_i = NULL; - if ((error = git_repository__ensure_not_bare(repo, "checkout tree")) < 0) - return error; + if (!treeish && !repo) { + giterr_set(GITERR_CHECKOUT, + "Must provide either repository or tree to checkout"); + return -1; + } + if (treeish && repo && git_object_owner(treeish) != repo) { + giterr_set(GITERR_CHECKOUT, + "Object to checkout does not match repository"); + return -1; + } + + if (!repo) + repo = git_object_owner(treeish); if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { giterr_set( @@ -1420,8 +1442,7 @@ int git_checkout_head( git_tree *head = NULL; git_iterator *head_i = NULL; - if ((error = git_repository__ensure_not_bare(repo, "checkout head")) < 0) - return error; + assert(repo); if (!(error = checkout_lookup_head_tree(&head, repo)) && !(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL))) From d4f98ba4f124a836ed964a71137a6dae28358704 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 21 Jun 2013 12:29:03 -0700 Subject: [PATCH 043/367] Addition checkout target directory tests This adds additonal tests of the checkout target directory option including using it to dump data from bare repos. --- tests-clar/checkout/index.c | 71 +++++++++++++++++++++++++++++++++++++ tests-clar/checkout/tree.c | 44 +++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index 16584ce22..c7500db1d 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -515,6 +515,7 @@ void test_checkout_index__target_directory(void) opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; opts.target_directory = "alternative"; + cl_assert(!git_path_isdir("alternative")); opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; opts.notify_cb = checkout_count_callback; @@ -533,4 +534,74 @@ void test_checkout_index__target_directory(void) check_file_contents("./alternative/README", "hey there\n"); check_file_contents("./alternative/branch_file.txt", "hi\nbye!\n"); check_file_contents("./alternative/new.txt", "my new file\n"); + + cl_git_pass(git_futils_rmdir_r( + "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_checkout_index__target_directory_from_bare(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_index *index; + git_object *head = NULL; + checkout_counts cts; + memset(&cts, 0, sizeof(cts)); + + test_checkout_index__cleanup(); + + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_assert(git_repository_is_bare(g_repo)); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_revparse_single(&head, g_repo, "HEAD^{tree}")); + cl_git_pass(git_index_read_tree(index, (const git_tree *)head)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &cts; + + /* fail to checkout a bare repo */ + cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); + + opts.target_directory = "alternative"; + cl_assert(!git_path_isdir("alternative")); + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_assert_equal_i(0, cts.n_untracked); + cl_assert_equal_i(0, cts.n_ignored); + cl_assert_equal_i(3, cts.n_updates); + + check_file_contents("./alternative/README", "hey there\n"); + check_file_contents("./alternative/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./alternative/new.txt", "my new file\n"); + + cl_git_pass(git_futils_rmdir_r( + "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_checkout_index__can_get_repo_from_index(void) +{ + git_index *index; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_checkout_index(NULL, index, &opts)); + + check_file_contents("./testrepo/README", "hey there\n"); + check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); + + git_index_free(index); } diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 5835ab05b..0e65f28c8 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -596,6 +596,8 @@ void test_checkout_tree__fails_when_dir_in_use(void) cl_git_pass(p_chdir("../..")); cl_assert(git_path_is_empty_dir("testrepo/a")); + + git_object_free(obj); #endif } @@ -628,5 +630,47 @@ void test_checkout_tree__can_continue_when_dir_in_use(void) cl_git_pass(p_chdir("../..")); cl_assert(git_path_is_empty_dir("testrepo/a")); + + git_object_free(obj); #endif } + +void test_checkout_tree__target_directory_from_bare(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_oid oid; + checkout_counts cts; + memset(&cts, 0, sizeof(cts)); + + test_checkout_tree__cleanup(); /* cleanup default checkout */ + + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_assert(git_repository_is_bare(g_repo)); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &cts; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); + cl_git_pass(git_object_lookup(&g_object, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_fail(git_checkout_tree(g_repo, g_object, &opts)); + + opts.target_directory = "alternative"; + cl_assert(!git_path_isdir("alternative")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &opts)); + + cl_assert_equal_i(0, cts.n_untracked); + cl_assert_equal_i(0, cts.n_ignored); + cl_assert_equal_i(3, cts.n_updates); + + check_file_contents("./alternative/README", "hey there\n"); + check_file_contents("./alternative/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./alternative/new.txt", "my new file\n"); + + cl_git_pass(git_futils_rmdir_r( + "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); +} From a7ea40955e5bd881508e2a99e2b73b1a1fbf78e3 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Sun, 23 Jun 2013 01:25:34 +0200 Subject: [PATCH 044/367] Do not redefine WC_ERR_INVALID_CHARS WC_ERR_INVALID_CHARS might be already defined by the Windows SDK. Signed-off-by: Sven Strickroth --- src/win32/error.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/win32/error.c b/src/win32/error.c index 4a9a0631f..a62a07e82 100644 --- a/src/win32/error.c +++ b/src/win32/error.c @@ -12,7 +12,9 @@ # include #endif +#ifndef WC_ERR_INVALID_CHARS #define WC_ERR_INVALID_CHARS 0x80 +#endif char *git_win32_get_error_message(DWORD error_code) { From 8294e8cfff284a05084f7f9b91931e136a0c119d Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sat, 22 Jun 2013 17:15:31 -0700 Subject: [PATCH 045/367] Constrain mkdir calls to avoid extra mkdirs This updates the calls that make the subdirectories for objects to use a base directory above which git_futils_mkdir won't walk any higher. This prevents attempts to mkdir all the way up to the root of the filesystem. Also, this moves the objects_dir into the loose backend structure and removes the separate allocation, plus does some preformatting of the objects_dir value to guarantee a trailing slash, etc. --- src/odb_loose.c | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/odb_loose.c b/src/odb_loose.c index e78172cf6..76ed8e232 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -33,7 +33,9 @@ typedef struct loose_backend { int object_zlib_level; /** loose object zlib compression level. */ int fsync_object_files; /** loose object file fsync flag. */ - char *objects_dir; + + size_t objects_dirlen; + char objects_dir[GIT_FLEX_ARRAY]; } loose_backend; /* State structure for exploring directories, @@ -56,24 +58,30 @@ typedef struct { * ***********************************************************/ -static int object_file_name(git_buf *name, const char *dir, const git_oid *id) +static int object_file_name( + git_buf *name, const loose_backend *be, const git_oid *id) { - git_buf_sets(name, dir); - - /* expand length for 40 hex sha1 chars + 2 * '/' + '\0' */ - if (git_buf_grow(name, git_buf_len(name) + GIT_OID_HEXSZ + 3) < 0) + /* expand length for object root + 40 hex sha1 chars + 2 * '/' + '\0' */ + if (git_buf_grow(name, be->objects_dirlen + GIT_OID_HEXSZ + 3) < 0) return -1; + git_buf_set(name, be->objects_dir, be->objects_dirlen); git_path_to_dir(name); /* loose object filename: aa/aaa... (41 bytes) */ - git_oid_pathfmt(name->ptr + git_buf_len(name), id); + git_oid_pathfmt(name->ptr + name->size, id); name->size += GIT_OID_HEXSZ + 1; name->ptr[name->size] = '\0'; return 0; } +static int object_mkdir(const git_buf *name, const loose_backend *be) +{ + return git_futils_mkdir( + name->ptr + be->objects_dirlen, be->objects_dir, GIT_OBJECT_DIR_MODE, + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); +} static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) { @@ -457,7 +465,7 @@ static int locate_object( loose_backend *backend, const git_oid *oid) { - int error = object_file_name(object_location, backend->objects_dir, oid); + int error = object_file_name(object_location, backend, oid); if (!error && !git_path_exists(object_location->ptr)) return GIT_ENOTFOUND; @@ -769,8 +777,8 @@ static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) int error = 0; if (git_filebuf_hash(oid, &stream->fbuf) < 0 || - object_file_name(&final_path, backend->objects_dir, oid) < 0 || - git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0) + object_file_name(&final_path, backend, oid) < 0 || + object_mkdir(&final_path, backend) < 0) error = -1; /* * Don't try to add an existing object to the repository. This @@ -880,8 +888,8 @@ static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const v git_filebuf_write(&fbuf, header, header_len); git_filebuf_write(&fbuf, data, len); - if (object_file_name(&final_path, backend->objects_dir, oid) < 0 || - git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0 || + if (object_file_name(&final_path, backend, oid) < 0 || + object_mkdir(&final_path, backend) < 0 || git_filebuf_commit_at(&fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE) < 0) error = -1; @@ -898,7 +906,6 @@ static void loose_backend__free(git_odb_backend *_backend) assert(_backend); backend = (loose_backend *)_backend; - git__free(backend->objects_dir); git__free(backend); } @@ -909,13 +916,20 @@ int git_odb_backend_loose( int do_fsync) { loose_backend *backend; + size_t objects_dirlen; - backend = git__calloc(1, sizeof(loose_backend)); + assert(backend_out && objects_dir); + + objects_dirlen = strlen(objects_dir); + + backend = git__calloc(1, sizeof(loose_backend) + objects_dirlen + 2); GITERR_CHECK_ALLOC(backend); backend->parent.version = GIT_ODB_BACKEND_VERSION; - backend->objects_dir = git__strdup(objects_dir); - GITERR_CHECK_ALLOC(backend->objects_dir); + backend->objects_dirlen = objects_dirlen; + memcpy(backend->objects_dir, objects_dir, objects_dirlen); + if (backend->objects_dir[backend->objects_dirlen - 1] != '/') + backend->objects_dir[backend->objects_dirlen++] = '/'; if (compression_level < 0) compression_level = Z_BEST_SPEED; From 3d3ea4dc564922a3662298a7cfc2fc8b24149901 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sat, 22 Jun 2013 20:58:32 -0700 Subject: [PATCH 046/367] Add O_CLOEXEC to open calls --- src/fileops.c | 6 ++++-- src/posix.c | 4 ++-- src/posix.h | 3 +++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index ae240fcd2..1f58fa5cd 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -61,9 +61,11 @@ int git_futils_creat_locked(const char *path, const mode_t mode) wchar_t buf[GIT_WIN_PATH]; git__utf8_to_16(buf, GIT_WIN_PATH, path); - fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); + fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | + O_EXCL | O_BINARY | O_CLOEXEC, mode); #else - fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | + O_EXCL | O_BINARY | O_CLOEXEC, mode); #endif if (fd < 0) { diff --git a/src/posix.c b/src/posix.c index 5d526d33c..b75109b83 100644 --- a/src/posix.c +++ b/src/posix.c @@ -111,12 +111,12 @@ int p_open(const char *path, int flags, ...) va_end(arg_list); } - return open(path, flags | O_BINARY, mode); + return open(path, flags | O_BINARY | O_CLOEXEC, mode); } int p_creat(const char *path, mode_t mode) { - return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode); + return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode); } int p_getcwd(char *buffer_out, size_t size) diff --git a/src/posix.h b/src/posix.h index 719c8a04c..40bcc1ab0 100644 --- a/src/posix.h +++ b/src/posix.h @@ -25,6 +25,9 @@ #if !defined(O_BINARY) #define O_BINARY 0 #endif +#if !defined(O_CLOEXEC) +#define O_CLOEXEC 0 +#endif typedef int git_file; From e1967164574816b8bf6740ea17d08eeb26c091d2 Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Mon, 24 Jun 2013 15:33:41 +0200 Subject: [PATCH 047/367] Fixed most documentation header bugs Fixed a few header @param and @return typos with the help of -Wdocumentation in Xcode. The following warnings have not been fixed: common.h:213 - Not sure how the documentation format is for '...' notes.h:102 - Correct @param name but empty text notes.h:111 - Correct @param name but empty text pack.h:140 - @return missing text pack.h:148 - @return missing text --- include/git2/attr.h | 2 +- include/git2/commit.h | 2 +- include/git2/config.h | 6 +++--- include/git2/diff.h | 10 +++++----- include/git2/errors.h | 2 +- include/git2/index.h | 2 +- include/git2/indexer.h | 2 +- include/git2/merge.h | 2 +- include/git2/oid.h | 6 +++--- include/git2/pack.h | 2 +- include/git2/refs.h | 12 ++++++------ include/git2/refspec.h | 2 +- include/git2/remote.h | 4 ++-- include/git2/repository.h | 2 +- include/git2/stash.h | 2 +- include/git2/submodule.h | 4 ++-- include/git2/tree.h | 8 ++++---- 17 files changed, 35 insertions(+), 35 deletions(-) diff --git a/include/git2/attr.h b/include/git2/attr.h index 0d8a910f2..f256ff861 100644 --- a/include/git2/attr.h +++ b/include/git2/attr.h @@ -162,7 +162,7 @@ GIT_EXTERN(int) git_attr_get( * Then you could loop through the 3 values to get the settings for * the three attributes you asked about. * - * @param values An array of num_attr entries that will have string + * @param values_out An array of num_attr entries that will have string * pointers written into it for the values of the attributes. * You should not modify or free the values that are written * into this array (although of course, you should free the diff --git a/include/git2/commit.h b/include/git2/commit.h index 20b345f84..544d21d87 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -235,7 +235,7 @@ GIT_EXTERN(int) git_commit_nth_gen_ancestor( * * @param parent_count Number of parents for this commit * - * @param parents[] Array of `parent_count` pointers to `git_commit` + * @param parents Array of `parent_count` pointers to `git_commit` * objects that will be used as the parents for this commit. This * array may be NULL if `parent_count` is 0 (root commit). All the * given commits must be owned by the `repo`. diff --git a/include/git2/config.h b/include/git2/config.h index 59b4307be..827d43544 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -119,7 +119,7 @@ GIT_EXTERN(int) git_config_find_xdg(char *out, size_t length); * If /etc/gitconfig doesn't exist, it will look for * %PROGRAMFILES%\Git\etc\gitconfig. - * @param global_config_path Buffer to store the path in + * @param out Buffer to store the path in * @param length size of the buffer in bytes * @return 0 if a system configuration file has been * found. Its path will be stored in `buffer`. @@ -335,8 +335,8 @@ GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, c * @param name the variable's name * @param regexp regular expression to filter which variables we're * interested in. Use NULL to indicate all - * @param fn the function to be called on each value of the variable - * @param data opaque pointer to pass to the callback + * @param callback the function to be called on each value of the variable + * @param payload opaque pointer to pass to the callback */ GIT_EXTERN(int) git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload); diff --git a/include/git2/diff.h b/include/git2/diff.h index f352f2843..43029c49c 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -747,7 +747,7 @@ GIT_EXTERN(int) git_diff_print_raw( * letters for your own purposes. This function does just that. By the * way, unmodified will return a space (i.e. ' '). * - * @param delta_t The git_delta_t value to look up + * @param status The git_delta_t value to look up * @return The single character label for that code */ GIT_EXTERN(char) git_diff_status_char(git_delta_t status); @@ -918,7 +918,7 @@ GIT_EXTERN(int) git_diff_patch_num_lines_in_hunk( * @param new_lineno Line number in new file or -1 if line is deleted * @param patch The patch to look in * @param hunk_idx The index of the hunk - * @param line_of_index The index of the line in the hunk + * @param line_of_hunk The index of the line in the hunk * @return 0 on success, <0 on failure */ GIT_EXTERN(int) git_diff_patch_get_line_in_hunk( @@ -1017,7 +1017,7 @@ GIT_EXTERN(int) git_diff_blobs( * @param old_as_path Treat old blob as if it had this filename; can be NULL * @param new_blob Blob for new side of diff, or NULL for empty blob * @param new_as_path Treat new blob as if it had this filename; can be NULL - * @param options Options for diff, or NULL for default options + * @param opts Options for diff, or NULL for default options * @return 0 on success or error code < 0 */ GIT_EXTERN(int) git_diff_patch_from_blobs( @@ -1048,7 +1048,7 @@ GIT_EXTERN(int) git_diff_patch_from_blobs( * @param options Options for diff, or NULL for default options * @param file_cb Callback for "file"; made once if there is a diff; can be NULL * @param hunk_cb Callback for each hunk in diff; can be NULL - * @param line_cb Callback for each line in diff; can be NULL + * @param data_cb Callback for each line in diff; can be NULL * @param payload Payload passed to each callback function * @return 0 on success, GIT_EUSER on non-zero callback return, or error code */ @@ -1078,7 +1078,7 @@ GIT_EXTERN(int) git_diff_blob_to_buffer( * @param buffer Raw data for new side of diff, or NULL for empty * @param buffer_len Length of raw data for new side of diff * @param buffer_as_path Treat buffer as if it had this filename; can be NULL - * @param options Options for diff, or NULL for default options + * @param opts Options for diff, or NULL for default options * @return 0 on success or error code < 0 */ GIT_EXTERN(int) git_diff_patch_from_blob_and_buffer( diff --git a/include/git2/errors.h b/include/git2/errors.h index caf9e62b8..2032a436a 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -100,7 +100,7 @@ GIT_EXTERN(void) giterr_clear(void); * * @param error_class One of the `git_error_t` enum above describing the * general subsystem that is responsible for the error. - * @param message The formatted error message to keep + * @param string The formatted error message to keep */ GIT_EXTERN(void) giterr_set_str(int error_class, const char *string); diff --git a/include/git2/index.h b/include/git2/index.h index 399d7c9a8..51694aded 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -646,7 +646,7 @@ GIT_EXTERN(int) git_index_conflict_next( /** * Frees a `git_index_conflict_iterator`. * - * @param it pointer to the iterator + * @param iterator pointer to the iterator */ GIT_EXTERN(void) git_index_conflict_iterator_free( git_index_conflict_iterator *iterator); diff --git a/include/git2/indexer.h b/include/git2/indexer.h index 262dcd154..4db072c9b 100644 --- a/include/git2/indexer.h +++ b/include/git2/indexer.h @@ -21,7 +21,7 @@ typedef struct git_indexer_stream git_indexer_stream; * @param out where to store the indexer instance * @param path to the directory where the packfile should be stored * @param progress_cb function to call with progress information - * @param progress_payload payload for the progress callback + * @param progress_cb_payload payload for the progress callback */ GIT_EXTERN(int) git_indexer_stream_new( git_indexer_stream **out, diff --git a/include/git2/merge.h b/include/git2/merge.h index ce3ed8ed2..cef6f775b 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -141,7 +141,7 @@ GIT_EXTERN(int) git_merge_head_from_oid( /** * Frees a `git_merge_head` * - * @param the merge head to free + * @param head merge head to free */ GIT_EXTERN(void) git_merge_head_free( git_merge_head *head); diff --git a/include/git2/oid.h b/include/git2/oid.h index 018cead4a..662338d93 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -85,7 +85,7 @@ GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw); * needed for an oid encoded in hex (40 bytes). Only the * oid digits are written; a '\\0' terminator must be added * by the caller if it is required. - * @param oid oid structure to format. + * @param id oid structure to format. */ GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id); @@ -96,7 +96,7 @@ GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id); * If the number of bytes is > GIT_OID_HEXSZ, extra bytes * will be zeroed; if not, a '\0' terminator is NOT added. * @param n number of characters to write into out string - * @param oid oid structure to format. + * @param id oid structure to format. */ GIT_EXTERN(void) git_oid_nfmt(char *out, size_t n, const git_oid *id); @@ -118,7 +118,7 @@ GIT_EXTERN(void) git_oid_pathfmt(char *out, const git_oid *id); /** * Format a git_oid into a newly allocated c-string. * - * @param oid the oid structure to format + * @param id the oid structure to format * @return the c-string; NULL if memory is exhausted. Caller must * deallocate the string with git__free(). */ diff --git a/include/git2/pack.h b/include/git2/pack.h index 242bddd25..cc1f48add 100644 --- a/include/git2/pack.h +++ b/include/git2/pack.h @@ -112,7 +112,7 @@ GIT_EXTERN(int) git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid * @param pb The packbuilder * @param path to the directory where the packfile and index should be stored * @param progress_cb function to call with progress information from the indexer (optional) - * @param progress_payload payload for the progress callback (optional) + * @param progress_cb_payload payload for the progress callback (optional) * * @return 0 or an error code */ diff --git a/include/git2/refs.h b/include/git2/refs.h index 1b6184be5..795f7ab27 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -62,7 +62,7 @@ GIT_EXTERN(int) git_reference_name_to_id( * * @param out pointer in which to store the reference * @param repo the repository in which to look - * @param shrothand the short name for the reference + * @param shorthand the short name for the reference * @return 0 or an error code */ GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, const char *shorthand); @@ -198,7 +198,7 @@ GIT_EXTERN(const char *) git_reference_name(const git_reference *ref); * If a direct reference is passed as an argument, a copy of that * reference is returned. This copy must be manually freed too. * - * @param resolved_ref Pointer to the peeled reference + * @param out Pointer to the peeled reference * @param ref The reference * @return 0 or an error code */ @@ -266,7 +266,7 @@ GIT_EXTERN(int) git_reference_set_target( * the reflog if it exists. * * @param ref The reference to rename - * @param name The new name for the reference + * @param new_name The new name for the reference * @param force Overwrite an existing reference * @return 0 on success, EINVALIDSPEC, EEXISTS or an error code * @@ -375,7 +375,7 @@ GIT_EXTERN(int) git_reference_iterator_glob_new( * * @param out pointer in which to store the reference * @param iter the iterator - * @param 0, GIT_ITEROVER if there are no more; or an error code + * @return 0, GIT_ITEROVER if there are no more; or an error code */ GIT_EXTERN(int) git_reference_next(git_reference **out, git_reference_iterator *iter); @@ -506,9 +506,9 @@ GIT_EXTERN(int) git_reference_normalize_name( * If you pass `GIT_OBJ_ANY` as the target type, then the object * will be peeled until a non-tag object is met. * - * @param peeled Pointer to the peeled git_object + * @param out Pointer to the peeled git_object * @param ref The reference to be processed - * @param target_type The type of the requested object (GIT_OBJ_COMMIT, + * @param type The type of the requested object (GIT_OBJ_COMMIT, * GIT_OBJ_TAG, GIT_OBJ_TREE, GIT_OBJ_BLOB or GIT_OBJ_ANY). * @return 0 on success, GIT_EAMBIGUOUS, GIT_ENOTFOUND or an error code */ diff --git a/include/git2/refspec.h b/include/git2/refspec.h index c0b410cbf..d96b83ce2 100644 --- a/include/git2/refspec.h +++ b/include/git2/refspec.h @@ -55,7 +55,7 @@ GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec); /** * Get the refspec's direction. * - * @param the refspec + * @param spec refspec * @return GIT_DIRECTION_FETCH or GIT_DIRECTION_PUSH */ GIT_EXTERN(git_direction) git_refspec_direction(const git_refspec *spec); diff --git a/include/git2/remote.h b/include/git2/remote.h index 3f43916b5..45d15d0a3 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -259,7 +259,7 @@ GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void * @param progress_cb function to call with progress information. Be aware that * this is called inline with network and indexing operations, so performance * may be affected. - * @param progress_payload payload for the progress callback + * @param payload payload for the progress callback * @return 0 or an error code */ GIT_EXTERN(int) git_remote_download( @@ -320,7 +320,7 @@ GIT_EXTERN(int) git_remote_update_tips(git_remote *remote); * Return whether a string is a valid remote URL * * @param url the url to check - * @param 1 if the url is valid, 0 otherwise + * @return 1 if the url is valid, 0 otherwise */ GIT_EXTERN(int) git_remote_valid_url(const char *url); diff --git a/include/git2/repository.h b/include/git2/repository.h index 4fbd913b1..2164cfac1 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -519,7 +519,7 @@ typedef int (*git_repository_mergehead_foreach_cb)(const git_oid *oid, * * @param repo A repository object * @param callback Callback function - * @param apyload Pointer to callback data (optional) + * @param payload Pointer to callback data (optional) * @return 0 on success, GIT_ENOTFOUND, GIT_EUSER or error */ GIT_EXTERN(int) git_repository_mergehead_foreach(git_repository *repo, diff --git a/include/git2/stash.h b/include/git2/stash.h index cf8bc9d4c..68d1b5413 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -89,7 +89,7 @@ typedef int (*git_stash_cb)( * * @param repo Repository where to find the stash. * - * @param callabck Callback to invoke per found stashed state. The most recent + * @param callback Callback to invoke per found stashed state. The most recent * stash state will be enumerated first. * * @param payload Extra parameter to callback function. diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 004665050..91b5300ae 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -481,7 +481,7 @@ GIT_EXTERN(int) git_submodule_sync(git_submodule *submodule); * function will return distinct `git_repository` objects. This will only * work if the submodule is checked out into the working directory. * - * @param subrepo Pointer to the submodule repo which was opened + * @param repo Pointer to the submodule repo which was opened * @param submodule Submodule to be opened * @return 0 on success, <0 if submodule repo could not be opened. */ @@ -531,7 +531,7 @@ GIT_EXTERN(int) git_submodule_status( * This can be useful if you want to know if the submodule is present in the * working directory at this point in time, etc. * - * @param status Combination of first four `GIT_SUBMODULE_STATUS` flags + * @param location_status Combination of first four `GIT_SUBMODULE_STATUS` flags * @param submodule Submodule for which to get status * @return 0 on success, <0 on error */ diff --git a/include/git2/tree.h b/include/git2/tree.h index d673f50c4..65d8cc16e 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -38,7 +38,7 @@ GIT_EXTERN(int) git_tree_lookup( * * @see git_object_lookup_prefix * - * @param tree pointer to the looked up tree + * @param out pointer to the looked up tree * @param repo the repo to use when locating the tree. * @param id identity of the tree to locate. * @param len the length of the short identifier @@ -136,7 +136,7 @@ GIT_EXTERN(const git_tree_entry *) git_tree_entry_byoid( * * @param out Pointer where to store the tree entry * @param root Previously loaded tree which is the root of the relative path - * @param subtree_path Path to the contained entry + * @param path Path to the contained entry * @return 0 on success; GIT_ENOTFOUND if the path does not exist */ GIT_EXTERN(int) git_tree_entry_bypath( @@ -212,7 +212,7 @@ GIT_EXTERN(int) git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entr * * You must call `git_object_free()` on the object when you are done with it. * - * @param object pointer to the converted object + * @param object_out pointer to the converted object * @param repo repository where to lookup the pointed object * @param entry a tree entry * @return 0 or an error code @@ -251,7 +251,7 @@ GIT_EXTERN(void) git_treebuilder_clear(git_treebuilder *bld); /** * Get the number of entries listed in a treebuilder * - * @param tree a previously loaded treebuilder. + * @param bld a previously loaded treebuilder. * @return the number of entries in the treebuilder */ GIT_EXTERN(unsigned int) git_treebuilder_entrycount(git_treebuilder *bld); From 32c12ea6a9cafd76a746af2e2be9366c95752f5b Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 24 Jun 2013 09:19:24 -0700 Subject: [PATCH 048/367] Work around reparse point stat issues In theory, p_stat should never return an S_ISLNK result, but due to the current implementation on Windows with mount points it is possible that it will. For now, work around that by allowing a link in the path to a directory being created. If it is really a problem, then the issue will be caught on the next iteration of the loop, but typically this will be the right thing to do. --- src/fileops.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fileops.c b/src/fileops.c index ae240fcd2..95b15c604 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -348,7 +348,8 @@ int git_futils_mkdir( int tmp_errno = errno; /* ignore error if directory already exists */ - if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { + if (p_stat(make_path.ptr, &st) < 0 || + !(S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) { errno = tmp_errno; giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr); goto done; From f3f4c6b5bea91351a7bdb1d94e76924e76d0fcee Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 24 Jun 2013 11:56:35 -0700 Subject: [PATCH 049/367] Fix checkout tests on Windows --- tests-clar/checkout/index.c | 11 ++++++++--- tests-clar/checkout/tree.c | 9 ++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index c7500db1d..9d8b321ae 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -26,6 +26,10 @@ void test_checkout_index__initialize(void) void test_checkout_index__cleanup(void) { cl_git_sandbox_cleanup(); + + /* try to remove alternative dir */ + if (git_path_isdir("alternative")) + git_futils_rmdir_r("alternative", NULL, GIT_RMDIR_REMOVE_FILES); } void test_checkout_index__cannot_checkout_a_bare_repository(void) @@ -576,9 +580,10 @@ void test_checkout_index__target_directory_from_bare(void) cl_assert_equal_i(0, cts.n_ignored); cl_assert_equal_i(3, cts.n_updates); - check_file_contents("./alternative/README", "hey there\n"); - check_file_contents("./alternative/branch_file.txt", "hi\nbye!\n"); - check_file_contents("./alternative/new.txt", "my new file\n"); + /* files will have been filtered if needed, so strip CR */ + check_file_contents_nocr("./alternative/README", "hey there\n"); + check_file_contents_nocr("./alternative/branch_file.txt", "hi\nbye!\n"); + check_file_contents_nocr("./alternative/new.txt", "my new file\n"); cl_git_pass(git_futils_rmdir_r( "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 0e65f28c8..e4bfbce06 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -24,6 +24,9 @@ void test_checkout_tree__cleanup(void) g_object = NULL; cl_git_sandbox_cleanup(); + + if (git_path_isdir("alternative")) + git_futils_rmdir_r("alternative", NULL, GIT_RMDIR_REMOVE_FILES); } void test_checkout_tree__cannot_checkout_a_non_treeish(void) @@ -667,9 +670,9 @@ void test_checkout_tree__target_directory_from_bare(void) cl_assert_equal_i(0, cts.n_ignored); cl_assert_equal_i(3, cts.n_updates); - check_file_contents("./alternative/README", "hey there\n"); - check_file_contents("./alternative/branch_file.txt", "hi\nbye!\n"); - check_file_contents("./alternative/new.txt", "my new file\n"); + check_file_contents_nocr("./alternative/README", "hey there\n"); + check_file_contents_nocr("./alternative/branch_file.txt", "hi\nbye!\n"); + check_file_contents_nocr("./alternative/new.txt", "my new file\n"); cl_git_pass(git_futils_rmdir_r( "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); From 8c510b8313bd933e6d3b44f3ce88999d349a8dd7 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Mon, 24 Jun 2013 21:02:42 +0200 Subject: [PATCH 050/367] Fix a leak in the local transport code. --- src/transports/local.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/transports/local.c b/src/transports/local.c index 4bf1c876a..550060958 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -571,6 +571,8 @@ 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; @@ -584,18 +586,6 @@ static int local_close(git_transport *transport) t->url = NULL; } - return 0; -} - -static void local_free(git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - size_t i; - git_remote_head *head; - - /* Close the transport, if it's still open. */ - local_close(transport); - git_vector_foreach(&t->refs, i, head) { git__free(head->name); git__free(head); @@ -603,6 +593,16 @@ static void local_free(git_transport *transport) git_vector_free(&t->refs); + return 0; +} + +static void local_free(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + /* Close the transport, if it's still open. */ + local_close(transport); + /* Free the transport */ git__free(t); } From c0e58e430b5befcdbcd937193e420d86fd3658a4 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 25 Jun 2013 00:12:19 +0200 Subject: [PATCH 051/367] test-rename: This is not a decimal, silly --- tests-clar/diff/rename.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 79f407057..2b1873bd5 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -968,10 +968,10 @@ static void write_similarity_file_two(const char *filename, size_t b_lines) size_t i; for (i = 0; i < b_lines; i++) - git_buf_printf(&contents, "%0.2d - bbbbb\r\n", (int)(i+1)); + git_buf_printf(&contents, "%02d - bbbbb\r\n", (int)(i+1)); for (i = b_lines; i < 50; i++) - git_buf_printf(&contents, "%0.2d - aaaaa%s", (int)(i+1), (i == 49 ? "" : "\r\n")); + git_buf_printf(&contents, "%02d - aaaaa%s", (int)(i+1), (i == 49 ? "" : "\r\n")); cl_git_pass( git_futils_writebuffer(&contents, filename, O_RDWR|O_CREAT, 0777)); From eddc1f1ed78898a4ca41480045b1d0d5b075e773 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 25 Jun 2013 00:14:45 +0200 Subject: [PATCH 052/367] libgit2 v0.19.0 "gut merge" Minor point release! We got a lot of rather large features that we wanted to get settled in: - New (threadsafe) cache for objects - Iterator for Status - New Merge APIs - SSH support on *NIX - Function context on diff - Namespaces support - Index add/update/remove with wildcard support - Iterator for References - Fetch and push refspecs for Remotes - Rename support in Status - New 'sys/` namespace for external headers with low-level APIs As always, this comes with hundreds of bug fixes and performance improvements. We're faster and better than ever. And we haven't broken many APIs this time! Build stuff. --- include/git2/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/git2/version.h b/include/git2/version.h index 630d51526..d8a915fac 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,9 +7,9 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "0.18.0" +#define LIBGIT2_VERSION "0.19.0" #define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 18 +#define LIBGIT2_VER_MINOR 19 #define LIBGIT2_VER_REVISION 0 #endif From edbaa63a7c3c319621b773bad5851b2b48c9d175 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Tue, 25 Jun 2013 09:04:04 +0200 Subject: [PATCH 053/367] Unbreak git_remote_ls on a local transport after disconnecting. --- src/transports/local.c | 27 +++++++++++++++------------ tests-clar/network/remote/local.c | 12 ++++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/transports/local.c b/src/transports/local.c index 550060958..2a85e95e7 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -119,15 +119,24 @@ on_error: static int store_refs(transport_local *t) { - unsigned int i; + size_t i; + git_remote_head *head; git_strarray ref_names = {0}; assert(t); - if (git_reference_list(&ref_names, t->repo) < 0 || - git_vector_init(&t->refs, ref_names.count, NULL) < 0) + if (git_reference_list(&ref_names, t->repo) < 0) goto on_error; + /* Clear all heads we might have fetched in a previous connect */ + git_vector_foreach(&t->refs, i, head) { + git__free(head->name); + git__free(head); + } + + /* Clear the vector so we can reuse it */ + git_vector_clear(&t->refs); + /* Sort the references first */ git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); @@ -571,8 +580,6 @@ 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; @@ -586,13 +593,6 @@ static int local_close(git_transport *transport) t->url = NULL; } - git_vector_foreach(&t->refs, i, head) { - git__free(head->name); - git__free(head); - } - - git_vector_free(&t->refs); - return 0; } @@ -600,6 +600,8 @@ static void local_free(git_transport *transport) { transport_local *t = (transport_local *)transport; + git_vector_free(&t->refs); + /* Close the transport, if it's still open. */ local_close(transport); @@ -632,6 +634,7 @@ int git_transport_local(git_transport **out, git_remote *owner, void *param) t->parent.read_flags = local_read_flags; t->parent.cancel = local_cancel; + git_vector_init(&t->refs, 0, NULL); t->owner = owner; *out = (git_transport *) t; diff --git a/tests-clar/network/remote/local.c b/tests-clar/network/remote/local.c index 3cb8a25d6..d5d75fdc6 100644 --- a/tests-clar/network/remote/local.c +++ b/tests-clar/network/remote/local.c @@ -75,6 +75,18 @@ void test_network_remote_local__retrieve_advertised_references(void) cl_assert_equal_i(how_many_refs, 28); } +void test_network_remote_local__retrieve_advertised_references_after_disconnect(void) +{ + int how_many_refs = 0; + + connect_to_local_repository(cl_fixture("testrepo.git")); + git_remote_disconnect(remote); + + cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); + + cl_assert_equal_i(how_many_refs, 28); +} + void test_network_remote_local__retrieve_advertised_references_from_spaced_repository(void) { int how_many_refs = 0; From 9728cfde5f3685cb11302560a67754104d618ea2 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Tue, 25 Jun 2013 11:17:55 +0300 Subject: [PATCH 054/367] Make sure we don't leak memory again. --- src/transports/local.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/transports/local.c b/src/transports/local.c index 2a85e95e7..a9da8146c 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -599,6 +599,13 @@ static int local_close(git_transport *transport) static void local_free(git_transport *transport) { transport_local *t = (transport_local *)transport; + size_t i; + git_remote_head *head; + + git_vector_foreach(&t->refs, i, head) { + git__free(head->name); + git__free(head); + } git_vector_free(&t->refs); From 022a45e0848d4cc517889aaf55573bf5f5004662 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Tue, 25 Jun 2013 16:43:15 +0200 Subject: [PATCH 055/367] Revert "Work around reparse point stat issues" This reverts commit 32c12ea6a9cafd76a746af2e2be9366c95752f5b. --- src/fileops.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index d5f6acfad..1f58fa5cd 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -350,8 +350,7 @@ int git_futils_mkdir( int tmp_errno = errno; /* ignore error if directory already exists */ - if (p_stat(make_path.ptr, &st) < 0 || - !(S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) { + if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { errno = tmp_errno; giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr); goto done; From 4753711235b817082460c2781749c27ef2d9bc78 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Tue, 25 Jun 2013 16:46:06 +0200 Subject: [PATCH 056/367] Correctly handle junctions A junction has S_IFDIR | S_IFLNK set, however, only one makes sense. Signed-off-by: Sven Strickroth --- src/win32/posix_w32.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index f04974428..036632e2a 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -90,6 +90,9 @@ static int do_lstat( if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) fMode |= S_IFLNK; + if ((fMode & (S_IFDIR | S_IFLNK)) == (S_IFDIR | S_IFLNK)) // junction + fMode ^= S_IFLNK; + buf->st_ino = 0; buf->st_gid = 0; buf->st_uid = 0; From 3736b64f0520f1fb0c79cf6ef29eeca7507c167c Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Tue, 25 Jun 2013 18:36:37 +0200 Subject: [PATCH 057/367] Prefer younger merge bases over older ones. git-core prefers younger merge bases over older ones in case that multiple valid merge bases exists. --- src/commit_list.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commit_list.c b/src/commit_list.c index bd5b5201a..64416e54d 100644 --- a/src/commit_list.c +++ b/src/commit_list.c @@ -36,7 +36,7 @@ git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_ git_commit_list *p; while ((p = *pp) != NULL) { - if (git_commit_list_time_cmp(p->item, item) < 0) + if (git_commit_list_time_cmp(p->item, item) > 0) break; pp = &p->next; From 24ba6d3f8cec2524a3e18157dd9149bbfb654650 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Tue, 25 Jun 2013 22:55:13 +0200 Subject: [PATCH 058/367] Add a test case. --- tests-clar/revwalk/mergebase.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests-clar/revwalk/mergebase.c b/tests-clar/revwalk/mergebase.c index e2617ab0e..a2dbbc738 100644 --- a/tests-clar/revwalk/mergebase.c +++ b/tests-clar/revwalk/mergebase.c @@ -123,6 +123,18 @@ void test_revwalk_mergebase__no_common_ancestor_returns_ENOTFOUND(void) cl_assert_equal_sz(4, behind); } +void test_revwalk_mergebase__prefer_youngest_merge_base(void) +{ + git_oid result, one, two, expected; + + cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f ")); + cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + + cl_git_pass(git_merge_base(&result, _repo, &one, &two)); + cl_assert(git_oid_cmp(&result, &expected) == 0); +} + void test_revwalk_mergebase__no_off_by_one_missing(void) { git_oid result, one, two; From c7974b49d04bc318d61a010d2c5d2e75095f410b Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 26 Jun 2013 12:03:33 -0700 Subject: [PATCH 059/367] Fail on unmodified deltas when they're unexpected --- tests-clar/diff/submodules.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index 6e52a6319..46fe6c21c 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -47,7 +47,7 @@ static void check_diff_patches(git_diff_list *diff, const char **expected) for (d = 0; d < num_d; ++d, git_diff_patch_free(patch)) { cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); - if (delta->status == GIT_DELTA_UNMODIFIED) + if (delta->status == GIT_DELTA_UNMODIFIED && expected[d] == NULL) continue; if (expected[d] && !strcmp(expected[d], "")) From c67ff958c4d8e37a717c77dd9cdb4bdfc88a6fd8 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 27 Jun 2013 07:38:27 -0700 Subject: [PATCH 060/367] Fix bug marking submodule diffs as unmodified There was a bug where submodules whose HEAD had not been moved were being marked as having an UNMODIFIED delta record instead of being left MODIFIED. This fixes that and fixes the tests to notice if a submodule has been incorrectly marked as UNMODIFIED. --- src/diff_patch.c | 6 +++++- tests-clar/diff/submodules.c | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/diff_patch.c b/src/diff_patch.c index 9060d0a24..1b4adac03 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -10,6 +10,7 @@ #include "diff_driver.h" #include "diff_patch.h" #include "diff_xdiff.h" +#include "fileops.h" /* cached information about a single span in a diff */ typedef struct diff_patch_line diff_patch_line; @@ -175,9 +176,12 @@ static int diff_patch_load(git_diff_patch *patch, git_diff_output *output) goto cleanup; } - /* if we were previously missing an oid, update MODIFIED->UNMODIFIED */ + /* if previously missing an oid, and now that we have it the two sides + * are the same (and not submodules), update MODIFIED -> UNMODIFIED + */ if (incomplete_data && patch->ofile.file->mode == patch->nfile.file->mode && + patch->ofile.file->mode != GIT_FILEMODE_COMMIT && git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid) && patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ patch->delta->status = GIT_DELTA_UNMODIFIED; diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index 46fe6c21c..5de46732b 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -47,8 +47,10 @@ static void check_diff_patches(git_diff_list *diff, const char **expected) for (d = 0; d < num_d; ++d, git_diff_patch_free(patch)) { cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); - if (delta->status == GIT_DELTA_UNMODIFIED && expected[d] == NULL) + if (delta->status == GIT_DELTA_UNMODIFIED) { + cl_assert(expected[d] == NULL); continue; + } if (expected[d] && !strcmp(expected[d], "")) continue; From 1e9dd60f141cbe0c0eecf2628f7dd6f67d185b8d Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 27 Jun 2013 22:29:05 -0700 Subject: [PATCH 061/367] Test submodules with empty index or orphaned head In both of these cases, the submodule data should still be loaded just (obviously) without the data that comes from either the index or the HEAD. This fixes a bug in the orphaned head case. --- src/submodule.c | 7 +++- tests-clar/submodule/lookup.c | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/submodule.c b/src/submodule.c index 89eba2aa4..dcd58d016 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -1176,8 +1176,11 @@ static int load_submodule_config_from_head( git_iterator *i; const git_index_entry *entry; - if ((error = git_repository_head_tree(&head, repo)) < 0) - return error; + /* if we can't look up current head, then there's no submodule in it */ + if (git_repository_head_tree(&head, repo) < 0) { + giterr_clear(); + return 0; + } if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) { git_tree_free(head); diff --git a/tests-clar/submodule/lookup.c b/tests-clar/submodule/lookup.c index acf8f6462..013bbdf96 100644 --- a/tests-clar/submodule/lookup.c +++ b/tests-clar/submodule/lookup.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "submodule_helpers.h" #include "posix.h" +#include "git2/sys/repository.h" static git_repository *g_repo = NULL; @@ -112,3 +113,73 @@ void test_submodule_lookup__foreach(void) cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); cl_assert_equal_i(8, data.count); } + +void test_submodule_lookup__lookup_even_with_orphaned_head(void) +{ + git_reference *orphan; + git_submodule *sm; + + /* orphan the head */ + cl_git_pass(git_reference_symbolic_create( + &orphan, g_repo, "HEAD", "refs/heads/garbage", 1)); + git_reference_free(orphan); + + /* lookup existing */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is not in HEAD */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is neither in HEAD nor index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only")); + cl_assert(sm); + + /* lookup git repo subdir that is not added as submodule */ + cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, g_repo, "not-submodule")); + + /* lookup existing directory that is not a submodule */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "just_a_dir")); + + /* lookup existing file that is not a submodule */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "just_a_file")); + + /* lookup non-existent item */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "no_such_file")); +} + +void test_submodule_lookup__lookup_even_with_missing_index(void) +{ + git_index *idx; + git_submodule *sm; + + /* give the repo an empty index */ + cl_git_pass(git_index_new(&idx)); + git_repository_set_index(g_repo, idx); + git_index_free(idx); + + /* lookup existing */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is not in HEAD */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is neither in HEAD nor index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only")); + cl_assert(sm); + + /* lookup git repo subdir that is not added as submodule */ + cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, g_repo, "not-submodule")); + + /* lookup existing directory that is not a submodule */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "just_a_dir")); + + /* lookup existing file that is not a submodule */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "just_a_file")); + + /* lookup non-existent item */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "no_such_file")); +} From c4ac556ee7171ee206e11687907d5dbee3ce0a6d Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 29 Jun 2013 12:48:58 +0200 Subject: [PATCH 062/367] Fix compilation warnings --- src/diff_tform.c | 2 +- src/thread-utils.h | 2 +- src/win32/findfile.c | 8 +++----- tests-clar/clar/sandbox.h | 2 +- tests-clar/diff/rename.c | 2 +- tests-clar/merge/trees/automerge.c | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/diff_tform.c b/src/diff_tform.c index 8c4e96ecf..b137bd319 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -900,7 +900,7 @@ find_best_matches: } /* otherwise, if we just overwrote a source, update mapping */ else if (j > i && match_srcs[i].similarity > 0) { - match_tgts[match_srcs[i].idx].idx = j; + match_tgts[match_srcs[i].idx].idx = (uint32_t)j; } num_updates++; diff --git a/src/thread-utils.h b/src/thread-utils.h index 83148188d..f19a2ba2c 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -104,7 +104,7 @@ GIT_INLINE(void *) git___compare_and_swap( { volatile void *foundval; #if defined(GIT_WIN32) - foundval = InterlockedCompareExchangePointer(ptr, newval, oldval); + foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); #elif defined(__GNUC__) foundval = __sync_val_compare_and_swap(ptr, oldval, newval); #else diff --git a/src/win32/findfile.c b/src/win32/findfile.c index 5dd3de13d..9d9051bff 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -156,7 +156,7 @@ static int win32_find_git_in_registry( } static int win32_find_existing_dirs( - git_buf *out, const wchar_t *tmpl[], char *temp[]) + git_buf *out, const wchar_t *tmpl[]) { struct git_win32__path path16; git_buf buf = GIT_BUF_INIT; @@ -209,7 +209,6 @@ int git_win32__find_system_dirs(git_buf *out) int git_win32__find_global_dirs(git_buf *out) { - char *temp[3]; static const wchar_t *global_tmpls[4] = { L"%HOME%\\", L"%HOMEDRIVE%%HOMEPATH%\\", @@ -217,12 +216,11 @@ int git_win32__find_global_dirs(git_buf *out) NULL, }; - return win32_find_existing_dirs(out, global_tmpls, temp); + return win32_find_existing_dirs(out, global_tmpls); } 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", @@ -233,5 +231,5 @@ int git_win32__find_xdg_dirs(git_buf *out) NULL, }; - return win32_find_existing_dirs(out, global_tmpls, temp); + return win32_find_existing_dirs(out, global_tmpls); } diff --git a/tests-clar/clar/sandbox.h b/tests-clar/clar/sandbox.h index 1ca6fcae8..5622bfab7 100644 --- a/tests-clar/clar/sandbox.h +++ b/tests-clar/clar/sandbox.h @@ -45,7 +45,7 @@ find_tmp_path(char *buffer, size_t length) #else DWORD env_len; - if ((env_len = GetEnvironmentVariable("CLAR_TMP", buffer, length)) > 0 && + if ((env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length)) > 0 && env_len < length) return 0; diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 2b1873bd5..9efd9281c 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -516,7 +516,7 @@ void test_diff_rename__working_directory_changes(void) cl_git_pass(git_oid_fromstr(&id, blobsha)); cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); cl_git_pass(git_buf_set( - &content, git_blob_rawcontent(blob), git_blob_rawsize(blob))); + &content, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob))); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); git_blob_free(blob); diff --git a/tests-clar/merge/trees/automerge.c b/tests-clar/merge/trees/automerge.c index 04a7beff6..746ce5068 100644 --- a/tests-clar/merge/trees/automerge.c +++ b/tests-clar/merge/trees/automerge.c @@ -122,7 +122,7 @@ void test_merge_trees_automerge__automerge(void) cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE)); cl_git_pass(git_object_lookup((git_object **)&blob, repo, &entry->oid, GIT_OBJ_BLOB)); - cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, entry->file_size) == 0); + cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, (size_t)entry->file_size) == 0); git_index_free(index); git_blob_free(blob); From d90390c162418deb62302b3f56835ff781c0cfee Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 29 Jun 2013 13:38:27 +0200 Subject: [PATCH 063/367] test: Fix memory leak --- tests-clar/checkout/index.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index 9d8b321ae..982bf9ee5 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -587,6 +587,8 @@ void test_checkout_index__target_directory_from_bare(void) cl_git_pass(git_futils_rmdir_r( "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); + + git_object_free(head); } void test_checkout_index__can_get_repo_from_index(void) From 0b170f4dcb7f9e47eb849035a505661dda2eb6f8 Mon Sep 17 00:00:00 2001 From: Andrej Mitrovic Date: Mon, 1 Jul 2013 00:56:54 +0200 Subject: [PATCH 064/367] Fix docs to use proper enum names that exist. --- include/git2/refs.h | 14 +++++++------- include/git2/repository.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/git2/refs.h b/include/git2/refs.h index 795f7ab27..205bfe59d 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -32,7 +32,7 @@ GIT_BEGIN_DECL * @param out pointer to the looked-up reference * @param repo the repository to look up the reference * @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...) - * @return 0 on success, ENOTFOUND, EINVALIDSPEC or an error code. + * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code. */ GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo, const char *name); @@ -49,7 +49,7 @@ GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo, * @param out Pointer to oid to be filled in * @param repo The repository in which to look up the reference * @param name The long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...) - * @return 0 on success, ENOTFOUND, EINVALIDSPEC or an error code. + * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code. */ GIT_EXTERN(int) git_reference_name_to_id( git_oid *out, git_repository *repo, const char *name); @@ -94,7 +94,7 @@ GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, co * @param name The name of the reference * @param target The target of the reference * @param force Overwrite existing references - * @return 0 on success, EEXISTS, EINVALIDSPEC or an error code + * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repository *repo, const char *name, const char *target, int force); @@ -126,7 +126,7 @@ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repositor * @param name The name of the reference * @param id The object id pointed to by the reference. * @param force Overwrite existing references - * @return 0 on success, EEXISTS, EINVALIDSPEC or an error code + * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force); @@ -225,7 +225,7 @@ GIT_EXTERN(git_repository *) git_reference_owner(const git_reference *ref); * @param out Pointer to the newly created reference * @param ref The reference * @param target The new target for the reference - * @return 0 on success, EINVALIDSPEC or an error code + * @return 0 on success, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_reference_symbolic_set_target( git_reference **out, @@ -268,7 +268,7 @@ GIT_EXTERN(int) git_reference_set_target( * @param ref The reference to rename * @param new_name The new name for the reference * @param force Overwrite an existing reference - * @return 0 on success, EINVALIDSPEC, EEXISTS or an error code + * @return 0 on success, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code * */ GIT_EXTERN(int) git_reference_rename( @@ -488,7 +488,7 @@ typedef enum { * @param name Reference name to be checked. * @param flags Flags to constrain name validation rules - see the * GIT_REF_FORMAT constants above. - * @return 0 on success, GIT_EBUFS if buffer is too small, EINVALIDSPEC + * @return 0 on success, GIT_EBUFS if buffer is too small, GIT_EINVALIDSPEC * or an error code. */ GIT_EXTERN(int) git_reference_normalize_name( diff --git a/include/git2/repository.h b/include/git2/repository.h index 2164cfac1..7cc4a1341 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -178,7 +178,7 @@ GIT_EXTERN(int) git_repository_init( * when initializing a new repo. Details of individual values are: * * * BARE - Create a bare repository with no working directory. - * * NO_REINIT - Return an EEXISTS error if the repo_path appears to + * * NO_REINIT - Return an GIT_EEXISTS error if the repo_path appears to * already be an git repository. * * NO_DOTGIT_DIR - Normally a "/.git/" will be appended to the repo * path for non-bare repos (if it is not already there), but @@ -601,7 +601,7 @@ GIT_EXTERN(int) git_repository_set_head_detached( * If the HEAD is already detached and points to a Tag, the HEAD is * updated into making it point to the peeled Commit, and 0 is returned. * - * If the HEAD is already detached and points to a non commitish, the HEAD is + * If the HEAD is already detached and points to a non commitish, the HEAD is * unaltered, and -1 is returned. * * Otherwise, the HEAD will be detached and point to the peeled Commit. From 278ce7468d3870bb18d69bd8177bae406d6cede4 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 1 Jul 2013 10:20:38 -0700 Subject: [PATCH 065/367] Add helpful buffer shorten function --- src/buffer.c | 9 +++++++++ src/buffer.h | 1 + src/config_file.c | 2 +- src/indexer.c | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/buffer.c b/src/buffer.c index 6e3ffe560..b5b2fd678 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -259,6 +259,15 @@ void git_buf_truncate(git_buf *buf, size_t len) } } +void git_buf_shorten(git_buf *buf, size_t amount) +{ + if (amount > buf->size) + amount = buf->size; + + buf->size = buf->size - amount; + buf->ptr[buf->size] = '\0'; +} + void git_buf_rtruncate_at_char(git_buf *buf, char separator) { ssize_t idx = git_buf_rfind_next(buf, separator); diff --git a/src/buffer.h b/src/buffer.h index 5402f3827..f3e1d506f 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -91,6 +91,7 @@ int git_buf_vprintf(git_buf *buf, const char *format, va_list ap); void git_buf_clear(git_buf *buf); void git_buf_consume(git_buf *buf, const char *end); void git_buf_truncate(git_buf *buf, size_t len); +void git_buf_shorten(git_buf *buf, size_t amount); void git_buf_rtruncate_at_char(git_buf *path, char separator); int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...); diff --git a/src/config_file.c b/src/config_file.c index dec952115..2b0732a13 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1395,7 +1395,7 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i * standard, this character **has** to be last one in the buf, with * no whitespace after it */ assert(is_multiline_var(value->ptr)); - git_buf_truncate(value, git_buf_len(value) - 1); + git_buf_shorten(value, 1); proc_line = fixup_line(line, in_quotes); if (proc_line == NULL) { diff --git a/src/indexer.c b/src/indexer.c index 1b5339f23..1b638cd8a 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -602,7 +602,7 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress * git_vector_sort(&idx->objects); git_buf_sets(&filename, idx->pack->pack_name); - git_buf_truncate(&filename, filename.size - strlen("pack")); + git_buf_shorten(&filename, strlen("pack")); git_buf_puts(&filename, "idx"); if (git_buf_oom(&filename)) return -1; From 55ededfd398b783fa4fbe54b8aa406c19228fbc6 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 1 Jul 2013 10:21:14 -0700 Subject: [PATCH 066/367] Make refspec_transform paranoid about arguments --- src/clone.c | 4 ++-- src/refspec.c | 34 ++++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/clone.c b/src/clone.c index 5b6c6f77d..5c11872cc 100644 --- a/src/clone.c +++ b/src/clone.c @@ -204,7 +204,7 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) /* Get the remote's HEAD. This is always the first ref in remote->refs. */ remote_head = NULL; - + if (!remote->transport->ls(remote->transport, get_head_callback, &remote_head)) return -1; @@ -220,7 +220,7 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) memset(&dummy_spec, 0, sizeof(git_refspec)); head_info.refspec = &dummy_spec; } - + /* Determine the remote tracking reference name from the local master */ if (git_refspec_transform_r( &remote_master_name, diff --git a/src/refspec.c b/src/refspec.c index a907df84c..492c6ed3f 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -225,25 +225,31 @@ int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, co return refspec_transform_internal(out, outlen, spec->dst, spec->src, name); } -static int refspec_transform(git_buf *out, const char *from, const char *to, const char *name) +static int refspec_transform( + git_buf *out, const char *from, const char *to, const char *name) { - if (git_buf_sets(out, to) < 0) + size_t to_len = to ? strlen(to) : 0; + size_t from_len = from ? strlen(from) : 0; + size_t name_len = name ? strlen(name) : 0; + + if (git_buf_set(out, to, to_len) < 0) return -1; - /* - * No '*' at the end means that it's mapped to one specific - * branch, so no actual transformation is needed. - */ - if (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*') - return 0; + if (to_len > 0) { + /* No '*' at the end of 'to' means that refspec is mapped to one + * specific branch, so no actual transformation is needed. + */ + if (out->ptr[to_len - 1] != '*') + return 0; + git_buf_shorten(out, 1); /* remove trailing '*' copied from 'to' */ + } - git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */ - git_buf_puts(out, name + strlen(from) - 1); + if (from_len > 0) /* ignore trailing '*' from 'from' */ + from_len--; + if (from_len > name_len) + from_len = name_len; - if (git_buf_oom(out)) - return -1; - - return 0; + return git_buf_put(out, name + from_len, name_len - from_len); } int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name) From f8ccd6c935f12c9c31754d22ba81352178980284 Mon Sep 17 00:00:00 2001 From: Andrej Mitrovic Date: Tue, 2 Jul 2013 20:23:54 +0200 Subject: [PATCH 067/367] Fix small typo in docs for git_repository_message. --- include/git2/repository.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2/repository.h b/include/git2/repository.h index 7cc4a1341..c81051969 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -471,7 +471,7 @@ GIT_EXTERN(int) git_repository_index(git_index **out, git_repository *repo); * @param out Buffer to write data into or NULL to just read required size * @param len Length of `out` buffer in bytes * @param repo Repository to read prepared message from - * @return GIT_ENOUTFOUND if no message exists, other value < 0 for other + * @return GIT_ENOTFOUND if no message exists, other value < 0 for other * errors, or total bytes in message (may be > `len`) on success */ GIT_EXTERN(int) git_repository_message(char *out, size_t len, git_repository *repo); From 9b6075b25fb6fef570dd29c2b797db8bc34cf0b1 Mon Sep 17 00:00:00 2001 From: yorah Date: Wed, 3 Jul 2013 17:07:20 +0200 Subject: [PATCH 068/367] Fix segfault in git_status_foreach_ext() Add tests for the `GIT_STATUS_SHOW_XXX` flags. --- src/diff.c | 2 +- src/status.c | 12 +-- tests-clar/status/status_data.h | 127 ++++++++++++++++++++++++++++++++ tests-clar/status/worktree.c | 48 ++++++++++++ 4 files changed, 183 insertions(+), 6 deletions(-) diff --git a/src/diff.c b/src/diff.c index 26e117402..9e9528ef9 100644 --- a/src/diff.c +++ b/src/diff.c @@ -1273,7 +1273,7 @@ int git_diff__paired_foreach( git_vector_sort(&idx2wd->deltas); } } - else if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) + else if (head2idx != NULL && head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) strcomp = git__strcasecmp; for (i = 0, j = 0; i < i_max || j < j_max; ) { diff --git a/src/status.c b/src/status.c index e520c1017..7da94edc1 100644 --- a/src/status.c +++ b/src/status.c @@ -257,6 +257,7 @@ int git_status_list_new( opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; int error = 0; unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS; + git_diff_list *head2idx = NULL; assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); @@ -307,8 +308,10 @@ int git_status_list_new( &status->head2idx, repo, head, NULL, &diffopt)) < 0) goto done; + head2idx = status->head2idx; + if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && - (error = git_diff_find_similar(status->head2idx, NULL)) < 0) + (error = git_diff_find_similar(head2idx, NULL)) < 0) goto done; } @@ -324,15 +327,14 @@ int git_status_list_new( if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { if ((error = git_diff__paired_foreach( - status->head2idx, NULL, status_collect, status)) < 0) + head2idx, NULL, status_collect, status)) < 0) goto done; - git_diff_list_free(status->head2idx); - status->head2idx = NULL; + head2idx = NULL; } if ((error = git_diff__paired_foreach( - status->head2idx, status->idx2wd, status_collect, status)) < 0) + head2idx, status->idx2wd, status_collect, status)) < 0) goto done; if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY) diff --git a/tests-clar/status/status_data.h b/tests-clar/status/status_data.h index a41bde7c2..27587843b 100644 --- a/tests-clar/status/status_data.h +++ b/tests-clar/status/status_data.h @@ -250,3 +250,130 @@ static const unsigned int entry_statuses4[] = { }; static const int entry_count4 = 23; + + +/* entries for a copy of tests/resources/status with options + * passed to the status call in order to only get the differences + * between the HEAD and the index (changes to be committed) + */ + +static const char *entry_paths5[] = { + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", +}; + +static const unsigned int entry_statuses5[] = { + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW, +}; + +static const int entry_count5 = 8; + + +/* entries for a copy of tests/resources/status with options + * passed to the status call in order to only get the differences + * between the workdir and the index (changes not staged, untracked files) + */ + +static const char *entry_paths6[] = { + "file_deleted", + "ignored_file", + "modified_file", + "new_file", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir/deleted_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses6[] = { + GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, +}; + +static const int entry_count6 = 13; + + +/* entries for a copy of tests/resources/status with options + * passed to the status call in order to get the differences + * between the HEAD and the index and then between the workdir + * and the index. + */ + +static const char *entry_paths7[] = { + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "file_deleted", + "ignored_file", + "modified_file", + "new_file", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir/deleted_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses7[] = { + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, +}; + +static const int entry_count7 = 21; diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 920671e13..ac993767a 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -40,6 +40,54 @@ void test_status_worktree__whole_repository(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } +void assert_show(const int entry_counts, const char *entry_paths[], + const unsigned int entry_statuses[], git_status_show_t show) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_counts; + counts.expected_paths = entry_paths; + counts.expected_statuses = entry_statuses; + + opts.flags = GIT_STATUS_OPT_DEFAULTS; + opts.show = show; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) + ); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_status_worktree__show_index_and_workdir(void) +{ + assert_show(entry_count0, entry_paths0, entry_statuses0, + GIT_STATUS_SHOW_INDEX_AND_WORKDIR); +} + +void test_status_worktree__show_index_only(void) +{ + assert_show(entry_count5, entry_paths5, entry_statuses5, + GIT_STATUS_SHOW_INDEX_ONLY); +} + +void test_status_worktree__show_workdir_only(void) +{ + assert_show(entry_count6, entry_paths6, entry_statuses6, + GIT_STATUS_SHOW_WORKDIR_ONLY); +} + +void test_status_worktree__show_index_then_workdir_only(void) +{ + assert_show(entry_count7, entry_paths7, entry_statuses7, + GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); +} + /* this test is equivalent to t18-status.c:statuscb1 */ void test_status_worktree__empty_repository(void) { From 178aa39cc297cb2d1750bcff0a3839c6ac0840a7 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 3 Jul 2013 11:42:43 -0700 Subject: [PATCH 069/367] Be more thread aware with some index updates The index isn't really thread safe for the most part, but we can easily be more careful and avoid double frees and the like, which are serious problems (as opposed to a lookup which might return the incorrect value but if the index in being updated, that is much harder to avoid). --- src/index.c | 76 ++++++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/src/index.c b/src/index.c index 1d46779bf..ffa84e53f 100644 --- a/src/index.c +++ b/src/index.c @@ -261,6 +261,22 @@ static int reuc_icmp(const void *a, const void *b) return strcasecmp(info_a->path, info_b->path); } +static void index_entry_reuc_free(git_index_reuc_entry *reuc) +{ + if (!reuc) + return; + git__free(reuc->path); + git__free(reuc); +} + +static void index_entry_free(git_index_entry *entry) +{ + if (!entry) + return; + git__free(entry->path); + git__free(entry); +} + static unsigned int index_create_mode(unsigned int mode) { if (S_ISLNK(mode)) @@ -367,11 +383,8 @@ static void index_entries_free(git_vector *entries) { size_t i; - for (i = 0; i < entries->length; ++i) { - git_index_entry *e = git_vector_get(entries, i); - git__free(e->path); - git__free(e); - } + for (i = 0; i < entries->length; ++i) + index_entry_free(git__swap(entries->contents[i], NULL)); git_vector_clear(entries); } @@ -670,15 +683,6 @@ static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, return 0; } -static void index_entry_reuc_free(git_index_reuc_entry *reuc) -{ - if (!reuc) - return; - - git__free(reuc->path); - git__free(reuc); -} - static git_index_entry *index_entry_dup(const git_index_entry *source_entry) { git_index_entry *entry; @@ -697,14 +701,6 @@ static git_index_entry *index_entry_dup(const git_index_entry *source_entry) return entry; } -static void index_entry_free(git_index_entry *entry) -{ - if (!entry) - return; - git__free(entry->path); - git__free(entry); -} - static int index_insert(git_index *index, git_index_entry *entry, int replace) { size_t path_length, position; @@ -1357,14 +1353,11 @@ int git_index_reuc_remove(git_index *index, size_t position) void git_index_reuc_clear(git_index *index) { size_t i; - git_index_reuc_entry *reuc; assert(index); - git_vector_foreach(&index->reuc, i, reuc) { - git__free(reuc->path); - git__free(reuc); - } + for (i = 0; i < index->reuc.length; ++i) + index_entry_reuc_free(git__swap(index->reuc.contents[i], NULL)); git_vector_clear(&index->reuc); } @@ -1958,8 +1951,9 @@ int git_index_entry_stage(const git_index_entry *entry) } typedef struct read_tree_data { - git_index *index; git_vector *old_entries; + git_vector *new_entries; + git_vector_cmp entries_search; } read_tree_data; static int read_tree_cb( @@ -1990,7 +1984,7 @@ static int read_tree_cb( skey.stage = 0; if (!git_vector_bsearch2( - &pos, data->old_entries, data->index->entries_search, &skey) && + &pos, data->old_entries, data->entries_search, &skey) && (old_entry = git_vector_get(data->old_entries, pos)) != NULL && entry->mode == old_entry->mode && git_oid_equal(&entry->oid, &old_entry->oid)) @@ -2008,7 +2002,7 @@ static int read_tree_cb( entry->path = git_buf_detach(&path); git_buf_free(&path); - if (git_vector_insert(&data->index->entries, entry) < 0) { + if (git_vector_insert(data->new_entries, entry) < 0) { index_entry_free(entry); return -1; } @@ -2022,22 +2016,22 @@ int git_index_read_tree(git_index *index, const git_tree *tree) git_vector entries = GIT_VECTOR_INIT; read_tree_data data; + git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ + + data.old_entries = &index->entries; + data.new_entries = &entries; + data.entries_search = index->entries_search; + git_vector_sort(&index->entries); - git_vector_set_cmp(&entries, index->entries._cmp); - git_vector_swap(&entries, &index->entries); - - git_index_clear(index); - - data.index = index; - data.old_entries = &entries; - error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data); - index_entries_free(&entries); - git_vector_free(&entries); + git_vector_sort(&entries); - git_vector_sort(&index->entries); + git_index_clear(index); + + git_vector_swap(&entries, &index->entries); + git_vector_free(&entries); return error; } From 2a16914c35fe52785959647666da145be354b12f Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 3 Jul 2013 12:20:34 -0700 Subject: [PATCH 070/367] Remove GIT_STATUS_SHOW_INDEX_THEN_WORKDIR option This option serves no benefit now that the git_status_list API is available. It was of questionable value before and now it would just be a bad idea to use it rather than the indexed API. --- include/git2/status.h | 15 ++++----- src/diff.c | 2 ++ src/status.c | 17 ++-------- tests-clar/status/status_data.h | 57 --------------------------------- tests-clar/status/worktree.c | 6 ---- 5 files changed, 12 insertions(+), 85 deletions(-) diff --git a/include/git2/status.h b/include/git2/status.h index 63aea2f3b..fd0e83104 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -60,25 +60,24 @@ typedef int (*git_status_cb)( const char *path, unsigned int status_flags, void *payload); /** - * For extended status, select the files on which to report status. + * Select the files on which to report status. + * + * With `git_status_foreach_ext`, this will control which changes get + * callbacks. With `git_status_list_new`, these will control which + * changes are included in the list. * * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This roughly - * matches `git status --porcelain` where each file gets a callback - * indicating its status in the index and in the working directory. + * matches `git status --porcelain` regarding which files are + * included and in what order. * - GIT_STATUS_SHOW_INDEX_ONLY only gives status based on HEAD to index * comparison, not looking at working directory changes. * - GIT_STATUS_SHOW_WORKDIR_ONLY only gives status based on index to * working directory comparison, not comparing the index to the HEAD. - * - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR runs index-only then workdir-only, - * issuing (up to) two callbacks per file (first index, then workdir). - * This is slightly more efficient than separate calls and can make it - * easier to emulate plain `git status` text output. */ typedef enum { GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0, GIT_STATUS_SHOW_INDEX_ONLY = 1, GIT_STATUS_SHOW_WORKDIR_ONLY = 2, - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR = 3, } git_status_show_t; /** diff --git a/src/diff.c b/src/diff.c index 9e9528ef9..0980f412a 100644 --- a/src/diff.c +++ b/src/diff.c @@ -1249,6 +1249,8 @@ int git_diff__paired_foreach( i_max = head2idx ? head2idx->deltas.length : 0; j_max = idx2wd ? idx2wd->deltas.length : 0; + if (!i_max && !j_max) + return 0; /* At some point, tree-to-index diffs will probably never ignore case, * even if that isn't true now. Index-to-workdir diffs may or may not diff --git a/src/status.c b/src/status.c index 7da94edc1..ccb8d37da 100644 --- a/src/status.c +++ b/src/status.c @@ -257,9 +257,8 @@ int git_status_list_new( opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; int error = 0; unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS; - git_diff_list *head2idx = NULL; - assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); + assert(show <= GIT_STATUS_SHOW_WORKDIR_ONLY); *out = NULL; @@ -308,10 +307,8 @@ int git_status_list_new( &status->head2idx, repo, head, NULL, &diffopt)) < 0) goto done; - head2idx = status->head2idx; - if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && - (error = git_diff_find_similar(head2idx, NULL)) < 0) + (error = git_diff_find_similar(status->head2idx, NULL)) < 0) goto done; } @@ -325,16 +322,8 @@ int git_status_list_new( goto done; } - if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { - if ((error = git_diff__paired_foreach( - head2idx, NULL, status_collect, status)) < 0) - goto done; - - head2idx = NULL; - } - if ((error = git_diff__paired_foreach( - head2idx, status->idx2wd, status_collect, status)) < 0) + status->head2idx, status->idx2wd, status_collect, status)) < 0) goto done; if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY) diff --git a/tests-clar/status/status_data.h b/tests-clar/status/status_data.h index 27587843b..3efa934ea 100644 --- a/tests-clar/status/status_data.h +++ b/tests-clar/status/status_data.h @@ -320,60 +320,3 @@ static const unsigned int entry_statuses6[] = { }; static const int entry_count6 = 13; - - -/* entries for a copy of tests/resources/status with options - * passed to the status call in order to get the differences - * between the HEAD and the index and then between the workdir - * and the index. - */ - -static const char *entry_paths7[] = { - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - "file_deleted", - "ignored_file", - "modified_file", - "new_file", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_modified_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - "subdir/deleted_file", - "subdir/modified_file", - "subdir/new_file", - "\xe8\xbf\x99", -}; - -static const unsigned int entry_statuses7[] = { - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, -}; - -static const int entry_count7 = 21; diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index ac993767a..0e315cd60 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -82,12 +82,6 @@ void test_status_worktree__show_workdir_only(void) GIT_STATUS_SHOW_WORKDIR_ONLY); } -void test_status_worktree__show_index_then_workdir_only(void) -{ - assert_show(entry_count7, entry_paths7, entry_statuses7, - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); -} - /* this test is equivalent to t18-status.c:statuscb1 */ void test_status_worktree__empty_repository(void) { From 219f318c051a37c05dd66687695ebec9f1a90e53 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Wed, 3 Jul 2013 22:02:29 +0200 Subject: [PATCH 071/367] Fix a crash if git_remote_set_cred_acquire_cb wasn't called before connecting. Fixes #1700. --- src/transports/ssh.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index a312c8d08..8e3657dde 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -345,14 +345,16 @@ static int _git_ssh_setup_conn( if (user && pass) { if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0) goto on_error; - } else { + } else if (t->owner->cred_acquire_cb) { if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, user, GIT_CREDTYPE_USERPASS_PLAINTEXT | GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE, t->owner->cred_acquire_payload) < 0) return -1; - } + } else { + goto on_error; + } assert(t->cred); if (!user) { From f6bd0863352f70905b3068284f8ccd6beaa9a590 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Wed, 3 Jul 2013 22:02:44 +0200 Subject: [PATCH 072/367] Fix a probable leak. --- src/transports/ssh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 8e3657dde..f2480b2cd 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -351,7 +351,7 @@ static int _git_ssh_setup_conn( user, GIT_CREDTYPE_USERPASS_PLAINTEXT | GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE, t->owner->cred_acquire_payload) < 0) - return -1; + goto on_error; } else { goto on_error; } From a5f9b5f8d8c2dbcd1bec92065e5df7bae76d3c7c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 5 Jul 2013 16:59:38 -0700 Subject: [PATCH 073/367] Diff hunk context off by one on long lines The diff hunk context string that is returned to xdiff need not be NUL terminated because the xdiff code just copies the number of bytes that you report directly into the output. There was an off by one in the diff driver code when the header context was longer than the output buffer size, the output buffer length included the NUL byte which was copied into the hunk header. Fixes #1710 --- src/diff_driver.c | 7 ++++--- tests-clar/diff/diff_helpers.c | 5 +++-- tests-clar/diff/drivers.c | 31 +++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/diff_driver.c b/src/diff_driver.c index 469be0d14..77b0e9f3e 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -373,10 +373,11 @@ static long diff_context_find( !ctxt->match_line(ctxt->driver, ctxt->line.ptr, ctxt->line.size)) return -1; - git_buf_truncate(&ctxt->line, (size_t)out_size); - git_buf_copy_cstr(out, (size_t)out_size, &ctxt->line); + if (out_size > (long)ctxt->line.size) + out_size = ctxt->line.size; + memcpy(out, ctxt->line.ptr, (size_t)out_size); - return (long)ctxt->line.size; + return out_size; } void git_diff_find_context_init( diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index 4e23792a6..a5c322b4d 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -70,8 +70,9 @@ int diff_hunk_cb( diff_expects *e = payload; GIT_UNUSED(delta); - GIT_UNUSED(header); - GIT_UNUSED(header_len); + + /* confirm no NUL bytes in header text */ + while (header_len--) cl_assert('\0' != *header++); e->hunks++; e->hunk_old_lines += range->old_lines; diff --git a/tests-clar/diff/drivers.c b/tests-clar/diff/drivers.c index 06ab2ff14..e02dd5c68 100644 --- a/tests-clar/diff/drivers.c +++ b/tests-clar/diff/drivers.c @@ -123,3 +123,34 @@ void test_diff_drivers__patterns(void) git_tree_free(one); } +void test_diff_drivers__long_lines(void) +{ + const char *base = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non nisi ligula. Ut viverra enim sed lobortis suscipit.\nPhasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissim risus. Suspendisse at nisi quis turpis fringilla rutrum id sit amet nulla.\nNam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\nMauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\nAliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n"; + git_index *idx; + git_diff_list *diff; + git_diff_patch *patch; + char *actual; + const char *expected = "diff --git a/longlines.txt b/longlines.txt\nindex c1ce6ef..0134431 100644\n--- a/longlines.txt\n+++ b/longlines.txt\n@@ -3,3 +3,5 @@ Phasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissi\n Nam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\n Mauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\n Aliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n+newline\n+newline\n"; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/longlines.txt", base); + cl_git_pass(git_repository_index(&idx, g_repo)); + cl_git_pass(git_index_add_bypath(idx, "longlines.txt")); + cl_git_pass(git_index_write(idx)); + git_index_free(idx); + + cl_git_append2file("empty_standard_repo/longlines.txt", "newline\nnewline\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + cl_assert_equal_sz(1, git_diff_num_deltas(diff)); + cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); + cl_git_pass(git_diff_patch_to_str(&actual, patch)); + + cl_assert_equal_s(expected, actual); + + free(actual); + git_diff_patch_free(patch); + git_diff_list_free(diff); +} + From 2274993be5acf3a9fcf62abbf1f00d06fb513624 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Tue, 9 Jul 2013 12:52:25 +0200 Subject: [PATCH 074/367] Make the git_signature const in the stash API. --- include/git2/stash.h | 2 +- src/stash.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/git2/stash.h b/include/git2/stash.h index 68d1b5413..b48d33f5d 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -57,7 +57,7 @@ typedef enum { GIT_EXTERN(int) git_stash_save( git_oid *out, git_repository *repo, - git_signature *stasher, + const git_signature *stasher, const char *message, unsigned int flags); diff --git a/src/stash.c b/src/stash.c index 1222634d5..48f19144d 100644 --- a/src/stash.c +++ b/src/stash.c @@ -117,7 +117,7 @@ static int build_tree_from_index(git_tree **out, git_index *index) static int commit_index( git_commit **i_commit, git_index *index, - git_signature *stasher, + const git_signature *stasher, const char *message, const git_commit *parent) { @@ -267,7 +267,7 @@ cleanup: static int commit_untracked( git_commit **u_commit, git_index *index, - git_signature *stasher, + const git_signature *stasher, const char *message, git_commit *i_commit, uint32_t flags) @@ -354,7 +354,7 @@ cleanup: static int commit_worktree( git_oid *w_commit_oid, git_index *index, - git_signature *stasher, + const git_signature *stasher, const char *message, git_commit *i_commit, git_commit *b_commit, @@ -431,7 +431,7 @@ cleanup: static int update_reflog( git_oid *w_commit_oid, git_repository *repo, - git_signature *stasher, + const git_signature *stasher, const char *message) { git_reference *stash = NULL; @@ -510,7 +510,7 @@ static int reset_index_and_workdir( int git_stash_save( git_oid *out, git_repository *repo, - git_signature *stasher, + const git_signature *stasher, const char *message, uint32_t flags) { From a3c062dbf8c8060b9f0cedc2855f506736d25c73 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 9 Jul 2013 09:58:33 -0700 Subject: [PATCH 075/367] Make SSH APIs present even without SSH support The SSH APIs will just return an error code and state that the library was built without SSH support if they are called in that case. --- include/git2/transport.h | 87 ++++++++++--------- src/transport.c | 4 +- src/transports/cred.c | 67 ++++++++++----- src/transports/ssh.c | 181 ++++++++++++++++++++------------------- 4 files changed, 191 insertions(+), 148 deletions(-) diff --git a/include/git2/transport.h b/include/git2/transport.h index 81bb3abe1..21061fc78 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -36,14 +36,15 @@ typedef enum { } git_credtype_t; /* The base structure for all credential types */ -typedef struct git_cred { +typedef struct git_cred git_cred; + +struct git_cred { git_credtype_t credtype; - void (*free)( - struct git_cred *cred); -} git_cred; + void (*free)(git_cred *cred); +}; /* A plaintext username and password */ -typedef struct git_cred_userpass_plaintext { +typedef struct { git_cred parent; char *username; char *password; @@ -51,6 +52,9 @@ typedef struct git_cred_userpass_plaintext { #ifdef GIT_SSH typedef LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*git_cred_sign_callback)); +#else +typedef int (*git_cred_sign_callback)(void *, ...); +#endif /* A ssh key file and passphrase */ typedef struct git_cred_ssh_keyfile_passphrase { @@ -68,7 +72,6 @@ typedef struct git_cred_ssh_publickey { void *sign_callback; void *sign_data; } git_cred_ssh_publickey; -#endif /** * Creates a new plain-text username and password credential object. @@ -84,7 +87,6 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new( const char *username, const char *password); -#ifdef GIT_SSH /** * Creates a new ssh key file and passphrase credential object. * The supplied credential parameter will be internally duplicated. @@ -116,9 +118,8 @@ GIT_EXTERN(int) git_cred_ssh_publickey_new( git_cred **out, const char *publickey, size_t publickey_len, - git_cred_sign_callback, + git_cred_sign_callback sign_fn, void *sign_data); -#endif /** * Signature of a function which acquires a credential object. @@ -152,17 +153,21 @@ typedef enum { typedef void (*git_transport_message_cb)(const char *str, int len, void *data); -typedef struct git_transport { +typedef struct git_transport git_transport; + +struct git_transport { unsigned int version; /* Set progress and error callbacks */ - int (*set_callbacks)(struct git_transport *transport, + int (*set_callbacks)( + git_transport *transport, git_transport_message_cb progress_cb, git_transport_message_cb error_cb, void *payload); /* Connect the transport to the remote repository, using the given * direction. */ - int (*connect)(struct git_transport *transport, + int (*connect)( + git_transport *transport, const char *url, git_cred_acquire_cb cred_acquire_cb, void *cred_acquire_payload, @@ -172,17 +177,19 @@ typedef struct git_transport { /* This function may be called after a successful call to connect(). The * provided callback is invoked for each ref discovered on the remote * end. */ - int (*ls)(struct git_transport *transport, + int (*ls)( + git_transport *transport, git_headlist_cb list_cb, void *payload); /* Executes the push whose context is in the git_push object. */ - int (*push)(struct git_transport *transport, git_push *push); + int (*push)(git_transport *transport, git_push *push); /* This function may be called after a successful call to connect(), when * the direction is FETCH. The function performs a negotiation to calculate * the wants list for the fetch. */ - int (*negotiate_fetch)(struct git_transport *transport, + int (*negotiate_fetch)( + git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count); @@ -190,28 +197,29 @@ typedef struct git_transport { /* This function may be called after a successful call to negotiate_fetch(), * when the direction is FETCH. This function retrieves the pack file for * the fetch from the remote end. */ - int (*download_pack)(struct git_transport *transport, + int (*download_pack)( + git_transport *transport, git_repository *repo, git_transfer_progress *stats, git_transfer_progress_callback progress_cb, void *progress_payload); /* Checks to see if the transport is connected */ - int (*is_connected)(struct git_transport *transport); + int (*is_connected)(git_transport *transport); /* Reads the flags value previously passed into connect() */ - int (*read_flags)(struct git_transport *transport, int *flags); + int (*read_flags)(git_transport *transport, int *flags); /* Cancels any outstanding transport operation */ - void (*cancel)(struct git_transport *transport); + void (*cancel)(git_transport *transport); /* This function is the reverse of connect() -- it terminates the * connection to the remote end. */ - int (*close)(struct git_transport *transport); + int (*close)(git_transport *transport); /* Frees/destructs the git_transport object. */ - void (*free)(struct git_transport *transport); -} git_transport; + void (*free)(git_transport *transport); +}; #define GIT_TRANSPORT_VERSION 1 #define GIT_TRANSPORT_INIT {GIT_TRANSPORT_VERSION} @@ -299,35 +307,36 @@ typedef enum { GIT_SERVICE_RECEIVEPACK = 4, } git_smart_service_t; -struct git_smart_subtransport; +typedef struct git_smart_subtransport git_smart_subtransport; +typedef struct git_smart_subtransport_stream git_smart_subtransport_stream; /* A stream used by the smart transport to read and write data * from a subtransport */ -typedef struct git_smart_subtransport_stream { +struct git_smart_subtransport_stream { /* The owning subtransport */ - struct git_smart_subtransport *subtransport; + git_smart_subtransport *subtransport; int (*read)( - struct git_smart_subtransport_stream *stream, - char *buffer, - size_t buf_size, - size_t *bytes_read); + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read); int (*write)( - struct git_smart_subtransport_stream *stream, - const char *buffer, - size_t len); + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len); void (*free)( - struct git_smart_subtransport_stream *stream); -} git_smart_subtransport_stream; + git_smart_subtransport_stream *stream); +}; /* An implementation of a subtransport which carries data for the * smart transport */ -typedef struct git_smart_subtransport { +struct git_smart_subtransport { int (* action)( git_smart_subtransport_stream **out, - struct git_smart_subtransport *transport, + git_smart_subtransport *transport, const char *url, git_smart_service_t action); @@ -337,10 +346,10 @@ typedef struct git_smart_subtransport { * * 1. UPLOADPACK_LS -> UPLOADPACK * 2. RECEIVEPACK_LS -> RECEIVEPACK */ - int (* close)(struct git_smart_subtransport *transport); + int (*close)(git_smart_subtransport *transport); - void (* free)(struct git_smart_subtransport *transport); -} git_smart_subtransport; + void (*free)(git_smart_subtransport *transport); +}; /* A function which creates a new subtransport for the smart transport */ typedef int (*git_smart_subtransport_cb)( diff --git a/src/transport.c b/src/transport.c index 37c244c97..354789db1 100644 --- a/src/transport.c +++ b/src/transport.c @@ -73,7 +73,7 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void * /* It could be a SSH remote path. Check to see if there's a : * SSH is an unsupported transport mechanism in this version of libgit2 */ if (!definition && strrchr(url, ':')) - definition = &dummy_transport_definition; + definition = &dummy_transport_definition; #else /* For other systems, perform the SSH check first, to avoid going to the * filesystem if it is not necessary */ @@ -97,7 +97,7 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void * *callback = definition->fn; *param = definition->param; - + return 0; } diff --git a/src/transports/cred.c b/src/transports/cred.c index ba5de6e93..cb909fa40 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -68,7 +68,7 @@ static void ssh_keyfile_passphrase_free(struct git_cred *cred) if (c->publickey) { git__free(c->publickey); } - + git__free(c->privatekey); if (c->passphrase) { @@ -82,12 +82,28 @@ static void ssh_keyfile_passphrase_free(struct git_cred *cred) git__free(c); } +static void ssh_publickey_free(struct git_cred *cred) +{ + git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; + + git__free(c->publickey); + + c->sign_callback = NULL; + c->sign_data = NULL; + + memset(c, 0, sizeof(*c)); + + git__free(c); +} +#endif + int git_cred_ssh_keyfile_passphrase_new( git_cred **cred, const char *publickey, const char *privatekey, const char *passphrase) { +#ifdef GIT_SSH git_cred_ssh_keyfile_passphrase *c; assert(cred && privatekey); @@ -97,15 +113,15 @@ int git_cred_ssh_keyfile_passphrase_new( c->parent.credtype = GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE; c->parent.free = ssh_keyfile_passphrase_free; - + c->privatekey = git__strdup(privatekey); GITERR_CHECK_ALLOC(c->privatekey); - + if (publickey) { c->publickey = git__strdup(publickey); GITERR_CHECK_ALLOC(c->publickey); } - + if (passphrase) { c->passphrase = git__strdup(passphrase); GITERR_CHECK_ALLOC(c->passphrase); @@ -113,29 +129,27 @@ int git_cred_ssh_keyfile_passphrase_new( *cred = &c->parent; return 0; -} +#else + GIT_UNUSED(publickey); + GIT_UNUSED(privatekey); + GIT_UNUSED(passphrase); -static void ssh_publickey_free(struct git_cred *cred) -{ - git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; + assert(cred); + *cred = NULL; - git__free(c->publickey); - - c->sign_callback = NULL; - c->sign_data = NULL; - - memset(c, 0, sizeof(*c)); - - git__free(c); + giterr_set(GITERR_INVALID, "Cannot create SSH credential. Library was built without SSH support"); + return -1; +#endif } int git_cred_ssh_publickey_new( git_cred **cred, const char *publickey, size_t publickey_len, - LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)), + git_cred_sign_callback sign_callback, void *sign_data) { +#ifdef GIT_SSH git_cred_ssh_publickey *c; if (!cred) @@ -146,17 +160,28 @@ int git_cred_ssh_publickey_new( c->parent.credtype = GIT_CREDTYPE_SSH_PUBLICKEY; c->parent.free = ssh_publickey_free; - + c->publickey = git__malloc(publickey_len); GITERR_CHECK_ALLOC(c->publickey); - + memcpy(c->publickey, publickey, publickey_len); - + c->publickey_len = publickey_len; c->sign_callback = sign_callback; c->sign_data = sign_data; *cred = &c->parent; return 0; -} +#else + GIT_UNUSED(publickey); + GIT_UNUSED(publickey_len); + GIT_UNUSED(sign_callback); + GIT_UNUSED(sign_data); + + assert(cred); + *cred = NULL; + + giterr_set(GITERR_INVALID, "Cannot create SSH credential. Library was built without SSH support"); + return -1; #endif +} diff --git a/src/transports/ssh.c b/src/transports/ssh.c index a312c8d08..3ae23cbbf 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -5,13 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifdef GIT_SSH - #include "git2.h" #include "buffer.h" #include "netops.h" #include "smart.h" +#ifdef GIT_SSH + #include #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) @@ -46,27 +46,27 @@ typedef struct { static int gen_proto(git_buf *request, const char *cmd, const char *url) { char *repo; - + if (!git__prefixcmp(url, prefix_ssh)) { url = url + strlen(prefix_ssh); repo = strchr(url, '/'); } else { repo = strchr(url, ':'); } - + if (!repo) { return -1; } - + int len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1; - + git_buf_grow(request, len); git_buf_printf(request, "%s '%s'", cmd, repo); git_buf_putc(request, '\0'); - + if (git_buf_oom(request)) return -1; - + return 0; } @@ -74,11 +74,11 @@ static int send_command(ssh_stream *s) { int error; git_buf request = GIT_BUF_INIT; - + error = gen_proto(&request, s->cmd, s->url); if (error < 0) goto cleanup; - + error = libssh2_channel_exec( s->channel, request.ptr @@ -86,9 +86,9 @@ static int send_command(ssh_stream *s) if (0 != error) goto cleanup; - + s->sent_command = 1; - + cleanup: git_buf_free(&request); return error; @@ -101,18 +101,18 @@ static int ssh_stream_read( size_t *bytes_read) { ssh_stream *s = (ssh_stream *)stream; - + *bytes_read = 0; - + if (!s->sent_command && send_command(s) < 0) return -1; - + int rc = libssh2_channel_read(s->channel, buffer, buf_size); if (rc < 0) return -1; - + *bytes_read = rc; - + return 0; } @@ -122,15 +122,15 @@ static int ssh_stream_write( size_t len) { ssh_stream *s = (ssh_stream *)stream; - + if (!s->sent_command && send_command(s) < 0) return -1; - + int rc = libssh2_channel_write(s->channel, buffer, len); if (rc < 0) { return -1; } - + return rc; } @@ -139,26 +139,26 @@ static void ssh_stream_free(git_smart_subtransport_stream *stream) ssh_stream *s = (ssh_stream *)stream; ssh_subtransport *t = OWNING_SUBTRANSPORT(s); int ret; - + GIT_UNUSED(ret); - + t->current_stream = NULL; - + if (s->channel) { libssh2_channel_close(s->channel); libssh2_channel_free(s->channel); s->channel = NULL; } - + if (s->session) { libssh2_session_free(s->session), s->session = NULL; } - + if (s->socket.socket) { ret = gitno_close(&s->socket); assert(!ret); } - + git__free(s->url); git__free(s); } @@ -170,26 +170,26 @@ static int ssh_stream_alloc( git_smart_subtransport_stream **stream) { ssh_stream *s; - + if (!stream) return -1; - + s = git__calloc(sizeof(ssh_stream), 1); GITERR_CHECK_ALLOC(s); - + s->parent.subtransport = &t->parent; s->parent.read = ssh_stream_read; s->parent.write = ssh_stream_write; s->parent.free = ssh_stream_free; - + s->cmd = cmd; s->url = git__strdup(url); - + if (!s->url) { git__free(s); return -1; } - + *stream = &s->parent; return 0; } @@ -201,14 +201,14 @@ static int git_ssh_extract_url_parts( { char *colon, *at; const char *start; - + colon = strchr(url, ':'); - + if (colon == NULL) { giterr_set(GITERR_NET, "Malformed URL: missing :"); return -1; } - + at = strchr(url, '@'); if (at) { start = at+1; @@ -217,9 +217,9 @@ static int git_ssh_extract_url_parts( start = url; *username = git__strdup(default_user); } - + *host = git__substrdup(start, colon - start); - + return 0; } @@ -235,7 +235,7 @@ static int _git_ssh_authenticate_session( case GIT_CREDTYPE_USERPASS_PLAINTEXT: { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; rc = libssh2_userauth_password( - session, + session, c->username, c->password ); @@ -244,7 +244,7 @@ static int _git_ssh_authenticate_session( case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: { git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; rc = libssh2_userauth_publickey_fromfile( - session, + session, user, c->publickey, c->privatekey, @@ -268,7 +268,7 @@ static int _git_ssh_authenticate_session( rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; } } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - + return rc; } @@ -281,31 +281,31 @@ static int _git_ssh_session_create if (!session) { return -1; } - + LIBSSH2_SESSION* s = libssh2_session_init(); if (!s) return -1; - + int rc = 0; do { rc = libssh2_session_startup(s, socket.socket); } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - + if (0 != rc) { goto on_error; } - + libssh2_session_set_blocking(s, 1); - + *session = s; - + return 0; - + on_error: if (s) { libssh2_session_free(s), s = NULL; } - + return -1; } @@ -321,13 +321,13 @@ static int _git_ssh_setup_conn( ssh_stream *s; LIBSSH2_SESSION* session=NULL; LIBSSH2_CHANNEL* channel=NULL; - + *stream = NULL; if (ssh_stream_alloc(t, url, cmd, stream) < 0) return -1; - + s = (ssh_stream *)*stream; - + if (!git__prefixcmp(url, prefix_ssh)) { url = url + strlen(prefix_ssh); if (gitno_extract_url_parts(&host, &port, &user, &pass, url, default_port) < 0) @@ -338,10 +338,10 @@ static int _git_ssh_setup_conn( port = git__strdup(default_port); GITERR_CHECK_ALLOC(port); } - + if (gitno_connect(&s->socket, host, port, 0) < 0) goto on_error; - + if (user && pass) { if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0) goto on_error; @@ -354,26 +354,26 @@ static int _git_ssh_setup_conn( return -1; } assert(t->cred); - + if (!user) { user = git__strdup(default_user); } - + if (_git_ssh_session_create(&session, s->socket) < 0) goto on_error; - + if (_git_ssh_authenticate_session(session, user, t->cred) < 0) goto on_error; - + channel = libssh2_channel_open_session(session); if (!channel) goto on_error; - + libssh2_channel_set_blocking(channel, 1); - + s->session = session; s->channel = channel; - + t->current_stream = s; git__free(host); git__free(port); @@ -381,11 +381,11 @@ static int _git_ssh_setup_conn( git__free(pass); return 0; - + on_error: if (*stream) ssh_stream_free(*stream); - + git__free(host); git__free(port); git__free(user); @@ -404,7 +404,7 @@ static int ssh_uploadpack_ls( { if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0) return -1; - + return 0; } @@ -414,12 +414,12 @@ static int ssh_uploadpack( git_smart_subtransport_stream **stream) { GIT_UNUSED(url); - + if (t->current_stream) { *stream = &t->current_stream->parent; return 0; } - + giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK"); return -1; } @@ -431,7 +431,7 @@ static int ssh_receivepack_ls( { if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0) return -1; - + return 0; } @@ -441,12 +441,12 @@ static int ssh_receivepack( git_smart_subtransport_stream **stream) { GIT_UNUSED(url); - + if (t->current_stream) { *stream = &t->current_stream->parent; return 0; } - + giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK"); return -1; } @@ -458,21 +458,21 @@ static int _ssh_action( git_smart_service_t action) { ssh_subtransport *t = (ssh_subtransport *) subtransport; - + switch (action) { case GIT_SERVICE_UPLOADPACK_LS: return ssh_uploadpack_ls(t, url, stream); - + case GIT_SERVICE_UPLOADPACK: return ssh_uploadpack(t, url, stream); - + case GIT_SERVICE_RECEIVEPACK_LS: return ssh_receivepack_ls(t, url, stream); - + case GIT_SERVICE_RECEIVEPACK: return ssh_receivepack(t, url, stream); } - + *stream = NULL; return -1; } @@ -480,40 +480,49 @@ static int _ssh_action( static int _ssh_close(git_smart_subtransport *subtransport) { ssh_subtransport *t = (ssh_subtransport *) subtransport; - + assert(!t->current_stream); - + GIT_UNUSED(t); - + return 0; } static void _ssh_free(git_smart_subtransport *subtransport) { ssh_subtransport *t = (ssh_subtransport *) subtransport; - + assert(!t->current_stream); - + git__free(t); } +#endif -int git_smart_subtransport_ssh(git_smart_subtransport **out, git_transport *owner) +int git_smart_subtransport_ssh( + git_smart_subtransport **out, git_transport *owner) { +#ifdef GIT_SSH ssh_subtransport *t; - - if (!out) - return -1; - + + assert(out); + t = git__calloc(sizeof(ssh_subtransport), 1); GITERR_CHECK_ALLOC(t); - + t->owner = (transport_smart *)owner; t->parent.action = _ssh_action; t->parent.close = _ssh_close; t->parent.free = _ssh_free; - + *out = (git_smart_subtransport *) t; return 0; -} +#else + GIT_UNUSED(owner); + assert(out); + *out = NULL; + + giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support"); + return -1; #endif +} From 5813bc2194e9adefb2d7a25a5039c36879417c11 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 9 Jul 2013 12:01:16 -0700 Subject: [PATCH 076/367] Lots of SSH credential stuff can be left on Much of the SSH credential creation API can be left enabled even on platforms with no SSH support. We really just have to give an error when you attempt to open the SSH connection. --- src/transports/cred.c | 50 ++++++++++--------------------------------- 1 file changed, 11 insertions(+), 39 deletions(-) diff --git a/src/transports/cred.c b/src/transports/cred.c index cb909fa40..6e51c2bf7 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -59,20 +59,17 @@ int git_cred_userpass_plaintext_new( return 0; } -#ifdef GIT_SSH static void ssh_keyfile_passphrase_free(struct git_cred *cred) { - git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; - size_t pass_len = strlen(c->passphrase); - - if (c->publickey) { - git__free(c->publickey); - } + git_cred_ssh_keyfile_passphrase *c = + (git_cred_ssh_keyfile_passphrase *)cred; + git__free(c->publickey); git__free(c->privatekey); if (c->passphrase) { /* Zero the memory which previously held the passphrase */ + size_t pass_len = strlen(c->passphrase); git__memzero(c->passphrase, pass_len); git__free(c->passphrase); } @@ -95,7 +92,6 @@ static void ssh_publickey_free(struct git_cred *cred) git__free(c); } -#endif int git_cred_ssh_keyfile_passphrase_new( git_cred **cred, @@ -103,7 +99,6 @@ int git_cred_ssh_keyfile_passphrase_new( const char *privatekey, const char *passphrase) { -#ifdef GIT_SSH git_cred_ssh_keyfile_passphrase *c; assert(cred && privatekey); @@ -129,17 +124,6 @@ int git_cred_ssh_keyfile_passphrase_new( *cred = &c->parent; return 0; -#else - GIT_UNUSED(publickey); - GIT_UNUSED(privatekey); - GIT_UNUSED(passphrase); - - assert(cred); - *cred = NULL; - - giterr_set(GITERR_INVALID, "Cannot create SSH credential. Library was built without SSH support"); - return -1; -#endif } int git_cred_ssh_publickey_new( @@ -149,22 +133,22 @@ int git_cred_ssh_publickey_new( git_cred_sign_callback sign_callback, void *sign_data) { -#ifdef GIT_SSH git_cred_ssh_publickey *c; - if (!cred) - return -1; + assert(cred); - c = git__malloc(sizeof(git_cred_ssh_publickey)); + c = git__calloc(1, sizeof(git_cred_ssh_publickey)); GITERR_CHECK_ALLOC(c); c->parent.credtype = GIT_CREDTYPE_SSH_PUBLICKEY; c->parent.free = ssh_publickey_free; - c->publickey = git__malloc(publickey_len); - GITERR_CHECK_ALLOC(c->publickey); + if (publickey_len > 0) { + c->publickey = git__malloc(publickey_len); + GITERR_CHECK_ALLOC(c->publickey); - memcpy(c->publickey, publickey, publickey_len); + memcpy(c->publickey, publickey, publickey_len); + } c->publickey_len = publickey_len; c->sign_callback = sign_callback; @@ -172,16 +156,4 @@ int git_cred_ssh_publickey_new( *cred = &c->parent; return 0; -#else - GIT_UNUSED(publickey); - GIT_UNUSED(publickey_len); - GIT_UNUSED(sign_callback); - GIT_UNUSED(sign_data); - - assert(cred); - *cred = NULL; - - giterr_set(GITERR_INVALID, "Cannot create SSH credential. Library was built without SSH support"); - return -1; -#endif } From 03d9b930ee6b90ab17f031e596ad6d15ee313ca8 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 9 Jul 2013 14:45:58 -0700 Subject: [PATCH 077/367] Indent with tabs --- src/transports/cred.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/transports/cred.c b/src/transports/cred.c index 6e51c2bf7..1d8527e39 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -67,12 +67,12 @@ static void ssh_keyfile_passphrase_free(struct git_cred *cred) git__free(c->publickey); git__free(c->privatekey); - if (c->passphrase) { - /* Zero the memory which previously held the passphrase */ + if (c->passphrase) { + /* Zero the memory which previously held the passphrase */ size_t pass_len = strlen(c->passphrase); - git__memzero(c->passphrase, pass_len); - git__free(c->passphrase); - } + git__memzero(c->passphrase, pass_len); + git__free(c->passphrase); + } memset(c, 0, sizeof(*c)); @@ -83,10 +83,10 @@ static void ssh_publickey_free(struct git_cred *cred) { git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; - git__free(c->publickey); + git__free(c->publickey); - c->sign_callback = NULL; - c->sign_data = NULL; + c->sign_callback = NULL; + c->sign_data = NULL; memset(c, 0, sizeof(*c)); @@ -109,10 +109,10 @@ int git_cred_ssh_keyfile_passphrase_new( c->parent.credtype = GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE; c->parent.free = ssh_keyfile_passphrase_free; - c->privatekey = git__strdup(privatekey); + c->privatekey = git__strdup(privatekey); GITERR_CHECK_ALLOC(c->privatekey); - if (publickey) { + if (publickey) { c->publickey = git__strdup(publickey); GITERR_CHECK_ALLOC(c->publickey); } @@ -129,9 +129,9 @@ int git_cred_ssh_keyfile_passphrase_new( int git_cred_ssh_publickey_new( git_cred **cred, const char *publickey, - size_t publickey_len, + size_t publickey_len, git_cred_sign_callback sign_callback, - void *sign_data) + void *sign_data) { git_cred_ssh_publickey *c; @@ -150,9 +150,9 @@ int git_cred_ssh_publickey_new( memcpy(c->publickey, publickey, publickey_len); } - c->publickey_len = publickey_len; - c->sign_callback = sign_callback; - c->sign_data = sign_data; + c->publickey_len = publickey_len; + c->sign_callback = sign_callback; + c->sign_data = sign_data; *cred = &c->parent; return 0; From a4456929a8890a9ac1441db343c21040665ce253 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 9 Jul 2013 16:16:24 -0700 Subject: [PATCH 078/367] Make credential clearing consistent This makes all of the credential objects use the same pattern to clear the contents and call git__memzero when done. Much of this information is probably not sensitive, but it also seems better to just clear consistently. --- src/transports/cred.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/transports/cred.c b/src/transports/cred.c index 1d8527e39..d713f8992 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -12,16 +12,17 @@ static void plaintext_free(struct git_cred *cred) { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; - size_t pass_len = strlen(c->password); git__free(c->username); /* Zero the memory which previously held the password */ - git__memzero(c->password, pass_len); - git__free(c->password); - - memset(c, 0, sizeof(*c)); + if (c->password) { + size_t pass_len = strlen(c->password); + git__memzero(c->password, pass_len); + git__free(c->password); + } + git__memzero(c, sizeof(*c)); git__free(c); } @@ -74,8 +75,7 @@ static void ssh_keyfile_passphrase_free(struct git_cred *cred) git__free(c->passphrase); } - memset(c, 0, sizeof(*c)); - + git__memzero(c, sizeof(*c)); git__free(c); } @@ -85,11 +85,7 @@ static void ssh_publickey_free(struct git_cred *cred) git__free(c->publickey); - c->sign_callback = NULL; - c->sign_data = NULL; - - memset(c, 0, sizeof(*c)); - + git__memzero(c, sizeof(*c)); git__free(c); } From 290e14798598922fbff7189f64f997bad35c4f84 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 9 Jul 2013 16:17:41 -0700 Subject: [PATCH 079/367] Add GIT_CAP_SSH if library was built with SSH This also adds a test that actually calls git_libgit2_capabilities and git_libgit2_version. --- include/git2/common.h | 3 ++- src/util.c | 3 +++ tests-clar/core/caps.c | 31 +++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests-clar/core/caps.c diff --git a/include/git2/common.h b/include/git2/common.h index b52e13918..d7df7327e 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -105,7 +105,8 @@ GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev); */ typedef enum { GIT_CAP_THREADS = ( 1 << 0 ), - GIT_CAP_HTTPS = ( 1 << 1 ) + GIT_CAP_HTTPS = ( 1 << 1 ), + GIT_CAP_SSH = ( 1 << 2 ), } git_cap_t; /** diff --git a/src/util.c b/src/util.c index c543a3d21..ad7603829 100644 --- a/src/util.c +++ b/src/util.c @@ -32,6 +32,9 @@ int git_libgit2_capabilities() #endif #if defined(GIT_SSL) || defined(GIT_WINHTTP) | GIT_CAP_HTTPS +#endif +#if defined(GIT_SSH) + | GIT_CAP_SSH #endif ; } diff --git a/tests-clar/core/caps.c b/tests-clar/core/caps.c new file mode 100644 index 000000000..68a518ed7 --- /dev/null +++ b/tests-clar/core/caps.c @@ -0,0 +1,31 @@ +#include "clar_libgit2.h" + +void test_core_caps__0(void) +{ + int major, minor, rev, caps; + + git_libgit2_version(&major, &minor, &rev); + cl_assert_equal_i(LIBGIT2_VER_MAJOR, major); + cl_assert_equal_i(LIBGIT2_VER_MINOR, minor); + cl_assert_equal_i(LIBGIT2_VER_REVISION, rev); + + caps = git_libgit2_capabilities(); + +#ifdef GIT_THREADS + cl_assert((caps & GIT_CAP_THREADS) != 0); +#else + cl_assert((caps & GIT_CAP_THREADS) == 0); +#endif + +#if defined(GIT_SSL) || defined(GIT_WINHTTP) + cl_assert((caps & GIT_CAP_HTTPS) != 0); +#else + cl_assert((caps & GIT_CAP_HTTPS) == 0); +#endif + +#if defined(GIT_SSH) + cl_assert((caps & GIT_CAP_SSH) != 0); +#else + cl_assert((caps & GIT_CAP_SSH) == 0); +#endif +} From 07fba63e9eda509d1eee13df0b325dbd4be2f3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sat, 6 Jul 2013 23:51:40 +0200 Subject: [PATCH 080/367] Fix return value in git_config_get_multivar If there is not an error, the return value was always the return value of the last call to file->get_multivar With this commit GIT_ENOTFOUND is only returned if all the calls to filge-get_multivar return GIT_ENOTFOUND. --- src/config.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index 068c40260..aaad7d87c 100644 --- a/src/config.c +++ b/src/config.c @@ -535,6 +535,7 @@ int git_config_get_multivar( file_internal *internal; git_config_backend *file; int ret = GIT_ENOTFOUND; + int err; size_t i; /* @@ -547,9 +548,15 @@ int git_config_get_multivar( continue; file = internal->file; - ret = file->get_multivar(file, name, regexp, cb, payload); - if (ret < 0 && ret != GIT_ENOTFOUND) - return ret; + err = file->get_multivar(file, name, regexp, cb, payload); + switch (err) { + case GIT_OK: + ret = GIT_OK; + case GIT_ENOTFOUND: + break; + default: + return err; + } } return (ret == GIT_ENOTFOUND) ? config_error_notfound(name) : 0; From 7b5c0d18460b6cb0a65543b92002a29644dbb458 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 9 Jul 2013 16:45:23 -0700 Subject: [PATCH 081/367] Add more tests for git_config_get_multivar The old tests didn't try failing lookups or lookups across multiple config files with some having the pattern and some not having it. --- tests-clar/config/multivar.c | 56 ++++++++++++++++++++++++---- tests-clar/resources/config/config11 | 2 +- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c index 0bda6bcec..efc431502 100644 --- a/tests-clar/config/multivar.c +++ b/tests-clar/config/multivar.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" -static const char *_name = "remote.fancy.url"; +static const char *_name = "remote.ab.url"; void test_config_multivar__initialize(void) { @@ -46,20 +46,60 @@ static int cb(const git_config_entry *entry, void *data) return 0; } +static void check_get_multivar( + git_config *cfg, int expected, int expected_patterned) +{ + int n = 0; + + if (expected > 0) { + cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(expected, n); + } else { + cl_assert_equal_i(GIT_ENOTFOUND, + git_config_get_multivar(cfg, _name, NULL, cb, &n)); + } + + n = 0; + + if (expected_patterned > 0) { + cl_git_pass(git_config_get_multivar(cfg, _name, "example", cb, &n)); + cl_assert_equal_i(expected_patterned, n); + } else { + cl_assert_equal_i(GIT_ENOTFOUND, + git_config_get_multivar(cfg, _name, "example", cb, &n)); + } +} + void test_config_multivar__get(void) { git_config *cfg; - int n; cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + check_get_multivar(cfg, 2, 1); - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); - cl_assert(n == 2); + /* add another that has the _name entry */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config9", GIT_CONFIG_LEVEL_SYSTEM, 1)); + check_get_multivar(cfg, 3, 2); - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "example", cb, &n)); - cl_assert(n == 1); + /* add another that does not have the _name entry */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config0", GIT_CONFIG_LEVEL_GLOBAL, 1)); + check_get_multivar(cfg, 3, 2); + + /* add another that does not have the _name entry at the end */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config1", GIT_CONFIG_LEVEL_APP, 1)); + check_get_multivar(cfg, 3, 2); + + /* drop original file */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config2", GIT_CONFIG_LEVEL_LOCAL, 1)); + check_get_multivar(cfg, 1, 1); + + /* drop other file with match */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config3", GIT_CONFIG_LEVEL_SYSTEM, 1)); + check_get_multivar(cfg, 0, 0); + + /* reload original file (add different place in order) */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config11", GIT_CONFIG_LEVEL_SYSTEM, 1)); + check_get_multivar(cfg, 2, 1); git_config_free(cfg); } diff --git a/tests-clar/resources/config/config11 b/tests-clar/resources/config/config11 index 7331862a5..1d8a74470 100644 --- a/tests-clar/resources/config/config11 +++ b/tests-clar/resources/config/config11 @@ -1,4 +1,4 @@ -[remote "fancy"] +[remote "ab"] url = git://github.com/libgit2/libgit2 url = git://git.example.com/libgit2 From e4fda954d6d914609498fc3bcbd27b4e2b5834d3 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 9 Jul 2013 16:46:18 -0700 Subject: [PATCH 082/367] A little git_config_get_multivar code cleanup --- src/config.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/config.c b/src/config.c index aaad7d87c..2a058549f 100644 --- a/src/config.c +++ b/src/config.c @@ -534,8 +534,7 @@ int git_config_get_multivar( { file_internal *internal; git_config_backend *file; - int ret = GIT_ENOTFOUND; - int err; + int ret = GIT_ENOTFOUND, err; size_t i; /* @@ -548,15 +547,10 @@ int git_config_get_multivar( continue; file = internal->file; - err = file->get_multivar(file, name, regexp, cb, payload); - switch (err) { - case GIT_OK: - ret = GIT_OK; - case GIT_ENOTFOUND: - break; - default: - return err; - } + if (!(err = file->get_multivar(file, name, regexp, cb, payload))) + ret = 0; + else if (err != GIT_ENOTFOUND) + return err; } return (ret == GIT_ENOTFOUND) ? config_error_notfound(name) : 0; From 367c1903e9aefd07de0d5be98c56640d13e3420d Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Wed, 10 Jul 2013 10:29:09 +0200 Subject: [PATCH 083/367] Add some missing error messages. --- src/transports/ssh.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index f2480b2cd..bb3843886 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -353,24 +353,31 @@ static int _git_ssh_setup_conn( t->owner->cred_acquire_payload) < 0) goto on_error; } else { - goto on_error; - } + giterr_set(GITERR_NET, "Cannot set up SSH connection without credentials"); + goto on_error; + } assert(t->cred); if (!user) { user = git__strdup(default_user); } - if (_git_ssh_session_create(&session, s->socket) < 0) + if (_git_ssh_session_create(&session, s->socket) < 0) { + giterr_set(GITERR_NET, "Failed to initialize SSH session"); goto on_error; - - if (_git_ssh_authenticate_session(session, user, t->cred) < 0) + } + + if (_git_ssh_authenticate_session(session, user, t->cred) < 0) { + giterr_set(GITERR_NET, "Failed to authenticate SSH session"); goto on_error; - + } + channel = libssh2_channel_open_session(session); - if (!channel) - goto on_error; - + if (!channel) { + giterr_set(GITERR_NET, "Failed to open SSH channel"); + goto on_error; + } + libssh2_channel_set_blocking(channel, 1); s->session = session; From 08bf80fa2bf95e17af79a1da5832aefb275d9565 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Wed, 10 Jul 2013 10:29:32 +0200 Subject: [PATCH 084/367] Tab indent. --- src/transports/ssh.c | 182 ++++++++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 90 deletions(-) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index bb3843886..2eaaa45c9 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -46,27 +46,27 @@ typedef struct { static int gen_proto(git_buf *request, const char *cmd, const char *url) { char *repo; - + if (!git__prefixcmp(url, prefix_ssh)) { url = url + strlen(prefix_ssh); repo = strchr(url, '/'); } else { repo = strchr(url, ':'); } - + if (!repo) { return -1; } - + int len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1; - + git_buf_grow(request, len); git_buf_printf(request, "%s '%s'", cmd, repo); git_buf_putc(request, '\0'); - + if (git_buf_oom(request)) return -1; - + return 0; } @@ -74,11 +74,11 @@ static int send_command(ssh_stream *s) { int error; git_buf request = GIT_BUF_INIT; - + error = gen_proto(&request, s->cmd, s->url); if (error < 0) goto cleanup; - + error = libssh2_channel_exec( s->channel, request.ptr @@ -86,9 +86,9 @@ static int send_command(ssh_stream *s) if (0 != error) goto cleanup; - + s->sent_command = 1; - + cleanup: git_buf_free(&request); return error; @@ -101,18 +101,18 @@ static int ssh_stream_read( size_t *bytes_read) { ssh_stream *s = (ssh_stream *)stream; - + *bytes_read = 0; - + if (!s->sent_command && send_command(s) < 0) return -1; - + int rc = libssh2_channel_read(s->channel, buffer, buf_size); if (rc < 0) return -1; - + *bytes_read = rc; - + return 0; } @@ -122,15 +122,15 @@ static int ssh_stream_write( size_t len) { ssh_stream *s = (ssh_stream *)stream; - + if (!s->sent_command && send_command(s) < 0) return -1; - + int rc = libssh2_channel_write(s->channel, buffer, len); if (rc < 0) { return -1; } - + return rc; } @@ -139,26 +139,26 @@ static void ssh_stream_free(git_smart_subtransport_stream *stream) ssh_stream *s = (ssh_stream *)stream; ssh_subtransport *t = OWNING_SUBTRANSPORT(s); int ret; - + GIT_UNUSED(ret); - + t->current_stream = NULL; - + if (s->channel) { libssh2_channel_close(s->channel); libssh2_channel_free(s->channel); s->channel = NULL; } - + if (s->session) { libssh2_session_free(s->session), s->session = NULL; } - + if (s->socket.socket) { ret = gitno_close(&s->socket); assert(!ret); } - + git__free(s->url); git__free(s); } @@ -170,26 +170,26 @@ static int ssh_stream_alloc( git_smart_subtransport_stream **stream) { ssh_stream *s; - + if (!stream) return -1; - + s = git__calloc(sizeof(ssh_stream), 1); GITERR_CHECK_ALLOC(s); - + s->parent.subtransport = &t->parent; s->parent.read = ssh_stream_read; s->parent.write = ssh_stream_write; s->parent.free = ssh_stream_free; - + s->cmd = cmd; s->url = git__strdup(url); - + if (!s->url) { git__free(s); return -1; } - + *stream = &s->parent; return 0; } @@ -201,14 +201,14 @@ static int git_ssh_extract_url_parts( { char *colon, *at; const char *start; - + colon = strchr(url, ':'); - + if (colon == NULL) { giterr_set(GITERR_NET, "Malformed URL: missing :"); return -1; } - + at = strchr(url, '@'); if (at) { start = at+1; @@ -217,9 +217,9 @@ static int git_ssh_extract_url_parts( start = url; *username = git__strdup(default_user); } - + *host = git__substrdup(start, colon - start); - + return 0; } @@ -235,7 +235,7 @@ static int _git_ssh_authenticate_session( case GIT_CREDTYPE_USERPASS_PLAINTEXT: { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; rc = libssh2_userauth_password( - session, + session, c->username, c->password ); @@ -244,7 +244,7 @@ static int _git_ssh_authenticate_session( case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: { git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; rc = libssh2_userauth_publickey_fromfile( - session, + session, user, c->publickey, c->privatekey, @@ -267,13 +267,12 @@ static int _git_ssh_authenticate_session( default: rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; } - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - return rc; + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + return rc; } -static int _git_ssh_session_create -( +static int _git_ssh_session_create( LIBSSH2_SESSION** session, gitno_socket socket ) @@ -281,31 +280,30 @@ static int _git_ssh_session_create if (!session) { return -1; } - + LIBSSH2_SESSION* s = libssh2_session_init(); - if (!s) - return -1; - - int rc = 0; - do { - rc = libssh2_session_startup(s, socket.socket); - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - if (0 != rc) { - goto on_error; - } - + if (!s) + return -1; + + int rc = 0; + do { + rc = libssh2_session_startup(s, socket.socket); + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + if (0 != rc) + goto on_error; + libssh2_session_set_blocking(s, 1); - + *session = s; - + return 0; - + on_error: if (s) { libssh2_session_free(s), s = NULL; } - + return -1; } @@ -321,13 +319,13 @@ static int _git_ssh_setup_conn( ssh_stream *s; LIBSSH2_SESSION* session=NULL; LIBSSH2_CHANNEL* channel=NULL; - + *stream = NULL; if (ssh_stream_alloc(t, url, cmd, stream) < 0) return -1; - + s = (ssh_stream *)*stream; - + if (!git__prefixcmp(url, prefix_ssh)) { url = url + strlen(prefix_ssh); if (gitno_extract_url_parts(&host, &port, &user, &pass, url, default_port) < 0) @@ -338,10 +336,10 @@ static int _git_ssh_setup_conn( port = git__strdup(default_port); GITERR_CHECK_ALLOC(port); } - + if (gitno_connect(&s->socket, host, port, 0) < 0) goto on_error; - + if (user && pass) { if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0) goto on_error; @@ -357,11 +355,11 @@ static int _git_ssh_setup_conn( goto on_error; } assert(t->cred); - + if (!user) { user = git__strdup(default_user); } - + if (_git_ssh_session_create(&session, s->socket) < 0) { giterr_set(GITERR_NET, "Failed to initialize SSH session"); goto on_error; @@ -379,10 +377,10 @@ static int _git_ssh_setup_conn( } libssh2_channel_set_blocking(channel, 1); - + s->session = session; s->channel = channel; - + t->current_stream = s; git__free(host); git__free(port); @@ -390,11 +388,15 @@ static int _git_ssh_setup_conn( git__free(pass); return 0; - + on_error: + s->session = NULL; + s->channel = NULL; + t->current_stream = NULL; + if (*stream) ssh_stream_free(*stream); - + git__free(host); git__free(port); git__free(user); @@ -413,7 +415,7 @@ static int ssh_uploadpack_ls( { if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0) return -1; - + return 0; } @@ -423,12 +425,12 @@ static int ssh_uploadpack( git_smart_subtransport_stream **stream) { GIT_UNUSED(url); - + if (t->current_stream) { *stream = &t->current_stream->parent; return 0; } - + giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK"); return -1; } @@ -440,7 +442,7 @@ static int ssh_receivepack_ls( { if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0) return -1; - + return 0; } @@ -450,12 +452,12 @@ static int ssh_receivepack( git_smart_subtransport_stream **stream) { GIT_UNUSED(url); - + if (t->current_stream) { *stream = &t->current_stream->parent; return 0; } - + giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK"); return -1; } @@ -467,21 +469,21 @@ static int _ssh_action( git_smart_service_t action) { ssh_subtransport *t = (ssh_subtransport *) subtransport; - + switch (action) { case GIT_SERVICE_UPLOADPACK_LS: return ssh_uploadpack_ls(t, url, stream); - + case GIT_SERVICE_UPLOADPACK: return ssh_uploadpack(t, url, stream); - + case GIT_SERVICE_RECEIVEPACK_LS: return ssh_receivepack_ls(t, url, stream); - + case GIT_SERVICE_RECEIVEPACK: return ssh_receivepack(t, url, stream); } - + *stream = NULL; return -1; } @@ -489,38 +491,38 @@ static int _ssh_action( static int _ssh_close(git_smart_subtransport *subtransport) { ssh_subtransport *t = (ssh_subtransport *) subtransport; - + assert(!t->current_stream); - + GIT_UNUSED(t); - + return 0; } static void _ssh_free(git_smart_subtransport *subtransport) { ssh_subtransport *t = (ssh_subtransport *) subtransport; - + assert(!t->current_stream); - + git__free(t); } int git_smart_subtransport_ssh(git_smart_subtransport **out, git_transport *owner) { ssh_subtransport *t; - + if (!out) return -1; - + t = git__calloc(sizeof(ssh_subtransport), 1); GITERR_CHECK_ALLOC(t); - + t->owner = (transport_smart *)owner; t->parent.action = _ssh_action; t->parent.close = _ssh_close; t->parent.free = _ssh_free; - + *out = (git_smart_subtransport *) t; return 0; } From c2de6b1adf49f8588dd59188774c96b783181e2f Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 10 Jul 2013 10:21:24 -0700 Subject: [PATCH 085/367] Bring SSH error reporting up to base standards The SSH error checking and reporting could still be further improved by using the libssh2 native methods to get error info, but at least this ensures that all error codes are checked and translated into libgit2 error messages. --- src/transports/cred.c | 3 +- src/transports/ssh.c | 151 ++++++++++++++++++++---------------------- 2 files changed, 73 insertions(+), 81 deletions(-) diff --git a/src/transports/cred.c b/src/transports/cred.c index d713f8992..a6727e902 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -33,8 +33,7 @@ int git_cred_userpass_plaintext_new( { git_cred_userpass_plaintext *c; - if (!cred) - return -1; + assert(cred); c = git__malloc(sizeof(git_cred_userpass_plaintext)); GITERR_CHECK_ALLOC(c); diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 0155dd745..7fb53bc3c 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -55,6 +55,7 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) } if (!repo) { + giterr_set(GITERR_NET, "Malformed git protocol URL"); return -1; } @@ -79,13 +80,11 @@ static int send_command(ssh_stream *s) if (error < 0) goto cleanup; - error = libssh2_channel_exec( - s->channel, - request.ptr - ); - - if (0 != error) + error = libssh2_channel_exec(s->channel, request.ptr); + if (error < 0) { + giterr_set(GITERR_NET, "SSH could not execute request"); goto cleanup; + } s->sent_command = 1; @@ -100,6 +99,7 @@ static int ssh_stream_read( size_t buf_size, size_t *bytes_read) { + int rc; ssh_stream *s = (ssh_stream *)stream; *bytes_read = 0; @@ -107,9 +107,10 @@ static int ssh_stream_read( if (!s->sent_command && send_command(s) < 0) return -1; - int rc = libssh2_channel_read(s->channel, buffer, buf_size); - if (rc < 0) + if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < 0) { + giterr_set(GITERR_NET, "SSH could not read data"); return -1; + } *bytes_read = rc; @@ -126,12 +127,12 @@ static int ssh_stream_write( if (!s->sent_command && send_command(s) < 0) return -1; - int rc = libssh2_channel_write(s->channel, buffer, len); - if (rc < 0) { + if (libssh2_channel_write(s->channel, buffer, len) < 0) { + giterr_set(GITERR_NET, "SSH could not write data"); return -1; } - return rc; + return 0; } static void ssh_stream_free(git_smart_subtransport_stream *stream) @@ -151,12 +152,13 @@ static void ssh_stream_free(git_smart_subtransport_stream *stream) } if (s->session) { - libssh2_session_free(s->session), s->session = NULL; + libssh2_session_free(s->session); + s->session = NULL; } if (s->socket.socket) { - ret = gitno_close(&s->socket); - assert(!ret); + (void)gitno_close(&s->socket); + /* can't do anything here with error return value */ } git__free(s->url); @@ -171,8 +173,7 @@ static int ssh_stream_alloc( { ssh_stream *s; - if (!stream) - return -1; + assert(stream); s = git__calloc(sizeof(ssh_stream), 1); GITERR_CHECK_ALLOC(s); @@ -183,8 +184,8 @@ static int ssh_stream_alloc( s->parent.free = ssh_stream_free; s->cmd = cmd; - s->url = git__strdup(url); + s->url = git__strdup(url); if (!s->url) { git__free(s); return -1; @@ -217,8 +218,10 @@ static int git_ssh_extract_url_parts( start = url; *username = git__strdup(default_user); } + GITERR_CHECK_ALLOC(*username); *host = git__substrdup(start, colon - start); + GITERR_CHECK_ALLOC(*host); return 0; } @@ -229,67 +232,63 @@ static int _git_ssh_authenticate_session( git_cred* cred) { int rc; + do { switch (cred->credtype) { - case GIT_CREDTYPE_USERPASS_PLAINTEXT: { - git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; - rc = libssh2_userauth_password( - session, - c->username, - c->password - ); - break; - } - case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: { - git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; - rc = libssh2_userauth_publickey_fromfile( - session, - user, - c->publickey, - c->privatekey, - c->passphrase - ); - break; - } - case GIT_CREDTYPE_SSH_PUBLICKEY: { - git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; - rc = libssh2_userauth_publickey( - session, - user, - (const unsigned char *)c->publickey, - c->publickey_len, - c->sign_callback, - &c->sign_data - ); - break; - } - default: - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + case GIT_CREDTYPE_USERPASS_PLAINTEXT: { + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + rc = libssh2_userauth_password(session, c->username, c->password); + break; + } + case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: { + git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; + rc = libssh2_userauth_publickey_fromfile( + session, user, c->publickey, c->privatekey, c->passphrase); + break; + } + case GIT_CREDTYPE_SSH_PUBLICKEY: { + git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; + rc = libssh2_userauth_publickey( + session, user, (const unsigned char *)c->publickey, + c->publickey_len, c->sign_callback, &c->sign_data); + break; + } + default: + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; } } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - return rc; + if (rc != 0) { + giterr_set(GITERR_NET, "Failed to authenticate SSH session"); + return -1; + } + + return 0; } static int _git_ssh_session_create( LIBSSH2_SESSION** session, gitno_socket socket) { - if (!session) { + int rc = 0; + LIBSSH2_SESSION* s; + + assert(session); + + s = libssh2_session_init(); + if (!s) { + giterr_set(GITERR_NET, "Failed to initialize SSH session"); return -1; } - LIBSSH2_SESSION* s = libssh2_session_init(); - if (!s) - return -1; - - int rc = 0; do { rc = libssh2_session_startup(s, socket.socket); } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); if (0 != rc) { - goto on_error; + libssh2_session_free(s); + giterr_set(GITERR_NET, "Failed to start SSH session"); + return -1; } libssh2_session_set_blocking(s, 1); @@ -297,21 +296,13 @@ static int _git_ssh_session_create( *session = s; return 0; - -on_error: - if (s) { - libssh2_session_free(s), s = NULL; - } - - return -1; } static int _git_ssh_setup_conn( ssh_subtransport *t, const char *url, const char *cmd, - git_smart_subtransport_stream **stream -) + git_smart_subtransport_stream **stream) { char *host, *port=NULL, *user=NULL, *pass=NULL; const char *default_port="22"; @@ -343,12 +334,17 @@ static int _git_ssh_setup_conn( if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0) goto on_error; } else if (t->owner->cred_acquire_cb) { - if (t->owner->cred_acquire_cb(&t->cred, - t->owner->url, - user, - GIT_CREDTYPE_USERPASS_PLAINTEXT | GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE, + if (t->owner->cred_acquire_cb( + &t->cred, t->owner->url, user, + GIT_CREDTYPE_USERPASS_PLAINTEXT | + GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE, t->owner->cred_acquire_payload) < 0) goto on_error; + + if (!t->cred) { + giterr_set(GITERR_NET, "Callback failed to initialize SSH credentials"); + goto on_error; + } } else { giterr_set(GITERR_NET, "Cannot set up SSH connection without credentials"); goto on_error; @@ -357,17 +353,14 @@ static int _git_ssh_setup_conn( if (!user) { user = git__strdup(default_user); + GITERR_CHECK_ALLOC(user); } - if (_git_ssh_session_create(&session, s->socket) < 0) { - giterr_set(GITERR_NET, "Failed to initialize SSH session"); + if (_git_ssh_session_create(&session, s->socket) < 0) goto on_error; - } - if (_git_ssh_authenticate_session(session, user, t->cred) < 0) { - giterr_set(GITERR_NET, "Failed to authenticate SSH session"); + if (_git_ssh_authenticate_session(session, user, t->cred) < 0) goto on_error; - } channel = libssh2_channel_open_session(session); if (!channel) { @@ -402,7 +395,7 @@ on_error: git__free(pass); if (session) - libssh2_session_free(session), session = NULL; + libssh2_session_free(session); return -1; } From 33c8c6f0b81badadf805d18f39f79384a6494bac Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 10 Jul 2013 10:48:32 -0700 Subject: [PATCH 086/367] trivial whitespace fixup --- src/transports/smart_protocol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 636616717..0cd5e831d 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -372,7 +372,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c return error; if (pkt->type == GIT_PKT_NAK || - (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) { + (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) { git__free(pkt); break; } From d39fff36484e908438beb17ee043689962182460 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sun, 23 Jun 2013 20:33:57 -0700 Subject: [PATCH 087/367] Basic framework for log command --- examples/Makefile | 2 +- examples/log.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 examples/log.c diff --git a/examples/Makefile b/examples/Makefile index 140cc4da9..6288906df 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -3,7 +3,7 @@ CC = gcc CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers LFLAGS = -L../build -lgit2 -lz -APPS = general showindex diff rev-list cat-file status +APPS = general showindex diff rev-list cat-file status log all: $(APPS) diff --git a/examples/log.c b/examples/log.c new file mode 100644 index 000000000..f92b66996 --- /dev/null +++ b/examples/log.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +static void check(int error, const char *message) +{ + if (error) { + fprintf(stderr, "%s (%d)\n", message, error); + exit(1); + } +} + +static int check_str_param(const char *arg, const char *pat, const char **val) +{ + size_t len = strlen(pat); + if (strncmp(arg, pat, len)) + return 0; + *val = (const char *)(arg + len); + return 1; +} + +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: log []\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + int i; + char *a; + const char *dir = "."; + git_repository *repo; + + git_threads_init(); + + for (i = 1; i < argc; ++i) { + a = argv[i]; + + if (a[0] != '-') { + } + else if (!check_str_param(a, "--git-dir=", &dir)) + usage("Unknown argument", a); + } + + check(git_repository_open_ext(&repo, dir, 0, NULL), + "Could not open repository"); + + git_repository_free(repo); + git_threads_shutdown(); + + return 0; +} From d2ce27dd494b65f54b2d110b4defd69aea976115 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 24 Jun 2013 23:16:06 -0700 Subject: [PATCH 088/367] Add public API for pathspec matching This adds a new public API for compiling pathspecs and matching them against the working directory, the index, or a tree from the repository. This also reworks the pathspec internals to allow the sharing of code between the existing internal usage of pathspec matching and the new external API. While this is working and the new API is ready for discussion, I think there is still an incorrect behavior in which patterns are always matched against the full path of an entry without taking the subdirectories into account (so "s*" will match "subdir/file" even though it wouldn't with core Git). Further enhancements are coming, but this was a good place to take a functional snapshot. --- include/git2/index.h | 8 + include/git2/pathspec.h | 202 +++++++++++++++++ src/checkout.c | 10 +- src/diff.c | 12 +- src/index.c | 42 ++-- src/index.h | 6 +- src/pathspec.c | 452 +++++++++++++++++++++++++++++++++---- src/pathspec.h | 43 ++-- tests-clar/repo/pathspec.c | 385 +++++++++++++++++++++++++++++++ 9 files changed, 1063 insertions(+), 97 deletions(-) create mode 100644 include/git2/pathspec.h create mode 100644 tests-clar/repo/pathspec.c diff --git a/include/git2/index.h b/include/git2/index.h index 51694aded..1fb77efa3 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -138,6 +138,14 @@ typedef enum { GIT_INDEX_ADD_CHECK_PATHSPEC = (1u << 2), } git_index_add_option_t; +/** + * Match any index stage. + * + * Some index APIs take a stage to match; pass this value to match + * any entry matching the path regardless of stage. + */ +#define GIT_INDEX_STAGE_ANY -1 + /** @name Index File Functions * * These functions work on the index file itself. diff --git a/include/git2/pathspec.h b/include/git2/pathspec.h new file mode 100644 index 000000000..8122d9927 --- /dev/null +++ b/include/git2/pathspec.h @@ -0,0 +1,202 @@ +/* + * 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_git_pathspec_h__ +#define INCLUDE_git_pathspec_h__ + +#include "common.h" +#include "types.h" +#include "strarray.h" + +/** + * Compiled pathspec + */ +typedef struct git_pathspec git_pathspec; + +/** + * List of filenames matching a pathspec + */ +typedef struct git_pathspec_match_list git_pathspec_match_list; + +/** + * Options controlling how pathspec match should be executed + * + * - GIT_PATHSPEC_IGNORE_CASE forces match to ignore case; otherwise + * match will use native case sensitivity of platform + * - GIT_PATHSPEC_USE_CASE forces case sensitive match; otherwise + * match will use native case sensitivity of platform + * - GIT_PATHSPEC_NO_GLOB disables glob patterns and just uses simple + * string comparison for matching + * - GIT_PATHSPEC_NO_MATCH_ERROR means the match function will return + * GIT_ENOTFOUND if no matches are found; otherwise it will return 0 + * for success and `git_pathspec_match_list_entrycount` will be 0. + * - GIT_PATHSPEC_FIND_FAILURES only applies to a git_pathspec_match_list; + * it means to check file names against all unmatched patterns so that + * at the end of a match we can identify patterns that did not match any + * files. + * - GIT_PATHSPEC_FAILURES_ONLY only applies to a git_pathspec_match_list; + * it means to only check for mismatches and not record matched paths. + */ +typedef enum { + GIT_PATHSPEC_DEFAULT = 0, + GIT_PATHSPEC_IGNORE_CASE = (1u << 0), + GIT_PATHSPEC_USE_CASE = (1u << 1), + GIT_PATHSPEC_NO_GLOB = (1u << 2), + GIT_PATHSPEC_NO_MATCH_ERROR = (1u << 3), + GIT_PATHSPEC_FIND_FAILURES = (1u << 4), + GIT_PATHSPEC_FAILURES_ONLY = (1u << 5), +} git_pathspec_flag_t; + +/** + * Compile a pathspec + * + * @param out Output of the compiled pathspec + * @param flags Combination of git_pathspec_flag_t values + * @param pathspec A git_strarray of the paths to match + * @return 0 on success, <0 on failure + */ +GIT_EXTERN(int) git_pathspec_new( + git_pathspec **out, const git_strarray *pathspec); + +/** + * Free a pathspec + * + * @param ps The compiled pathspec + */ +GIT_EXTERN(void) git_pathspec_free(git_pathspec *ps); + +/** + * Try to match a path against a pathspec + * + * Unlike most of the other pathspec matching functions, this will not + * fall back on the native case-sensitivity for your platform. You must + * explicitly pass flags to control case sensitivity or else this will + * fall back on being case sensitive. + * + * @param ps The compiled pathspec + * @param flags Match flags to influence matching behavior + * @param path The pathname to attempt to match + * @return 1 is path matches spec, 0 if it does not + */ +GIT_EXTERN(int) git_pathspec_matches_path( + const git_pathspec *ps, uint32_t flags, const char *path); + +/** + * Match a pathspec against the working directory of a repository. + * + * This returns a `git_patchspec_match` object that contains the list of + * all files matching the given pathspec in the working directory of the + * repository. This handles git ignores (i.e. ignored files will not be + * considered to match the `pathspec` unless the file is tracked in the + * index). + * + * @param out Object with list of matching items + * @param repo The repository in which to match; bare repo is an error + * @param flags Options to control matching behavior + * @param ps Pathspec to be matched + * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and + * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used + */ +GIT_EXTERN(int) git_pathspec_match_workdir( + git_pathspec_match_list **out, + git_repository *repo, + uint32_t flags, + git_pathspec *ps); + +/** + * Match a pathspec against entries in an index. + * + * This returns a `git_patchspec_match` object that contains the list of + * all files matching the given pathspec in the index. + * + * NOTE: At the moment, the case sensitivity of this match is controlled + * by the current case-sensitivity of the index object itself and the + * USE_CASE and IGNORE_CASE flags will have no effect. This behavior will + * be corrected in a future release. + * + * @param out Object with list of matching items + * @param inex The index in which to match + * @param flags Options to control matching behavior + * @param ps Pathspec to be matched + * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and + * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used + */ +GIT_EXTERN(int) git_pathspec_match_index( + git_pathspec_match_list **out, + git_index *index, + uint32_t flags, + git_pathspec *ps); + +/** + * Match a pathspec against files in a tree. + * + * This returns a `git_patchspec_match` object that contains the list of + * all files matching the given pathspec in the given tree. + * + * @param out Object with list of matching items + * @param inex The index in which to match + * @param flags Options to control matching behavior + * @param ps Pathspec to be matched + * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and + * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used + */ +GIT_EXTERN(int) git_pathspec_match_tree( + git_pathspec_match_list **out, + git_tree *tree, + uint32_t flags, + git_pathspec *ps); + +/** + * Free memory associates with a git_pathspec_match_list + * + * @param m The git_pathspec_match_list to be freed + */ +GIT_EXTERN(void) git_pathspec_match_list_free(git_pathspec_match_list *m); + +/** + * Get the number of items in a match list. + * + * @param m The git_pathspec_match_list object + * @return Number of items in match list + */ +GIT_EXTERN(size_t) git_pathspec_match_list_entrycount( + const git_pathspec_match_list *m); + +/** + * Get a matching filename by position. + * + * @param m The git_pathspec_match_list object + * @param pos The index into the list + * @return The filename of the match + */ +GIT_EXTERN(const char *) git_pathspec_match_list_entry( + const git_pathspec_match_list *m, size_t pos); + +/** + * Get the number of pathspec items that did not match. + * + * This will be zero unless you passed GIT_PATHSPEC_FIND_FAILURES when + * generating the git_pathspec_match_list. + * + * @param m The git_pathspec_match_list object + * @return Number of items in original pathspec that had no matches + */ +GIT_EXTERN(size_t) git_pathspec_match_list_failed_entrycount( + const git_pathspec_match_list *m); + +/** + * Get an original pathspec string that had no matches. + * + * This will be return NULL for positions out of range. + * + * @param m The git_pathspec_match_list object + * @param pos The index into the failed items + * @return The pathspec pattern that didn't match anything + */ +GIT_EXTERN(const char *) git_pathspec_match_list_failed_entry( + const git_pathspec_match_list *m, size_t pos); + +#endif diff --git a/src/checkout.c b/src/checkout.c index 8f9ec64e4..ec9da7e2e 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -246,10 +246,10 @@ static int checkout_action_wd_only( bool remove = false; git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; - if (!git_pathspec_match_path( + if (!git_pathspec__match( pathspec, wd->path, (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL)) + git_iterator_ignore_case(workdir), NULL, NULL)) return 0; /* check if item is tracked in the index but not in the checkout diff */ @@ -607,7 +607,7 @@ static int checkout_get_actions( uint32_t *actions = NULL; if (data->opts.paths.count > 0 && - git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0) + git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0) return -1; if ((error = git_iterator_current(&wditem, workdir)) < 0 && @@ -659,7 +659,7 @@ static int checkout_get_actions( goto fail; } - git_pathspec_free(&pathspec); + git_pathspec__vfree(&pathspec); git_pool_clear(&pathpool); return 0; @@ -670,7 +670,7 @@ fail: *actions_ptr = NULL; git__free(actions); - git_pathspec_free(&pathspec); + git_pathspec__vfree(&pathspec); git_pool_clear(&pathpool); return error; diff --git a/src/diff.c b/src/diff.c index 0980f412a..56232ebf4 100644 --- a/src/diff.c +++ b/src/diff.c @@ -81,11 +81,11 @@ static int diff_delta__from_one( DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) return 0; - if (!git_pathspec_match_path( + if (!git_pathspec__match( &diff->pathspec, entry->path, DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), - &matched_pathspec)) + &matched_pathspec, NULL)) return 0; delta = diff_delta__alloc(diff, status, entry->path); @@ -387,7 +387,7 @@ static int diff_list_apply_options( DIFF_FLAG_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE, icase); /* initialize pathspec from options */ - if (git_pathspec_init(&diff->pathspec, &opts->pathspec, pool) < 0) + if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) return -1; } @@ -473,7 +473,7 @@ static void diff_list_free(git_diff_list *diff) } git_vector_free(&diff->deltas); - git_pathspec_free(&diff->pathspec); + git_pathspec__vfree(&diff->pathspec); git_pool_clear(&diff->pool); git__memzero(diff, sizeof(*diff)); @@ -634,11 +634,11 @@ static int maybe_modified( bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR); const char *matched_pathspec; - if (!git_pathspec_match_path( + if (!git_pathspec__match( &diff->pathspec, oitem->path, DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), - &matched_pathspec)) + &matched_pathspec, NULL)) return 0; memset(&noid, 0, sizeof(noid)); diff --git a/src/index.c b/src/index.c index ffa84e53f..21efd2c63 100644 --- a/src/index.c +++ b/src/index.c @@ -101,8 +101,6 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) static bool is_index_extended(git_index *index); static int write_index(git_index *index, git_filebuf *file); -static int index_find(size_t *at_pos, git_index *index, const char *path, int stage); - static void index_entry_free(git_index_entry *entry); static void index_entry_reuc_free(git_index_reuc_entry *reuc); @@ -114,7 +112,7 @@ static int index_srch(const void *key, const void *array_member) ret = strcmp(srch_key->path, entry->path); - if (ret == 0) + if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY) ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry); return ret; @@ -128,7 +126,7 @@ static int index_isrch(const void *key, const void *array_member) ret = strcasecmp(srch_key->path, entry->path); - if (ret == 0) + if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY) ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry); return ret; @@ -562,7 +560,7 @@ const git_index_entry *git_index_get_bypath( git_vector_sort(&index->entries); - if (index_find(&pos, index, path, stage) < 0) { + if (git_index__find(&pos, index, path, stage) < 0) { giterr_set(GITERR_INDEX, "Index does not contain %s", path); return NULL; } @@ -719,7 +717,8 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) entry->flags |= GIT_IDXENTRY_NAMEMASK; /* look if an entry with this path already exists */ - if (!index_find(&position, index, entry->path, GIT_IDXENTRY_STAGE(entry))) { + if (!git_index__find( + &position, index, entry->path, GIT_IDXENTRY_STAGE(entry))) { existing = (git_index_entry **)&index->entries.contents[position]; /* update filemode to existing values if stat is not trusted */ @@ -831,7 +830,7 @@ int git_index_remove(git_index *index, const char *path, int stage) git_vector_sort(&index->entries); - if (index_find(&position, index, path, stage) < 0) { + if (git_index__find(&position, index, path, stage) < 0) { giterr_set(GITERR_INDEX, "Index does not contain %s at stage %d", path, stage); return GIT_ENOTFOUND; @@ -887,7 +886,8 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage) return error; } -static int index_find(size_t *at_pos, git_index *index, const char *path, int stage) +int git_index__find( + size_t *at_pos, git_index *index, const char *path, int stage) { struct entry_srch_key srch_key; @@ -896,7 +896,8 @@ static int index_find(size_t *at_pos, git_index *index, const char *path, int st srch_key.path = path; srch_key.stage = stage; - return git_vector_bsearch2(at_pos, &index->entries, index->entries_search, &srch_key); + return git_vector_bsearch2( + at_pos, &index->entries, index->entries_search, &srch_key); } int git_index_find(size_t *at_pos, git_index *index, const char *path) @@ -2053,7 +2054,7 @@ int git_index_add_all( git_iterator *wditer = NULL; const git_index_entry *wd = NULL; git_index_entry *entry; - git_pathspec_context ps; + git_pathspec ps; const char *match; size_t existing; bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0; @@ -2074,7 +2075,7 @@ int git_index_add_all( if (git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE) < 0) return -1; - if ((error = git_pathspec_context_init(&ps, paths)) < 0) + if ((error = git_pathspec__init(&ps, paths)) < 0) return error; /* optionally check that pathspec doesn't mention any ignored files */ @@ -2091,14 +2092,14 @@ int git_index_add_all( while (!(error = git_iterator_advance(&wd, wditer))) { /* check if path actually matches */ - if (!git_pathspec_match_path( - &ps.pathspec, wd->path, no_fnmatch, ignorecase, &match)) + if (!git_pathspec__match( + &ps.pathspec, wd->path, no_fnmatch, ignorecase, &match, NULL)) continue; /* skip ignored items that are not already in the index */ if ((flags & GIT_INDEX_ADD_FORCE) == 0 && git_iterator_current_is_ignored(wditer) && - index_find(&existing, index, wd->path, 0) < 0) + git_index__find(&existing, index, wd->path, 0) < 0) continue; /* issue notification callback if requested */ @@ -2148,7 +2149,7 @@ int git_index_add_all( cleanup: git_iterator_free(wditer); - git_pathspec_context_free(&ps); + git_pathspec__clear(&ps); return error; } @@ -2168,13 +2169,13 @@ static int index_apply_to_all( { int error = 0; size_t i; - git_pathspec_context ps; + git_pathspec ps; const char *match; git_buf path = GIT_BUF_INIT; assert(index); - if ((error = git_pathspec_context_init(&ps, paths)) < 0) + if ((error = git_pathspec__init(&ps, paths)) < 0) return error; git_vector_sort(&index->entries); @@ -2183,8 +2184,9 @@ static int index_apply_to_all( git_index_entry *entry = git_vector_get(&index->entries, i); /* check if path actually matches */ - if (!git_pathspec_match_path( - &ps.pathspec, entry->path, false, index->ignore_case, &match)) + if (!git_pathspec__match( + &ps.pathspec, entry->path, false, index->ignore_case, + &match, NULL)) continue; /* issue notification callback if requested */ @@ -2231,7 +2233,7 @@ static int index_apply_to_all( } git_buf_free(&path); - git_pathspec_context_free(&ps); + git_pathspec__clear(&ps); return error; } diff --git a/src/index.h b/src/index.h index a59107a7b..40577e105 100644 --- a/src/index.h +++ b/src/index.h @@ -47,13 +47,17 @@ struct git_index_conflict_iterator { size_t cur; }; -extern void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st); +extern void git_index_entry__init_from_stat( + git_index_entry *entry, struct stat *st); extern size_t git_index__prefix_position(git_index *index, const char *path); extern int git_index_entry__cmp(const void *a, const void *b); extern int git_index_entry__cmp_icase(const void *a, const void *b); +extern int git_index__find( + size_t *at_pos, git_index *index, const char *path, int stage); + extern void git_index__set_ignore_case(git_index *index, bool ignore_case); #endif diff --git a/src/pathspec.c b/src/pathspec.c index f029836d0..35421dbef 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -5,9 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "git2/pathspec.h" #include "pathspec.h" #include "buf_text.h" #include "attr_file.h" +#include "iterator.h" +#include "repository.h" +#include "index.h" /* what is the common non-wildcard prefix for all items in the pathspec */ char *git_pathspec_prefix(const git_strarray *pathspec) @@ -56,7 +60,7 @@ bool git_pathspec_is_empty(const git_strarray *pathspec) } /* build a vector of fnmatch patterns to evaluate efficiently */ -int git_pathspec_init( +int git_pathspec__vinit( git_vector *vspec, const git_strarray *strspec, git_pool *strpool) { size_t i; @@ -93,7 +97,7 @@ int git_pathspec_init( } /* free data from the pathspec vector */ -void git_pathspec_free(git_vector *vspec) +void git_pathspec__vfree(git_vector *vspec) { git_attr_fnmatch *match; unsigned int i; @@ -106,60 +110,91 @@ void git_pathspec_free(git_vector *vspec) git_vector_free(vspec); } +struct pathspec_match_context { + int fnmatch_flags; + int (*strcomp)(const char *, const char *); + int (*strncomp)(const char *, const char *, size_t); +}; + +static void pathspec_match_context_init( + struct pathspec_match_context *ctxt, + bool disable_fnmatch, + bool casefold) +{ + if (disable_fnmatch) + ctxt->fnmatch_flags = -1; + else if (casefold) + ctxt->fnmatch_flags = FNM_CASEFOLD; + else + ctxt->fnmatch_flags = 0; + + if (casefold) { + ctxt->strcomp = git__strcasecmp; + ctxt->strncomp = git__strncasecmp; + } else { + ctxt->strcomp = git__strcmp; + ctxt->strncomp = git__strncmp; + } +} + +static int pathspec_match_one( + const git_attr_fnmatch *match, + struct pathspec_match_context *ctxt, + const char *path) +{ + int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH; + + if (result == FNM_NOMATCH) + result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0; + + if (ctxt->fnmatch_flags >= 0 && result == FNM_NOMATCH) + result = p_fnmatch(match->pattern, path, ctxt->fnmatch_flags); + + /* if we didn't match, look for exact dirname prefix match */ + if (result == FNM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && + ctxt->strncomp(path, match->pattern, match->length) == 0 && + path[match->length] == '/') + result = 0; + + if (result == 0) + return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1; + return -1; +} + /* match a path against the vectorized pathspec */ -bool git_pathspec_match_path( - git_vector *vspec, +bool git_pathspec__match( + const git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold, - const char **matched_pathspec) + const char **matched_pathspec, + size_t *matched_at) { size_t i; - git_attr_fnmatch *match; - int fnmatch_flags = 0; - int (*use_strcmp)(const char *, const char *); - int (*use_strncmp)(const char *, const char *, size_t); + const git_attr_fnmatch *match; + struct pathspec_match_context ctxt; if (matched_pathspec) *matched_pathspec = NULL; + if (matched_at) + *matched_at = GIT_PATHSPEC_NOMATCH; if (!vspec || !vspec->length) return true; - if (disable_fnmatch) - fnmatch_flags = -1; - else if (casefold) - fnmatch_flags = FNM_CASEFOLD; - - if (casefold) { - use_strcmp = git__strcasecmp; - use_strncmp = git__strncasecmp; - } else { - use_strcmp = git__strcmp; - use_strncmp = git__strncmp; - } + pathspec_match_context_init(&ctxt, disable_fnmatch, casefold); git_vector_foreach(vspec, i, match) { - int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH; + int result = pathspec_match_one(match, &ctxt, path); - if (result == FNM_NOMATCH) - result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0; - - if (fnmatch_flags >= 0 && result == FNM_NOMATCH) - result = p_fnmatch(match->pattern, path, fnmatch_flags); - - /* if we didn't match, look for exact dirname prefix match */ - if (result == FNM_NOMATCH && - (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && - use_strncmp(path, match->pattern, match->length) == 0 && - path[match->length] == '/') - result = 0; - - if (result == 0) { + if (result >= 0) { if (matched_pathspec) *matched_pathspec = match->pattern; + if (matched_at) + *matched_at = i; - return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; + return (result != 0); } } @@ -167,27 +202,344 @@ bool git_pathspec_match_path( } -int git_pathspec_context_init( - git_pathspec_context *ctxt, const git_strarray *paths) +int git_pathspec__init(git_pathspec *ps, const git_strarray *paths) { int error = 0; - memset(ctxt, 0, sizeof(*ctxt)); + memset(ps, 0, sizeof(*ps)); - ctxt->prefix = git_pathspec_prefix(paths); + ps->prefix = git_pathspec_prefix(paths); - if ((error = git_pool_init(&ctxt->pool, 1, 0)) < 0 || - (error = git_pathspec_init(&ctxt->pathspec, paths, &ctxt->pool)) < 0) - git_pathspec_context_free(ctxt); + if ((error = git_pool_init(&ps->pool, 1, 0)) < 0 || + (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) + git_pathspec__clear(ps); return error; } -void git_pathspec_context_free( - git_pathspec_context *ctxt) +void git_pathspec__clear(git_pathspec *ps) { - git__free(ctxt->prefix); - git_pathspec_free(&ctxt->pathspec); - git_pool_clear(&ctxt->pool); - memset(ctxt, 0, sizeof(*ctxt)); + git__free(ps->prefix); + git_pathspec__vfree(&ps->pathspec); + git_pool_clear(&ps->pool); + memset(ps, 0, sizeof(*ps)); +} + +int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec) +{ + int error = 0; + git_pathspec *ps = git__malloc(sizeof(git_pathspec)); + GITERR_CHECK_ALLOC(ps); + + if ((error = git_pathspec__init(ps, pathspec)) < 0) { + git__free(ps); + return error; + } + + GIT_REFCOUNT_INC(ps); + *out = ps; + return 0; +} + +static void pathspec_free(git_pathspec *ps) +{ + git_pathspec__clear(ps); + git__free(ps); +} + +void git_pathspec_free(git_pathspec *ps) +{ + if (!ps) + return; + GIT_REFCOUNT_DEC(ps, pathspec_free); +} + +int git_pathspec_matches_path( + const git_pathspec *ps, uint32_t flags, const char *path) +{ + bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0; + bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0; + + assert(ps && path); + + return (0 != git_pathspec__match( + &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL)); +} + +static void pathspec_match_free(git_pathspec_match_list *m) +{ + git_pathspec_free(m->pathspec); + m->pathspec = NULL; + + git_array_clear(m->matches); + git_array_clear(m->failures); + git_pool_clear(&m->pool); + git__free(m); +} + +static git_pathspec_match_list *pathspec_match_alloc(git_pathspec *ps) +{ + git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list)); + + if (m != NULL && git_pool_init(&m->pool, 1, 0) < 0) { + pathspec_match_free(m); + m = NULL; + } + + /* need to keep reference to pathspec and increment refcount because + * failures array stores pointers to the pattern strings of the + * pathspec that had no matches + */ + GIT_REFCOUNT_INC(ps); + m->pathspec = ps; + + return m; +} + +GIT_INLINE(void) pathspec_mark_pattern(uint8_t *used, size_t pos, size_t *ct) +{ + if (!used[pos]) { + used[pos] = 1; + (*ct)++; + } +} + +static int pathspec_match_from_iterator( + git_pathspec_match_list **out, + git_iterator *iter, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m; + const git_index_entry *entry = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t pos, used_ct = 0, found_files = 0; + git_index *index = NULL; + uint8_t *used_patterns = NULL; + char **file; + + *out = m = pathspec_match_alloc(ps); + GITERR_CHECK_ALLOC(m); + + if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0) + goto done; + + if (patterns->length > 0) { + used_patterns = git__calloc(patterns->length, sizeof(uint8_t)); + GITERR_CHECK_ALLOC(used_patterns); + } + + if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR && + (error = git_repository_index__weakptr( + &index, git_iterator_owner(iter))) < 0) + goto done; + + pathspec_match_context_init(&ctxt, + (flags & GIT_PATHSPEC_NO_GLOB) != 0, git_iterator_ignore_case(iter)); + + while (!(error = git_iterator_advance(&entry, iter))) { + int result = -1; + + for (pos = 0; pos < patterns->length; ++pos) { + const git_attr_fnmatch *pat = git_vector_get(patterns, pos); + + result = pathspec_match_one(pat, &ctxt, entry->path); + if (result >= 0) + break; + } + + /* no matches for this path */ + if (result < 0) + continue; + + /* if result was a negative pattern match, then don't list file */ + if (!result) { + pathspec_mark_pattern(used_patterns, pos, &used_ct); + continue; + } + + /* check if path is untracked and ignored */ + if (index != NULL && + git_iterator_current_is_ignored(iter) && + git_index__find(NULL, index, entry->path, GIT_INDEX_STAGE_ANY) < 0) + continue; + + /* mark the matched pattern as used */ + pathspec_mark_pattern(used_patterns, pos, &used_ct); + ++found_files; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) { + for (++pos; pos < patterns->length; ++pos) { + const git_attr_fnmatch *pat = git_vector_get(patterns, pos); + if (used_patterns[pos]) + continue; + + if (pathspec_match_one(pat, &ctxt, entry->path) > 0) + pathspec_mark_pattern(used_patterns, pos, &used_ct); + } + } + + /* if only looking at failures, exit early or just continue */ + if (failures_only) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched path into matches array */ + if ((file = git_array_alloc(m->matches)) == NULL || + (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) { + error = -1; + goto done; + } + } + + if (error < 0 && error != GIT_ITEROVER) + goto done; + error = 0; + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length) { + for (pos = 0; pos < patterns->length; ++pos) { + const git_attr_fnmatch *pat = git_vector_get(patterns, pos); + if (used_patterns[pos]) + continue; + + if ((file = git_array_alloc(m->failures)) == NULL || + (*file = git_pool_strdup(&m->pool, pat->pattern)) == NULL) { + error = -1; + goto done; + } + } + } + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) { + giterr_set(GITERR_INVALID, "No matching files were found"); + error = GIT_ENOTFOUND; + } + +done: + git__free(used_patterns); + + if (error < 0) { + pathspec_match_free(m); + *out = NULL; + } + + return error; +} + +static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags) +{ + git_iterator_flag_t f = 0; + + if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0) + f |= GIT_ITERATOR_IGNORE_CASE; + else if ((flags & GIT_PATHSPEC_USE_CASE) != 0) + f |= GIT_ITERATOR_DONT_IGNORE_CASE; + + return f; +} + +int git_pathspec_match_workdir( + git_pathspec_match_list **out, + git_repository *repo, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_iterator *iter; + + assert(out && repo); + + if (!(error = git_iterator_for_workdir( + &iter, repo, pathspec_match_iter_flags(flags), NULL, NULL))) { + + error = pathspec_match_from_iterator(out, iter, flags, ps); + + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_index( + git_pathspec_match_list **out, + git_index *index, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_iterator *iter; + + assert(out && index); + + if (!(error = git_iterator_for_index( + &iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) { + + error = pathspec_match_from_iterator(out, iter, flags, ps); + + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_tree( + git_pathspec_match_list **out, + git_tree *tree, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_iterator *iter; + + assert(out && tree); + + if (!(error = git_iterator_for_tree( + &iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) { + + error = pathspec_match_from_iterator(out, iter, flags, ps); + + git_iterator_free(iter); + } + + return error; +} + +void git_pathspec_match_list_free(git_pathspec_match_list *m) +{ + pathspec_match_free(m); +} + +size_t git_pathspec_match_list_entrycount( + const git_pathspec_match_list *m) +{ + return git_array_size(m->matches); +} + +const char *git_pathspec_match_list_entry( + const git_pathspec_match_list *m, size_t pos) +{ + char **entry = git_array_get(m->matches, pos); + return entry ? *entry : NULL; +} + +size_t git_pathspec_match_list_failed_entrycount( + const git_pathspec_match_list *m) +{ + return git_array_size(m->failures); +} + +const char * git_pathspec_match_list_failed_entry( + const git_pathspec_match_list *m, size_t pos) +{ + char **entry = git_array_get(m->failures, pos); + return entry ? *entry : NULL; } diff --git a/src/pathspec.h b/src/pathspec.h index f6509df4c..e7edfea38 100644 --- a/src/pathspec.h +++ b/src/pathspec.h @@ -8,9 +8,27 @@ #define INCLUDE_pathspec_h__ #include "common.h" +#include #include "buffer.h" #include "vector.h" #include "pool.h" +#include "array.h" + +/* public compiled pathspec */ +struct git_pathspec { + git_refcount rc; + char *prefix; + git_vector pathspec; + git_pool pool; +}; + +/* public interface to pathspec matching */ +struct git_pathspec_match_list { + git_pathspec *pathspec; + git_array_t(char *) matches; + git_array_t(char *) failures; + git_pool pool; +}; /* what is the common non-wildcard prefix for all items in the pathspec */ extern char *git_pathspec_prefix(const git_strarray *pathspec); @@ -19,36 +37,31 @@ extern char *git_pathspec_prefix(const git_strarray *pathspec); extern bool git_pathspec_is_empty(const git_strarray *pathspec); /* build a vector of fnmatch patterns to evaluate efficiently */ -extern int git_pathspec_init( +extern int git_pathspec__vinit( git_vector *vspec, const git_strarray *strspec, git_pool *strpool); /* free data from the pathspec vector */ -extern void git_pathspec_free(git_vector *vspec); +extern void git_pathspec__vfree(git_vector *vspec); + +#define GIT_PATHSPEC_NOMATCH ((size_t)-1) /* * Match a path against the vectorized pathspec. * The matched pathspec is passed back into the `matched_pathspec` parameter, * unless it is passed as NULL by the caller. */ -extern bool git_pathspec_match_path( - git_vector *vspec, +extern bool git_pathspec__match( + const git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold, - const char **matched_pathspec); + const char **matched_pathspec, + size_t *matched_at); /* easy pathspec setup */ -typedef struct { - char *prefix; - git_vector pathspec; - git_pool pool; -} git_pathspec_context; +extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths); -extern int git_pathspec_context_init( - git_pathspec_context *ctxt, const git_strarray *paths); - -extern void git_pathspec_context_free( - git_pathspec_context *ctxt); +extern void git_pathspec__clear(git_pathspec *ps); #endif diff --git a/tests-clar/repo/pathspec.c b/tests-clar/repo/pathspec.c new file mode 100644 index 000000000..334066b67 --- /dev/null +++ b/tests-clar/repo/pathspec.c @@ -0,0 +1,385 @@ +#include "clar_libgit2.h" +#include "git2/pathspec.h" + +static git_repository *g_repo; + +void test_repo_pathspec__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_repo_pathspec__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static char *str0[] = { "*_file", "new_file", "garbage" }; +static char *str1[] = { "*_FILE", "NEW_FILE", "GARBAGE" }; +static char *str2[] = { "staged_*" }; +static char *str3[] = { "!subdir", "*_file", "new_file" }; +static char *str4[] = { "*" }; +static char *str5[] = { "S*" }; + +void test_repo_pathspec__workdir0(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*_file", "new_file", "garbage" } */ + s.strings = str0; s.count = ARRAY_SIZE(str0); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 0)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES | GIT_PATHSPEC_FAILURES_ONLY, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir1(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*_FILE", "NEW_FILE", "GARBAGE" } */ + s.strings = str1; s.count = ARRAY_SIZE(str1); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_IGNORE_CASE, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_USE_CASE, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_fail(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_NO_MATCH_ERROR, ps)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir2(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "staged_*" } */ + s.strings = str2; s.count = ARRAY_SIZE(str2); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_fail(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_NO_MATCH_ERROR, ps)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir3(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "!subdir", "*_file", "new_file" } */ + s.strings = str3; s.count = ARRAY_SIZE(str3); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("new_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7)); + + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir4(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*" } */ + s.strings = str4; s.count = ARRAY_SIZE(str4); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(13, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("è¿™", git_pathspec_match_list_entry(m, 12)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + + +void test_repo_pathspec__index0(void) +{ + git_index *idx; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* { "*_file", "new_file", "garbage" } */ + s.strings = str0; s.count = ARRAY_SIZE(str0); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_index(&m, idx, 0, ps)); + cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s("staged_new_file_deleted_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 7)); + cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 8)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 9)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); + cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); + git_index_free(idx); +} + +void test_repo_pathspec__index1(void) +{ + /* Currently the USE_CASE and IGNORE_CASE flags don't work on the + * index because the index sort order for the index iterator is + * set by the index itself. I think the correct fix is for the + * index not to embed a global sort order but to support traversal + * in either case sensitive or insensitive order in a stateless + * manner. + * + * Anyhow, as it is, there is no point in doing this test. + */ +#if 0 + git_index *idx; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* { "*_FILE", "NEW_FILE", "GARBAGE" } */ + s.strings = str1; s.count = ARRAY_SIZE(str1); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_USE_CASE, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); + git_index_free(idx); +#endif +} + +void test_repo_pathspec__tree0(void) +{ + git_object *tree; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*_file", "new_file", "garbage" } */ + s.strings = str0; s.count = ARRAY_SIZE(str0); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(4, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); + cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); + cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__tree5(void) +{ + git_object *tree; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "S*" } */ + s.strings = str5; s.count = ARRAY_SIZE(str5); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("subdir.txt", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__in_memory(void) +{ + static char *strings[] = { "one", "two*", "!three*", "*four" }; + git_strarray s = { strings, ARRAY_SIZE(strings) }; + git_pathspec *ps; + + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_assert(git_pathspec_matches_path(ps, 0, "one")); + cl_assert(!git_pathspec_matches_path(ps, 0, "ONE")); + cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_IGNORE_CASE, "ONE")); + cl_assert(git_pathspec_matches_path(ps, 0, "two")); + cl_assert(git_pathspec_matches_path(ps, 0, "two.txt")); + cl_assert(!git_pathspec_matches_path(ps, 0, "three.txt")); + cl_assert(git_pathspec_matches_path(ps, 0, "anything.four")); + cl_assert(!git_pathspec_matches_path(ps, 0, "three.four")); + cl_assert(!git_pathspec_matches_path(ps, 0, "nomatch")); + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two")); + cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two*")); + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "anyfour")); + cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "*four")); + + git_pathspec_free(ps); +} From 0d44d3dc84d6996c72d49e6bb8036b9c8edec9e0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 24 Jun 2013 23:21:23 -0700 Subject: [PATCH 089/367] Extending log example code This adds more command line processing to the example version of log. In particular, this adds the funky command line processing that allows an arbitrary series of revisions followed by an arbitrary number of paths and/or glob patterns. The actual logging part still isn't implemented. --- examples/log.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/examples/log.c b/examples/log.c index f92b66996..83e5cbcc0 100644 --- a/examples/log.c +++ b/examples/log.c @@ -32,24 +32,71 @@ static void usage(const char *message, const char *arg) int main(int argc, char *argv[]) { - int i; + int i, j, last_nonoption, force_files = -1; char *a; const char *dir = "."; git_repository *repo; + git_revwalk *walker; + git_revspec revs; git_threads_init(); - for (i = 1; i < argc; ++i) { + for (i = 1, last_nonoption = 1; i < argc; ++i) { a = argv[i]; - if (a[0] != '-') { + if (a[0] != '-' || force_files > 0) { + /* condense args not prefixed with '-' to start of argv */ + if (last_nonoption != i) + argv[last_nonoption] = a; + last_nonoption++; } + else if (!strcmp(a, "--")) + force_files = last_nonoption; /* copy all args as filenames */ else if (!check_str_param(a, "--git-dir=", &dir)) usage("Unknown argument", a); } check(git_repository_open_ext(&repo, dir, 0, NULL), "Could not open repository"); + check(git_revwalk_new(&walker, repo), + "Could not create revision walker"); + + if (force_files < 0) + force_files = last_nonoption; + + for (i = 1; i < force_files; ) { + printf("option '%s'\n", argv[i]); + + if (!git_revparse(&revs, repo, argv[i])) { + char str[GIT_OID_HEXSZ+1]; + + if (revs.from) { + git_oid_tostr(str, sizeof(str), git_object_id(revs.from)); + printf("revwalk from %s\n", str); + } + if (revs.to) { + git_oid_tostr(str, sizeof(str), git_object_id(revs.to)); + printf("revwalk to %s\n", str); + } + + /* push / hide / merge-base in revwalker */ + + ++i; + } else { + /* shift array down */ + for (a = argv[i], j = i + 1; j < force_files; ++j) + argv[j - 1] = argv[j]; + argv[--force_files] = a; + } + } + + if (i == 1) { + /* no revs pushed so push HEAD */ + printf("revwalk HEAD\n"); + } + + for (i = force_files; i < last_nonoption; ++i) + printf("file %s\n", argv[i]); git_repository_free(repo); git_threads_shutdown(); From d0628e2feeaeebf78a6b67fa4ca3fc658aa4744a Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 25 Jun 2013 15:39:13 -0700 Subject: [PATCH 090/367] More progress on log example --- examples/log.c | 220 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 152 insertions(+), 68 deletions(-) diff --git a/examples/log.c b/examples/log.c index 83e5cbcc0..5603d743d 100644 --- a/examples/log.c +++ b/examples/log.c @@ -3,21 +3,15 @@ #include #include -static void check(int error, const char *message) +static void check(int error, const char *message, const char *arg) { - if (error) { + if (!error) + return; + if (arg) + fprintf(stderr, "%s '%s' (%d)\n", message, arg, error); + else fprintf(stderr, "%s (%d)\n", message, error); - exit(1); - } -} - -static int check_str_param(const char *arg, const char *pat, const char **val) -{ - size_t len = strlen(pat); - if (strncmp(arg, pat, len)) - return 0; - *val = (const char *)(arg + len); - return 1; + exit(1); } static void usage(const char *message, const char *arg) @@ -30,75 +24,165 @@ static void usage(const char *message, const char *arg) exit(1); } +struct log_state { + git_repository *repo; + const char *repodir; + git_revwalk *walker; + int hide; + int sorting; +}; + +static void set_sorting(struct log_state *s, unsigned int sort_mode) +{ + if (!s->repo) { + if (!s->repodir) s->repodir = "."; + check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), + "Could not open repository", s->repodir); + } + + if (!s->walker) + check(git_revwalk_new(&s->walker, s->repo), + "Could not create revision walker", NULL); + + if (sort_mode == GIT_SORT_REVERSE) + s->sorting = s->sorting ^ GIT_SORT_REVERSE; + else + s->sorting = sort_mode | (s->sorting & GIT_SORT_REVERSE); + + git_revwalk_sorting(s->walker, s->sorting); +} + +static void push_rev(struct log_state *s, git_object *obj, int hide) +{ + hide = s->hide ^ hide; + + if (!s->walker) + check(git_revwalk_new(&s->walker, s->repo), + "Could not create revision walker", NULL); + + if (!obj) + check(git_revwalk_push_head(s->walker), + "Could not find repository HEAD", NULL); + else if (hide) + check(git_revwalk_hide(s->walker, git_object_id(obj)), + "Reference does not refer to a commit", NULL); + else + check(git_revwalk_push(s->walker, git_object_id(obj)), + "Reference does not refer to a commit", NULL); + + git_object_free(obj); +} + +static int add_revision(struct log_state *s, const char *revstr) +{ + git_revspec revs; + int hide = 0; + + if (!s->repo) { + if (!s->repodir) s->repodir = "."; + check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), + "Could not open repository", s->repodir); + } + + if (!revstr) + push_rev(s, NULL, hide); + else if (*revstr == '^') { + revs.flags = GIT_REVPARSE_SINGLE; + hide = !hide; + if (!git_revparse_single(&revs.from, s->repo, revstr + 1)) + return -1; + } else + if (!git_revparse(&revs, s->repo, revstr)) + return -1; + + if ((revs.flags & GIT_REVPARSE_SINGLE) != 0) + push_rev(s, revs.from, hide); + else { + push_rev(s, revs.to, hide); + + if ((revs.flags & GIT_REVPARSE_MERGE_BASE) != 0) { + git_oid base; + check(git_merge_base(&base, s->repo, + git_object_id(revs.from), git_object_id(revs.to)), + "Could not find merge base", revstr); + check(git_object_lookup(&revs.to, s->repo, &base, GIT_OBJ_COMMIT), + "Could not find merge base commit", NULL); + + push_rev(s, revs.to, hide); + } + + push_rev(s, revs.from, !hide); + } + + return 0; +} + +struct log_options { + int show_diff; + int skip; + int min_parents, max_parents; + git_time_t before; + git_time_t after; + char *author; + char *committer; + +}; + int main(int argc, char *argv[]) { - int i, j, last_nonoption, force_files = -1; + int i, count = 0; char *a; - const char *dir = "."; - git_repository *repo; - git_revwalk *walker; - git_revspec revs; + struct log_state s; + git_strarray paths; + git_oid oid; + git_commit *commit; + char buf[GIT_OID_HEXSZ + 1]; git_threads_init(); - for (i = 1, last_nonoption = 1; i < argc; ++i) { + memset(&s, 0, sizeof(s)); + + for (i = 1; i < argc; ++i) { a = argv[i]; - if (a[0] != '-' || force_files > 0) { - /* condense args not prefixed with '-' to start of argv */ - if (last_nonoption != i) - argv[last_nonoption] = a; - last_nonoption++; - } - else if (!strcmp(a, "--")) - force_files = last_nonoption; /* copy all args as filenames */ - else if (!check_str_param(a, "--git-dir=", &dir)) - usage("Unknown argument", a); - } - - check(git_repository_open_ext(&repo, dir, 0, NULL), - "Could not open repository"); - check(git_revwalk_new(&walker, repo), - "Could not create revision walker"); - - if (force_files < 0) - force_files = last_nonoption; - - for (i = 1; i < force_files; ) { - printf("option '%s'\n", argv[i]); - - if (!git_revparse(&revs, repo, argv[i])) { - char str[GIT_OID_HEXSZ+1]; - - if (revs.from) { - git_oid_tostr(str, sizeof(str), git_object_id(revs.from)); - printf("revwalk from %s\n", str); - } - if (revs.to) { - git_oid_tostr(str, sizeof(str), git_object_id(revs.to)); - printf("revwalk to %s\n", str); - } - - /* push / hide / merge-base in revwalker */ - + if (a[0] != '-') { + if (!add_revision(&s, a)) + ++count; + else /* try failed revision parse as filename */ + break; + } else if (!strcmp(a, "--")) { ++i; - } else { - /* shift array down */ - for (a = argv[i], j = i + 1; j < force_files; ++j) - argv[j - 1] = argv[j]; - argv[--force_files] = a; + break; } + else if (!strcmp(a, "--date-order")) + set_sorting(&s, GIT_SORT_TIME); + else if (!strcmp(a, "--topo-order")) + set_sorting(&s, GIT_SORT_TOPOLOGICAL); + else if (!strcmp(a, "--reverse")) + set_sorting(&s, GIT_SORT_REVERSE); + else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) + s.repodir = a + strlen("--git-dir="); + else + usage("Unsupported argument", a); } - if (i == 1) { - /* no revs pushed so push HEAD */ - printf("revwalk HEAD\n"); + if (!count) + add_revision(&s, NULL); + + paths.strings = &argv[i]; + paths.count = argc - i; + + while (!git_revwalk_next(&oid, s.walker)) { + check(git_commit_lookup(&commit, s.repo, &oid), + "Failed to look up commit", NULL); + git_commit_free(commit); + + git_oid_tostr(buf, sizeof(buf), &oid); + printf("%s\n", buf); } - for (i = force_files; i < last_nonoption; ++i) - printf("file %s\n", argv[i]); - - git_repository_free(repo); + git_revwalk_free(s.walker); + git_repository_free(s.repo); git_threads_shutdown(); return 0; From 8ba0ff69725251fa375520d9c69c8a053725c4b6 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 25 Jun 2013 15:39:44 -0700 Subject: [PATCH 091/367] rev-parse example --- examples/Makefile | 2 +- examples/rev-parse.c | 106 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 examples/rev-parse.c diff --git a/examples/Makefile b/examples/Makefile index 6288906df..95e46f0c6 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -3,7 +3,7 @@ CC = gcc CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers LFLAGS = -L../build -lgit2 -lz -APPS = general showindex diff rev-list cat-file status log +APPS = general showindex diff rev-list cat-file status log rev-parse all: $(APPS) diff --git a/examples/rev-parse.c b/examples/rev-parse.c new file mode 100644 index 000000000..cdbb61e46 --- /dev/null +++ b/examples/rev-parse.c @@ -0,0 +1,106 @@ +#include +#include +#include +#include + +static void check(int error, const char *message, const char *arg) +{ + if (!error) + return; + if (arg) + fprintf(stderr, "%s %s (%d)\n", message, arg, error); + else + fprintf(stderr, "%s(%d)\n", message, error); + exit(1); +} + +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: rev-parse [ --option ] ...\n"); + exit(1); +} + +struct parse_state { + git_repository *repo; + const char *repodir; + int not; +}; + +static int parse_revision(struct parse_state *ps, const char *revstr) +{ + git_revspec rs; + char str[GIT_OID_HEXSZ + 1]; + + if (!ps->repo) { + if (!ps->repodir) + ps->repodir = "."; + check(git_repository_open_ext(&ps->repo, ps->repodir, 0, NULL), + "Could not open repository from", ps->repodir); + } + + check(git_revparse(&rs, ps->repo, revstr), "Could not parse", revstr); + + if ((rs.flags & GIT_REVPARSE_SINGLE) != 0) { + git_oid_tostr(str, sizeof(str), git_object_id(rs.from)); + printf("%s\n", str); + git_object_free(rs.from); + } + else if ((rs.flags & GIT_REVPARSE_RANGE) != 0) { + git_oid_tostr(str, sizeof(str), git_object_id(rs.to)); + printf("%s\n", str); + git_object_free(rs.to); + + if ((rs.flags & GIT_REVPARSE_MERGE_BASE) != 0) { + git_oid base; + check(git_merge_base(&base, ps->repo, + git_object_id(rs.from), git_object_id(rs.to)), + "Could not find merge base", revstr); + + git_oid_tostr(str, sizeof(str), &base); + printf("%s\n", str); + } + + git_oid_tostr(str, sizeof(str), git_object_id(rs.from)); + printf("^%s\n", str); + git_object_free(rs.from); + } + else { + check(0, "Invalid results from git_revparse", revstr); + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + int i; + char *a; + struct parse_state ps; + + git_threads_init(); + + memset(&ps, 0, sizeof(ps)); + + for (i = 1; i < argc; ++i) { + a = argv[i]; + + if (a[0] != '-') { + if (parse_revision(&ps, a) != 0) + break; + } else if (!strcmp(a, "--not")) + ps.not = !ps.not; + else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) + ps.repodir = a + strlen("--git-dir="); + else + usage("Cannot handle argument", a); + } + + git_repository_free(ps.repo); + git_threads_shutdown(); + + return 0; +} From f094f9052fba43707cb5662a362511eeea4c4af5 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 1 Jul 2013 15:41:01 -0700 Subject: [PATCH 092/367] Add raw header access to commit API --- include/git2/commit.h | 8 ++++++++ src/commit.c | 47 ++++++++++++++++++++++++++++++++++--------- src/commit.h | 1 + 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/include/git2/commit.h b/include/git2/commit.h index 544d21d87..fc0551be1 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -129,6 +129,14 @@ GIT_EXTERN(const git_signature *) git_commit_committer(const git_commit *commit) */ GIT_EXTERN(const git_signature *) git_commit_author(const git_commit *commit); +/** + * Get the full raw text of the commit header. + * + * @param commit a previously loaded commit + * @return the header text of the commit + */ +GIT_EXTERN(const char *) git_commit_raw_header(const git_commit *commit); + /** * Get the tree pointed to by a commit. * diff --git a/src/commit.c b/src/commit.c index 1ab9b34f7..cf50c2d37 100644 --- a/src/commit.c +++ b/src/commit.c @@ -41,6 +41,7 @@ void git_commit__free(void *_commit) git_signature_free(commit->author); git_signature_free(commit->committer); + git__free(commit->raw_header); git__free(commit->message); git__free(commit->message_encoding); git__free(commit); @@ -171,11 +172,33 @@ int git_commit_create( int git_commit__parse(void *_commit, git_odb_object *odb_obj) { git_commit *commit = _commit; - const char *buffer = git_odb_object_data(odb_obj); - const char *buffer_end = buffer + git_odb_object_size(odb_obj); + const char *buffer_start = git_odb_object_data(odb_obj), *buffer; + const char *buffer_end = buffer_start + git_odb_object_size(odb_obj); git_oid parent_id; + size_t parent_count = 0, header_len; - if (git_vector_init(&commit->parent_ids, 4, NULL) < 0) + /* find end-of-header (counting parents as we go) */ + for (buffer = buffer_start; buffer < buffer_end; ++buffer) { + if (!strncmp("\n\n", buffer, 2)) { + ++buffer; + break; + } + if (!strncmp("\nparent ", buffer, strlen("\nparent "))) + ++parent_count; + } + + header_len = buffer - buffer_start; + commit->raw_header = git__strndup(buffer_start, header_len); + GITERR_CHECK_ALLOC(commit->raw_header); + + /* point "buffer" to header data */ + buffer = commit->raw_header; + buffer_end = commit->raw_header + header_len; + + if (parent_count < 1) + parent_count = 1; + + if (git_vector_init(&commit->parent_ids, parent_count, NULL) < 0) return -1; if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) @@ -208,8 +231,8 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) return -1; - /* Parse add'l header entries until blank line found */ - while (buffer < buffer_end && *buffer != '\n') { + /* Parse add'l header entries */ + while (buffer < buffer_end) { const char *eoln = buffer; while (eoln < buffer_end && *eoln != '\n') ++eoln; @@ -223,15 +246,18 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) if (eoln < buffer_end && *eoln == '\n') ++eoln; - buffer = eoln; } - /* buffer is now at the end of the header, double-check and move forward into the message */ - if (buffer < buffer_end && *buffer == '\n') - buffer++; + /* point "buffer" to data after header */ + buffer = git_odb_object_data(odb_obj); + buffer_end = buffer + git_odb_object_size(odb_obj); - /* parse commit message */ + buffer += header_len; + if (*buffer == '\n') + ++buffer; + + /* extract commit message */ if (buffer <= buffer_end) { commit->message = git__strndup(buffer, buffer_end - buffer); GITERR_CHECK_ALLOC(commit->message); @@ -255,6 +281,7 @@ GIT_COMMIT_GETTER(const git_signature *, author, commit->author) GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer) GIT_COMMIT_GETTER(const char *, message, commit->message) GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) +GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header) GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length) diff --git a/src/commit.h b/src/commit.h index d0981b125..70d8fc690 100644 --- a/src/commit.h +++ b/src/commit.h @@ -25,6 +25,7 @@ struct git_commit { char *message_encoding; char *message; + char *raw_header; }; void git_commit__free(void *commit); From f44c4fa108ff7e326607812246c3c056c1b901cc Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 1 Jul 2013 15:41:32 -0700 Subject: [PATCH 093/367] Add basic commit formatting to log output --- examples/log.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/examples/log.c b/examples/log.c index 5603d743d..12c1d5666 100644 --- a/examples/log.c +++ b/examples/log.c @@ -117,6 +117,32 @@ static int add_revision(struct log_state *s, const char *revstr) return 0; } +static void print_time(const git_time *intime, const char *prefix) +{ + char sign, out[32]; + struct tm intm; + int offset, hours, minutes; + time_t t; + + offset = intime->offset; + if (offset < 0) { + sign = '-'; + offset = -offset; + } else { + sign = '+'; + } + + hours = offset / 60; + minutes = offset % 60; + + t = (time_t)intime->time + (intime->offset * 60); + + gmtime_r(&t, &intm); + strftime(out, sizeof(out), "%a %b %d %T %Y", &intm); + + printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); +} + struct log_options { int show_diff; int skip; @@ -125,7 +151,6 @@ struct log_options { git_time_t after; char *author; char *committer; - }; int main(int argc, char *argv[]) @@ -173,12 +198,39 @@ int main(int argc, char *argv[]) paths.count = argc - i; while (!git_revwalk_next(&oid, s.walker)) { + const git_signature *sig; + const char *scan, *eol; + check(git_commit_lookup(&commit, s.repo, &oid), "Failed to look up commit", NULL); - git_commit_free(commit); git_oid_tostr(buf, sizeof(buf), &oid); - printf("%s\n", buf); + printf("commit %s\n", buf); + + if ((count = (int)git_commit_parentcount(commit)) > 1) { + printf("Merge:"); + for (i = 0; i < count; ++i) { + git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); + printf(" %s", buf); + } + printf("\n"); + } + + if ((sig = git_commit_author(commit)) != NULL) { + printf("Author: %s <%s>\n", sig->name, sig->email); + print_time(&sig->when, "Date: "); + } + printf("\n"); + + for (scan = git_commit_message(commit); scan && *scan; ) { + for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; + + printf(" %.*s\n", (int)(eol - scan), scan); + scan = *eol ? eol + 1 : NULL; + } + printf("\n"); + + git_commit_free(commit); } git_revwalk_free(s.walker); From 2b3bd8ecd88a403f9d034aa3a4d1e14c5e904255 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 3 Jul 2013 14:53:39 -0700 Subject: [PATCH 094/367] Fix example/log.c minor diffs with git log --- examples/log.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/log.c b/examples/log.c index 12c1d5666..61b789263 100644 --- a/examples/log.c +++ b/examples/log.c @@ -56,9 +56,11 @@ static void push_rev(struct log_state *s, git_object *obj, int hide) { hide = s->hide ^ hide; - if (!s->walker) + if (!s->walker) { check(git_revwalk_new(&s->walker, s->repo), "Could not create revision walker", NULL); + git_revwalk_sorting(s->walker, s->sorting); + } if (!obj) check(git_revwalk_push_head(s->walker), @@ -138,7 +140,7 @@ static void print_time(const git_time *intime, const char *prefix) t = (time_t)intime->time + (intime->offset * 60); gmtime_r(&t, &intm); - strftime(out, sizeof(out), "%a %b %d %T %Y", &intm); + strftime(out, sizeof(out), "%a %b %e %T %Y", &intm); printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); } @@ -166,6 +168,7 @@ int main(int argc, char *argv[]) git_threads_init(); memset(&s, 0, sizeof(s)); + s.sorting = GIT_SORT_TIME; for (i = 1; i < argc; ++i) { a = argv[i]; From 5a169711fa9875bc98c30c49b5e9ea06ebbbcfeb Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 3 Jul 2013 15:08:54 -0700 Subject: [PATCH 095/367] fix bug with order args and no revision --- examples/log.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/log.c b/examples/log.c index 61b789263..f7aabf0b4 100644 --- a/examples/log.c +++ b/examples/log.c @@ -86,9 +86,12 @@ static int add_revision(struct log_state *s, const char *revstr) "Could not open repository", s->repodir); } - if (!revstr) + if (!revstr) { push_rev(s, NULL, hide); - else if (*revstr == '^') { + return 0; + } + + if (*revstr == '^') { revs.flags = GIT_REVPARSE_SINGLE; hide = !hide; if (!git_revparse_single(&revs.from, s->repo, revstr + 1)) From 733c4f3aca212d90459fb21cfbc137f09ff6c951 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 3 Jul 2013 15:12:14 -0700 Subject: [PATCH 096/367] more examples/log.c bug fixing --- examples/log.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/log.c b/examples/log.c index f7aabf0b4..ba411d7a4 100644 --- a/examples/log.c +++ b/examples/log.c @@ -94,11 +94,11 @@ static int add_revision(struct log_state *s, const char *revstr) if (*revstr == '^') { revs.flags = GIT_REVPARSE_SINGLE; hide = !hide; - if (!git_revparse_single(&revs.from, s->repo, revstr + 1)) - return -1; - } else - if (!git_revparse(&revs, s->repo, revstr)) + + if (git_revparse_single(&revs.from, s->repo, revstr + 1) < 0) return -1; + } else if (git_revparse(&revs, s->repo, revstr) < 0) + return -1; if ((revs.flags & GIT_REVPARSE_SINGLE) != 0) push_rev(s, revs.from, hide); From a8b5f116bc39f884c8888adae2fd3f9b96d972c0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 3 Jul 2013 17:00:50 -0700 Subject: [PATCH 097/367] Fix example/log.c pathspec handling of merges This fixes the way the example log program decides if a merge commit should be shown when a pathspec is given. Also makes it easier to use the pathspec API to just check "does a tree match anything in the pathspec" without allocating a match list. --- examples/log.c | 124 ++++++++++++++++++++++++++++++++++++------------- include/git2.h | 1 + src/pathspec.c | 21 +++++---- 3 files changed, 106 insertions(+), 40 deletions(-) diff --git a/examples/log.c b/examples/log.c index ba411d7a4..50e81efad 100644 --- a/examples/log.c +++ b/examples/log.c @@ -158,15 +158,73 @@ struct log_options { char *committer; }; +static void print_commit(git_commit *commit) +{ + char buf[GIT_OID_HEXSZ + 1]; + int i, count; + const git_signature *sig; + const char *scan, *eol; + + git_oid_tostr(buf, sizeof(buf), git_commit_id(commit)); + printf("commit %s\n", buf); + + if ((count = (int)git_commit_parentcount(commit)) > 1) { + printf("Merge:"); + for (i = 0; i < count; ++i) { + git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); + printf(" %s", buf); + } + printf("\n"); + } + + if ((sig = git_commit_author(commit)) != NULL) { + printf("Author: %s <%s>\n", sig->name, sig->email); + print_time(&sig->when, "Date: "); + } + printf("\n"); + + for (scan = git_commit_message(commit); scan && *scan; ) { + for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; + + printf(" %.*s\n", (int)(eol - scan), scan); + scan = *eol ? eol + 1 : NULL; + } + printf("\n"); +} + +static int match_with_parent( + git_commit *commit, int i, git_diff_options *opts) +{ + git_commit *parent; + git_tree *a, *b; + git_diff_list *diff; + int ndeltas; + + check(git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL); + check(git_commit_tree(&a, parent), "Tree for parent", NULL); + check(git_commit_tree(&b, commit), "Tree for commit", NULL); + check(git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts), + "Checking diff between parent and commit", NULL); + + ndeltas = (int)git_diff_num_deltas(diff); + + git_diff_list_free(diff); + git_tree_free(a); + git_tree_free(b); + git_commit_free(parent); + + return ndeltas > 0; +} + int main(int argc, char *argv[]) { - int i, count = 0; + int i, count = 0, parents; char *a; struct log_state s; - git_strarray paths; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_oid oid; git_commit *commit; - char buf[GIT_OID_HEXSZ + 1]; + git_pathspec *ps = NULL; git_threads_init(); @@ -200,45 +258,47 @@ int main(int argc, char *argv[]) if (!count) add_revision(&s, NULL); - paths.strings = &argv[i]; - paths.count = argc - i; - - while (!git_revwalk_next(&oid, s.walker)) { - const git_signature *sig; - const char *scan, *eol; + diffopts.pathspec.strings = &argv[i]; + diffopts.pathspec.count = argc - i; + count = 0; + if (diffopts.pathspec.count > 0) + check(git_pathspec_new(&ps, &diffopts.pathspec), + "Building pathspec", NULL); + for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) { check(git_commit_lookup(&commit, s.repo, &oid), "Failed to look up commit", NULL); - git_oid_tostr(buf, sizeof(buf), &oid); - printf("commit %s\n", buf); + parents = (int)git_commit_parentcount(commit); - if ((count = (int)git_commit_parentcount(commit)) > 1) { - printf("Merge:"); - for (i = 0; i < count; ++i) { - git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); - printf(" %s", buf); + if (diffopts.pathspec.count > 0) { + int unmatched = parents; + + if (parents == 0) { + git_tree *tree; + check(git_commit_tree(&tree, commit), "Get tree", NULL); + if (git_pathspec_match_tree( + NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0) + unmatched = 1; + git_tree_free(tree); + } else if (parents == 1) { + unmatched = match_with_parent(commit, 0, &diffopts) ? 0 : 1; + } else { + for (i = 0; i < parents; ++i) { + if (match_with_parent(commit, i, &diffopts)) + unmatched--; + } } - printf("\n"); + + if (unmatched > 0) + continue; } - if ((sig = git_commit_author(commit)) != NULL) { - printf("Author: %s <%s>\n", sig->name, sig->email); - print_time(&sig->when, "Date: "); - } - printf("\n"); - - for (scan = git_commit_message(commit); scan && *scan; ) { - for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; - - printf(" %.*s\n", (int)(eol - scan), scan); - scan = *eol ? eol + 1 : NULL; - } - printf("\n"); - - git_commit_free(commit); + print_commit(commit); + ++count; } + git_pathspec_free(ps); git_revwalk_free(s.walker); git_repository_free(s.repo); git_threads_shutdown(); diff --git a/include/git2.h b/include/git2.h index 5f9fc4824..e8638a830 100644 --- a/include/git2.h +++ b/include/git2.h @@ -56,5 +56,6 @@ #include "git2/message.h" #include "git2/pack.h" #include "git2/stash.h" +#include "git2/pathspec.h" #endif diff --git a/src/pathspec.c b/src/pathspec.c index 35421dbef..021f38f1c 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -311,7 +311,7 @@ static int pathspec_match_from_iterator( git_pathspec *ps) { int error = 0; - git_pathspec_match_list *m; + git_pathspec_match_list *m = NULL; const git_index_entry *entry = NULL; struct pathspec_match_context ctxt; git_vector *patterns = &ps->pathspec; @@ -322,8 +322,13 @@ static int pathspec_match_from_iterator( uint8_t *used_patterns = NULL; char **file; - *out = m = pathspec_match_alloc(ps); - GITERR_CHECK_ALLOC(m); + if (out) { + *out = m = pathspec_match_alloc(ps); + GITERR_CHECK_ALLOC(m); + } else { + failures_only = true; + find_failures = false; + } if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0) goto done; @@ -385,7 +390,7 @@ static int pathspec_match_from_iterator( } /* if only looking at failures, exit early or just continue */ - if (failures_only) { + if (failures_only || !out) { if (used_ct == patterns->length) break; continue; @@ -429,7 +434,7 @@ done: if (error < 0) { pathspec_match_free(m); - *out = NULL; + if (out) *out = NULL; } return error; @@ -456,7 +461,7 @@ int git_pathspec_match_workdir( int error = 0; git_iterator *iter; - assert(out && repo); + assert(repo); if (!(error = git_iterator_for_workdir( &iter, repo, pathspec_match_iter_flags(flags), NULL, NULL))) { @@ -478,7 +483,7 @@ int git_pathspec_match_index( int error = 0; git_iterator *iter; - assert(out && index); + assert(index); if (!(error = git_iterator_for_index( &iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) { @@ -500,7 +505,7 @@ int git_pathspec_match_tree( int error = 0; git_iterator *iter; - assert(out && tree); + assert(tree); if (!(error = git_iterator_for_tree( &iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) { From bc6f0839ebd5794c99d5b488d431188a162a064d Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 5 Jul 2013 15:22:21 -0700 Subject: [PATCH 098/367] Add a bunch more features to log example --- examples/log.c | 126 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 112 insertions(+), 14 deletions(-) diff --git a/examples/log.c b/examples/log.c index 50e81efad..dbbc42914 100644 --- a/examples/log.c +++ b/examples/log.c @@ -148,16 +148,6 @@ static void print_time(const git_time *intime, const char *prefix) printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); } -struct log_options { - int show_diff; - int skip; - int min_parents, max_parents; - git_time_t before; - git_time_t after; - char *author; - char *committer; -}; - static void print_commit(git_commit *commit) { char buf[GIT_OID_HEXSZ + 1]; @@ -192,6 +182,37 @@ static void print_commit(git_commit *commit) printf("\n"); } +static int print_diff( + const git_diff_delta *delta, + const git_diff_range *range, + char usage, + const char *line, + size_t line_len, + void *data) +{ + (void)delta; (void)range; (void)usage; (void)line_len; (void)data; + fputs(line, stdout); + return 0; +} + +static int match_int(int *value, const char *arg, int allow_negative) +{ + char *found; + *value = (int)strtol(arg, &found, 10); + return (found && *found == '\0' && (allow_negative || *value >= 0)); +} + +static int match_int_arg( + int *value, const char *arg, const char *pfx, int allow_negative) +{ + size_t pfxlen = strlen(pfx); + if (strncmp(arg, pfx, pfxlen) != 0) + return 0; + if (!match_int(value, arg + pfxlen, allow_negative)) + usage("Invalid value after argument", arg); + return 1; +} + static int match_with_parent( git_commit *commit, int i, git_diff_options *opts) { @@ -216,14 +237,25 @@ static int match_with_parent( return ndeltas > 0; } +struct log_options { + int show_diff; + int skip, limit; + int min_parents, max_parents; + git_time_t before; + git_time_t after; + char *author; + char *committer; +}; + int main(int argc, char *argv[]) { - int i, count = 0, parents; + int i, count = 0, printed = 0, parents; char *a; struct log_state s; + struct log_options opt; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_oid oid; - git_commit *commit; + git_commit *commit = NULL; git_pathspec *ps = NULL; git_threads_init(); @@ -231,6 +263,9 @@ int main(int argc, char *argv[]) memset(&s, 0, sizeof(s)); s.sorting = GIT_SORT_TIME; + memset(&opt, 0, sizeof(opt)); + opt.max_parents = -1; + for (i = 1; i < argc; ++i) { a = argv[i]; @@ -251,6 +286,33 @@ int main(int argc, char *argv[]) set_sorting(&s, GIT_SORT_REVERSE); else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) s.repodir = a + strlen("--git-dir="); + else if (match_int_arg(&opt.skip, a, "--skip=", 0)) + /* found valid --skip */; + else if (match_int_arg(&opt.limit, a, "--max-count=", 0)) + /* found valid --max-count */; + else if (a[1] >= '0' && a[1] <= '9') { + if (!match_int(&opt.limit, a + 1, 0)) + usage("Invalid limit on number of commits", a); + } else if (!strcmp(a, "-n")) { + if (i + 1 == argc || !match_int(&opt.limit, argv[i], 0)) + usage("Argument -n not followed by valid count", argv[i]); + else + ++i; + } + else if (!strcmp(a, "--merges")) + opt.min_parents = 2; + else if (!strcmp(a, "--no-merges")) + opt.max_parents = 1; + else if (!strcmp(a, "--no-min-parents")) + opt.min_parents = 0; + else if (!strcmp(a, "--no-max-parents")) + opt.max_parents = -1; + else if (match_int_arg(&opt.max_parents, a, "--max-parents=", 1)) + /* found valid --max-parents */; + else if (match_int_arg(&opt.min_parents, a, "--min-parents=", 0)) + /* found valid --min_parents */; + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) + opt.show_diff = 1; else usage("Unsupported argument", a); } @@ -260,16 +322,21 @@ int main(int argc, char *argv[]) diffopts.pathspec.strings = &argv[i]; diffopts.pathspec.count = argc - i; - count = 0; if (diffopts.pathspec.count > 0) check(git_pathspec_new(&ps, &diffopts.pathspec), "Building pathspec", NULL); + printed = count = 0; + for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) { check(git_commit_lookup(&commit, s.repo, &oid), "Failed to look up commit", NULL); parents = (int)git_commit_parentcount(commit); + if (parents < opt.min_parents) + continue; + if (opt.max_parents > 0 && parents > opt.max_parents) + continue; if (diffopts.pathspec.count > 0) { int unmatched = parents; @@ -294,8 +361,39 @@ int main(int argc, char *argv[]) continue; } + if (count++ < opt.skip) + continue; + if (printed++ >= opt.limit) { + git_commit_free(commit); + break; + } + print_commit(commit); - ++count; + + if (opt.show_diff) { + git_tree *a = NULL, *b = NULL; + git_diff_list *diff = NULL; + + if (parents > 1) + continue; + check(git_commit_tree(&b, commit), "Get tree", NULL); + if (parents == 1) { + git_commit *parent; + check(git_commit_parent(&parent, commit, 0), "Get parent", NULL); + check(git_commit_tree(&a, parent), "Tree for parent", NULL); + git_commit_free(parent); + } + + check(git_diff_tree_to_tree( + &diff, git_commit_owner(commit), a, b, &diffopts), + "Diff commit with parent", NULL); + check(git_diff_print_patch(diff, print_diff, NULL), + "Displaying diff", NULL); + + git_diff_list_free(diff); + git_tree_free(a); + git_tree_free(b); + } } git_pathspec_free(ps); From 9abc78ae6157167682f061b4f73eea51ab7d7342 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sun, 7 Jul 2013 21:56:11 -0700 Subject: [PATCH 099/367] Convert commit->parent_ids to git_array_t This converts the array of parent SHAs from a git_vector where each SHA has to be separately allocated to a git_array_t where all the SHAs can be kept in one block. Since the two collections have almost identical APIs, there isn't much involved in making the change. I did add an API to git_array_t so that it could be allocated at a precise initial size. --- src/array.h | 3 +++ src/commit.c | 29 +++++++---------------------- src/commit.h | 4 ++-- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/array.h b/src/array.h index 2d77c71a0..707570624 100644 --- a/src/array.h +++ b/src/array.h @@ -30,6 +30,9 @@ #define git_array_init(a) \ do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) +#define git_array_init_to_size(a, desired) \ + do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0) + #define git_array_clear(a) \ do { git__free((a).ptr); git_array_init(a); } while (0) diff --git a/src/commit.c b/src/commit.c index cf50c2d37..cc912a7be 100644 --- a/src/commit.c +++ b/src/commit.c @@ -19,24 +19,11 @@ #include -static void clear_parents(git_commit *commit) -{ - size_t i; - - for (i = 0; i < commit->parent_ids.length; ++i) { - git_oid *parent = git_vector_get(&commit->parent_ids, i); - git__free(parent); - } - - git_vector_clear(&commit->parent_ids); -} - void git_commit__free(void *_commit) { git_commit *commit = _commit; - clear_parents(commit); - git_vector_free(&commit->parent_ids); + git_array_clear(commit->parent_ids); git_signature_free(commit->author); git_signature_free(commit->committer); @@ -44,6 +31,7 @@ void git_commit__free(void *_commit) git__free(commit->raw_header); git__free(commit->message); git__free(commit->message_encoding); + git__free(commit); } @@ -198,8 +186,8 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) if (parent_count < 1) parent_count = 1; - if (git_vector_init(&commit->parent_ids, parent_count, NULL) < 0) - return -1; + git_array_init_to_size(commit->parent_ids, parent_count); + GITERR_CHECK_ARRAY(commit->parent_ids); if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) goto bad_buffer; @@ -209,13 +197,10 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) */ while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { - git_oid *new_id = git__malloc(sizeof(git_oid)); + git_oid *new_id = git_array_alloc(commit->parent_ids); GITERR_CHECK_ALLOC(new_id); git_oid_cpy(new_id, &parent_id); - - if (git_vector_insert(&commit->parent_ids, new_id) < 0) - return -1; } commit->author = git__malloc(sizeof(git_signature)); @@ -284,7 +269,7 @@ GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header) GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) -GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length) +GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids)) GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id); int git_commit_tree(git_tree **tree_out, const git_commit *commit) @@ -298,7 +283,7 @@ const git_oid *git_commit_parent_id( { assert(commit); - return git_vector_get(&commit->parent_ids, n); + return git_array_get(commit->parent_ids, n); } int git_commit_parent( diff --git a/src/commit.h b/src/commit.h index 70d8fc690..22fc898a1 100644 --- a/src/commit.h +++ b/src/commit.h @@ -10,14 +10,14 @@ #include "git2/commit.h" #include "tree.h" #include "repository.h" -#include "vector.h" +#include "array.h" #include struct git_commit { git_object object; - git_vector parent_ids; + git_array_t(git_oid) parent_ids; git_oid tree_id; git_signature *author; From 3e96ecf219bd9b84c3a7faec61e818766f60e0d9 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 8 Jul 2013 09:53:24 -0700 Subject: [PATCH 100/367] Improve include/git2/pathspec.h docs --- include/git2/pathspec.h | 75 +++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/include/git2/pathspec.h b/include/git2/pathspec.h index 8122d9927..6d97bb326 100644 --- a/include/git2/pathspec.h +++ b/include/git2/pathspec.h @@ -25,20 +25,22 @@ typedef struct git_pathspec_match_list git_pathspec_match_list; * Options controlling how pathspec match should be executed * * - GIT_PATHSPEC_IGNORE_CASE forces match to ignore case; otherwise - * match will use native case sensitivity of platform + * match will use native case sensitivity of platform filesystem * - GIT_PATHSPEC_USE_CASE forces case sensitive match; otherwise - * match will use native case sensitivity of platform + * match will use native case sensitivity of platform filesystem * - GIT_PATHSPEC_NO_GLOB disables glob patterns and just uses simple * string comparison for matching - * - GIT_PATHSPEC_NO_MATCH_ERROR means the match function will return - * GIT_ENOTFOUND if no matches are found; otherwise it will return 0 - * for success and `git_pathspec_match_list_entrycount` will be 0. - * - GIT_PATHSPEC_FIND_FAILURES only applies to a git_pathspec_match_list; - * it means to check file names against all unmatched patterns so that - * at the end of a match we can identify patterns that did not match any - * files. - * - GIT_PATHSPEC_FAILURES_ONLY only applies to a git_pathspec_match_list; - * it means to only check for mismatches and not record matched paths. + * - GIT_PATHSPEC_NO_MATCH_ERROR means the match functions return error + * code GIT_ENOTFOUND if no matches are found; otherwise no matches is + * still success (return 0) but `git_pathspec_match_list_entrycount` + * will indicate 0 matches. + * - GIT_PATHSPEC_FIND_FAILURES means that the `git_pathspec_match_list` + * should track which patterns matched which files so that at the end of + * the match we can identify patterns that did not match any files. + * - GIT_PATHSPEC_FAILURES_ONLY means that the `git_pathspec_match_list` + * does not need to keep the actual matching filenames. Use this to + * just test if there were any matches at all or in combination with + * GIT_PATHSPEC_FIND_FAILURES to validate a pathspec. */ typedef enum { GIT_PATHSPEC_DEFAULT = 0, @@ -54,7 +56,6 @@ typedef enum { * Compile a pathspec * * @param out Output of the compiled pathspec - * @param flags Combination of git_pathspec_flag_t values * @param pathspec A git_strarray of the paths to match * @return 0 on success, <0 on failure */ @@ -77,7 +78,7 @@ GIT_EXTERN(void) git_pathspec_free(git_pathspec *ps); * fall back on being case sensitive. * * @param ps The compiled pathspec - * @param flags Match flags to influence matching behavior + * @param flags Combination of git_pathspec_flag_t options to control match * @param path The pathname to attempt to match * @return 1 is path matches spec, 0 if it does not */ @@ -87,18 +88,24 @@ GIT_EXTERN(int) git_pathspec_matches_path( /** * Match a pathspec against the working directory of a repository. * - * This returns a `git_patchspec_match` object that contains the list of - * all files matching the given pathspec in the working directory of the - * repository. This handles git ignores (i.e. ignored files will not be + * This matches the pathspec against the current files in the working + * directory of the repository. It is an error to invoke this on a bare + * repo. This handles git ignores (i.e. ignored files will not be * considered to match the `pathspec` unless the file is tracked in the * index). * - * @param out Object with list of matching items + * If `out` is not NULL, this returns a `git_patchspec_match_list`. That + * contains the list of all matched filenames (unless you pass the + * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of + * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES` + * flag). You must call `git_pathspec_match_list_free()` on this object. + * + * @param out Output list of matches; pass NULL to just get return value * @param repo The repository in which to match; bare repo is an error - * @param flags Options to control matching behavior + * @param flags Combination of git_pathspec_flag_t options to control match * @param ps Pathspec to be matched * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and - * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used + * the GIT_PATHSPEC_NO_MATCH_ERROR flag was given */ GIT_EXTERN(int) git_pathspec_match_workdir( git_pathspec_match_list **out, @@ -109,17 +116,22 @@ GIT_EXTERN(int) git_pathspec_match_workdir( /** * Match a pathspec against entries in an index. * - * This returns a `git_patchspec_match` object that contains the list of - * all files matching the given pathspec in the index. + * This matches the pathspec against the files in the repository index. * * NOTE: At the moment, the case sensitivity of this match is controlled * by the current case-sensitivity of the index object itself and the * USE_CASE and IGNORE_CASE flags will have no effect. This behavior will * be corrected in a future release. * - * @param out Object with list of matching items - * @param inex The index in which to match - * @param flags Options to control matching behavior + * If `out` is not NULL, this returns a `git_patchspec_match_list`. That + * contains the list of all matched filenames (unless you pass the + * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of + * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES` + * flag). You must call `git_pathspec_match_list_free()` on this object. + * + * @param out Output list of matches; pass NULL to just get return value + * @param index The index to match against + * @param flags Combination of git_pathspec_flag_t options to control match * @param ps Pathspec to be matched * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used @@ -133,12 +145,17 @@ GIT_EXTERN(int) git_pathspec_match_index( /** * Match a pathspec against files in a tree. * - * This returns a `git_patchspec_match` object that contains the list of - * all files matching the given pathspec in the given tree. + * This matches the pathspec against the files in the given tree. * - * @param out Object with list of matching items - * @param inex The index in which to match - * @param flags Options to control matching behavior + * If `out` is not NULL, this returns a `git_patchspec_match_list`. That + * contains the list of all matched filenames (unless you pass the + * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of + * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES` + * flag). You must call `git_pathspec_match_list_free()` on this object. + * + * @param out Output list of matches; pass NULL to just get return value + * @param tree The root-level tree to match against + * @param flags Combination of git_pathspec_flag_t options to control match * @param ps Pathspec to be matched * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used From 6fc5a58197bf04e1b5c6ca1bdb5765e90d3eb106 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 8 Jul 2013 22:42:02 -0700 Subject: [PATCH 101/367] Basic bit vector This is a simple bit vector object that is not resizable after the initial allocation but can be of arbitrary size. It will keep the bti vector entirely on the stack for vectors 64 bits or less, and will allocate the vector on the heap for larger sizes. The API is uniform regardless of storage location. This is very basic right now and all the APIs are inline functions, but it is useful for storing an array of boolean values. --- src/bitvec.h | 92 ++++++++++++++++++++++++++++++++++++++++ tests-clar/core/bitvec.c | 64 ++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 src/bitvec.h create mode 100644 tests-clar/core/bitvec.c diff --git a/src/bitvec.h b/src/bitvec.h new file mode 100644 index 000000000..a033f534f --- /dev/null +++ b/src/bitvec.h @@ -0,0 +1,92 @@ +/* + * 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_bitvec_h__ +#define INCLUDE_bitvec_h__ + +#include "util.h" + +/* + * This is a silly little fixed length bit vector type that will store + * vectors of 64 bits or less directly in the structure and allocate + * memory for vectors longer than 64 bits. You can use the two versions + * transparently through the API and avoid heap allocation completely when + * using a short bit vector as a result. + */ +typedef struct { + size_t length; + union { + uint8_t *ptr; + uint64_t bits; + } u; +} git_bitvec; + +GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) +{ + if (capacity < 64) { + bv->length = 0; + bv->u.bits = 0; + return 0; + } + + bv->length = (capacity + 7) / 8; + bv->u.ptr = git__calloc(bv->length, 1); + return bv->u.ptr ? 0 : -1; +} + +#define GIT_BITVEC_MASK_INLINE(BIT) (((uint64_t)1) << BIT) + +#define GIT_BITVEC_MASK_BYTE(BIT) (((uint8_t)1) << ((BIT) & 0x07)) +#define GIT_BITVEC_INDEX_BYTE(BIT) ((BIT) >> 3) + +GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) +{ + if (!bv->length) { + assert(bit < 64); + + if (on) + bv->u.bits |= GIT_BITVEC_MASK_INLINE(bit); + else + bv->u.bits &= ~GIT_BITVEC_MASK_INLINE(bit); + } else { + assert(bit < bv->length * 8); + + if (on) + bv->u.ptr[GIT_BITVEC_INDEX_BYTE(bit)] |= GIT_BITVEC_MASK_BYTE(bit); + else + bv->u.ptr[GIT_BITVEC_INDEX_BYTE(bit)] &= ~GIT_BITVEC_MASK_BYTE(bit); + } +} + +GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) +{ + if (!bv->length) { + assert(bit < 64); + return (bv->u.bits & GIT_BITVEC_MASK_INLINE(bit)) != 0; + } else { + assert(bit < bv->length * 8); + return (bv->u.ptr[GIT_BITVEC_INDEX_BYTE(bit)] & + GIT_BITVEC_MASK_BYTE(bit)) != 0; + } +} + +GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) +{ + if (!bv->length) + bv->u.bits = 0; + else + memset(bv->u.ptr, 0, bv->length); +} + +GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) +{ + if (bv->length) { + git__free(bv->u.ptr); + memset(bv, 0, sizeof(*bv)); + } +} + +#endif diff --git a/tests-clar/core/bitvec.c b/tests-clar/core/bitvec.c new file mode 100644 index 000000000..48d7b99f0 --- /dev/null +++ b/tests-clar/core/bitvec.c @@ -0,0 +1,64 @@ +#include "clar_libgit2.h" +#include "bitvec.h" + +#if 0 +static void print_bitvec(git_bitvec *bv) +{ + int b; + + if (!bv->length) { + for (b = 63; b >= 0; --b) + fprintf(stderr, "%d", (bv->u.bits & (1ul << b)) ? 1 : 0); + } else { + for (b = bv->length * 8; b >= 0; --b) + fprintf(stderr, "%d", (bv->u.ptr[b >> 3] & (b & 0x0ff)) ? 1 : 0); + } + fprintf(stderr, "\n"); +} +#endif + +static void set_some_bits(git_bitvec *bv, size_t length) +{ + size_t i; + + for (i = 0; i < length; ++i) { + if (i % 3 == 0 || i % 7 == 0) + git_bitvec_set(bv, i, true); + } +} + +static void check_some_bits(git_bitvec *bv, size_t length) +{ + size_t i; + + for (i = 0; i < length; ++i) + cl_assert_equal_b(i % 3 == 0 || i % 7 == 0, git_bitvec_get(bv, i)); +} + +void test_core_bitvec__0(void) +{ + git_bitvec bv; + + cl_git_pass(git_bitvec_init(&bv, 32)); + set_some_bits(&bv, 16); + check_some_bits(&bv, 16); + git_bitvec_clear(&bv); + set_some_bits(&bv, 32); + check_some_bits(&bv, 32); + git_bitvec_clear(&bv); + set_some_bits(&bv, 64); + check_some_bits(&bv, 64); + git_bitvec_free(&bv); + + cl_git_pass(git_bitvec_init(&bv, 128)); + set_some_bits(&bv, 32); + check_some_bits(&bv, 32); + set_some_bits(&bv, 128); + check_some_bits(&bv, 128); + git_bitvec_free(&bv); + + cl_git_pass(git_bitvec_init(&bv, 4000)); + set_some_bits(&bv, 4000); + check_some_bits(&bv, 4000); + git_bitvec_free(&bv); +} From 2b672d5b646edf94ae315a9f968611ff65508c90 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 8 Jul 2013 22:46:36 -0700 Subject: [PATCH 102/367] Add git_pathspec_match_diff API This adds an additional pathspec API that will match a pathspec against a diff object. This is convenient if you want to handle renames (so you need the whole diff and can't use the pathspec constraint built into the diff API) but still want to tell if the diff had any files that matched the pathspec. When the pathspec is matched against a diff, instead of keeping a list of filenames that matched, instead the API keeps the list of git_diff_deltas that matched and they can be retrieved via a new API git_pathspec_match_list_diff_entry. There are a couple of other minor API extensions here that were mostly for the sake of convenience and to reduce dependencies on knowing the internal data structure between files inside the library. --- include/git2/diff.h | 8 + include/git2/pathspec.h | 41 +++++ src/array.h | 2 + src/diff.c | 10 ++ src/diff.h | 2 + src/pathspec.c | 314 ++++++++++++++++++++++++++++--------- src/pathspec.h | 12 +- tests-clar/diff/pathspec.c | 92 +++++++++++ 8 files changed, 404 insertions(+), 77 deletions(-) create mode 100644 tests-clar/diff/pathspec.c diff --git a/include/git2/diff.h b/include/git2/diff.h index 43029c49c..121c9df5c 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -797,6 +797,14 @@ GIT_EXTERN(size_t) git_diff_num_deltas_of_type( git_diff_list *diff, git_delta_t type); +/** + * Check if deltas are sorted case sensitively or insensitively. + * + * @param diff Diff list to check + * @return 0 if case sensitive, 1 if case is ignored + */ +GIT_EXTERN(int) git_diff_is_sorted_icase(const git_diff_list *diff); + /** * Return the diff delta and patch for an entry in the diff list. * diff --git a/include/git2/pathspec.h b/include/git2/pathspec.h index 6d97bb326..a835f8e52 100644 --- a/include/git2/pathspec.h +++ b/include/git2/pathspec.h @@ -10,6 +10,7 @@ #include "common.h" #include "types.h" #include "strarray.h" +#include "diff.h" /** * Compiled pathspec @@ -166,6 +167,30 @@ GIT_EXTERN(int) git_pathspec_match_tree( uint32_t flags, git_pathspec *ps); +/** + * Match a pathspec against files in a diff list. + * + * This matches the pathspec against the files in the given diff list. + * + * If `out` is not NULL, this returns a `git_patchspec_match_list`. That + * contains the list of all matched filenames (unless you pass the + * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of + * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES` + * flag). You must call `git_pathspec_match_list_free()` on this object. + * + * @param out Output list of matches; pass NULL to just get return value + * @param diff A generated diff list + * @param flags Combination of git_pathspec_flag_t options to control match + * @param ps Pathspec to be matched + * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and + * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used + */ +GIT_EXTERN(int) git_pathspec_match_diff( + git_pathspec_match_list **out, + git_diff_list *diff, + uint32_t flags, + git_pathspec *ps); + /** * Free memory associates with a git_pathspec_match_list * @@ -185,6 +210,9 @@ GIT_EXTERN(size_t) git_pathspec_match_list_entrycount( /** * Get a matching filename by position. * + * This routine cannot be used if the match list was generated by + * `git_pathspec_match_diff`. If so, it will always return NULL. + * * @param m The git_pathspec_match_list object * @param pos The index into the list * @return The filename of the match @@ -192,6 +220,19 @@ GIT_EXTERN(size_t) git_pathspec_match_list_entrycount( GIT_EXTERN(const char *) git_pathspec_match_list_entry( const git_pathspec_match_list *m, size_t pos); +/** + * Get a matching diff delta by position. + * + * This routine can only be used if the match list was generated by + * `git_pathspec_match_diff`. Otherwise it will always return NULL. + * + * @param m The git_pathspec_match_list object + * @param pos The index into the list + * @return The filename of the match + */ +GIT_EXTERN(const git_diff_delta *) git_pathspec_match_list_diff_entry( + const git_pathspec_match_list *m, size_t pos); + /** * Get the number of pathspec items that did not match. * diff --git a/src/array.h b/src/array.h index 707570624..248010425 100644 --- a/src/array.h +++ b/src/array.h @@ -66,4 +66,6 @@ GIT_INLINE(void *) git_array_grow(git_array_generic_t *a, size_t item_size) #define git_array_size(a) (a).size +#define git_array_valid_index(a, i) ((i) < (a).size) + #endif diff --git a/src/diff.c b/src/diff.c index 56232ebf4..cc7be451f 100644 --- a/src/diff.c +++ b/src/diff.c @@ -247,6 +247,11 @@ GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta) return str; } +const char *git_diff_delta__path(const git_diff_delta *delta) +{ + return diff_delta__path(delta); +} + int git_diff_delta__cmp(const void *a, const void *b) { const git_diff_delta *da = a, *db = b; @@ -1235,6 +1240,11 @@ size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type) return count; } +int git_diff_is_sorted_icase(const git_diff_list *diff) +{ + return (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0; +} + int git_diff__paired_foreach( git_diff_list *head2idx, git_diff_list *idx2wd, diff --git a/src/diff.h b/src/diff.h index 6ef03ee7c..d09a130bc 100644 --- a/src/diff.h +++ b/src/diff.h @@ -76,6 +76,8 @@ extern void git_diff_list_addref(git_diff_list *diff); extern int git_diff_delta__cmp(const void *a, const void *b); extern int git_diff_delta__casecmp(const void *a, const void *b); +extern const char *git_diff_delta__path(const git_diff_delta *delta); + extern bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta); diff --git a/src/pathspec.c b/src/pathspec.c index 021f38f1c..625726e0b 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -6,12 +6,15 @@ */ #include "git2/pathspec.h" +#include "git2/diff.h" #include "pathspec.h" #include "buf_text.h" #include "attr_file.h" #include "iterator.h" #include "repository.h" #include "index.h" +#include "bitvec.h" +#include "diff.h" /* what is the common non-wildcard prefix for all items in the pathspec */ char *git_pathspec_prefix(const git_strarray *pathspec) @@ -162,6 +165,28 @@ static int pathspec_match_one( return -1; } +static int git_pathspec__match_at( + size_t *matched_at, + const git_vector *vspec, + struct pathspec_match_context *ctxt, + const char *path0, + const char *path1) +{ + int result = GIT_ENOTFOUND; + size_t i = 0; + const git_attr_fnmatch *match; + + git_vector_foreach(vspec, i, match) { + if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0) + break; + if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0) + break; + } + + *matched_at = i; + return result; +} + /* match a path against the vectorized pathspec */ bool git_pathspec__match( const git_vector *vspec, @@ -171,8 +196,8 @@ bool git_pathspec__match( const char **matched_pathspec, size_t *matched_at) { - size_t i; - const git_attr_fnmatch *match; + int result; + size_t pos; struct pathspec_match_context ctxt; if (matched_pathspec) @@ -185,20 +210,18 @@ bool git_pathspec__match( pathspec_match_context_init(&ctxt, disable_fnmatch, casefold); - git_vector_foreach(vspec, i, match) { - int result = pathspec_match_one(match, &ctxt, path); - - if (result >= 0) { - if (matched_pathspec) - *matched_pathspec = match->pattern; - if (matched_at) - *matched_at = i; - - return (result != 0); + result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL); + if (result >= 0) { + if (matched_pathspec) { + const git_attr_fnmatch *match = git_vector_get(vspec, pos); + *matched_pathspec = match->pattern; } + + if (matched_at) + *matched_at = pos; } - return false; + return (result > 0); } @@ -277,7 +300,8 @@ static void pathspec_match_free(git_pathspec_match_list *m) git__free(m); } -static git_pathspec_match_list *pathspec_match_alloc(git_pathspec *ps) +static git_pathspec_match_list *pathspec_match_alloc( + git_pathspec *ps, int datatype) { git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list)); @@ -292,16 +316,73 @@ static git_pathspec_match_list *pathspec_match_alloc(git_pathspec *ps) */ GIT_REFCOUNT_INC(ps); m->pathspec = ps; + m->datatype = datatype; return m; } -GIT_INLINE(void) pathspec_mark_pattern(uint8_t *used, size_t pos, size_t *ct) +GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos) { - if (!used[pos]) { - used[pos] = 1; - (*ct)++; + if (!git_bitvec_get(used, pos)) { + git_bitvec_set(used, pos, true); + return 1; } + + return 0; +} + +static size_t pathspec_mark_remaining( + git_bitvec *used, + git_vector *patterns, + struct pathspec_match_context *ctxt, + size_t start, + const char *path0, + const char *path1) +{ + size_t count = 0; + + if (path1 == path0) + path1 = NULL; + + for (; start < patterns->length; ++start) { + const git_attr_fnmatch *pat = git_vector_get(patterns, start); + + if (git_bitvec_get(used, start)) + continue; + + if (path0 && pathspec_match_one(pat, ctxt, path0) > 0) + count += pathspec_mark_pattern(used, start); + else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0) + count += pathspec_mark_pattern(used, start); + } + + return count; +} + +static int pathspec_build_failure_array( + git_pathspec_string_array_t *failures, + git_vector *patterns, + git_bitvec *used, + git_pool *pool) +{ + size_t pos; + char **failed; + const git_attr_fnmatch *pat; + + for (pos = 0; pos < patterns->length; ++pos) { + if (git_bitvec_get(used, pos)) + continue; + + if ((failed = git_array_alloc(*failures)) == NULL) + return -1; + + pat = git_vector_get(patterns, pos); + + if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL) + return -1; + } + + return 0; } static int pathspec_match_from_iterator( @@ -315,47 +396,37 @@ static int pathspec_match_from_iterator( const git_index_entry *entry = NULL; struct pathspec_match_context ctxt; git_vector *patterns = &ps->pathspec; - bool find_failures = (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; - bool failures_only = (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; size_t pos, used_ct = 0, found_files = 0; git_index *index = NULL; - uint8_t *used_patterns = NULL; + git_bitvec used_patterns; char **file; + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + if (out) { - *out = m = pathspec_match_alloc(ps); + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS); GITERR_CHECK_ALLOC(m); - } else { - failures_only = true; - find_failures = false; } if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0) goto done; - if (patterns->length > 0) { - used_patterns = git__calloc(patterns->length, sizeof(uint8_t)); - GITERR_CHECK_ALLOC(used_patterns); - } - if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR && (error = git_repository_index__weakptr( &index, git_iterator_owner(iter))) < 0) goto done; - pathspec_match_context_init(&ctxt, - (flags & GIT_PATHSPEC_NO_GLOB) != 0, git_iterator_ignore_case(iter)); + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_iterator_ignore_case(iter)); while (!(error = git_iterator_advance(&entry, iter))) { - int result = -1; - - for (pos = 0; pos < patterns->length; ++pos) { - const git_attr_fnmatch *pat = git_vector_get(patterns, pos); - - result = pathspec_match_one(pat, &ctxt, entry->path); - if (result >= 0) - break; - } + /* search for match with entry->path */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, entry->path, NULL); /* no matches for this path */ if (result < 0) @@ -363,31 +434,24 @@ static int pathspec_match_from_iterator( /* if result was a negative pattern match, then don't list file */ if (!result) { - pathspec_mark_pattern(used_patterns, pos, &used_ct); + used_ct += pathspec_mark_pattern(&used_patterns, pos); continue; } - /* check if path is untracked and ignored */ + /* check if path is ignored and untracked */ if (index != NULL && git_iterator_current_is_ignored(iter) && git_index__find(NULL, index, entry->path, GIT_INDEX_STAGE_ANY) < 0) continue; /* mark the matched pattern as used */ - pathspec_mark_pattern(used_patterns, pos, &used_ct); + used_ct += pathspec_mark_pattern(&used_patterns, pos); ++found_files; /* if find_failures is on, check if any later patterns also match */ - if (find_failures && used_ct < patterns->length) { - for (++pos; pos < patterns->length; ++pos) { - const git_attr_fnmatch *pat = git_vector_get(patterns, pos); - if (used_patterns[pos]) - continue; - - if (pathspec_match_one(pat, &ctxt, entry->path) > 0) - pathspec_mark_pattern(used_patterns, pos, &used_ct); - } - } + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL); /* if only looking at failures, exit early or just continue */ if (failures_only || !out) { @@ -397,7 +461,7 @@ static int pathspec_match_from_iterator( } /* insert matched path into matches array */ - if ((file = git_array_alloc(m->matches)) == NULL || + if ((file = (char **)git_array_alloc(m->matches)) == NULL || (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) { error = -1; goto done; @@ -409,19 +473,10 @@ static int pathspec_match_from_iterator( error = 0; /* insert patterns that had no matches into failures array */ - if (find_failures && used_ct < patterns->length) { - for (pos = 0; pos < patterns->length; ++pos) { - const git_attr_fnmatch *pat = git_vector_get(patterns, pos); - if (used_patterns[pos]) - continue; - - if ((file = git_array_alloc(m->failures)) == NULL || - (*file = git_pool_strdup(&m->pool, pat->pattern)) == NULL) { - error = -1; - goto done; - } - } - } + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; /* if every pattern failed to match, then we have failed */ if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) { @@ -430,7 +485,7 @@ static int pathspec_match_from_iterator( } done: - git__free(used_patterns); + git_bitvec_free(&used_patterns); if (error < 0) { pathspec_match_free(m); @@ -518,33 +573,142 @@ int git_pathspec_match_tree( return error; } +int git_pathspec_match_diff( + git_pathspec_match_list **out, + git_diff_list *diff, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t i, pos, used_ct = 0, found_deltas = 0; + const git_diff_delta *delta, **match; + git_bitvec used_patterns; + + assert(diff); + + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + + if (out) { + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF); + GITERR_CHECK_ALLOC(m); + } + + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_diff_is_sorted_icase(diff)); + + git_vector_foreach(&diff->deltas, i, delta) { + /* search for match with delta */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path); + + /* no matches for this path */ + if (result < 0) + continue; + + /* mark the matched pattern as used */ + used_ct += pathspec_mark_pattern(&used_patterns, pos); + + /* if result was a negative pattern match, then don't list file */ + if (!result) + continue; + + ++found_deltas; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, + delta->old_file.path, delta->new_file.path); + + /* if only looking at failures, exit early or just continue */ + if (failures_only || !out) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched delta into matches array */ + if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) { + error = -1; + goto done; + } else { + *match = delta; + } + } + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) { + giterr_set(GITERR_INVALID, "No matching deltas were found"); + error = GIT_ENOTFOUND; + } + +done: + git_bitvec_free(&used_patterns); + + if (error < 0) { + pathspec_match_free(m); + if (out) *out = NULL; + } + + return error; +} + void git_pathspec_match_list_free(git_pathspec_match_list *m) { - pathspec_match_free(m); + if (m) + pathspec_match_free(m); } size_t git_pathspec_match_list_entrycount( const git_pathspec_match_list *m) { - return git_array_size(m->matches); + return m ? git_array_size(m->matches) : 0; } const char *git_pathspec_match_list_entry( const git_pathspec_match_list *m, size_t pos) { - char **entry = git_array_get(m->matches, pos); - return entry ? *entry : NULL; + if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const char **)git_array_get(m->matches, pos)); +} + +const git_diff_delta *git_pathspec_match_list_diff_entry( + const git_pathspec_match_list *m, size_t pos) +{ + if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const git_diff_delta **)git_array_get(m->matches, pos)); } size_t git_pathspec_match_list_failed_entrycount( const git_pathspec_match_list *m) { - return git_array_size(m->failures); + return m ? git_array_size(m->failures) : 0; } const char * git_pathspec_match_list_failed_entry( const git_pathspec_match_list *m, size_t pos) { - char **entry = git_array_get(m->failures, pos); + char **entry = m ? git_array_get(m->failures, pos) : NULL; + return entry ? *entry : NULL; } + diff --git a/src/pathspec.h b/src/pathspec.h index e7edfea38..40cd21c3f 100644 --- a/src/pathspec.h +++ b/src/pathspec.h @@ -22,12 +22,20 @@ struct git_pathspec { git_pool pool; }; +enum { + PATHSPEC_DATATYPE_STRINGS = 0, + PATHSPEC_DATATYPE_DIFF = 1, +}; + +typedef git_array_t(char *) git_pathspec_string_array_t; + /* public interface to pathspec matching */ struct git_pathspec_match_list { git_pathspec *pathspec; - git_array_t(char *) matches; - git_array_t(char *) failures; + git_array_t(void *) matches; + git_pathspec_string_array_t failures; git_pool pool; + int datatype; }; /* what is the common non-wildcard prefix for all items in the pathspec */ diff --git a/tests-clar/diff/pathspec.c b/tests-clar/diff/pathspec.c new file mode 100644 index 000000000..332b513b3 --- /dev/null +++ b/tests-clar/diff/pathspec.c @@ -0,0 +1,92 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_pathspec__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_diff_pathspec__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_pathspec__0(void) +{ + const char *a_commit = "26a125ee"; /* the current HEAD */ + const char *b_commit = "0017bd4a"; /* the start */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff = NULL; + git_strarray paths = { NULL, 1 }; + char *path; + git_pathspec *ps; + git_pathspec_match_list *matches; + + cl_assert(a); + cl_assert(b); + + path = "*_file"; + paths.strings = &path; + cl_git_pass(git_pathspec_new(&ps, &paths)); + + cl_git_pass(git_pathspec_match_tree(&matches, a, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(7, git_pathspec_match_list_entrycount(matches)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(matches,0)); + cl_assert(git_pathspec_match_list_diff_entry(matches,0) == NULL); + git_pathspec_match_list_free(matches); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, NULL, a, &opts)); + + cl_git_pass(git_pathspec_match_diff( + &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(7, git_pathspec_match_list_entrycount(matches)); + cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); + cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); + cl_assert_equal_s("current_file", + git_pathspec_match_list_diff_entry(matches,0)->new_file.path); + cl_assert_equal_i(GIT_DELTA_ADDED, + git_pathspec_match_list_diff_entry(matches,0)->status); + git_pathspec_match_list_free(matches); + + git_diff_list_free(diff); + diff = NULL; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + + cl_git_pass(git_pathspec_match_diff( + &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(3, git_pathspec_match_list_entrycount(matches)); + cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); + cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); + cl_assert_equal_s("subdir/current_file", + git_pathspec_match_list_diff_entry(matches,0)->new_file.path); + cl_assert_equal_i(GIT_DELTA_DELETED, + git_pathspec_match_list_diff_entry(matches,0)->status); + git_pathspec_match_list_free(matches); + + git_diff_list_free(diff); + diff = NULL; + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); + + cl_git_pass(git_pathspec_match_diff( + &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(4, git_pathspec_match_list_entrycount(matches)); + cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); + cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); + cl_assert_equal_s("modified_file", + git_pathspec_match_list_diff_entry(matches,0)->new_file.path); + cl_assert_equal_i(GIT_DELTA_MODIFIED, + git_pathspec_match_list_diff_entry(matches,0)->status); + git_pathspec_match_list_free(matches); + + git_diff_list_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); +} From 406dd556e20117b3cc2e5c53410d65314fd14056 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Wed, 10 Jul 2013 21:05:47 +0200 Subject: [PATCH 103/367] bitvec: Simplify the bit vector code --- src/bitvec.h | 61 +++++++++++++++++++--------------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/src/bitvec.h b/src/bitvec.h index a033f534f..fd6f0ccf8 100644 --- a/src/bitvec.h +++ b/src/bitvec.h @@ -19,58 +19,43 @@ typedef struct { size_t length; union { - uint8_t *ptr; + uint64_t *words; uint64_t bits; } u; } git_bitvec; GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) { - if (capacity < 64) { - bv->length = 0; - bv->u.bits = 0; - return 0; + memset(bv, 0x0, sizeof(*bv)); + + if (capacity >= 64) { + bv->length = (capacity / 64) + 1; + bv->u.words = git__calloc(bv->length, sizeof(uint64_t)); + if (!bv->u.words) + return -1; } - bv->length = (capacity + 7) / 8; - bv->u.ptr = git__calloc(bv->length, 1); - return bv->u.ptr ? 0 : -1; + return 0; } -#define GIT_BITVEC_MASK_INLINE(BIT) (((uint64_t)1) << BIT) - -#define GIT_BITVEC_MASK_BYTE(BIT) (((uint8_t)1) << ((BIT) & 0x07)) -#define GIT_BITVEC_INDEX_BYTE(BIT) ((BIT) >> 3) +#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64)) +#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits) GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) { - if (!bv->length) { - assert(bit < 64); + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + uint64_t mask = GIT_BITVEC_MASK(bit); - if (on) - bv->u.bits |= GIT_BITVEC_MASK_INLINE(bit); - else - bv->u.bits &= ~GIT_BITVEC_MASK_INLINE(bit); - } else { - assert(bit < bv->length * 8); - - if (on) - bv->u.ptr[GIT_BITVEC_INDEX_BYTE(bit)] |= GIT_BITVEC_MASK_BYTE(bit); - else - bv->u.ptr[GIT_BITVEC_INDEX_BYTE(bit)] &= ~GIT_BITVEC_MASK_BYTE(bit); - } + if (on) + *word |= mask; + else + *word &= ~mask; } GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) { - if (!bv->length) { - assert(bit < 64); - return (bv->u.bits & GIT_BITVEC_MASK_INLINE(bit)) != 0; - } else { - assert(bit < bv->length * 8); - return (bv->u.ptr[GIT_BITVEC_INDEX_BYTE(bit)] & - GIT_BITVEC_MASK_BYTE(bit)) != 0; - } + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + return (*word & GIT_BITVEC_MASK(bit)) != 0; } GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) @@ -78,15 +63,13 @@ GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) if (!bv->length) bv->u.bits = 0; else - memset(bv->u.ptr, 0, bv->length); + memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t)); } GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) { - if (bv->length) { - git__free(bv->u.ptr); - memset(bv, 0, sizeof(*bv)); - } + if (bv->length) + git__free(bv->u.words); } #endif From 0105b55e8f7e4d8194400b341b6e0425182ff636 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 27 Jun 2013 15:26:31 -0700 Subject: [PATCH 104/367] Add another submodule test of dirty wd --- tests-clar/diff/submodules.c | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index 5de46732b..1c9940cc5 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -121,6 +121,45 @@ void test_diff_submodules__dirty_submodule(void) git_diff_list_free(diff); } +void test_diff_submodules__dirty_submodule_2(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff = NULL; + char *smpath = "testrepo"; + static const char *expected_none[] = { NULL, "" }; + static const char *expected_dirty[] = { + "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */ + "" + }; + + setup_submodules(); + + cl_git_pass(git_submodule_reload_all(g_repo)); + + opts.flags = GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_UNMODIFIED; + opts.pathspec.count = 1; + opts.pathspec.strings = &smpath; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_none); + git_diff_list_free(diff); + + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_list_free(diff); + + cl_git_pass(git_submodule_reload_all(g_repo)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_list_free(diff); +} + void test_diff_submodules__submod2_index_to_wd(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; From 12f8fe0054505d6c3228a5186516f7c5f28d5165 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 27 Jun 2013 15:43:12 -0700 Subject: [PATCH 105/367] More improvements to submodule diff tests This controls for the diff.mnemonicprefix setting so that can't break the tests. Also, this expands one test to emulate an ObjectiveGit test more closely. --- tests-clar/diff/submodules.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index 1c9940cc5..e3c4e66a3 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -86,6 +86,7 @@ void test_diff_submodules__unmodified_submodule(void) opts.flags = GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_INCLUDE_UNMODIFIED; + opts.old_prefix = "a"; opts.new_prefix = "b"; cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); check_diff_patches(diff, expected); @@ -115,6 +116,7 @@ void test_diff_submodules__dirty_submodule(void) opts.flags = GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_INCLUDE_UNMODIFIED; + opts.old_prefix = "a"; opts.new_prefix = "b"; cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); check_diff_patches(diff, expected); @@ -124,9 +126,9 @@ void test_diff_submodules__dirty_submodule(void) void test_diff_submodules__dirty_submodule_2(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff_list *diff = NULL, *diff2 = NULL; char *smpath = "testrepo"; - static const char *expected_none[] = { NULL, "" }; + static const char *expected_none[] = { "" }; static const char *expected_dirty[] = { "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */ "" @@ -136,9 +138,11 @@ void test_diff_submodules__dirty_submodule_2(void) cl_git_pass(git_submodule_reload_all(g_repo)); - opts.flags = GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_INCLUDE_UNMODIFIED; + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_UNTRACKED_CONTENT | + GIT_DIFF_RECURSE_UNTRACKED_DIRS | + GIT_DIFF_DISABLE_PATHSPEC_MATCH; + opts.old_prefix = "a"; opts.new_prefix = "b"; opts.pathspec.count = 1; opts.pathspec.strings = &smpath; @@ -151,6 +155,18 @@ void test_diff_submodules__dirty_submodule_2(void) cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); check_diff_patches(diff, expected_dirty); + + { + git_tree *head; + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + cl_git_pass(git_diff_tree_to_index(&diff2, g_repo, head, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_list_free(diff2); + + check_diff_patches(diff, expected_dirty); + } + git_diff_list_free(diff); cl_git_pass(git_submodule_reload_all(g_repo)); @@ -179,6 +195,7 @@ void test_diff_submodules__submod2_index_to_wd(void) setup_submodules2(); opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); check_diff_patches(diff, expected); @@ -201,6 +218,7 @@ void test_diff_submodules__submod2_head_to_index(void) cl_git_pass(git_repository_head_tree(&head, g_repo)); opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; cl_git_pass(git_diff_tree_to_index(&diff, g_repo, head, NULL, &opts)); check_diff_patches(diff, expected); From 49621a34af7160d4afbf08a5be15e0e4f3a47791 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 27 Jun 2013 15:46:46 -0700 Subject: [PATCH 106/367] Fix memory leak in test --- tests-clar/diff/submodules.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index e3c4e66a3..ffaae3ce4 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -163,6 +163,7 @@ void test_diff_submodules__dirty_submodule_2(void) cl_git_pass(git_diff_tree_to_index(&diff2, g_repo, head, NULL, &opts)); cl_git_pass(git_diff_merge(diff, diff2)); git_diff_list_free(diff2); + git_tree_free(head); check_diff_patches(diff, expected_dirty); } From 3e7d7100e2be8f6c1fe290842ee9a19b854a7046 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 27 Jun 2013 16:12:00 -0700 Subject: [PATCH 107/367] Fix diff test helper to show parent file/line --- tests-clar/diff/submodules.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index ffaae3ce4..c7bdf6d75 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -37,7 +37,8 @@ void test_diff_submodules__cleanup(void) cl_fixture_cleanup("submod2_target"); } -static void check_diff_patches(git_diff_list *diff, const char **expected) +static void check_diff_patches_at_line( + git_diff_list *diff, const char **expected, const char *file, int line) { const git_diff_delta *delta; git_diff_patch *patch = NULL; @@ -48,24 +49,30 @@ static void check_diff_patches(git_diff_list *diff, const char **expected) cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); if (delta->status == GIT_DELTA_UNMODIFIED) { - cl_assert(expected[d] == NULL); + clar__assert(expected[d] == NULL, file, line, "found UNMODIFIED delta where modified was expected", NULL, 1); continue; } if (expected[d] && !strcmp(expected[d], "")) continue; - if (expected[d] && !strcmp(expected[d], "")) - cl_assert(0); + if (expected[d] && !strcmp(expected[d], "")) { + cl_git_pass(git_diff_patch_to_str(&patch_text, patch)); + clar__assert(0, file, line, "expected end of deltas, but found more", patch_text, 1); + } cl_git_pass(git_diff_patch_to_str(&patch_text, patch)); - cl_assert_equal_s(expected[d], patch_text); + clar__assert_equal_s(expected[d], patch_text, file, line, + "expected diff did not match actual diff", 1); git__free(patch_text); } - cl_assert(expected[d] && !strcmp(expected[d], "")); + clar__assert(expected[d] && !strcmp(expected[d], ""), file, line, "found fewer deltas than expected", expected[d], 1); } +#define check_diff_patches(diff, exp) \ + check_diff_patches_at_line(diff, exp, __FILE__, __LINE__) + void test_diff_submodules__unmodified_submodule(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; From 4535f04409a9379ace7cdda0b53a62d3cdf20676 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 27 Jun 2013 16:12:44 -0700 Subject: [PATCH 108/367] More diff submodule tests for cache issues The submodules code caches data about submodules in a way that can cause problems. This adds some tests that try making various modifications to the state of a submodule to see where we can catch out problems in the submodule caching. Right now, I've put in an extra git_submodule_reload_all so that the test will pass, but with that commented out, the test fails. I'm working on fixing the broken version of the test at which point I'll commit the fix and delete the extra reload that makes the test pass. --- tests-clar/diff/submodules.c | 150 +++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index c7bdf6d75..9b77897b7 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -234,3 +234,153 @@ void test_diff_submodules__submod2_head_to_index(void) git_tree_free(head); } + +void test_diff_submodules__invalid_cache(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff = NULL; + git_submodule *sm; + char *smpath = "sm_changed_head"; + git_repository *smrepo; + git_index *smindex; + static const char *expected_baseline[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "" + }; + static const char *expected_unchanged[] = { "" }; + static const char *expected_dirty[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247-dirty\n", + "" + }; + static const char *expected_moved[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..0910a13 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 0910a13dfa2210496f6c590d75bc360dd11b2a1b\n", + "" + }; + static const char *expected_moved_dirty[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..0910a13 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 0910a13dfa2210496f6c590d75bc360dd11b2a1b-dirty\n", + "" + }; + + setup_submodules2(); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + opts.pathspec.count = 1; + opts.pathspec.strings = &smpath; + + /* baseline */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_baseline); + git_diff_list_free(diff); + + /* update index with new HEAD */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); + cl_git_pass(git_submodule_add_to_index(sm, 1)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_list_free(diff); + + /* create untracked file in submodule working directory */ + cl_git_mkfile("submod2/sm_changed_head/new_around_here", "hello"); + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_NONE); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_list_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_UNTRACKED); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_list_free(diff); + + /* modify tracked file in submodule working directory */ + cl_git_append2file( + "submod2/sm_changed_head/file_to_modify", "\nmore stuff\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_list_free(diff); + + cl_git_pass(git_submodule_reload_all(g_repo)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_list_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_list_free(diff); + + /* add file to index in submodule */ + cl_git_pass(git_submodule_open(&smrepo, sm)); + cl_git_pass(git_repository_index(&smindex, smrepo)); + cl_git_pass(git_index_add_bypath(smindex, "file_to_modify")); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_UNTRACKED); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_list_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_list_free(diff); + + /* commit changed index of submodule */ + { + git_object *parent; + git_oid tree_id, commit_id; + git_tree *tree; + git_signature *sig; + + cl_git_pass(git_revparse_single(&parent, smrepo, "HEAD")); + cl_git_pass(git_index_write_tree(&tree_id, smindex)); + cl_git_pass(git_index_write(smindex)); + cl_git_pass(git_tree_lookup(&tree, smrepo, &tree_id)); + cl_git_pass(git_signature_new(&sig, "Sm Test", "sm@tester.test", 1372350000, 480)); + cl_git_pass(git_commit_create_v( + &commit_id, smrepo, "HEAD", sig, sig, NULL, + "Move it", tree, 1, parent)); + git_object_free(parent); + git_tree_free(tree); + git_signature_free(sig); + } + + /* THIS RELOAD SHOULD NOT BE REQUIRED */ + cl_git_pass(git_submodule_reload_all(g_repo)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_moved); + git_diff_list_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_ALL); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_list_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_NONE); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_moved_dirty); + git_diff_list_free(diff); + + p_unlink("submod2/sm_changed_head/new_around_here"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_moved); + git_diff_list_free(diff); + + git_index_free(smindex); + git_repository_free(smrepo); +} From 41f1f9d732a7cdd50d58948224ca0693a68995dc Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 27 Jun 2013 16:52:00 -0700 Subject: [PATCH 109/367] Add API to get path to index file --- include/git2/index.h | 8 ++++++++ src/index.c | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/include/git2/index.h b/include/git2/index.h index 1fb77efa3..b44535601 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -239,6 +239,14 @@ GIT_EXTERN(int) git_index_read(git_index *index); */ GIT_EXTERN(int) git_index_write(git_index *index); +/** + * Get the full path to the index file on disk. + * + * @param index an existing index object + * @return path to index file or NULL for in-memory index + */ +GIT_EXTERN(const char *) git_index_path(git_index *index); + /** * Read a tree into the index file with stats * diff --git a/src/index.c b/src/index.c index 21efd2c63..bd5e192f3 100644 --- a/src/index.c +++ b/src/index.c @@ -516,6 +516,12 @@ int git_index_write(git_index *index) return 0; } +const char * git_index_path(git_index *index) +{ + assert(index); + return index->index_file_path; +} + int git_index_write_tree(git_oid *oid, git_index *index) { git_repository *repo; From e807860fa9b5278932a0ba25f84b4035b0ebda84 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 27 Jun 2013 16:52:38 -0700 Subject: [PATCH 110/367] Add timestamp check to submodule status This is probably not the final form of this change, but this is a preliminary version of checking a timestamp to see if the cached working directory HEAD OID matches the current. Right now, this uses the timestamp on the index and is, like most of our timestamp checking, subject to having only second accuracy. --- src/submodule.c | 42 +++++++++++++++++++++++++++---- src/submodule.h | 48 +++++++++++++++++++++++------------- tests-clar/diff/submodules.c | 13 +++++++--- 3 files changed, 77 insertions(+), 26 deletions(-) diff --git a/src/submodule.c b/src/submodule.c index dcd58d016..e6ed7e33e 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -9,9 +9,7 @@ #include "git2/config.h" #include "git2/sys/config.h" #include "git2/types.h" -#include "git2/repository.h" #include "git2/index.h" -#include "git2/submodule.h" #include "buffer.h" #include "buf_text.h" #include "vector.h" @@ -544,6 +542,15 @@ const git_oid *git_submodule_wd_id(git_submodule *submodule) { assert(submodule); + /* if we know the submodule index timestamp and it has moved, then + * let's reload the working directory information for the submodule + */ + if (submodule->wd_head_path != NULL && + git_futils_filestamp_check( + &submodule->wd_stamp, submodule->wd_head_path)) + submodule->flags &= ~GIT_SUBMODULE_STATUS__WD_OID_VALID; + + /* load unless we think we have a valid oid */ if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { git_repository *subrepo; @@ -702,11 +709,36 @@ int git_submodule_open( /* if we have opened the submodule successfully, let's grab the HEAD OID */ if (!error) { + git_buf buf = GIT_BUF_INIT; + + /* For now, let's just the index timestamp... + * + * git_buf_joinpath(&buf, git_repository_path(*subrepo), GIT_HEAD_FILE); + * if (!git_path_exists(buf.ptr)) { + */ + git_index *index; + if (!git_repository_index__weakptr(&index, *subrepo)) + git_buf_sets(&buf, git_index_path(index)); + else + git_buf_free(&buf); + /* } */ + + if (git_buf_len(&buf) > 0) { + git__free(submodule->wd_head_path); + submodule->wd_head_path = git_buf_detach(&buf); + } + if (!git_reference_name_to_id( - &submodule->wd_oid, *subrepo, GIT_HEAD_FILE)) + &submodule->wd_oid, *subrepo, GIT_HEAD_FILE)) { + submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; - else - giterr_clear(); + + if (submodule->wd_head_path) + git_futils_filestamp_check( + &submodule->wd_stamp, submodule->wd_head_path); + } + + giterr_clear(); } return error; diff --git a/src/submodule.h b/src/submodule.h index ba8e2518e..88d4f97c7 100644 --- a/src/submodule.h +++ b/src/submodule.h @@ -7,6 +7,10 @@ #ifndef INCLUDE_submodule_h__ #define INCLUDE_submodule_h__ +#include "git2/submodule.h" +#include "git2/repository.h" +#include "fileops.h" + /* Notes: * * Submodule information can be in four places: the index, the config files @@ -44,43 +48,53 @@ * an entry for every submodule found in the HEAD and index, and for every * submodule described in .gitmodules. The fields are as follows: * - * - `owner` is the git_repository containing this submodule * - `name` is the name of the submodule from .gitmodules. * - `path` is the path to the submodule from the repo root. It is almost * always the same as `name`. * - `url` is the url for the submodule. - * - `tree_oid` is the SHA1 for the submodule path in the repo HEAD. - * - `index_oid` is the SHA1 for the submodule recorded in the index. - * - `workdir_oid` is the SHA1 for the HEAD of the checked out submodule. * - `update` is a git_submodule_update_t value - see gitmodules(5) update. + * - `update_default` is the update value from the config * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore. + * - `ignore_default` is the ignore value from the config * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules. - * - `refcount` tracks how many hashmap entries there are for this submodule. - * It only comes into play if the name and path of the submodule differ. - * - `flags` is for internal use, tracking where this submodule has been - * found (head, index, config, workdir) and other misc info about it. + * + * - `owner` is the git_repository containing this submodule + * - `flags` after for internal use, tracking where this submodule has been + * found (head, index, config, workdir) and known status info, etc. + * - `head_oid` is the SHA1 for the submodule path in the repo HEAD. + * - `index_oid` is the SHA1 for the submodule recorded in the index. + * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule. + * - `wd_index_path` is the path to the index of the checked out submodule + * - `wd_last_index` is a timestamp of that submodule index so we can + * quickly check if the `wd_oid` should be rechecked + * - `refcount` tracks how many hash table entries in the + * git_submodule_cache there are for this submodule. It only comes into + * play if the name and path of the submodule differ. * * If the submodule has been added to .gitmodules but not yet git added, - * then the `index_oid` will be valid and zero. If the submodule has been - * deleted, but the delete has not been committed yet, then the `index_oid` - * will be set, but the `url` will be NULL. + * then the `index_oid` will be zero but still marked valid. If the + * submodule has been deleted, but the delete has not been committed yet, + * then the `index_oid` will be set, but the `url` will be NULL. */ struct git_submodule { - git_repository *owner; + /* information from config */ char *name; char *path; /* important: may point to same string data as "name" */ char *url; - uint32_t flags; - git_oid head_oid; - git_oid index_oid; - git_oid wd_oid; - /* information from config */ git_submodule_update_t update; git_submodule_update_t update_default; git_submodule_ignore_t ignore; git_submodule_ignore_t ignore_default; int fetch_recurse; + /* internal information */ + git_repository *owner; + uint32_t flags; + git_oid head_oid; + git_oid index_oid; + git_oid wd_oid; + char *wd_head_path; + git_futils_filestamp wd_stamp; int refcount; }; diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index 9b77897b7..c94fd57c6 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -333,29 +333,34 @@ void test_diff_submodules__invalid_cache(void) check_diff_patches(diff, expected_unchanged); git_diff_list_free(diff); + sleep(2); + /* commit changed index of submodule */ { git_object *parent; git_oid tree_id, commit_id; git_tree *tree; git_signature *sig; + git_reference *ref; - cl_git_pass(git_revparse_single(&parent, smrepo, "HEAD")); + cl_git_pass(git_revparse_ext(&parent, &ref, smrepo, "HEAD")); cl_git_pass(git_index_write_tree(&tree_id, smindex)); cl_git_pass(git_index_write(smindex)); cl_git_pass(git_tree_lookup(&tree, smrepo, &tree_id)); cl_git_pass(git_signature_new(&sig, "Sm Test", "sm@tester.test", 1372350000, 480)); cl_git_pass(git_commit_create_v( - &commit_id, smrepo, "HEAD", sig, sig, NULL, - "Move it", tree, 1, parent)); + &commit_id, smrepo, git_reference_name(ref), sig, sig, + NULL, "Move it", tree, 1, parent)); git_object_free(parent); git_tree_free(tree); + git_reference_free(ref); git_signature_free(sig); } - /* THIS RELOAD SHOULD NOT BE REQUIRED */ + /* THIS RELOAD SHOULD NOT BE REQUIRED cl_git_pass(git_submodule_reload_all(g_repo)); cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); + */ git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY); From 302a04b09ca706eeda9b8cceb50694f37973e348 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sat, 29 Jun 2013 12:41:39 -0700 Subject: [PATCH 111/367] Add accessors for refcount value --- src/thread-utils.h | 5 +++++ src/util.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/thread-utils.h b/src/thread-utils.h index f19a2ba2c..f8c4dc66d 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -43,6 +43,11 @@ GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) a->val = val; } +GIT_INLINE(int) git_atomic_get(git_atomic *a) +{ + return (int)a->val; +} + #ifdef GIT_THREADS #define git_thread pthread_t diff --git a/src/util.h b/src/util.h index e0088399c..a97c9bf39 100644 --- a/src/util.h +++ b/src/util.h @@ -221,6 +221,9 @@ typedef void (*git_refcount_freeptr)(void *r); #define GIT_REFCOUNT_OWNER(r) (((git_refcount *)(r))->owner) +#define GIT_REFCOUNT_VAL(r) git_atomic_get(&((git_refcount *)(r))->refcount) + + static signed char from_hex[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ From 3fe046cfdba414f69b09e76da2a550be96eeab7e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sat, 29 Jun 2013 13:13:38 -0700 Subject: [PATCH 112/367] Add BARE option to git_repository_open_ext This adds a BARE option to git_repository_open_ext which allows a fast open path that still knows how to read gitlinks and to search for the actual .git directory from a subdirectory. `git_repository_open_bare` is still simpler and faster, but having a gitlink aware fast open is very useful for submodules where we want to quickly be able to peek at the HEAD and index data without doing any other meaningful repo operations. --- include/git2/repository.h | 4 +++ src/repository.c | 15 +++++---- tests-clar/repo/open.c | 65 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/include/git2/repository.h b/include/git2/repository.h index c81051969..807d834fe 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -94,10 +94,14 @@ GIT_EXTERN(int) git_repository_discover( * changes from the `stat` system call). (E.g. Searching in a user's home * directory "/home/user/source/" will not return "/.git/" as the found * repo if "/" is a different filesystem than "/home".) + * * GIT_REPOSITORY_OPEN_BARE - Open repository as a bare repo regardless + * of core.bare config, and defer loading config file for faster setup. + * Unlike `git_repository_open_bare`, this can follow gitlinks. */ typedef enum { GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0), GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1), + GIT_REPOSITORY_OPEN_BARE = (1 << 2), } git_repository_open_flag_t; /** diff --git a/src/repository.c b/src/repository.c index ed9469c59..bd7ef5476 100644 --- a/src/repository.c +++ b/src/repository.c @@ -266,7 +266,7 @@ static int find_ceiling_dir_offset( buf[--len] = '\0'; if (!strncmp(path, buf2, len) && - path[len] == '/' && + (path[len] == '/' || !path[len]) && len > max_len) { max_len = len; @@ -322,17 +322,18 @@ static int find_repo( git_buf path = GIT_BUF_INIT; struct stat st; dev_t initial_device = 0; - bool try_with_dot_git = false; + bool try_with_dot_git = ((flags & GIT_REPOSITORY_OPEN_BARE) != 0); int ceiling_offset; git_buf_free(repo_path); - if ((error = git_path_prettify_dir(&path, start_path, NULL)) < 0) + if ((error = git_path_prettify(&path, start_path, NULL)) < 0) return error; ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); - if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0) + if (!try_with_dot_git && + (error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0) return error; while (!error && !git_buf_len(repo_path)) { @@ -384,7 +385,7 @@ static int find_repo( try_with_dot_git = !try_with_dot_git; } - if (!error && parent_path != NULL) { + if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { if (!git_buf_len(repo_path)) git_buf_clear(parent_path); else { @@ -460,7 +461,9 @@ int git_repository_open_ext( repo->path_repository = git_buf_detach(&path); GITERR_CHECK_ALLOC(repo->path_repository); - if ((error = load_config_data(repo)) < 0 || + if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) + repo->is_bare = 1; + else if ((error = load_config_data(repo)) < 0 || (error = load_workdir(repo, &parent)) < 0) { git_repository_free(repo); diff --git a/tests-clar/repo/open.c b/tests-clar/repo/open.c index 840858586..f386612a7 100644 --- a/tests-clar/repo/open.c +++ b/tests-clar/repo/open.c @@ -69,14 +69,23 @@ void test_repo_open__open_with_discover(void) cl_fixture_cleanup("attr"); } +static void make_gitlink_dir(const char *dir, const char *linktext) +{ + git_buf path = GIT_BUF_INIT; + + cl_git_pass(git_futils_mkdir(dir, NULL, 0777, GIT_MKDIR_VERIFY_DIR)); + cl_git_pass(git_buf_joinpath(&path, dir, ".git")); + cl_git_rewritefile(path.ptr, linktext); + git_buf_free(&path); +} + void test_repo_open__gitlinked(void) { /* need to have both repo dir and workdir set up correctly */ git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); git_repository *repo2; - cl_must_pass(p_mkdir("alternate", 0777)); - cl_git_mkfile("alternate/.git", "gitdir: ../empty_standard_repo/.git"); + make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git"); cl_git_pass(git_repository_open(&repo2, "alternate")); @@ -193,12 +202,11 @@ void test_repo_open__bad_gitlinks(void) cl_git_sandbox_init("attr"); - cl_git_pass(p_mkdir("alternate", 0777)); cl_git_pass(p_mkdir("invalid", 0777)); cl_git_pass(git_futils_mkdir_r("invalid2/.git", NULL, 0777)); for (scan = bad_links; *scan != NULL; scan++) { - cl_git_rewritefile("alternate/.git", *scan); + make_gitlink_dir("alternate", *scan); cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); } @@ -315,3 +323,52 @@ void test_repo_open__no_config(void) git_repository_free(repo); cl_fixture_cleanup("empty_standard_repo"); } + +void test_repo_open__force_bare(void) +{ + /* need to have both repo dir and workdir set up correctly */ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_repository *barerepo; + + make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git"); + + cl_assert(!git_repository_is_bare(repo)); + + cl_git_pass(git_repository_open(&barerepo, "alternate")); + cl_assert(!git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_pass(git_repository_open_bare( + &barerepo, "empty_standard_repo/.git")); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_fail(git_repository_open_bare(&barerepo, "alternate/.git")); + + cl_git_pass(git_repository_open_ext( + &barerepo, "alternate/.git", GIT_REPOSITORY_OPEN_BARE, NULL)); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_pass(p_mkdir("empty_standard_repo/subdir", 0777)); + cl_git_mkfile("empty_standard_repo/subdir/something.txt", "something"); + + cl_git_fail(git_repository_open_bare( + &barerepo, "empty_standard_repo/subdir")); + + cl_git_pass(git_repository_open_ext( + &barerepo, "empty_standard_repo/subdir", GIT_REPOSITORY_OPEN_BARE, NULL)); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_pass(p_mkdir("alternate/subdir", 0777)); + cl_git_pass(p_mkdir("alternate/subdir/sub2", 0777)); + cl_git_mkfile("alternate/subdir/sub2/something.txt", "something"); + + cl_git_fail(git_repository_open_bare(&barerepo, "alternate/subdir/sub2")); + + cl_git_pass(git_repository_open_ext( + &barerepo, "alternate/subdir/sub2", GIT_REPOSITORY_OPEN_BARE, NULL)); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); +} From 1aad6137d218830bc280c4327595182019164515 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sat, 29 Jun 2013 13:16:33 -0700 Subject: [PATCH 113/367] Submodule status improvements This fixes the way that submodule status is checked to bypass just about all of the caching in the submodule object. Based on the ignore value, it will try to do the minimum work necessary to find the current status of the submodule - but it will actually go to disk to get all of the current values. This also removes the custom refcounting stuff in favor of the common git_refcount style. Right now, it is still for internal purposes only, but it should make it easier to add true submodule refcounting in the future with a public git_submodule_free call that will allow bindings not to worry about the submodule object getting freed from underneath them. --- include/git2/submodule.h | 16 +- src/submodule.c | 654 +++++++++++++++++++-------------------- src/submodule.h | 38 ++- 3 files changed, 355 insertions(+), 353 deletions(-) diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 91b5300ae..3267bcd0f 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -119,19 +119,9 @@ typedef enum { GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13), } git_submodule_status_t; -#define GIT_SUBMODULE_STATUS__IN_FLAGS \ - (GIT_SUBMODULE_STATUS_IN_HEAD | \ - GIT_SUBMODULE_STATUS_IN_INDEX | \ - GIT_SUBMODULE_STATUS_IN_CONFIG | \ - GIT_SUBMODULE_STATUS_IN_WD) - -#define GIT_SUBMODULE_STATUS__INDEX_FLAGS \ - (GIT_SUBMODULE_STATUS_INDEX_ADDED | \ - GIT_SUBMODULE_STATUS_INDEX_DELETED | \ - GIT_SUBMODULE_STATUS_INDEX_MODIFIED) - -#define GIT_SUBMODULE_STATUS__WD_FLAGS \ - ~(GIT_SUBMODULE_STATUS__IN_FLAGS | GIT_SUBMODULE_STATUS__INDEX_FLAGS) +#define GIT_SUBMODULE_STATUS__IN_FLAGS 0x000Fu +#define GIT_SUBMODULE_STATUS__INDEX_FLAGS 0x0070u +#define GIT_SUBMODULE_STATUS__WD_FLAGS 0x3F80u #define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \ (((S) & ~GIT_SUBMODULE_STATUS__IN_FLAGS) == 0) diff --git a/src/submodule.c b/src/submodule.c index e6ed7e33e..0beedeb42 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -71,15 +71,11 @@ static int load_submodule_config(git_repository *repo); static git_config_backend *open_gitmodules(git_repository *, bool, const git_oid *); static int lookup_head_remote(git_buf *url, git_repository *repo); static int submodule_get(git_submodule **, git_repository *, const char *, const char *); -static void submodule_release(git_submodule *sm, int decr); -static int submodule_load_from_index(git_repository *, const git_index_entry *); -static int submodule_load_from_head(git_repository*, const char*, const git_oid*); static int submodule_load_from_config(const git_config_entry *, void *); static int submodule_load_from_wd_lite(git_submodule *, const char *, void *); static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool); -static void submodule_mode_mismatch(git_repository *, const char *, unsigned int); -static int submodule_index_status(unsigned int *status, git_submodule *sm); -static int submodule_wd_status(unsigned int *status, git_submodule *sm); +static void submodule_get_index_status(unsigned int *, git_submodule *); +static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t); static int submodule_cmp(const void *a, const void *b) { @@ -161,7 +157,7 @@ int git_submodule_foreach( * us from issuing a callback twice for a submodule where the name * and path are not the same. */ - if (sm->refcount > 1) { + if (GIT_REFCOUNT_VAL(sm) > 1) { if (git_vector_bsearch(NULL, &seen, sm) != GIT_ENOTFOUND) continue; if ((error = git_vector_insert(&seen, sm)) < 0) @@ -193,9 +189,7 @@ void git_submodule_config_free(git_repository *repo) if (smcfg == NULL) return; - git_strmap_foreach_value(smcfg, sm, { - submodule_release(sm,1); - }); + git_strmap_foreach_value(smcfg, sm, { git_submodule_free(sm); }); git_strmap_free(smcfg); } @@ -336,7 +330,7 @@ int git_submodule_add_finalize(git_submodule *sm) assert(sm); - if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 || + if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0) return error; @@ -346,7 +340,7 @@ int git_submodule_add_finalize(git_submodule *sm) int git_submodule_add_to_index(git_submodule *sm, int write_index) { int error; - git_repository *repo, *sm_repo = NULL; + git_repository *sm_repo = NULL; git_index *index; git_buf path = GIT_BUF_INIT; git_commit *head; @@ -355,14 +349,12 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index) assert(sm); - repo = sm->owner; - /* force reload of wd OID by git_submodule_open */ sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID; - if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || (error = git_buf_joinpath( - &path, git_repository_workdir(repo), sm->path)) < 0 || + &path, git_repository_workdir(sm->repo), sm->path)) < 0 || (error = git_submodule_open(&sm_repo, sm)) < 0) goto cleanup; @@ -422,7 +414,7 @@ int git_submodule_save(git_submodule *submodule) assert(submodule); - mods = open_gitmodules(submodule->owner, true, NULL); + mods = open_gitmodules(submodule->repo, true, NULL); if (!mods) { giterr_set(GITERR_SUBMODULE, "Adding submodules to a bare repository is not supported (for now)"); @@ -485,7 +477,7 @@ cleanup: git_repository *git_submodule_owner(git_submodule *submodule) { assert(submodule); - return submodule->owner; + return submodule->repo; } const char *git_submodule_name(git_submodule *submodule) @@ -542,20 +534,12 @@ const git_oid *git_submodule_wd_id(git_submodule *submodule) { assert(submodule); - /* if we know the submodule index timestamp and it has moved, then - * let's reload the working directory information for the submodule - */ - if (submodule->wd_head_path != NULL && - git_futils_filestamp_check( - &submodule->wd_stamp, submodule->wd_head_path)) - submodule->flags &= ~GIT_SUBMODULE_STATUS__WD_OID_VALID; - /* load unless we think we have a valid oid */ if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { git_repository *subrepo; /* calling submodule open grabs the HEAD OID if possible */ - if (!git_submodule_open(&subrepo, submodule)) + if (!git_submodule_open_bare(&subrepo, submodule)) git_repository_free(subrepo); else giterr_clear(); @@ -674,76 +658,70 @@ int git_submodule_sync(git_submodule *submodule) submodule, "url", submodule->url, true, true); } -int git_submodule_open( - git_repository **subrepo, - git_submodule *submodule) +static int git_submodule__open( + git_repository **subrepo, git_submodule *sm, bool bare) { int error; git_buf path = GIT_BUF_INIT; - git_repository *repo; - const char *workdir; + unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH; + const char *wd; - assert(submodule && subrepo); + assert(sm && subrepo); - repo = submodule->owner; - workdir = git_repository_workdir(repo); + if (git_repository__ensure_not_bare( + sm->repo, "open submodule repository") < 0) + return GIT_EBAREREPO; - if (!workdir) { - giterr_set(GITERR_REPOSITORY, - "Cannot open submodule repository in a bare repo"); - return GIT_ENOTFOUND; - } + wd = git_repository_workdir(sm->repo); - if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) { - giterr_set(GITERR_REPOSITORY, - "Cannot open submodule repository that is not checked out"); - return GIT_ENOTFOUND; - } - - if (git_buf_joinpath(&path, workdir, submodule->path) < 0) + if (git_buf_joinpath(&path, wd, sm->path) < 0 || + git_buf_joinpath(&path, path.ptr, DOT_GIT) < 0) return -1; - error = git_repository_open(subrepo, path.ptr); + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_SCANNED); + + if (bare) + flags |= GIT_REPOSITORY_OPEN_BARE; + + error = git_repository_open_ext(subrepo, path.ptr, flags, wd); + + /* if we opened the submodule successfully, grab HEAD OID, etc. */ + if (!error) { + sm->flags |= GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_SCANNED; + + if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; + else + giterr_clear(); + } else if (git_path_exists(path.ptr)) { + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED | + GIT_SUBMODULE_STATUS_IN_WD; + } else { + git_buf_rtruncate_at_char(&path, '/'); /* remove "/.git" */ + + if (git_path_isdir(path.ptr)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; + } git_buf_free(&path); - /* if we have opened the submodule successfully, let's grab the HEAD OID */ - if (!error) { - git_buf buf = GIT_BUF_INIT; - - /* For now, let's just the index timestamp... - * - * git_buf_joinpath(&buf, git_repository_path(*subrepo), GIT_HEAD_FILE); - * if (!git_path_exists(buf.ptr)) { - */ - git_index *index; - if (!git_repository_index__weakptr(&index, *subrepo)) - git_buf_sets(&buf, git_index_path(index)); - else - git_buf_free(&buf); - /* } */ - - if (git_buf_len(&buf) > 0) { - git__free(submodule->wd_head_path); - submodule->wd_head_path = git_buf_detach(&buf); - } - - if (!git_reference_name_to_id( - &submodule->wd_oid, *subrepo, GIT_HEAD_FILE)) { - - submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; - - if (submodule->wd_head_path) - git_futils_filestamp_check( - &submodule->wd_stamp, submodule->wd_head_path); - } - - giterr_clear(); - } - return error; } +int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm) +{ + return git_submodule__open(subrepo, sm, true); +} + +int git_submodule_open(git_repository **subrepo, git_submodule *sm) +{ + return git_submodule__open(subrepo, sm, false); +} + int git_submodule_reload_all(git_repository *repo) { assert(repo); @@ -751,74 +729,99 @@ int git_submodule_reload_all(git_repository *repo) return load_submodule_config(repo); } +static void submodule_update_from_index_entry( + git_submodule *sm, const git_index_entry *ie) +{ + bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0; + + if (!S_ISGITLINK(ie->mode)) { + if (!already_found) + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; + } else { + if (already_found) + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; + else + git_oid_cpy(&sm->index_oid, &ie->oid); + + sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID; + } +} + +static int submodule_update_index(git_submodule *sm) +{ + git_index *index; + const git_index_entry *ie; + + if (git_repository_index__weakptr(&index, sm->repo) < 0) + return -1; + + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID); + + if (!(ie = git_index_get_bypath(index, sm->path, 0))) + return 0; + + submodule_update_from_index_entry(sm, ie); + + return 0; +} + +static void submodule_update_from_head_data( + git_submodule *sm, mode_t mode, const git_oid *id) +{ + if (!S_ISGITLINK(mode)) + sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; + else { + git_oid_cpy(&sm->head_oid, id); + + sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID; + } +} + +static int submodule_update_head(git_submodule *submodule) +{ + git_tree *head = NULL; + git_tree_entry *te; + + submodule->flags = submodule->flags & + ~(GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID); + + /* if we can't look up file in current head, then done */ + if (git_repository_head_tree(&head, submodule->repo) < 0 || + git_tree_entry_bypath(&te, head, submodule->path) < 0) + giterr_clear(); + else + submodule_update_from_head_data(submodule, te->attr, &te->oid); + + git_tree_free(head); + return 0; +} + int git_submodule_reload(git_submodule *submodule) { - git_repository *repo; - git_index *index; - int error; - size_t pos; - git_tree *head; + int error = 0; git_config_backend *mods; assert(submodule); /* refresh index data */ - repo = submodule->owner; - if (git_repository_index__weakptr(&index, repo) < 0) + if (submodule_update_index(submodule) < 0) return -1; - submodule->flags = submodule->flags & - ~(GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS__INDEX_OID_VALID); - - if (!git_index_find(&pos, index, submodule->path)) { - const git_index_entry *entry = git_index_get_byindex(index, pos); - - if (S_ISGITLINK(entry->mode)) { - if ((error = submodule_load_from_index(repo, entry)) < 0) - return error; - } else { - submodule_mode_mismatch( - repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE); - } - } - /* refresh HEAD tree data */ - if (!(error = git_repository_head_tree(&head, repo))) { - git_tree_entry *te; - - submodule->flags = submodule->flags & - ~(GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS__HEAD_OID_VALID); - - if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) { - - if (S_ISGITLINK(te->attr)) { - error = submodule_load_from_head(repo, submodule->path, &te->oid); - } else { - submodule_mode_mismatch( - repo, submodule->path, - GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE); - } - - git_tree_entry_free(te); - } - else if (error == GIT_ENOTFOUND) { - giterr_clear(); - error = 0; - } - - git_tree_free(head); - } - - if (error < 0) - return error; + if (submodule_update_head(submodule) < 0) + return -1; /* refresh config data */ - if ((mods = open_gitmodules(repo, false, NULL)) != NULL) { + mods = open_gitmodules(submodule->repo, false, NULL); + if (mods != NULL) { git_buf path = GIT_BUF_INIT; git_buf_sets(&path, "submodule\\."); @@ -829,7 +832,7 @@ int git_submodule_reload(git_submodule *submodule) error = -1; else error = git_config_file_foreach_match( - mods, path.ptr, submodule_load_from_config, repo); + mods, path.ptr, submodule_load_from_config, submodule->repo); git_buf_free(&path); git_config_file_free(mods); @@ -848,40 +851,93 @@ int git_submodule_reload(git_submodule *submodule) return error; } -int git_submodule_status( - unsigned int *status, - git_submodule *submodule) +static void submodule_copy_oid_maybe( + git_oid *tgt, const git_oid *src, bool valid) { - int error = 0; - unsigned int status_val; - - assert(status && submodule); - - status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags); - - if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) { - if (!(error = submodule_index_status(&status_val, submodule))) - error = submodule_wd_status(&status_val, submodule); + if (tgt) { + if (valid) + memcpy(tgt, src, sizeof(*tgt)); + else + memset(tgt, 0, sizeof(*tgt)); } - - *status = status_val; - - return error; } -int git_submodule_location( - unsigned int *location_status, - git_submodule *submodule) +int git_submodule__status( + unsigned int *out_status, + git_oid *out_head_id, + git_oid *out_index_id, + git_oid *out_wd_id, + git_submodule *sm, + git_submodule_ignore_t ign) { - assert(location_status && submodule); + unsigned int status; + git_repository *smrepo = NULL; - *location_status = submodule->flags & - (GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD); + if (ign == GIT_SUBMODULE_IGNORE_DEFAULT) + ign = sm->ignore; + + /* only return location info if ignore == all */ + if (ign == GIT_SUBMODULE_IGNORE_ALL) { + *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS); + return 0; + } + + /* refresh the index OID */ + if (submodule_update_index(sm) < 0) + return -1; + + /* refresh the HEAD OID */ + if (submodule_update_head(sm) < 0) + return -1; + + /* for ignore == dirty, don't scan the working directory */ + if (ign == GIT_SUBMODULE_IGNORE_DIRTY) { + /* git_submodule_open_bare will load WD OID data */ + if (git_submodule_open_bare(&smrepo, sm) < 0) + giterr_clear(); + else + git_repository_free(smrepo); + smrepo = NULL; + } else if (git_submodule_open(&smrepo, sm) < 0) { + giterr_clear(); + smrepo = NULL; + } + + status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags); + + submodule_get_index_status(&status, sm); + submodule_get_wd_status(&status, sm, smrepo, ign); + + git_repository_free(smrepo); + + *out_status = status; + + submodule_copy_oid_maybe(out_head_id, &sm->head_oid, + (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0); + submodule_copy_oid_maybe(out_index_id, &sm->index_oid, + (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0); + submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid, + (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0); return 0; } +int git_submodule_status(unsigned int *status, git_submodule *sm) +{ + assert(status && sm); + + return git_submodule__status( + status, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_DEFAULT); +} + +int git_submodule_location(unsigned int *location, git_submodule *sm) +{ + assert(location && sm); + + return git_submodule__status( + location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL); +} + /* * INTERNAL FUNCTIONS @@ -889,54 +945,47 @@ int git_submodule_location( static git_submodule *submodule_alloc(git_repository *repo, const char *name) { + size_t namelen; git_submodule *sm; - if (!name || !strlen(name)) { + if (!name || !(namelen = strlen(name))) { giterr_set(GITERR_SUBMODULE, "Invalid submodule name"); return NULL; } - sm = git__calloc(1, sizeof(git_submodule)); + sm = git__calloc(1, sizeof(git_submodule) + namelen + 1); if (sm == NULL) - goto fail; + return NULL; - sm->path = sm->name = git__strdup(name); - if (!sm->name) - goto fail; - - sm->owner = repo; - sm->refcount = 1; + sm->name = sm->path = git__strdup(name); + if (!sm->name) { + git__free(sm); + return NULL; + } + GIT_REFCOUNT_INC(sm); + sm->repo = repo; return sm; - -fail: - submodule_release(sm, 0); - return NULL; } -static void submodule_release(git_submodule *sm, int decr) +static void submodule_release(git_submodule *sm) { if (!sm) return; - sm->refcount -= decr; + if (sm->path != sm->name) + git__free(sm->path); + git__free(sm->name); + git__free(sm->url); + git__memzero(sm, sizeof(*sm)); + git__free(sm); +} - if (sm->refcount == 0) { - if (sm->name != sm->path) { - git__free(sm->path); - sm->path = NULL; - } - - git__free(sm->name); - sm->name = NULL; - - git__free(sm->url); - sm->url = NULL; - - sm->owner = NULL; - - git__free(sm); - } +void git_submodule_free(git_submodule *sm) +{ + if (!sm) + return; + GIT_REFCOUNT_DEC(sm, submodule_release); } static int submodule_get( @@ -966,10 +1015,10 @@ static int submodule_get( pos = kh_put(str, smcfg, sm->name, &error); if (error < 0) { - submodule_release(sm, 1); + git_submodule_free(sm); sm = NULL; } else if (error == 0) { - submodule_release(sm, 1); + git_submodule_free(sm); sm = git_strmap_value_at(smcfg, pos); } else { git_strmap_set_value_at(smcfg, pos, sm); @@ -983,43 +1032,6 @@ static int submodule_get( return (sm != NULL) ? 0 : -1; } -static int submodule_load_from_index( - git_repository *repo, const git_index_entry *entry) -{ - git_submodule *sm; - - if (submodule_get(&sm, repo, entry->path, NULL) < 0) - return -1; - - if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) { - sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; - return 0; - } - - sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX; - - git_oid_cpy(&sm->index_oid, &entry->oid); - sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID; - - return 0; -} - -static int submodule_load_from_head( - git_repository *repo, const char *path, const git_oid *oid) -{ - git_submodule *sm; - - if (submodule_get(&sm, repo, path, NULL) < 0) - return -1; - - sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD; - - git_oid_cpy(&sm->head_oid, oid); - sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID; - - return 0; -} - static int submodule_config_error(const char *property, const char *value) { giterr_set(GITERR_INVALID, @@ -1079,11 +1091,11 @@ static int submodule_load_from_config( git_strmap_insert2(smcfg, alternate, sm, old_sm, error); if (error >= 0) - sm->refcount++; /* inserted under a new key */ + GIT_REFCOUNT_INC(sm); /* inserted under a new key */ /* if we replaced an old module under this key, release the old one */ if (old_sm && ((git_submodule *)old_sm) != sm) { - submodule_release(old_sm, 1); + git_submodule_free(old_sm); /* TODO: log warning about multiple submodules with same path */ } } @@ -1133,13 +1145,15 @@ static int submodule_load_from_config( static int submodule_load_from_wd_lite( git_submodule *sm, const char *name, void *payload) { - git_repository *repo = git_submodule_owner(sm); git_buf path = GIT_BUF_INIT; GIT_UNUSED(name); GIT_UNUSED(payload); - if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0) + if (git_repository_is_bare(sm->repo)) + return 0; + + if (git_buf_joinpath(&path, git_repository_workdir(sm->repo), sm->path) < 0) return -1; if (git_path_isdir(path.ptr)) @@ -1153,18 +1167,6 @@ static int submodule_load_from_wd_lite( return 0; } -static void submodule_mode_mismatch( - git_repository *repo, const char *path, unsigned int flag) -{ - khiter_t pos = git_strmap_lookup_index(repo->submodules, path); - - if (git_strmap_valid_index(repo->submodules, pos)) { - git_submodule *sm = git_strmap_value_at(repo->submodules, pos); - - sm->flags |= flag; - } -} - static int load_submodule_config_from_index( git_repository *repo, git_oid *gitmodules_oid) { @@ -1178,18 +1180,21 @@ static int load_submodule_config_from_index( return error; while (!(error = git_iterator_advance(&entry, i))) { + khiter_t pos = git_strmap_lookup_index(repo->submodules, entry->path); + git_submodule *sm; - if (S_ISGITLINK(entry->mode)) { - error = submodule_load_from_index(repo, entry); - if (error < 0) - break; - } else { - submodule_mode_mismatch( - repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE); + if (git_strmap_valid_index(repo->submodules, pos)) { + sm = git_strmap_value_at(repo->submodules, pos); - if (strcmp(entry->path, GIT_MODULES_FILE) == 0) - git_oid_cpy(gitmodules_oid, &entry->oid); - } + if (S_ISGITLINK(entry->mode)) + submodule_update_from_index_entry(sm, entry); + else + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; + } else if (S_ISGITLINK(entry->mode)) { + if (!submodule_get(&sm, repo, entry->path, NULL)) + submodule_update_from_index_entry(sm, entry); + } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0) + git_oid_cpy(gitmodules_oid, &entry->oid); } if (error == GIT_ITEROVER) @@ -1220,18 +1225,24 @@ static int load_submodule_config_from_head( } while (!(error = git_iterator_advance(&entry, i))) { + khiter_t pos = git_strmap_lookup_index(repo->submodules, entry->path); + git_submodule *sm; - if (S_ISGITLINK(entry->mode)) { - error = submodule_load_from_head(repo, entry->path, &entry->oid); - if (error < 0) - break; - } else { - submodule_mode_mismatch( - repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE); + if (git_strmap_valid_index(repo->submodules, pos)) { + sm = git_strmap_value_at(repo->submodules, pos); - if (strcmp(entry->path, GIT_MODULES_FILE) == 0 && - git_oid_iszero(gitmodules_oid)) - git_oid_cpy(gitmodules_oid, &entry->oid); + if (S_ISGITLINK(entry->mode)) + submodule_update_from_head_data( + sm, entry->mode, &entry->oid); + else + sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; + } else if (S_ISGITLINK(entry->mode)) { + if (!submodule_get(&sm, repo, entry->path, NULL)) + submodule_update_from_head_data( + sm, entry->mode, &entry->oid); + } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0 && + git_oid_iszero(gitmodules_oid)) { + git_oid_cpy(gitmodules_oid, &entry->oid); } } @@ -1416,7 +1427,7 @@ static int submodule_update_config( assert(submodule); - error = git_repository_config__weakptr(&config, submodule->owner); + error = git_repository_config__weakptr(&config, submodule->repo); if (error < 0) return error; @@ -1444,11 +1455,13 @@ cleanup: return error; } -static int submodule_index_status(unsigned int *status, git_submodule *sm) +static void submodule_get_index_status(unsigned int *status, git_submodule *sm) { const git_oid *head_oid = git_submodule_head_id(sm); const git_oid *index_oid = git_submodule_index_id(sm); + *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS; + if (!head_oid) { if (index_oid) *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED; @@ -1457,27 +1470,22 @@ static int submodule_index_status(unsigned int *status, git_submodule *sm) *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED; else if (!git_oid_equal(head_oid, index_oid)) *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED; - - return 0; } -static int submodule_wd_status(unsigned int *status, git_submodule *sm) +static void submodule_get_wd_status( + unsigned int *status, + git_submodule *sm, + git_repository *sm_repo, + git_submodule_ignore_t ign) { - int error = 0; - const git_oid *wd_oid, *index_oid; - git_repository *sm_repo = NULL; + const git_oid *index_oid = git_submodule_index_id(sm); + const git_oid *wd_oid = + (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL; + git_tree *sm_head = NULL; + git_diff_options opt = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff; - /* open repo now if we need it (so wd_id() call won't reopen) */ - if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE || - sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) && - (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0) - { - if ((error = git_submodule_open(&sm_repo, sm)) < 0) - return error; - } - - index_oid = git_submodule_index_id(sm); - wd_oid = git_submodule_wd_id(sm); + *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS; if (!index_oid) { if (wd_oid) @@ -1493,59 +1501,49 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm) else if (!git_oid_equal(index_oid, wd_oid)) *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED; - if (sm_repo != NULL) { - git_tree *sm_head; - git_diff_options opt = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff; + /* if we have no repo, then we're done */ + if (!sm_repo) + return; - /* the diffs below could be optimized with an early termination - * option to the git_diff functions, but for now this is sufficient - * (and certainly no worse that what core git does). - */ + /* the diffs below could be optimized with an early termination + * option to the git_diff functions, but for now this is sufficient + * (and certainly no worse that what core git does). + */ - /* perform head-to-index diff on submodule */ + if (ign == GIT_SUBMODULE_IGNORE_NONE) + opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0) - return error; - - if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE) - opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - - error = git_diff_tree_to_index(&diff, sm_repo, sm_head, NULL, &opt); - - if (!error) { + /* if we don't have an orphaned head, check diff with index */ + if (git_repository_head_tree(&sm_head, sm_repo) < 0) + giterr_clear(); + else { + /* perform head to index diff on submodule */ + if (git_diff_tree_to_index(&diff, sm_repo, sm_head, NULL, &opt) < 0) + giterr_clear(); + else { if (git_diff_num_deltas(diff) > 0) *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; - git_diff_list_free(diff); diff = NULL; } git_tree_free(sm_head); - - if (error < 0) - return error; - - /* perform index-to-workdir diff on submodule */ - - error = git_diff_index_to_workdir(&diff, sm_repo, NULL, &opt); - - if (!error) { - size_t untracked = - git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED); - - if (untracked > 0) - *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; - - if (git_diff_num_deltas(diff) != untracked) - *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; - - git_diff_list_free(diff); - diff = NULL; - } - - git_repository_free(sm_repo); } - return error; + /* perform index-to-workdir diff on submodule */ + if (git_diff_index_to_workdir(&diff, sm_repo, NULL, &opt) < 0) + giterr_clear(); + else { + size_t untracked = + git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED); + + if (untracked > 0) + *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; + + if (git_diff_num_deltas(diff) != untracked) + *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; + + git_diff_list_free(diff); + diff = NULL; + } } diff --git a/src/submodule.h b/src/submodule.h index 88d4f97c7..8db2fff29 100644 --- a/src/submodule.h +++ b/src/submodule.h @@ -48,6 +48,10 @@ * an entry for every submodule found in the HEAD and index, and for every * submodule described in .gitmodules. The fields are as follows: * + * - `rc` tracks the refcount of how many hash table entries in the + * git_submodule_cache there are for this submodule. It only comes into + * play if the name and path of the submodule differ. + * * - `name` is the name of the submodule from .gitmodules. * - `path` is the path to the submodule from the repo root. It is almost * always the same as `name`. @@ -58,18 +62,12 @@ * - `ignore_default` is the ignore value from the config * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules. * - * - `owner` is the git_repository containing this submodule + * - `repo` is the parent repository that contains this submodule. * - `flags` after for internal use, tracking where this submodule has been * found (head, index, config, workdir) and known status info, etc. * - `head_oid` is the SHA1 for the submodule path in the repo HEAD. * - `index_oid` is the SHA1 for the submodule recorded in the index. * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule. - * - `wd_index_path` is the path to the index of the checked out submodule - * - `wd_last_index` is a timestamp of that submodule index so we can - * quickly check if the `wd_oid` should be rechecked - * - `refcount` tracks how many hash table entries in the - * git_submodule_cache there are for this submodule. It only comes into - * play if the name and path of the submodule differ. * * If the submodule has been added to .gitmodules but not yet git added, * then the `index_oid` will be zero but still marked valid. If the @@ -77,9 +75,11 @@ * then the `index_oid` will be set, but the `url` will be NULL. */ struct git_submodule { + git_refcount rc; + /* information from config */ char *name; - char *path; /* important: may point to same string data as "name" */ + char *path; /* important: may just point to "name" string */ char *url; git_submodule_update_t update; git_submodule_update_t update_default; @@ -88,14 +88,11 @@ struct git_submodule { int fetch_recurse; /* internal information */ - git_repository *owner; + git_repository *repo; uint32_t flags; git_oid head_oid; git_oid index_oid; git_oid wd_oid; - char *wd_head_path; - git_futils_filestamp wd_stamp; - int refcount; }; /* Additional flags on top of public GIT_SUBMODULE_STATUS values */ @@ -113,4 +110,21 @@ enum { #define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \ ((S) & ~(0xFFFFFFFFu << 20)) +/* Internal status fn returns status and optionally the various OIDs */ +extern int git_submodule__status( + unsigned int *out_status, + git_oid *out_head_id, + git_oid *out_index_id, + git_oid *out_wd_id, + git_submodule *sm, + git_submodule_ignore_t ign); + +/* Open submodule repository as bare repo for quick HEAD check, etc. */ +extern int git_submodule_open_bare( + git_repository **repo, + git_submodule *submodule); + +/* Release reference to submodule object - not currently for external use */ +extern void git_submodule_free(git_submodule *sm); + #endif From 2e3e273e33894bc1089cfc09d89bd2cb144b108d Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sat, 29 Jun 2013 13:20:45 -0700 Subject: [PATCH 114/367] Update diff to new internal submodule status API Submodules now expose an internal status API that allows diff to get back the OID values from the submodule very easily and also to avoiding caching issues and to override the ignore setting for the submodule. --- src/diff.c | 14 ++++++++------ tests-clar/diff/submodules.c | 7 ------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/diff.c b/src/diff.c index cc7be451f..2b018188e 100644 --- a/src/diff.c +++ b/src/diff.c @@ -13,6 +13,7 @@ #include "pathspec.h" #include "index.h" #include "odb.h" +#include "submodule.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) @@ -595,7 +596,6 @@ static int maybe_modified_submodule( int error = 0; git_submodule *sub; unsigned int sm_status = 0; - const git_oid *sm_oid; *status = GIT_DELTA_UNMODIFIED; @@ -603,7 +603,9 @@ static int maybe_modified_submodule( !(error = git_submodule_lookup( &sub, diff->repo, info->nitem->path)) && git_submodule_ignore(sub) != GIT_SUBMODULE_IGNORE_ALL && - !(error = git_submodule_status(&sm_status, sub))) + !(error = git_submodule__status( + &sm_status, NULL, NULL, found_oid, sub, + GIT_SUBMODULE_IGNORE_DEFAULT))) { /* check IS_WD_UNMODIFIED because this case is only used * when the new side of the diff is the working directory @@ -611,10 +613,10 @@ static int maybe_modified_submodule( if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) *status = GIT_DELTA_MODIFIED; - /* grab OID while we are here */ - if (git_oid_iszero(&info->nitem->oid) && - (sm_oid = git_submodule_wd_id(sub)) != NULL) - git_oid_cpy(found_oid, sm_oid); + /* now that we have a HEAD OID, check if HEAD moved */ + if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && + !git_oid_equal(&info->oitem->oid, found_oid)) + *status = GIT_DELTA_MODIFIED; } /* GIT_EEXISTS means a dir with .git in it was found - ignore it */ diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index c94fd57c6..4a40affb2 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -333,8 +333,6 @@ void test_diff_submodules__invalid_cache(void) check_diff_patches(diff, expected_unchanged); git_diff_list_free(diff); - sleep(2); - /* commit changed index of submodule */ { git_object *parent; @@ -357,11 +355,6 @@ void test_diff_submodules__invalid_cache(void) git_signature_free(sig); } - /* THIS RELOAD SHOULD NOT BE REQUIRED - cl_git_pass(git_submodule_reload_all(g_repo)); - cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); - */ - git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY); cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); From f9775a37aa4ed042839a6b2f9d8e0dfbd73a2f09 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sat, 29 Jun 2013 23:22:31 -0700 Subject: [PATCH 115/367] Add ignore_submodules to diff options This adds correct support for an equivalent to --ignore-submodules in diff, where an actual ignore value can be passed to diff to override the per submodule settings in the configuration. This required tweaking the constants for ignore values so that zero would not be used and could represent an unset option to the diff. This was an opportunity to move the submodule values into include/git2/types.h and to rename the poorly named DEFAULT values for ignore and update constants to RESET instead. Now the GIT_DIFF_IGNORE_SUBMODULES flag is exactly the same as setting the ignore_submodules option to GIT_SUBMODULE_IGNORE_ALL (which is actually a minor change from the old behavior in that submodules will now be treated as UNMODIFIED deltas instead of being left out totally - if you set GIT_DIFF_INCLUDE_UNMODIFIED). This includes tests for the various new settings. --- include/git2/diff.h | 5 +- include/git2/submodule.h | 53 +++------------- include/git2/types.h | 34 +++++++++++ src/diff.c | 77 ++++++++++++----------- src/submodule.c | 112 +++++++++++++++++++++++----------- src/submodule.h | 8 +++ tests-clar/clar_libgit2.h | 3 + tests-clar/diff/submodules.c | 65 +++++++++++++++++++- tests-clar/submodule/modify.c | 10 +-- 9 files changed, 245 insertions(+), 122 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 121c9df5c..71a8b72bf 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -78,7 +78,7 @@ typedef enum { GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3), /** Ignore whitespace at end of line */ GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4), - /** Exclude submodules from the diff completely */ + /** Treat all submodules as unmodified */ GIT_DIFF_IGNORE_SUBMODULES = (1 << 5), /** Use the "patience diff" algorithm (currently unimplemented) */ GIT_DIFF_PATIENCE = (1 << 6), @@ -314,6 +314,8 @@ typedef int (*git_diff_notify_cb)( * - `notify_cb` is an optional callback function, notifying the consumer of * which files are being examined as the diff is generated * - `notify_payload` is the payload data to pass to the `notify_cb` function + * - `ignore_submodules` overrides the submodule ignore setting for all + * submodules in the diff. */ typedef struct { unsigned int version; /**< version for the struct */ @@ -326,6 +328,7 @@ typedef struct { git_off_t max_size; /**< defaults to 512MB */ git_diff_notify_cb notify_cb; void *notify_payload; + git_submodule_ignore_t ignore_submodules; /** << submodule ignore rule */ } git_diff_options; #define GIT_DIFF_OPTIONS_VERSION 1 diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 3267bcd0f..ba7a558d0 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -14,51 +14,18 @@ /** * @file git2/submodule.h * @brief Git submodule management utilities - * @defgroup git_submodule Git submodule management routines - * @ingroup Git - * @{ - */ -GIT_BEGIN_DECL - -/** - * Opaque structure representing a submodule. * * Submodule support in libgit2 builds a list of known submodules and keeps * it in the repository. The list is built from the .gitmodules file, the * .git/config file, the index, and the HEAD tree. Items in the working * directory that look like submodules (i.e. a git repo) but are not * mentioned in those places won't be tracked. - */ -typedef struct git_submodule git_submodule; - -/** - * Values that could be specified for the update rule of a submodule. * - * Use the DEFAULT value if you have altered the update value via - * `git_submodule_set_update()` and wish to reset to the original default. + * @defgroup git_submodule Git submodule management routines + * @ingroup Git + * @{ */ -typedef enum { - GIT_SUBMODULE_UPDATE_DEFAULT = -1, - GIT_SUBMODULE_UPDATE_CHECKOUT = 0, - GIT_SUBMODULE_UPDATE_REBASE = 1, - GIT_SUBMODULE_UPDATE_MERGE = 2, - GIT_SUBMODULE_UPDATE_NONE = 3 -} git_submodule_update_t; - -/** - * Values that could be specified for how closely to examine the - * working directory when getting submodule status. - * - * Use the DEFUALT value if you have altered the ignore value via - * `git_submodule_set_ignore()` and wish to reset to the original value. - */ -typedef enum { - GIT_SUBMODULE_IGNORE_DEFAULT = -1, /* reset to default */ - GIT_SUBMODULE_IGNORE_NONE = 0, /* any change or untracked == dirty */ - GIT_SUBMODULE_IGNORE_UNTRACKED = 1, /* dirty if tracked files change */ - GIT_SUBMODULE_IGNORE_DIRTY = 2, /* only dirty if HEAD moved */ - GIT_SUBMODULE_IGNORE_ALL = 3 /* never dirty */ -} git_submodule_ignore_t; +GIT_BEGIN_DECL /** * Return codes for submodule status. @@ -377,9 +344,9 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore( * submodule is in memory. You should call `git_submodule_save()` if you * want to persist the new ignore role. * - * Calling this again with GIT_SUBMODULE_IGNORE_DEFAULT or calling - * `git_submodule_reload()` will revert the rule to the value that was in the - * original config. + * Calling this again with GIT_SUBMODULE_IGNORE_RESET or calling + * `git_submodule_reload()` will revert the rule to the value that was in + * the original config. * * @return old value for ignore */ @@ -399,9 +366,9 @@ GIT_EXTERN(git_submodule_update_t) git_submodule_update( * This sets the update rule in memory for the submodule. You should call * `git_submodule_save()` if you want to persist the new update rule. * - * Calling this again with GIT_SUBMODULE_UPDATE_DEFAULT or calling - * `git_submodule_reload()` will revert the rule to the value that was in the - * original config. + * Calling this again with GIT_SUBMODULE_UPDATE_RESET or calling + * `git_submodule_reload()` will revert the rule to the value that was in + * the original config. * * @return old value for update */ diff --git a/include/git2/types.h b/include/git2/types.h index dc344075c..1da0d3ed2 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -229,6 +229,40 @@ typedef struct git_transfer_progress { */ typedef int (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload); +/** + * Opaque structure representing a submodule. + */ +typedef struct git_submodule git_submodule; + +/** + * Values that could be specified for the update rule of a submodule. + * + * Use the RESET value if you have altered the in-memory update value via + * `git_submodule_set_update()` and wish to reset to the original default. + */ +typedef enum { + GIT_SUBMODULE_UPDATE_RESET = -1, + GIT_SUBMODULE_UPDATE_CHECKOUT = 1, + GIT_SUBMODULE_UPDATE_REBASE = 2, + GIT_SUBMODULE_UPDATE_MERGE = 3, + GIT_SUBMODULE_UPDATE_NONE = 4 +} git_submodule_update_t; + +/** + * Values that could be specified for how closely to examine the + * working directory when getting submodule status. + * + * Use the RESET value if you have altered the in-memory ignore value via + * `git_submodule_set_ignore()` and wish to reset to the original value. + */ +typedef enum { + GIT_SUBMODULE_IGNORE_RESET = -1, /* reset to on-disk value */ + GIT_SUBMODULE_IGNORE_NONE = 1, /* any change or untracked == dirty */ + GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */ + GIT_SUBMODULE_IGNORE_DIRTY = 3, /* only dirty if HEAD moved */ + GIT_SUBMODULE_IGNORE_ALL = 4 /* never dirty */ +} git_submodule_ignore_t; + /** @} */ GIT_END_DECL diff --git a/src/diff.c b/src/diff.c index 2b018188e..5fa635f05 100644 --- a/src/diff.c +++ b/src/diff.c @@ -78,10 +78,6 @@ static int diff_delta__from_one( 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( &diff->pathspec, entry->path, DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), @@ -141,11 +137,6 @@ static int diff_delta__from_two( DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) return 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; @@ -431,8 +422,18 @@ static int diff_list_apply_options( if (!opts) { diff->opts.context_lines = config_int(cfg, "diff.context", 3); - if (config_bool(cfg, "diff.ignoreSubmodules", 0)) - diff->opts.flags |= GIT_DIFF_IGNORE_SUBMODULES; + /* add other defaults here */ + } + + /* if ignore_submodules not explicitly set, check diff config */ + if (diff->opts.ignore_submodules <= 0) { + const char *str; + + if (git_config_get_string(&str , cfg, "diff.ignoreSubmodules") < 0) + giterr_clear(); + else if (str != NULL && + git_submodule_parse_ignore(&diff->opts.ignore_submodules, str) < 0) + giterr_clear(); } /* if either prefix is not set, figure out appropriate value */ @@ -596,36 +597,44 @@ static int maybe_modified_submodule( int error = 0; git_submodule *sub; unsigned int sm_status = 0; + git_submodule_ignore_t ign = diff->opts.ignore_submodules; *status = GIT_DELTA_UNMODIFIED; - if (!DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) && - !(error = git_submodule_lookup( - &sub, diff->repo, info->nitem->path)) && - git_submodule_ignore(sub) != GIT_SUBMODULE_IGNORE_ALL && - !(error = git_submodule__status( - &sm_status, NULL, NULL, found_oid, sub, - GIT_SUBMODULE_IGNORE_DEFAULT))) - { - /* check IS_WD_UNMODIFIED because this case is only used - * when the new side of the diff is the working directory - */ - if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) - *status = GIT_DELTA_MODIFIED; + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || + ign == GIT_SUBMODULE_IGNORE_ALL) + return 0; - /* now that we have a HEAD OID, check if HEAD moved */ - if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && - !git_oid_equal(&info->oitem->oid, found_oid)) - *status = GIT_DELTA_MODIFIED; + if ((error = git_submodule_lookup( + &sub, diff->repo, info->nitem->path)) < 0) { + + /* GIT_EEXISTS means dir with .git in it was found - ignore it */ + if (error == GIT_EEXISTS) { + giterr_clear(); + error = 0; + } + return error; } - /* GIT_EEXISTS means a dir with .git in it was found - ignore it */ - if (error == GIT_EEXISTS) { - giterr_clear(); - error = 0; - } + if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) + return 0; - return error; + if ((error = git_submodule__status( + &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) + return error; + + /* check IS_WD_UNMODIFIED because this case is only used + * when the new side of the diff is the working directory + */ + if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) + *status = GIT_DELTA_MODIFIED; + + /* now that we have a HEAD OID, check if HEAD moved */ + if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && + !git_oid_equal(&info->oitem->oid, found_oid)) + *status = GIT_DELTA_MODIFIED; + + return 0; } static int maybe_modified( diff --git a/src/submodule.c b/src/submodule.c index 0beedeb42..685906332 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -30,6 +30,8 @@ static git_cvar_map _sm_update_map[] = { {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}, {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE}, + {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE}, + {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT}, }; static git_cvar_map _sm_ignore_map[] = { @@ -37,6 +39,8 @@ static git_cvar_map _sm_ignore_map[] = { {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, + {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE}, + {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL}, }; static kh_inline khint_t str_hash_no_trailing_slash(const char *s) @@ -406,11 +410,30 @@ cleanup: return error; } +const char *git_submodule_ignore_to_str(git_submodule_ignore_t ignore) +{ + int i; + for (i = 0; i < (int)ARRAY_SIZE(_sm_ignore_map); ++i) + if (_sm_ignore_map[i].map_value == ignore) + return _sm_ignore_map[i].str_match; + return NULL; +} + +const char *git_submodule_update_to_str(git_submodule_update_t update) +{ + int i; + for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i) + if (_sm_update_map[i].map_value == update) + return _sm_update_map[i].str_match; + return NULL; +} + int git_submodule_save(git_submodule *submodule) { int error = 0; git_config_backend *mods; git_buf key = GIT_BUF_INIT; + const char *val; assert(submodule); @@ -435,22 +458,14 @@ int git_submodule_save(git_submodule *submodule) goto cleanup; if (!(error = submodule_config_key_trunc_puts(&key, "update")) && - submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) - { - const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? - NULL : _sm_update_map[submodule->update].str_match; + (val = git_submodule_update_to_str(submodule->update)) != NULL) error = git_config_file_set_string(mods, key.ptr, val); - } if (error < 0) goto cleanup; if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) && - submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT) - { - const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ? - NULL : _sm_ignore_map[submodule->ignore].str_match; + (val = git_submodule_ignore_to_str(submodule->ignore)) != NULL) error = git_config_file_set_string(mods, key.ptr, val); - } if (error < 0) goto cleanup; @@ -554,7 +569,8 @@ const git_oid *git_submodule_wd_id(git_submodule *submodule) git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule) { assert(submodule); - return submodule->ignore; + return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ? + GIT_SUBMODULE_IGNORE_NONE : submodule->ignore; } git_submodule_ignore_t git_submodule_set_ignore( @@ -564,7 +580,7 @@ git_submodule_ignore_t git_submodule_set_ignore( assert(submodule); - if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT) + if (ignore == GIT_SUBMODULE_IGNORE_RESET) ignore = submodule->ignore_default; old = submodule->ignore; @@ -575,7 +591,8 @@ git_submodule_ignore_t git_submodule_set_ignore( git_submodule_update_t git_submodule_update(git_submodule *submodule) { assert(submodule); - return submodule->update; + return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ? + GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update; } git_submodule_update_t git_submodule_set_update( @@ -585,7 +602,7 @@ git_submodule_update_t git_submodule_set_update( assert(submodule); - if (update == GIT_SUBMODULE_UPDATE_DEFAULT) + if (update == GIT_SUBMODULE_UPDATE_RESET) update = submodule->update_default; old = submodule->update; @@ -616,6 +633,7 @@ int git_submodule_set_fetch_recurse_submodules( int git_submodule_init(git_submodule *submodule, int overwrite) { int error; + const char *val; /* write "submodule.NAME.url" */ @@ -632,14 +650,10 @@ int git_submodule_init(git_submodule *submodule, int overwrite) /* write "submodule.NAME.update" if not default */ - if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) - error = submodule_update_config( - submodule, "update", NULL, (overwrite != 0), false); - else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) - error = submodule_update_config( - submodule, "update", - _sm_update_map[submodule->update].str_match, - (overwrite != 0), false); + val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? + NULL : git_submodule_update_to_str(submodule->update); + error = submodule_update_config( + submodule, "update", val, (overwrite != 0), false); return error; } @@ -873,7 +887,7 @@ int git_submodule__status( unsigned int status; git_repository *smrepo = NULL; - if (ign == GIT_SUBMODULE_IGNORE_DEFAULT) + if (ign < GIT_SUBMODULE_IGNORE_NONE) ign = sm->ignore; /* only return location info if ignore == all */ @@ -926,8 +940,7 @@ int git_submodule_status(unsigned int *status, git_submodule *sm) { assert(status && sm); - return git_submodule__status( - status, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_DEFAULT); + return git_submodule__status(status, NULL, NULL, NULL, sm, 0); } int git_submodule_location(unsigned int *location, git_submodule *sm) @@ -964,7 +977,10 @@ static git_submodule *submodule_alloc(git_repository *repo, const char *name) } GIT_REFCOUNT_INC(sm); - sm->repo = repo; + sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE; + sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT; + sm->repo = repo; + return sm; } @@ -1039,6 +1055,34 @@ static int submodule_config_error(const char *property, const char *value) return -1; } +int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) { + *out = GIT_SUBMODULE_IGNORE_NONE; + return submodule_config_error("ignore", value); + } + + *out = (git_submodule_ignore_t)val; + return 0; +} + +int git_submodule_parse_update(git_submodule_update_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) { + *out = GIT_SUBMODULE_UPDATE_CHECKOUT; + return submodule_config_error("update", value); + } + + *out = (git_submodule_update_t)val; + return 0; +} + static int submodule_load_from_config( const git_config_entry *entry, void *data) { @@ -1120,22 +1164,18 @@ static int submodule_load_from_config( return -1; } else if (strcasecmp(property, "update") == 0) { - int val; - if (git_config_lookup_map_value( - &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) - return submodule_config_error("update", value); - sm->update_default = sm->update = (git_submodule_update_t)val; + if (git_submodule_parse_update(&sm->update, value) < 0) + return -1; + sm->update_default = sm->update; } else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) { if (git__parse_bool(&sm->fetch_recurse, value) < 0) return submodule_config_error("fetchRecurseSubmodules", value); } else if (strcasecmp(property, "ignore") == 0) { - int val; - if (git_config_lookup_map_value( - &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) - return submodule_config_error("ignore", value); - sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val; + if (git_submodule_parse_ignore(&sm->ignore, value) < 0) + return -1; + sm->ignore_default = sm->ignore; } /* ignore other unknown submodule properties */ diff --git a/src/submodule.h b/src/submodule.h index 8db2fff29..b05937503 100644 --- a/src/submodule.h +++ b/src/submodule.h @@ -127,4 +127,12 @@ extern int git_submodule_open_bare( /* Release reference to submodule object - not currently for external use */ extern void git_submodule_free(git_submodule *sm); +extern int git_submodule_parse_ignore( + git_submodule_ignore_t *out, const char *value); +extern int git_submodule_parse_update( + git_submodule_update_t *out, const char *value); + +extern const char *git_submodule_ignore_to_str(git_submodule_ignore_t); +extern const char *git_submodule_update_to_str(git_submodule_update_t); + #endif diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h index 3fcf45a37..5371b2864 100644 --- a/tests-clar/clar_libgit2.h +++ b/tests-clar/clar_libgit2.h @@ -29,6 +29,9 @@ void cl_git_report_failure(int, const char *, int, const char *); +#define cl_assert_at_line(expr,file,line) \ + clar__assert((expr) != 0, file, line, "Expression is not true: " #expr, NULL, 1) + #define cl_assert_equal_sz(sz1,sz2) cl_assert_equal_i((int)sz1, (int)(sz2)) /* diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index 4a40affb2..2e425bb11 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -49,7 +49,7 @@ static void check_diff_patches_at_line( cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); if (delta->status == GIT_DELTA_UNMODIFIED) { - clar__assert(expected[d] == NULL, file, line, "found UNMODIFIED delta where modified was expected", NULL, 1); + cl_assert_at_line(expected[d] == NULL, file, line); continue; } @@ -57,7 +57,7 @@ static void check_diff_patches_at_line( continue; if (expected[d] && !strcmp(expected[d], "")) { cl_git_pass(git_diff_patch_to_str(&patch_text, patch)); - clar__assert(0, file, line, "expected end of deltas, but found more", patch_text, 1); + cl_assert_at_line(!strcmp(expected[d], ""), file, line); } cl_git_pass(git_diff_patch_to_str(&patch_text, patch)); @@ -67,7 +67,7 @@ static void check_diff_patches_at_line( git__free(patch_text); } - clar__assert(expected[d] && !strcmp(expected[d], ""), file, line, "found fewer deltas than expected", expected[d], 1); + cl_assert_at_line(expected[d] && !strcmp(expected[d], ""), file, line); } #define check_diff_patches(diff, exp) \ @@ -382,3 +382,62 @@ void test_diff_submodules__invalid_cache(void) git_index_free(smindex); git_repository_free(smrepo); } + +void test_diff_submodules__diff_ignore_options(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff = NULL; + static const char *expected_normal[] = { + "", /* .gitmodules */ + NULL, /* not-submodule */ + NULL, /* not */ + "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ + "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */ + "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ + "" + }; + static const char *expected_ignore_all[] = { + "", /* .gitmodules */ + NULL, /* not-submodule */ + NULL, /* not */ + "" + }; + static const char *expected_ignore_dirty[] = { + "", /* .gitmodules */ + NULL, /* not-submodule */ + NULL, /* not */ + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ + "" + }; + + setup_submodules2(); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_normal); + git_diff_list_free(diff); + + opts.flags |= GIT_DIFF_IGNORE_SUBMODULES; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_all); + git_diff_list_free(diff); + + opts.flags &= ~GIT_DIFF_IGNORE_SUBMODULES; + opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_all); + git_diff_list_free(diff); + + opts.ignore_submodules = GIT_SUBMODULE_IGNORE_DIRTY; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_dirty); + git_diff_list_free(diff); +} diff --git a/tests-clar/submodule/modify.c b/tests-clar/submodule/modify.c index 94eb3738a..c0498ce6e 100644 --- a/tests-clar/submodule/modify.c +++ b/tests-clar/submodule/modify.c @@ -204,10 +204,10 @@ void test_submodule_modify__edit_and_save(void) cl_git_pass(git_submodule_set_url(sm1, old_url)); cl_assert_equal_i( (int)GIT_SUBMODULE_IGNORE_UNTRACKED, - (int)git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_DEFAULT)); + (int)git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_RESET)); cl_assert_equal_i( (int)GIT_SUBMODULE_UPDATE_REBASE, - (int)git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_DEFAULT)); + (int)git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_RESET)); cl_assert_equal_i( 1, git_submodule_set_fetch_recurse_submodules(sm1, old_fetchrecurse)); @@ -228,10 +228,10 @@ void test_submodule_modify__edit_and_save(void) cl_git_pass(git_submodule_save(sm1)); /* attempt to "revert" values */ - git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_DEFAULT); - git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_DEFAULT); + git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_RESET); + git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_RESET); - /* but ignore and update should NOT revert because the DEFAULT + /* but ignore and update should NOT revert because the RESET * should now be the newly saved value... */ cl_assert_equal_i( From b8df28a5dae65b617ce85e1937066093600880af Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sun, 30 Jun 2013 08:38:10 -0700 Subject: [PATCH 116/367] Clean up left over alloc change --- src/submodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/submodule.c b/src/submodule.c index 685906332..b5dacc42e 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -966,7 +966,7 @@ static git_submodule *submodule_alloc(git_repository *repo, const char *name) return NULL; } - sm = git__calloc(1, sizeof(git_submodule) + namelen + 1); + sm = git__calloc(1, sizeof(git_submodule)); if (sm == NULL) return NULL; From 9564229af42b68d205376853410c55b957546a14 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sun, 30 Jun 2013 08:43:07 -0700 Subject: [PATCH 117/367] Add tests for diff.ignoreSubmdules config --- tests-clar/diff/submodules.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index 2e425bb11..fe9cf1d98 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -387,6 +387,7 @@ void test_diff_submodules__diff_ignore_options(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff = NULL; + git_config *cfg; static const char *expected_normal[] = { "", /* .gitmodules */ NULL, /* not-submodule */ @@ -440,4 +441,32 @@ void test_diff_submodules__diff_ignore_options(void) cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); check_diff_patches(diff, expected_ignore_dirty); git_diff_list_free(diff); + + opts.ignore_submodules = 0; + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", false)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_normal); + git_diff_list_free(diff); + + cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", true)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_all); + git_diff_list_free(diff); + + cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "none")); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_normal); + git_diff_list_free(diff); + + cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "dirty")); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_dirty); + git_diff_list_free(diff); + + git_config_free(cfg); } From 125655fe3f0caf8b3d9fff2ec45ec694b34eed04 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 2 Jul 2013 16:49:57 -0700 Subject: [PATCH 118/367] Untracked directories with .git should be ignored This restores a behavior that was accidentally lost during some diff refactoring where an untracked directory that contains a .git item should be treated as IGNORED, not as UNTRACKED. The submodule code already detects this, but the diff code was not handling the scenario right. This also updates a number of existing tests that were actually exercising the behavior but did not have the right expectations in place. It actually makes the new `test_diff_submodules__diff_ignore_options` test feel much better because the "not-a-submodule" entries are now ignored instead of showing up as untracked items. Fixes #1697 --- src/diff.c | 14 ++++++- tests-clar/diff/submodules.c | 47 ++++-------------------- tests-clar/diff/workdir.c | 7 ++-- tests-clar/status/submodules.c | 21 ++++++----- tests-clar/submodule/status.c | 35 ++++++++++++------ tests-clar/submodule/submodule_helpers.c | 35 ++++++++++++++++++ tests-clar/submodule/submodule_helpers.h | 3 ++ 7 files changed, 97 insertions(+), 65 deletions(-) diff --git a/src/diff.c b/src/diff.c index 5fa635f05..e875d09b3 100644 --- a/src/diff.c +++ b/src/diff.c @@ -735,7 +735,7 @@ static int maybe_modified( /* 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 */ - if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) { + if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->oid)) { if (git_oid_iszero(&noid)) { if (git_diff__oid_for_file(diff->repo, nitem->path, nitem->mode, nitem->file_size, &noid) < 0) @@ -858,7 +858,7 @@ static int handle_unmatched_new_item( git_buf_clear(&info->ignore_prefix); } - if (S_ISDIR(nitem->mode)) { + if (nitem->mode == GIT_FILEMODE_TREE) { bool recurse_into_dir = contains_oitem; /* if not already inside an ignored dir, check if this is ignored */ @@ -962,6 +962,16 @@ static int handle_unmatched_new_item( else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) delta_type = GIT_DELTA_ADDED; + else if (nitem->mode == GIT_FILEMODE_COMMIT) { + git_submodule *sm; + + /* ignore things that are not actual submodules */ + if (git_submodule_lookup(&sm, info->repo, nitem->path) != 0) { + giterr_clear(); + delta_type = GIT_DELTA_IGNORED; + } + } + /* Actually create the record for this item if necessary */ if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0) return error; diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index fe9cf1d98..94804db22 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -5,36 +5,13 @@ static git_repository *g_repo = NULL; -static void setup_submodules(void) -{ - g_repo = cl_git_sandbox_init("submodules"); - cl_fixture_sandbox("testrepo.git"); - rewrite_gitmodules(git_repository_workdir(g_repo)); - p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); -} - -static void setup_submodules2(void) -{ - g_repo = cl_git_sandbox_init("submod2"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); - - rewrite_gitmodules(git_repository_workdir(g_repo)); - p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); - p_rename("submod2/not/.gitted", "submod2/not/.git"); -} - void test_diff_submodules__initialize(void) { } void test_diff_submodules__cleanup(void) { - cl_git_sandbox_cleanup(); - - cl_fixture_cleanup("testrepo.git"); - cl_fixture_cleanup("submod2_target"); + cleanup_fixture_submodules(); } static void check_diff_patches_at_line( @@ -88,7 +65,7 @@ void test_diff_submodules__unmodified_submodule(void) "" }; - setup_submodules(); + g_repo = setup_fixture_submodules(); opts.flags = GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED | @@ -115,7 +92,7 @@ void test_diff_submodules__dirty_submodule(void) "" }; - setup_submodules(); + g_repo = setup_fixture_submodules(); cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); @@ -141,7 +118,7 @@ void test_diff_submodules__dirty_submodule_2(void) "" }; - setup_submodules(); + g_repo = setup_fixture_submodules(); cl_git_pass(git_submodule_reload_all(g_repo)); @@ -190,8 +167,6 @@ void test_diff_submodules__submod2_index_to_wd(void) git_diff_list *diff = NULL; static const char *expected[] = { "", /* .gitmodules */ - NULL, /* not-submodule */ - NULL, /* not */ "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ @@ -200,7 +175,7 @@ void test_diff_submodules__submod2_index_to_wd(void) "" }; - setup_submodules2(); + g_repo = setup_fixture_submod2(); opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; opts.old_prefix = "a"; opts.new_prefix = "b"; @@ -221,7 +196,7 @@ void test_diff_submodules__submod2_head_to_index(void) "" }; - setup_submodules2(); + g_repo = setup_fixture_submod2(); cl_git_pass(git_repository_head_tree(&head, g_repo)); @@ -261,7 +236,7 @@ void test_diff_submodules__invalid_cache(void) "" }; - setup_submodules2(); + g_repo = setup_fixture_submod2(); opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; opts.old_prefix = "a"; opts.new_prefix = "b"; @@ -390,8 +365,6 @@ void test_diff_submodules__diff_ignore_options(void) git_config *cfg; static const char *expected_normal[] = { "", /* .gitmodules */ - NULL, /* not-submodule */ - NULL, /* not */ "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ @@ -401,20 +374,16 @@ void test_diff_submodules__diff_ignore_options(void) }; static const char *expected_ignore_all[] = { "", /* .gitmodules */ - NULL, /* not-submodule */ - NULL, /* not */ "" }; static const char *expected_ignore_dirty[] = { "", /* .gitmodules */ - NULL, /* not-submodule */ - NULL, /* not */ "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ "" }; - setup_submodules2(); + g_repo = setup_fixture_submod2(); opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; opts.old_prefix = "a"; opts.new_prefix = "b"; diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index 6a2504d09..c7fac1e48 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -776,6 +776,7 @@ void test_diff_workdir__submodules(void) opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS | GIT_DIFF_INCLUDE_UNTRACKED_CONTENT; @@ -806,7 +807,7 @@ void test_diff_workdir__submodules(void) * only significant difference is that those Added items will show up * as Untracked items in the pure libgit2 diff. * - * Then add in the two extra untracked items "not" and "not-submodule" + * Then add in the two extra ignored items "not" and "not-submodule" * to get the 12 files reported here. */ @@ -815,8 +816,8 @@ void test_diff_workdir__submodules(void) cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(8, exp.file_status[GIT_DELTA_UNTRACKED]); /* the following numbers match "git diff 873585" exactly */ diff --git a/tests-clar/status/submodules.c b/tests-clar/status/submodules.c index af8707721..7bfef503f 100644 --- a/tests-clar/status/submodules.c +++ b/tests-clar/status/submodules.c @@ -9,25 +9,19 @@ static git_repository *g_repo = NULL; void test_status_submodules__initialize(void) { - g_repo = cl_git_sandbox_init("submodules"); - - cl_fixture_sandbox("testrepo.git"); - - rewrite_gitmodules(git_repository_workdir(g_repo)); - - p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); } void test_status_submodules__cleanup(void) { - cl_git_sandbox_cleanup(); - cl_fixture_cleanup("testrepo.git"); + cleanup_fixture_submodules(); } void test_status_submodules__api(void) { git_submodule *sm; + g_repo = setup_fixture_submodules(); + cl_assert(git_submodule_lookup(NULL, g_repo, "nonexistent") == GIT_ENOTFOUND); cl_assert(git_submodule_lookup(NULL, g_repo, "modified") == GIT_ENOTFOUND); @@ -42,6 +36,8 @@ void test_status_submodules__0(void) { int counts = 0; + g_repo = setup_fixture_submodules(); + cl_assert(git_path_isdir("submodules/.git")); cl_assert(git_path_isdir("submodules/testrepo/.git")); cl_assert(git_path_isfile("submodules/.gitmodules")); @@ -86,6 +82,8 @@ void test_status_submodules__1(void) { status_entry_counts counts; + g_repo = setup_fixture_submodules(); + cl_assert(git_path_isdir("submodules/.git")); cl_assert(git_path_isdir("submodules/testrepo/.git")); cl_assert(git_path_isfile("submodules/.gitmodules")); @@ -104,6 +102,7 @@ void test_status_submodules__1(void) void test_status_submodules__single_file(void) { unsigned int status = 0; + g_repo = setup_fixture_submodules(); cl_git_pass( git_status_file(&status, g_repo, "testrepo") ); cl_assert(!status); } @@ -134,6 +133,8 @@ void test_status_submodules__moved_head(void) GIT_STATUS_WT_NEW }; + g_repo = setup_fixture_submodules(); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); cl_git_pass(git_submodule_open(&smrepo, sm)); @@ -192,6 +193,8 @@ void test_status_submodules__dirty_workdir_only(void) GIT_STATUS_WT_NEW }; + g_repo = setup_fixture_submodules(); + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); diff --git a/tests-clar/submodule/status.c b/tests-clar/submodule/status.c index 68110bdd5..7b29ac288 100644 --- a/tests-clar/submodule/status.c +++ b/tests-clar/submodule/status.c @@ -9,21 +9,12 @@ static git_repository *g_repo = NULL; void test_submodule_status__initialize(void) { - g_repo = cl_git_sandbox_init("submod2"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); - - /* must create submod2_target before rewrite so prettify will work */ - rewrite_gitmodules(git_repository_workdir(g_repo)); - p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); - p_rename("submod2/not/.gitted", "submod2/not/.git"); + g_repo = setup_fixture_submod2(); } void test_submodule_status__cleanup(void) { - cl_git_sandbox_cleanup(); - cl_fixture_cleanup("submod2_target"); + cleanup_fixture_submodules(); } void test_submodule_status__unchanged(void) @@ -326,6 +317,7 @@ void test_submodule_status__ignore_all(void) typedef struct { size_t counter; const char **paths; + int *statuses; } submodule_expectations; static int confirm_submodule_status( @@ -336,6 +328,7 @@ static int confirm_submodule_status( while (git__suffixcmp(exp->paths[exp->counter], "/") == 0) exp->counter++; + cl_assert_equal_i(exp->statuses[exp->counter], (int)status_flags); cl_assert_equal_s(exp->paths[exp->counter++], path); GIT_UNUSED(status_flags); @@ -365,7 +358,24 @@ void test_submodule_status__iterator(void) "sm_unchanged", NULL }; - submodule_expectations exp = { 0, expected }; + static int expected_flags[] = { + GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, /* ".gitmodules" */ + 0, /* "just_a_dir/" will be skipped */ + GIT_STATUS_CURRENT, /* "just_a_dir/contents" */ + GIT_STATUS_CURRENT, /* "just_a_file" */ + GIT_STATUS_IGNORED, /* "not" (contains .git) */ + GIT_STATUS_IGNORED, /* "not-submodule" (contains .git) */ + GIT_STATUS_CURRENT, /* "README.txt */ + GIT_STATUS_INDEX_NEW, /* "sm_added_and_uncommited" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_file" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_head" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_index" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_untracked_file" */ + GIT_STATUS_WT_MODIFIED, /* "sm_missing_commits" */ + GIT_STATUS_CURRENT, /* "sm_unchanged" */ + 0 + }; + submodule_expectations exp = { 0, expected, expected_flags }; git_status_options opts = GIT_STATUS_OPTIONS_INIT; cl_git_pass(git_iterator_for_workdir(&iter, g_repo, @@ -378,6 +388,7 @@ void test_submodule_status__iterator(void) opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; cl_git_pass(git_status_foreach_ext( diff --git a/tests-clar/submodule/submodule_helpers.c b/tests-clar/submodule/submodule_helpers.c index 0c3e79f71..a7807522b 100644 --- a/tests-clar/submodule/submodule_helpers.c +++ b/tests-clar/submodule/submodule_helpers.c @@ -82,3 +82,38 @@ void rewrite_gitmodules(const char *workdir) git_buf_free(&out_f); git_buf_free(&path); } + +git_repository *setup_fixture_submodules(void) +{ + git_repository *repo = cl_git_sandbox_init("submodules"); + + cl_fixture_sandbox("testrepo.git"); + + rewrite_gitmodules(git_repository_workdir(repo)); + p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); + + return repo; +} + +git_repository *setup_fixture_submod2(void) +{ + git_repository *repo = cl_git_sandbox_init("submod2"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); + + rewrite_gitmodules(git_repository_workdir(repo)); + p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); + p_rename("submod2/not/.gitted", "submod2/not/.git"); + + return repo; +} + +void cleanup_fixture_submodules(void) +{ + cl_git_sandbox_cleanup(); + + /* just try to clean up both possible extras */ + cl_fixture_cleanup("testrepo.git"); + cl_fixture_cleanup("submod2_target"); +} diff --git a/tests-clar/submodule/submodule_helpers.h b/tests-clar/submodule/submodule_helpers.h index 6b76a832e..1de15ca17 100644 --- a/tests-clar/submodule/submodule_helpers.h +++ b/tests-clar/submodule/submodule_helpers.h @@ -1,2 +1,5 @@ extern void rewrite_gitmodules(const char *workdir); +extern git_repository *setup_fixture_submodules(void); +extern git_repository *setup_fixture_submod2(void); +extern void cleanup_fixture_submodules(void); From d70ce9bd7a1acec34b283d3bc92da84fbbf79860 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 10 Jul 2013 15:38:57 -0700 Subject: [PATCH 119/367] Clarify docs for git_status_file --- include/git2/status.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/git2/status.h b/include/git2/status.h index fd0e83104..2f7c06726 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -234,12 +234,12 @@ GIT_EXTERN(int) git_status_foreach_ext( * This is not quite the same as calling `git_status_foreach_ext()` with * the pathspec set to the specified path. * - * @param status_flags The status value for the file + * @param status_flags Output combination of git_status_t values for file * @param repo A repository object - * @param path The file to retrieve status for, rooted at the repo's workdir + * @param path The file to retrieve status for relative to the repo workdir * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD, - * index, and work tree, GIT_EINVALIDPATH if `path` points at a folder, - * GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error. + * index, and work tree, GIT_EAMBIGUOUS if `path` matches multiple files + * or if it refers to a folder, and -1 on other errors. */ GIT_EXTERN(int) git_status_file( unsigned int *status_flags, From 814de0bcab99f82dc637ba7ae34df5a0e9e1138c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 11 Jul 2013 11:00:41 -0700 Subject: [PATCH 120/367] Update git__swap thread helper This makes git__swap use the __sync_lock_test_and_set primitive with GCC and the InterlockedExchangePointer primitive with MSVC. Previously is used compare_and_swap in a way that was probably unintuitive for most thinking (i.e. it could fail to swap in the value if another thread raced in). Now it will always succeed and the last thread to run in a race will win instead of the first thread. This also fixes up a little confusion between volatile void ** and void * volatile * that came up with the Win32 compiler. --- src/thread-utils.h | 57 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/thread-utils.h b/src/thread-utils.h index f8c4dc66d..04e02959f 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -38,16 +38,6 @@ typedef git_atomic git_atomic_ssize; #endif -GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) -{ - a->val = val; -} - -GIT_INLINE(int) git_atomic_get(git_atomic *a) -{ - return (int)a->val; -} - #ifdef GIT_THREADS #define git_thread pthread_t @@ -71,6 +61,17 @@ GIT_INLINE(int) git_atomic_get(git_atomic *a) #define git_cond_signal(c) pthread_cond_signal(c) #define git_cond_broadcast(c) pthread_cond_broadcast(c) +GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) +{ +#if defined(GIT_WIN32) + InterlockedExchange(&a->val, (LONG)val); +#elif defined(__GNUC__) + __sync_lock_test_and_set(&a->val, val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + GIT_INLINE(int) git_atomic_inc(git_atomic *a) { #if defined(GIT_WIN32) @@ -105,7 +106,7 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a) } GIT_INLINE(void *) git___compare_and_swap( - volatile void **ptr, void *oldval, void *newval) + void * volatile *ptr, void *oldval, void *newval) { volatile void *foundval; #if defined(GIT_WIN32) @@ -118,6 +119,16 @@ GIT_INLINE(void *) git___compare_and_swap( return (foundval == oldval) ? oldval : newval; } +GIT_INLINE(volatile void *) git___swap( + void * volatile *ptr, void *newval) +{ +#if defined(GIT_WIN32) + return InterlockedExchangePointer(ptr, newval); +#else + return __sync_lock_test_and_set(ptr, newval); +#endif +} + #ifdef GIT_ARCH_64 GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) @@ -156,6 +167,11 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #define git_cond_signal(c) (void)0 #define git_cond_broadcast(c) (void)0 +GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) +{ + a->val = val; +} + GIT_INLINE(int) git_atomic_inc(git_atomic *a) { return ++a->val; @@ -173,7 +189,7 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a) } GIT_INLINE(void *) git___compare_and_swap( - volatile void **ptr, void *oldval, void *newval) + void * volatile *ptr, void *oldval, void *newval) { if (*ptr == oldval) *ptr = newval; @@ -182,6 +198,14 @@ GIT_INLINE(void *) git___compare_and_swap( return oldval; } +GIT_INLINE(volatile void *) git___swap( + void * volatile *ptr, void *newval) +{ + volatile void *old = *ptr; + *ptr = newval; + return old; +} + #ifdef GIT_ARCH_64 GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) @@ -194,13 +218,18 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #endif +GIT_INLINE(int) git_atomic_get(git_atomic *a) +{ + return (int)a->val; +} + /* Atomically replace oldval with newval * @return oldval if it was replaced or newval if it was not */ #define git__compare_and_swap(P,O,N) \ - git___compare_and_swap((volatile void **)P, O, N) + git___compare_and_swap((void * volatile *)P, O, N) -#define git__swap(ptr, val) git__compare_and_swap(&ptr, ptr, val) +#define git__swap(ptr, val) (void *)git___swap((void * volatile *)&ptr, val) extern int git_online_cpus(void); From 584f2d3013df6744fa7b5c5398a78b96f31e25f4 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 11 Jul 2013 11:04:42 -0700 Subject: [PATCH 121/367] Fix warnings on Win64 --- src/commit.c | 3 ++- src/diff_driver.c | 2 +- tests-clar/diff/pathspec.c | 14 +++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/commit.c b/src/commit.c index cc912a7be..15a195fe5 100644 --- a/src/commit.c +++ b/src/commit.c @@ -163,7 +163,8 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) const char *buffer_start = git_odb_object_data(odb_obj), *buffer; const char *buffer_end = buffer_start + git_odb_object_size(odb_obj); git_oid parent_id; - size_t parent_count = 0, header_len; + uint32_t parent_count = 0; + size_t header_len; /* find end-of-header (counting parents as we go) */ for (buffer = buffer_start; buffer < buffer_end; ++buffer) { diff --git a/src/diff_driver.c b/src/diff_driver.c index 77b0e9f3e..e82dfa50d 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -374,7 +374,7 @@ static long diff_context_find( return -1; if (out_size > (long)ctxt->line.size) - out_size = ctxt->line.size; + out_size = (long)ctxt->line.size; memcpy(out, ctxt->line.ptr, (size_t)out_size); return out_size; diff --git a/tests-clar/diff/pathspec.c b/tests-clar/diff/pathspec.c index 332b513b3..4334a8901 100644 --- a/tests-clar/diff/pathspec.c +++ b/tests-clar/diff/pathspec.c @@ -34,7 +34,7 @@ void test_diff_pathspec__0(void) cl_git_pass(git_pathspec_new(&ps, &paths)); cl_git_pass(git_pathspec_match_tree(&matches, a, GIT_PATHSPEC_DEFAULT, ps)); - cl_assert_equal_i(7, git_pathspec_match_list_entrycount(matches)); + cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches)); cl_assert_equal_s("current_file", git_pathspec_match_list_entry(matches,0)); cl_assert(git_pathspec_match_list_diff_entry(matches,0) == NULL); git_pathspec_match_list_free(matches); @@ -43,13 +43,13 @@ void test_diff_pathspec__0(void) cl_git_pass(git_pathspec_match_diff( &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); - cl_assert_equal_i(7, git_pathspec_match_list_entrycount(matches)); + cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches)); cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); cl_assert_equal_s("current_file", git_pathspec_match_list_diff_entry(matches,0)->new_file.path); cl_assert_equal_i(GIT_DELTA_ADDED, - git_pathspec_match_list_diff_entry(matches,0)->status); + (int)git_pathspec_match_list_diff_entry(matches,0)->status); git_pathspec_match_list_free(matches); git_diff_list_free(diff); @@ -59,13 +59,13 @@ void test_diff_pathspec__0(void) cl_git_pass(git_pathspec_match_diff( &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); - cl_assert_equal_i(3, git_pathspec_match_list_entrycount(matches)); + cl_assert_equal_i(3, (int)git_pathspec_match_list_entrycount(matches)); cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_diff_entry(matches,0)->new_file.path); cl_assert_equal_i(GIT_DELTA_DELETED, - git_pathspec_match_list_diff_entry(matches,0)->status); + (int)git_pathspec_match_list_diff_entry(matches,0)->status); git_pathspec_match_list_free(matches); git_diff_list_free(diff); @@ -75,13 +75,13 @@ void test_diff_pathspec__0(void) cl_git_pass(git_pathspec_match_diff( &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); - cl_assert_equal_i(4, git_pathspec_match_list_entrycount(matches)); + cl_assert_equal_i(4, (int)git_pathspec_match_list_entrycount(matches)); cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); cl_assert_equal_s("modified_file", git_pathspec_match_list_diff_entry(matches,0)->new_file.path); cl_assert_equal_i(GIT_DELTA_MODIFIED, - git_pathspec_match_list_diff_entry(matches,0)->status); + (int)git_pathspec_match_list_diff_entry(matches,0)->status); git_pathspec_match_list_free(matches); git_diff_list_free(diff); From 0a1c8f55b35ebd35a7d267099257634483268ffd Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 11 Jul 2013 17:09:15 -0500 Subject: [PATCH 122/367] preload configuration paths --- src/fileops.c | 14 ++++++++++++++ src/fileops.h | 7 +++++++ src/global.c | 9 ++++----- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 1f58fa5cd..db53d4fce 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -627,6 +627,20 @@ static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = { git_futils_guess_xdg_dirs, }; +int git_futils_dirs_global_init(void) +{ + git_futils_dir_t i; + git_buf *path; + int error = 0; + + for (i = 0; i < GIT_FUTILS_DIR__MAX; i++) { + if ((error = git_futils_dirs_get(&path, i)) < 0) + break; + } + + return error; +} + static int git_futils_check_selector(git_futils_dir_t which) { if (which < GIT_FUTILS_DIR__MAX) diff --git a/src/fileops.h b/src/fileops.h index f4e059c83..d23ebaffb 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -309,6 +309,13 @@ typedef enum { GIT_FUTILS_DIR__MAX = 3, } git_futils_dir_t; +/** + * Configures global data for configuration file search paths. + * + * @return 0 on success, <0 on failure + */ +extern int git_futils_dirs_global_init(void); + /** * Get the search path for global/system/xdg files * diff --git a/src/global.c b/src/global.c index 2d40ca2fc..a06d0c81f 100644 --- a/src/global.c +++ b/src/global.c @@ -65,10 +65,8 @@ int git_threads_init(void) return -1; /* Initialize any other subsystems that have global state */ - if ((error = git_hash_global_init()) >= 0) - _tls_init = 1; - - if (error == 0) + if ((error = git_hash_global_init()) >= 0 && + (error = git_futils_dirs_global_init()) >= 0) _tls_init = 1; GIT_MEMORY_BARRIER; @@ -127,7 +125,8 @@ int git_threads_init(void) pthread_key_create(&_tls_key, &cb__free_status); /* Initialize any other subsystems that have global state */ - if ((error = git_hash_global_init()) >= 0) + if ((error = git_hash_global_init()) >= 0 && + (error = git_futils_dirs_global_init()) >= 0) _tls_init = 1; GIT_MEMORY_BARRIER; From 1c13b0bfdcfb510863a6e5e3238f8a461551aa0f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 12 Jul 2013 15:30:05 -0500 Subject: [PATCH 123/367] test that suggests tags arent fully peeled during push --- tests-clar/online/push.c | 12 ++++++++++++ .../36/f79b2846017d3761e0a02d0bccd573e0f90c57 | 2 ++ .../ee/a4f2705eeec2db3813f2430829afce99cd00b5 | Bin 0 -> 141 bytes .../push_src/.gitted/refs/tags/tag-commit-two | 1 + .../resources/push_src/.gitted/refs/tags/tag-tag | 1 + 5 files changed, 16 insertions(+) create mode 100644 tests-clar/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57 create mode 100644 tests-clar/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5 create mode 100644 tests-clar/resources/push_src/.gitted/refs/tags/tag-commit-two create mode 100644 tests-clar/resources/push_src/.gitted/refs/tags/tag-tag diff --git a/tests-clar/online/push.c b/tests-clar/online/push.c index 5dc7974c7..321180781 100644 --- a/tests-clar/online/push.c +++ b/tests-clar/online/push.c @@ -29,6 +29,7 @@ static git_oid _tag_commit; static git_oid _tag_tree; static git_oid _tag_blob; static git_oid _tag_lightweight; +static git_oid _tag_tag; static int cred_acquire_cb( git_cred **cred, @@ -272,6 +273,7 @@ void test_online_push__initialize(void) git_oid_fromstr(&_tag_tree, "ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e"); git_oid_fromstr(&_tag_blob, "b483ae7ba66decee9aee971f501221dea84b1498"); git_oid_fromstr(&_tag_lightweight, "951bbbb90e2259a4c8950db78946784fb53fcbce"); + git_oid_fromstr(&_tag_tag, "eea4f2705eeec2db3813f2430829afce99cd00b5"); /* Remote URL environment variable must be set. User and password are optional. */ _remote_url = cl_getenv("GITTEST_REMOTE_URL"); @@ -569,6 +571,16 @@ void test_online_push__tag_lightweight(void) exp_refs, ARRAY_SIZE(exp_refs), 0); } +void test_online_push__tag_to_tag(void) +{ + const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" }; + push_status exp_stats[] = { { "refs/tags/tag-tag", NULL } }; + expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0); +} + void test_online_push__force(void) { const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"}; diff --git a/tests-clar/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57 b/tests-clar/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57 new file mode 100644 index 000000000..0bc57f266 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57 @@ -0,0 +1,2 @@ +x%Œ] +ƒ0„ûœSì”ÄÍB)}é ¼@bµnÑéíf`f`>ö3(§nÞibˆC°èû¾ë0öhQ6¢BåMv¨‡à2&-ø÷M0Q)+ ®Œêæª tNsÚà¿E*;}àžøJϲN픹­§(th­ÔÖ@#”BŒËºC•?ÉÀTÃ…oÅyk7ÿ \ No newline at end of file diff --git a/tests-clar/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5 b/tests-clar/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5 new file mode 100644 index 0000000000000000000000000000000000000000..b7b81d5e33fba17a21de678d5f77bccda5635efa GIT binary patch literal 141 zcmV;80CN9$0WHj33WG2Z1mHgB6ng<>W6Y0GN?&>dFA(#m1-r#8l-|CIg&F318ukUG zb{CqSDKIFL?J#w&Hz;jX*2sh&yNFW=QCpQT4;Zu+{Cy{2U&P*Ho4-ri;1NH5i!jc# vR(ioT@u~Z|gpDd?ZUe11kjg4!uy+GAs1b!2=cU3Pe_R67iB|jobvZJ;wZ%S3 literal 0 HcmV?d00001 diff --git a/tests-clar/resources/push_src/.gitted/refs/tags/tag-commit-two b/tests-clar/resources/push_src/.gitted/refs/tags/tag-commit-two new file mode 100644 index 000000000..abb365080 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/refs/tags/tag-commit-two @@ -0,0 +1 @@ +36f79b2846017d3761e0a02d0bccd573e0f90c57 diff --git a/tests-clar/resources/push_src/.gitted/refs/tags/tag-tag b/tests-clar/resources/push_src/.gitted/refs/tags/tag-tag new file mode 100644 index 000000000..d6cd74804 --- /dev/null +++ b/tests-clar/resources/push_src/.gitted/refs/tags/tag-tag @@ -0,0 +1 @@ +eea4f2705eeec2db3813f2430829afce99cd00b5 From cdacd3d931a95068b6ef3410e240c73f305835a1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 12 Jul 2013 16:53:00 -0500 Subject: [PATCH 124/367] header files show up in vs --- CMakeLists.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34d56b3fa..1086005c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,7 @@ ELSE () ELSE() MESSAGE("http-parser was not found or is too old; using bundled 3rd-party sources.") INCLUDE_DIRECTORIES(deps/http-parser) - FILE(GLOB SRC_HTTP deps/http-parser/*.c) + FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h) ENDIF() ENDIF() @@ -148,7 +148,7 @@ ELSE() MESSAGE( "zlib was not found; using bundled 3rd-party sources." ) INCLUDE_DIRECTORIES(deps/zlib) ADD_DEFINITIONS(-DNO_VIZ -DSTDC -DNO_GZIP) - FILE(GLOB SRC_ZLIB deps/zlib/*.c) + FILE(GLOB SRC_ZLIB deps/zlib/*.c deps/zlib/*.h) ENDIF() IF(NOT LIBSSH2_LIBRARY) @@ -285,19 +285,19 @@ ENDIF() ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64) # Collect sourcefiles -FILE(GLOB SRC_H include/git2/*.h) +FILE(GLOB SRC_H include/git2.h include/git2/*.h include/git2/sys/*.h) # On Windows use specific platform sources IF (WIN32 AND NOT CYGWIN) ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501) - FILE(GLOB SRC_OS src/win32/*.c) + FILE(GLOB SRC_OS src/win32/*.c src/win32/*.h) ELSEIF (AMIGA) ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R) - FILE(GLOB SRC_OS src/amiga/*.c) + FILE(GLOB SRC_OS src/amiga/*.c src/amiga/*.h) ELSE() - FILE(GLOB SRC_OS src/unix/*.c) + FILE(GLOB SRC_OS src/unix/*.c src/unix/*.h) ENDIF() -FILE(GLOB SRC_GIT2 src/*.c src/transports/*.c src/xdiff/*.c) +FILE(GLOB SRC_GIT2 src/*.c src/*.h src/transports/*.c src/transports/*.h src/xdiff/*.c src/xdiff/*.h) # Determine architecture of the machine IF (CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -309,7 +309,7 @@ ELSE() ENDIF() # Compile and link libgit2 -ADD_LIBRARY(git2 ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1} ${WIN_RC}) +ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1} ${WIN_RC}) TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES}) TARGET_LINK_LIBRARIES(git2 ${SSH_LIBRARIES}) TARGET_OS_LIBRARIES(git2) @@ -359,7 +359,7 @@ IF (BUILD_CLAR) ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\") INCLUDE_DIRECTORIES(${CLAR_PATH}) - FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c) + FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h) SET(SRC_CLAR "${CLAR_PATH}/main.c" "${CLAR_PATH}/clar_libgit2.c" "${CLAR_PATH}/clar.c") ADD_CUSTOM_COMMAND( @@ -373,7 +373,7 @@ IF (BUILD_CLAR) ${CLAR_PATH}/clar.c PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite) - ADD_EXECUTABLE(libgit2_clar ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1}) + ADD_EXECUTABLE(libgit2_clar ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1}) TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES}) TARGET_LINK_LIBRARIES(libgit2_clar ${SSH_LIBRARIES}) @@ -409,7 +409,7 @@ IF (TAGS) ENDIF () IF (BUILD_EXAMPLES) - FILE(GLOB_RECURSE EXAMPLE_SRC examples/network/*.c) + FILE(GLOB_RECURSE EXAMPLE_SRC examples/network/*.c examples/network/*.h) ADD_EXECUTABLE(cgit2 ${EXAMPLE_SRC}) IF(WIN32) TARGET_LINK_LIBRARIES(cgit2 git2) From b3a559ddce5e23d7084546ca771fc16e96d24ed8 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 13 Jul 2013 13:55:03 +0200 Subject: [PATCH 125/367] submodule: Fix memory leaks --- src/submodule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/submodule.c b/src/submodule.c index b5dacc42e..b4e917561 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -798,7 +798,7 @@ static void submodule_update_from_head_data( static int submodule_update_head(git_submodule *submodule) { git_tree *head = NULL; - git_tree_entry *te; + git_tree_entry *te = NULL; submodule->flags = submodule->flags & ~(GIT_SUBMODULE_STATUS_IN_HEAD | @@ -811,6 +811,7 @@ static int submodule_update_head(git_submodule *submodule) else submodule_update_from_head_data(submodule, te->attr, &te->oid); + git_tree_entry_free(te); git_tree_free(head); return 0; } From d6cb13d7436793718f103687fe95d0f881487ad0 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 13 Jul 2013 14:00:05 +0200 Subject: [PATCH 126/367] tests: Fix memory leak --- tests-clar/diff/pathspec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests-clar/diff/pathspec.c b/tests-clar/diff/pathspec.c index 4334a8901..7b15ea04c 100644 --- a/tests-clar/diff/pathspec.c +++ b/tests-clar/diff/pathspec.c @@ -89,4 +89,5 @@ void test_diff_pathspec__0(void) git_tree_free(a); git_tree_free(b); + git_pathspec_free(ps); } From 80fd31faf773f3f50e2b5547b7171063f38dac17 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 13 Jul 2013 13:30:23 +0200 Subject: [PATCH 127/367] revparse: Don't return a reference when asked for a git object Fix #1722 --- src/revparse.c | 13 ++++++++++++ tests-clar/refs/revparse.c | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/revparse.c b/src/revparse.c index bcfb0843f..d21f08b53 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -685,6 +685,8 @@ int revparse__ext( git_reference *reference = NULL; git_object *base_rev = NULL; + bool should_return_reference = true; + assert(object_out && reference_out && repo && spec); *object_out = NULL; @@ -693,6 +695,8 @@ int revparse__ext( while (spec[pos]) { switch (spec[pos]) { case '^': + should_return_reference = false; + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) goto cleanup; @@ -725,6 +729,8 @@ int revparse__ext( { git_object *temp_object = NULL; + should_return_reference = false; + if ((error = extract_how_many(&n, spec, &pos)) < 0) goto cleanup; @@ -743,6 +749,8 @@ int revparse__ext( { git_object *temp_object = NULL; + should_return_reference = false; + if ((error = extract_path(&buf, spec, &pos)) < 0) goto cleanup; @@ -807,6 +815,11 @@ int revparse__ext( if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) goto cleanup; + if (!should_return_reference) { + git_reference_free(reference); + reference = NULL; + } + *object_out = base_rev; *reference_out = reference; *identifier_len_out = identifier_len; diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c index 69d92745c..9657054de 100644 --- a/tests-clar/refs/revparse.c +++ b/tests-clar/refs/revparse.c @@ -738,4 +738,45 @@ void test_refs_revparse__ext_can_expand_short_reference_names(void) "master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "refs/heads/master"); + + test_object_and_ref( + "HEAD", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "refs/heads/master"); + + test_object_and_ref( + "tags/test", + "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", + "refs/tags/test"); +} + +void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_revision(void) +{ + test_object_and_ref( + "HEAD~3", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", + NULL); + + test_object_and_ref( + "HEAD~0", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL); + + test_object_and_ref( + "HEAD^0", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL); + + test_object_and_ref( + "@{-1}@{0}", + "a4a7dce85cf63874e984719f4fdd239f5145052f", + NULL); +} + +void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_tree_content(void) +{ + test_object_and_ref( + "tags/test:readme.txt", + "0266163a49e280c4f5ed1e08facd36a2bd716bcf", + NULL); } From d6d34cd0f49621c6f5ea992820c4564f5e968b73 Mon Sep 17 00:00:00 2001 From: crazymaster Date: Sat, 13 Jul 2013 02:10:16 +0900 Subject: [PATCH 128/367] Add test for multi-byte characters --- tests-clar/object/blob/filter.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c index 042bddab7..50b4af4f4 100644 --- a/tests-clar/object/blob/filter.c +++ b/tests-clar/object/blob/filter.c @@ -5,7 +5,7 @@ #include "buf_text.h" static git_repository *g_repo = NULL; -#define NUM_TEST_OBJECTS 8 +#define NUM_TEST_OBJECTS 9 static git_oid g_oids[NUM_TEST_OBJECTS]; static const char *g_raw[NUM_TEST_OBJECTS] = { "", @@ -15,9 +15,10 @@ static const char *g_raw[NUM_TEST_OBJECTS] = { "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", "123\n\000\001\002\003\004abc\255\254\253\r\n", "\xEF\xBB\xBFThis is UTF-8\n", + "\xEF\xBB\xBFã»ã’ã»ã’\r\nã»ã’ã»ã’\r\n", "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" }; -static git_off_t g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, 12 }; +static git_off_t g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, -1, 12 }; static git_buf_text_stats g_stats[NUM_TEST_OBJECTS] = { { 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 2, 0, 6, 0 }, @@ -26,6 +27,7 @@ static git_buf_text_stats g_stats[NUM_TEST_OBJECTS] = { { 0, 0, 4, 4, 1, 31, 0 }, { 0, 1, 1, 2, 1, 9, 5 }, { GIT_BOM_UTF8, 0, 0, 1, 0, 16, 0 }, + { GIT_BOM_UTF8, 0, 2, 2, 2, 27, 0 }, { GIT_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 }, }; static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { @@ -36,6 +38,7 @@ static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 }, { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 }, + { "\xEF\xBB\xBFã»ã’ã»ã’\nã»ã’ã»ã’\n", 0, 29 }, { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } }; From 6550565af387119b080a65d71f77f1261752595b Mon Sep 17 00:00:00 2001 From: crazymaster Date: Sat, 13 Jul 2013 03:02:00 +0900 Subject: [PATCH 129/367] Fix gather_stats --- src/buf_text.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/buf_text.c b/src/buf_text.c index 443454b5f..bc8d04680 100644 --- a/src/buf_text.c +++ b/src/buf_text.c @@ -261,29 +261,34 @@ bool git_buf_text_gather_stats( /* Counting loop */ while (scan < end) { unsigned char c = *scan++; - - if ((c > 0x1F && c < 0x7F) || c > 0x9f) - stats->printable++; - else switch (c) { - case '\0': - stats->nul++; - stats->nonprintable++; - break; - case '\n': - stats->lf++; - break; - case '\r': - stats->cr++; - if (scan < end && *scan == '\n') - stats->crlf++; - break; - case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/ + if (c == '\r') { + stats->cr++; + if (scan < end && *scan == '\n') + stats->crlf++; + continue; + } + if (c == '\n') { + stats->lf++; + continue; + } + if (c == 127) + /* DEL */ + stats->nonprintable++; + else if (c < 32) { + switch (c) { + /* BS, HT, ESC and FF */ + case '\b': case '\t': case '\033': case '\014': stats->printable++; break; + case 0: + stats->nul++; + /* fall through */ default: stats->nonprintable++; - break; } + } + else + stats->printable++; } return (stats->nul > 0 || From 960431c3808a98333aa9642424bcc7fef581b78c Mon Sep 17 00:00:00 2001 From: Andy Lindeman Date: Sun, 14 Jul 2013 17:26:24 -0400 Subject: [PATCH 130/367] Fixes return type documentation --- include/git2/object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2/object.h b/include/git2/object.h index b91b04dba..f74f3dfd1 100644 --- a/include/git2/object.h +++ b/include/git2/object.h @@ -36,7 +36,7 @@ GIT_BEGIN_DECL * @param repo the repository to look up the object * @param id the unique identifier for the object * @param type the type of the object - * @return a reference to the object + * @return 0 or an error code */ GIT_EXTERN(int) git_object_lookup( git_object **object, From a91e4d6b21e141c2abc76b65b2d4c91d5d3e03cc Mon Sep 17 00:00:00 2001 From: crazymaster Date: Mon, 15 Jul 2013 07:19:42 +0900 Subject: [PATCH 131/367] Replace Japanese characters with the encoded hexadecimal values --- tests-clar/object/blob/filter.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c index 50b4af4f4..b5e9bb298 100644 --- a/tests-clar/object/blob/filter.c +++ b/tests-clar/object/blob/filter.c @@ -15,7 +15,7 @@ static const char *g_raw[NUM_TEST_OBJECTS] = { "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", "123\n\000\001\002\003\004abc\255\254\253\r\n", "\xEF\xBB\xBFThis is UTF-8\n", - "\xEF\xBB\xBFã»ã’ã»ã’\r\nã»ã’ã»ã’\r\n", + "\xEF\xBB\xBF0xE30x810x0xBB0xE30x810x920xE30x810x0xBB0xE30x810x92\r\n0xE30x810x0xBB0xE30x810x920xE30x810x0xBB0xE30x810x92\r\n", "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" }; static git_off_t g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, -1, 12 }; @@ -38,7 +38,7 @@ static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 }, { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 }, - { "\xEF\xBB\xBFã»ã’ã»ã’\nã»ã’ã»ã’\n", 0, 29 }, + { "\xEF\xBB\xBF0xE30x810x0xBB0xE30x810x920xE30x810x0xBB0xE30x810x92\n0xE30x810x0xBB0xE30x810x920xE30x810x0xBB0xE30x810x92\n", 0, 29 }, { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } }; From 19bee769d49467704f4d8ee36965c548cb40e3c6 Mon Sep 17 00:00:00 2001 From: crazymaster Date: Mon, 15 Jul 2013 07:39:16 +0900 Subject: [PATCH 132/367] Revert "Replace Japanese characters with the encoded hexadecimal values" This reverts commit a91e4d6b21e141c2abc76b65b2d4c91d5d3e03cc. --- tests-clar/object/blob/filter.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c index b5e9bb298..50b4af4f4 100644 --- a/tests-clar/object/blob/filter.c +++ b/tests-clar/object/blob/filter.c @@ -15,7 +15,7 @@ static const char *g_raw[NUM_TEST_OBJECTS] = { "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", "123\n\000\001\002\003\004abc\255\254\253\r\n", "\xEF\xBB\xBFThis is UTF-8\n", - "\xEF\xBB\xBF0xE30x810x0xBB0xE30x810x920xE30x810x0xBB0xE30x810x92\r\n0xE30x810x0xBB0xE30x810x920xE30x810x0xBB0xE30x810x92\r\n", + "\xEF\xBB\xBFã»ã’ã»ã’\r\nã»ã’ã»ã’\r\n", "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" }; static git_off_t g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, -1, 12 }; @@ -38,7 +38,7 @@ static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 }, { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 }, - { "\xEF\xBB\xBF0xE30x810x0xBB0xE30x810x920xE30x810x0xBB0xE30x810x92\n0xE30x810x0xBB0xE30x810x920xE30x810x0xBB0xE30x810x92\n", 0, 29 }, + { "\xEF\xBB\xBFã»ã’ã»ã’\nã»ã’ã»ã’\n", 0, 29 }, { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } }; From b74d4478df8c6d62c96b3bd067ae1987209583a6 Mon Sep 17 00:00:00 2001 From: crazymaster Date: Mon, 15 Jul 2013 07:41:39 +0900 Subject: [PATCH 133/367] Fix the initial line --- src/buf_text.c | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/buf_text.c b/src/buf_text.c index bc8d04680..472339def 100644 --- a/src/buf_text.c +++ b/src/buf_text.c @@ -261,34 +261,29 @@ bool git_buf_text_gather_stats( /* Counting loop */ while (scan < end) { unsigned char c = *scan++; - if (c == '\r') { - stats->cr++; - if (scan < end && *scan == '\n') - stats->crlf++; - continue; - } - if (c == '\n') { - stats->lf++; - continue; - } - if (c == 127) - /* DEL */ - stats->nonprintable++; - else if (c < 32) { - switch (c) { - /* BS, HT, ESC and FF */ - case '\b': case '\t': case '\033': case '\014': + + if (c > 0x1F && c != 0x7F) + stats->printable++; + else switch (c) { + case '\0': + stats->nul++; + stats->nonprintable++; + break; + case '\n': + stats->lf++; + break; + case '\r': + stats->cr++; + if (scan < end && *scan == '\n') + stats->crlf++; + break; + case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/ stats->printable++; break; - case 0: - stats->nul++; - /* fall through */ default: stats->nonprintable++; + break; } - } - else - stats->printable++; } return (stats->nul > 0 || From 2185dd6f99474b69287e6f3cd2e4a24c3a75155b Mon Sep 17 00:00:00 2001 From: crazymaster Date: Mon, 15 Jul 2013 07:59:04 +0900 Subject: [PATCH 134/367] Fix typo --- tests-clar/object/blob/filter.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c index 50b4af4f4..6293046d3 100644 --- a/tests-clar/object/blob/filter.c +++ b/tests-clar/object/blob/filter.c @@ -15,7 +15,7 @@ static const char *g_raw[NUM_TEST_OBJECTS] = { "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", "123\n\000\001\002\003\004abc\255\254\253\r\n", "\xEF\xBB\xBFThis is UTF-8\n", - "\xEF\xBB\xBFã»ã’ã»ã’\r\nã»ã’ã»ã’\r\n", + "\xEF\xBB\xBF0xE30x810xBB0xE30x810x920xE30x810xBB0xE30x810x92\r\n0xE30x810xBB0xE30x810x920xE30x810xBB0xE30x810x92\r\n", "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" }; static git_off_t g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, -1, 12 }; @@ -38,7 +38,7 @@ static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 }, { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 }, - { "\xEF\xBB\xBFã»ã’ã»ã’\nã»ã’ã»ã’\n", 0, 29 }, + { "\xEF\xBB\xBF0xE30x810xBB0xE30x810x920xE30x810xBB0xE30x810x92\n0xE30x810xBB0xE30x810x920xE30x810xBB0xE30x810x92\n", 0, 29 }, { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } }; From d0b25d9dff363976eea92509c359ca8e08aaebb5 Mon Sep 17 00:00:00 2001 From: crazymaster Date: Mon, 15 Jul 2013 08:14:00 +0900 Subject: [PATCH 135/367] Fix --- tests-clar/object/blob/filter.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c index 6293046d3..2b3954d9c 100644 --- a/tests-clar/object/blob/filter.c +++ b/tests-clar/object/blob/filter.c @@ -15,7 +15,7 @@ static const char *g_raw[NUM_TEST_OBJECTS] = { "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", "123\n\000\001\002\003\004abc\255\254\253\r\n", "\xEF\xBB\xBFThis is UTF-8\n", - "\xEF\xBB\xBF0xE30x810xBB0xE30x810x920xE30x810xBB0xE30x810x92\r\n0xE30x810xBB0xE30x810x920xE30x810xBB0xE30x810x92\r\n", + "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n", "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" }; static git_off_t g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, -1, 12 }; @@ -38,7 +38,7 @@ static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 }, { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 }, - { "\xEF\xBB\xBF0xE30x810xBB0xE30x810x920xE30x810xBB0xE30x810x92\n0xE30x810xBB0xE30x810x920xE30x810xBB0xE30x810x92\n", 0, 29 }, + { "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n", 0, 29 }, { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } }; From 9146f1e57ec4f2b6fa293c78d54f1383464ff5be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Duraffort?= Date: Mon, 15 Jul 2013 15:59:18 +0200 Subject: [PATCH 136/367] repository: clarify assignment and test order --- src/repository.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/repository.c b/src/repository.c index bd7ef5476..99ac56ef9 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1500,12 +1500,12 @@ int git_repository_is_empty(git_repository *repo) if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) return -1; - if (!(error = git_reference_type(head) == GIT_REF_SYMBOLIC)) + if (!((error = git_reference_type(head)) == GIT_REF_SYMBOLIC)) goto cleanup; - if (!(error = strcmp( + if (!(error = (strcmp( git_reference_symbolic_target(head), - GIT_REFS_HEADS_DIR "master") == 0)) + GIT_REFS_HEADS_DIR "master") == 0))) goto cleanup; error = repo_contains_no_reference(repo); From 8d6ef4bf78cc5d3a3cb277ecc4fcf0fdcdbc9f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Duraffort?= Date: Mon, 15 Jul 2013 15:59:35 +0200 Subject: [PATCH 137/367] index: fix potential memory leaks --- src/index.c | 12 +++++++++--- src/indexer.c | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/index.c b/src/index.c index bd5e192f3..0610eb5b9 100644 --- a/src/index.c +++ b/src/index.c @@ -1409,14 +1409,18 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 || !endptr || endptr == buffer || *endptr || - (unsigned)tmp > UINT_MAX) + (unsigned)tmp > UINT_MAX) { + index_entry_reuc_free(lost); return index_error_invalid("reading reuc entry stage"); + } lost->mode[i] = tmp; len = (endptr + 1) - buffer; - if (size <= len) + if (size <= len) { + index_entry_reuc_free(lost); return index_error_invalid("reading reuc entry stage"); + } size -= len; buffer += len; @@ -1426,8 +1430,10 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) for (i = 0; i < 3; i++) { if (!lost->mode[i]) continue; - if (size < 20) + if (size < 20) { + index_entry_reuc_free(lost); return index_error_invalid("reading reuc entry oid"); + } git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); size -= 20; diff --git a/src/indexer.c b/src/indexer.c index 1b638cd8a..09f962934 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -325,7 +325,7 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent /* FIXME: Parse the object instead of hashing it */ if (git_odb__hashobj(&oid, obj) < 0) { giterr_set(GITERR_INDEXER, "Failed to hash object"); - return -1; + goto on_error; } pentry = git__calloc(1, sizeof(struct git_pack_entry)); From 050af8bbe08b7cab7bfce044dcb51fb61ff1dc41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Duraffort?= Date: Mon, 15 Jul 2013 16:00:00 +0200 Subject: [PATCH 138/367] pack: fix memory leak in error path --- src/pack-objects.c | 4 +++- src/pack.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pack-objects.c b/src/pack-objects.c index 500104c55..7f427e3bd 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -505,8 +505,10 @@ static git_pobject **compute_write_order(git_packbuilder *pb) /* * Mark objects that are at the tip of tags. */ - if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) + if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) { + git__free(wo); return NULL; + } /* * Give the objects in the original recency order until diff --git a/src/pack.c b/src/pack.c index 7ce7099e0..497db38e8 100644 --- a/src/pack.c +++ b/src/pack.c @@ -329,8 +329,10 @@ static int pack_index_open(struct git_pack_file *p) memcpy(idx_name, p->pack_name, base_len); memcpy(idx_name + base_len, ".idx", sizeof(".idx")); - if ((error = git_mutex_lock(&p->lock)) < 0) + if ((error = git_mutex_lock(&p->lock)) < 0) { + git__free(idx_name); return error; + } if (p->index_version == -1) error = pack_index_check(idx_name, p); From c6451624c4631ccf039d0f3d5af6fbf4364f001c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Duraffort?= Date: Mon, 15 Jul 2013 16:00:07 +0200 Subject: [PATCH 139/367] Fix some more memory leaks in error path --- src/merge.c | 4 +++- src/odb.c | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/merge.c b/src/merge.c index 82d2e6f37..2e94ce1cd 100644 --- a/src/merge.c +++ b/src/merge.c @@ -1902,8 +1902,10 @@ static int write_merge_msg( entries = git__calloc(heads_len, sizeof(struct merge_msg_entry)); GITERR_CHECK_ALLOC(entries); - if (git_vector_init(&matching, heads_len, NULL) < 0) + if (git_vector_init(&matching, heads_len, NULL) < 0) { + git__free(entries); return -1; + } for (i = 0; i < heads_len; i++) entries[i].merge_head = heads[i]; diff --git a/src/odb.c b/src/odb.c index 8e62efd00..23eb4e12e 100644 --- a/src/odb.c +++ b/src/odb.c @@ -232,6 +232,7 @@ int git_odb__hashlink(git_oid *out, const char *path) link_data[size] = '\0'; if (read_len != (ssize_t)size) { giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path); + git__free(link_data); return -1; } From 85e1eded6a5302b25c339988a6828aa45fbc23d8 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Mon, 15 Jul 2013 16:31:25 +0200 Subject: [PATCH 140/367] Add `git_remote_owner` --- include/git2/remote.h | 8 ++++++++ src/remote.c | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/include/git2/remote.h b/include/git2/remote.h index 45d15d0a3..13b04367c 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -95,6 +95,14 @@ GIT_EXTERN(int) git_remote_load(git_remote **out, git_repository *repo, const ch */ GIT_EXTERN(int) git_remote_save(const git_remote *remote); +/** + * Get the remote's repository + * + * @param remote the remote + * @return a pointer to the repository + */ +GIT_EXTERN(git_repository *) git_remote_owner(const git_remote *remote); + /** * Get the remote's name * diff --git a/src/remote.c b/src/remote.c index 0e8354a11..158f3e938 100644 --- a/src/remote.c +++ b/src/remote.c @@ -467,6 +467,12 @@ const char *git_remote_name(const git_remote *remote) return remote->name; } +git_repository *git_remote_owner(const git_remote *remote) +{ + assert(remote); + return remote->repo; +} + const char *git_remote_url(const git_remote *remote) { assert(remote); From 51b0397a66336cd9b45f5562c0fb147052c2e086 Mon Sep 17 00:00:00 2001 From: Andy Lindeman Date: Mon, 15 Jul 2013 23:40:57 -0400 Subject: [PATCH 141/367] Small grammar fix in docs --- include/git2/tree.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2/tree.h b/include/git2/tree.h index 65d8cc16e..f1e7d0899 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -208,7 +208,7 @@ GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry); GIT_EXTERN(int) git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2); /** - * Convert a tree entry to the git_object it points too. + * Convert a tree entry to the git_object it points to. * * You must call `git_object_free()` on the object when you are done with it. * From e49dc6872d53dfa09d8a93dc5733328f2bd3204d Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 17 Jul 2013 14:06:31 -0700 Subject: [PATCH 142/367] Switch default calling convention to cdecl. --- CMakeLists.txt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1086005c3..2afa1e03d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,16 +30,13 @@ OPTION( ENABLE_TRACE "Enables tracing support" OFF ) OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF ) IF(MSVC) - # This option is only availalbe when building with MSVC. By default, - # libgit2 is build using the stdcall calling convention, as that's what - # the CLR expects by default and how the Windows API is built. + # This option is only availalbe when building with MSVC. By default, libgit2 + # is build using the cdecl calling convention, which is useful if you're + # writing C. However, the CLR and Win32 API both expect stdcall. # - # If you are writing a C or C++ program and want to link to libgit2, you - # have to either: - # - Add /Gz to the compiler options of _your_ program / library. - # - Turn this off by invoking CMake with the "-DSTDCALL=Off" argument. - # - OPTION( STDCALL "Build libgit2 with the __stdcall convention" ON ) + # If you are writing a CLR program and want to link to libgit2, you'll want + # to turn this on by invoking CMake with the "-DSTDCALL=ON" argument. + OPTION( STDCALL "Build libgit2 with the __stdcall convention" OFF ) # This option must match the settings used in your program, in particular if you # are linking statically From d55bed1a253a9f914521c29b9a2d2517a79c034d Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 17 Jul 2013 16:55:00 -0500 Subject: [PATCH 143/367] don't include ignored as rename candidates --- src/diff_tform.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/diff_tform.c b/src/diff_tform.c index b137bd319..ac5356a8c 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -590,11 +590,13 @@ static bool is_rename_target( return false; case GIT_DELTA_UNTRACKED: - case GIT_DELTA_IGNORED: if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED)) return false; break; + case GIT_DELTA_IGNORED: + return false; + default: /* all other status values should be checked */ break; } From 275d8d55b29c83bd8c165f5eeaafd76c7e0d966b Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Thu, 18 Jul 2013 09:37:59 -0700 Subject: [PATCH 144/367] Typo --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2afa1e03d..c937ba93c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ OPTION( ENABLE_TRACE "Enables tracing support" OFF ) OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF ) IF(MSVC) - # This option is only availalbe when building with MSVC. By default, libgit2 + # This option is only available when building with MSVC. By default, libgit2 # is build using the cdecl calling convention, which is useful if you're # writing C. However, the CLR and Win32 API both expect stdcall. # From 3e3d332b4c3642ecd33d83f234f97be6c8aee449 Mon Sep 17 00:00:00 2001 From: Martin Woodward Date: Fri, 19 Jul 2013 18:04:11 +0100 Subject: [PATCH 145/367] Tidy up the methods of contacting the project Updated the methods of getting involved with the project and asking questions. --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a2a18765a..3e08ac3ea 100644 --- a/README.md +++ b/README.md @@ -11,20 +11,23 @@ libgit2 is licensed under a **very permissive license** (GPLv2 with a special Li This basically means that you can link it (unmodified) with any kind of software without having to release its source code. -* Mailing list: ~~~~ - The libgit2 mailing list has - traditionally been hosted in Librelist, but Librelist is and has always - been a shitshow. We encourage you to [open an issue](https://github.com/libgit2/libgit2/issues) - on GitHub instead for any questions regarding the library. - * Archives: * Website: +* StackOverflow Tag: [libgit2](http://stackoverflow.com/questions/tagged/libgit2) +* Issues: * API documentation: * IRC: #libgit2 on irc.freenode.net. +* Mailing list: The libgit2 mailing list was + traditionally hosted in Librelist but has been deprecated. We encourage you to + [use StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) or [open an issue](https://github.com/libgit2/libgit2/issues) + on GitHub instead for any questions regarding the library. The mailing list archives are still available at + . + What It Can Do ================================== -libgit2 is already very usable. +libgit2 is already very usable and is being used in production for many applications including the GitHub.com site, in Plastic SCM +and also powering Microsoft's Visual Studio tools for Git. The library provides: * SHA conversions, formatting and shortening * abstracted ODB backend system @@ -128,8 +131,8 @@ Here are the bindings to libgit2 that are currently available: * Lua * luagit2 * .NET - * libgit2net, low level bindings * libgit2sharp + * libgit2net, low level bindings superceeded by libgit2sharp * Node.js * node-gitteh * nodegit From 6ca83665c77599f10914ae48e3f1952333d72827 Mon Sep 17 00:00:00 2001 From: Martin Woodward Date: Fri, 19 Jul 2013 18:20:58 +0100 Subject: [PATCH 146/367] Update contributing guidance to explain PR flow Updating the contributing guidance to explain a bit more about how we use PR's --- CONTRIBUTING.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28ef27f42..5c2eaec5e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,6 +48,12 @@ Please include a nice description of your changes with your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change merged in. +If you are working on a particular area then feel free to submit a PR that +highlights your work in progress (and flag in the PR title that it's not +ready to merge). This will help in getting visibility for your fix, allow +others to comment early on the changes and also let others know that you +are currently working on something. + ## Porting Code From Other Open-Source Projects `libgit2` is licensed under the terms of the GPL v2 with a linking @@ -57,14 +63,17 @@ The most common case is porting code from core Git. Git is a pure GPL project, which means that in order to port code to this project, we need the explicit permission of the author. Check the [`git.git-authors`](https://github.com/libgit2/libgit2/blob/development/git.git-authors) -file for authors who have already consented; feel free to add someone if -you've obtained their consent. +file for authors who have already consented. Other licenses have other requirements; check the license of the library you're porting code *from* to see what you need to do. As a general rule, MIT and BSD (3-clause) licenses are typically no problem. Apache 2.0 license typically doesn't work due to GPL incompatibility. +If you are pulling in code from core Git, another project or code you've pulled from +a forum / Stack Overflow then please flag this in your PR and also make sure you've +given proper credit to the original author in the code snippet. + ## Style Guide `libgit2` is written in [ANSI C](http://en.wikipedia.org/wiki/ANSI_C) From 41a93cc6e5adb17cab0281ac49996d02f94baa9d Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 19 Jul 2013 12:43:08 -0500 Subject: [PATCH 147/367] Clarify when to use github issues Suggest that github issues are to be used for bug reports, while questions about usage should be directed to StackOverflow. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3e08ac3ea..611d6f579 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,9 @@ release its source code. * IRC: #libgit2 on irc.freenode.net. * Mailing list: The libgit2 mailing list was traditionally hosted in Librelist but has been deprecated. We encourage you to - [use StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) or [open an issue](https://github.com/libgit2/libgit2/issues) - on GitHub instead for any questions regarding the library. The mailing list archives are still available at + [use StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) instead for any questions regarding + the library, and to or [open an issue](https://github.com/libgit2/libgit2/issues) + on GitHub for bug reports. The mailing list archives are still available at . From bef59b1be4ec9754959423d36138731577a4f413 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 19 Jul 2013 12:56:47 -0500 Subject: [PATCH 148/367] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 611d6f579..a89463b7c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ release its source code. * Mailing list: The libgit2 mailing list was traditionally hosted in Librelist but has been deprecated. We encourage you to [use StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) instead for any questions regarding - the library, and to or [open an issue](https://github.com/libgit2/libgit2/issues) + the library, or [open an issue](https://github.com/libgit2/libgit2/issues) on GitHub for bug reports. The mailing list archives are still available at . From b71071313f4800840ecc48cb18e5cedec8ef250e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 22 Jul 2013 11:01:19 -0700 Subject: [PATCH 149/367] git_reference_next_name must match git_reference_next The git_reference_next API silently skips invalid references when scanning the loose refs. The git_reference_next_name API should skip the same ones even though it isn't creating the reference object. This adds a test with a an invalid loose reference and makes sure that both APIs skip the same entries and generate the same results. --- src/refdb_fs.c | 16 +++++++++++++--- tests-clar/revwalk/basic.c | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index b9e283ac5..2f1a5b26c 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -452,6 +452,9 @@ static int loose_lookup( git_buf ref_file = GIT_BUF_INIT; int error = 0; + if (out) + *out = NULL; + error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL); if (error < 0) @@ -465,15 +468,17 @@ static int loose_lookup( goto done; } - *out = git_reference__alloc_symbolic(ref_name, target); + if (out) + *out = git_reference__alloc_symbolic(ref_name, target); } else { if ((error = loose_parse_oid(&oid, ref_name, &ref_file)) < 0) goto done; - *out = git_reference__alloc(ref_name, &oid, NULL); + if (out) + *out = git_reference__alloc(ref_name, &oid, NULL); } - if (*out == NULL) + if (out && *out == NULL) error = -1; done: @@ -679,6 +684,11 @@ static int refdb_fs_backend__iterator_next_name( if (git_strmap_exists(packfile, path)) continue; + if (loose_lookup(NULL, backend, path) != 0) { + giterr_clear(); + continue; + } + *out = path; return 0; } diff --git a/tests-clar/revwalk/basic.c b/tests-clar/revwalk/basic.c index e82776260..7c2fc000d 100644 --- a/tests-clar/revwalk/basic.c +++ b/tests-clar/revwalk/basic.c @@ -142,6 +142,28 @@ void test_revwalk_basic__glob_heads(void) cl_assert(i == 14); } +void test_revwalk_basic__glob_heads_with_invalid(void) +{ + int i; + git_oid oid; + + test_revwalk_basic__cleanup(); + + _repo = cl_git_sandbox_init("testrepo"); + cl_git_mkfile("testrepo/.git/refs/heads/garbage", "not-a-ref"); + + cl_git_pass(git_revwalk_new(&_walk, _repo)); + cl_git_pass(git_revwalk_push_glob(_walk, "heads")); + + for (i = 0; !git_revwalk_next(&oid, _walk); ++i) + /* walking */; + + /* git log --branches --oneline | wc -l => 16 */ + cl_assert_equal_i(16, i); + + cl_fixture_cleanup("testrepo"); +} + void test_revwalk_basic__push_head(void) { int i = 0; From c77342ef1c956f99f84f217359c73e8de65cdd1c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 22 Jul 2013 11:20:34 -0700 Subject: [PATCH 150/367] Use pool for loose refdb string allocations Instead of using lots of strdup calls, this adds a memory pool to the loose refs iteration code and uses it for keeping track of the loose refs array. Memory usage could probably be reduced even further by eliminating the vector and just scanning by adding the strlen of each ref, but that would be a more intrusive changes. This also updates the error handling to be more thorough about checking for failed allocations, etc. --- src/refdb_fs.c | 55 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 2f1a5b26c..acd82594b 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -560,7 +560,10 @@ typedef struct { git_reference_iterator parent; char *glob; + + git_pool pool; git_vector loose; + unsigned int loose_pos; khiter_t packed_pos; } refdb_fs_iter; @@ -568,24 +571,18 @@ typedef struct { static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) { refdb_fs_iter *iter = (refdb_fs_iter *) _iter; - char *loose_path; - size_t i; - - git_vector_foreach(&iter->loose, i, loose_path) { - git__free(loose_path); - } git_vector_free(&iter->loose); - - git__free(iter->glob); + git_pool_clear(&iter->pool); git__free(iter); } static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) { + int error = 0; git_strmap *packfile = backend->refcache.packfile; git_buf path = GIT_BUF_INIT; - git_iterator *fsit; + git_iterator *fsit = NULL; const git_index_entry *entry = NULL; if (!backend->path) /* do nothing if no path for loose refs */ @@ -594,15 +591,16 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) if (git_buf_printf(&path, "%s/refs", backend->path) < 0) return -1; - if (git_iterator_for_filesystem(&fsit, git_buf_cstr(&path), 0, NULL, NULL) < 0) - return -1; - - git_vector_init(&iter->loose, 8, NULL); - git_buf_sets(&path, GIT_REFS_DIR); + if ((error = git_iterator_for_filesystem( + &fsit, git_buf_cstr(&path), 0, NULL, NULL)) < 0 || + (error = git_vector_init(&iter->loose, 8, NULL)) < 0 || + (error = git_buf_sets(&path, GIT_REFS_DIR)) < 0) + goto cleanup; while (!git_iterator_advance(&entry, fsit)) { const char *ref_name; khiter_t pos; + char *ref_dup; git_buf_truncate(&path, strlen(GIT_REFS_DIR)); git_buf_puts(&path, entry->path); @@ -618,9 +616,16 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) ref->flags |= PACKREF_SHADOWED; } - git_vector_insert(&iter->loose, git__strdup(ref_name)); + if (!(ref_dup = git_pool_strdup(&iter->pool, ref_name))) { + error = -1; + goto cleanup; + } + + if ((error = git_vector_insert(&iter->loose, ref_dup)) < 0) + goto cleanup; } +cleanup: git_iterator_free(fsit); git_buf_free(&path); @@ -727,20 +732,26 @@ static int refdb_fs_backend__iterator( iter = git__calloc(1, sizeof(refdb_fs_iter)); GITERR_CHECK_ALLOC(iter); - if (glob != NULL) - iter->glob = git__strdup(glob); + if (git_pool_init(&iter->pool, 1, 0) < 0) + goto fail; + + if (glob != NULL && + (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) + goto fail; iter->parent.next = refdb_fs_backend__iterator_next; iter->parent.next_name = refdb_fs_backend__iterator_next_name; iter->parent.free = refdb_fs_backend__iterator_free; - if (iter_load_loose_paths(backend, iter) < 0) { - refdb_fs_backend__iterator_free((git_reference_iterator *)iter); - return -1; - } + if (iter_load_loose_paths(backend, iter) < 0) + goto fail; *out = (git_reference_iterator *)iter; return 0; + +fail: + refdb_fs_backend__iterator_free((git_reference_iterator *)iter); + return -1; } static bool ref_is_available( @@ -792,7 +803,7 @@ static int reference_path_available( return -1; } }); - + return 0; } From 989710d9828ef0f90b494903bd1e1bd8b20a8914 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 22 Jul 2013 11:22:55 -0700 Subject: [PATCH 151/367] Fix warning message about mismatched types --- src/fileops.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index db53d4fce..c01ff64db 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -630,13 +630,11 @@ static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = { int git_futils_dirs_global_init(void) { git_futils_dir_t i; - git_buf *path; + const git_buf *path; int error = 0; - for (i = 0; i < GIT_FUTILS_DIR__MAX; i++) { - if ((error = git_futils_dirs_get(&path, i)) < 0) - break; - } + for (i = 0; !error && i < GIT_FUTILS_DIR__MAX; i++) + error = git_futils_dirs_get(&path, i); return error; } From 4cee9b8618b949f55233f2350654d771a0dc2ece Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 22 Jul 2013 11:41:23 -0700 Subject: [PATCH 152/367] Update init and clean for revwalk::basic tests The new tests don't always want to use the same fixture data as the old ones so this makes it configurable on a per-test basis. --- tests-clar/revwalk/basic.c | 50 +++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/tests-clar/revwalk/basic.c b/tests-clar/revwalk/basic.c index 7c2fc000d..cb8fcb9c7 100644 --- a/tests-clar/revwalk/basic.c +++ b/tests-clar/revwalk/basic.c @@ -98,27 +98,46 @@ static int test_walk(git_revwalk *walk, const git_oid *root, return test_walk_only(walk, possible_results, results_count); } -static git_repository *_repo; -static git_revwalk *_walk; +static git_repository *_repo = NULL; +static git_revwalk *_walk = NULL; +static const char *_fixture = NULL; void test_revwalk_basic__initialize(void) { - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_revwalk_new(&_walk, _repo)); } void test_revwalk_basic__cleanup(void) { git_revwalk_free(_walk); - _walk = NULL; - git_repository_free(_repo); + + if (_fixture) + cl_git_sandbox_cleanup(); + else + git_repository_free(_repo); + + _fixture = NULL; _repo = NULL; + _walk = NULL; +} + +static void revwalk_basic_setup_walk(const char *fixture) +{ + if (fixture) { + _fixture = fixture; + _repo = cl_git_sandbox_init(fixture); + } else { + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + } + + cl_git_pass(git_revwalk_new(&_walk, _repo)); } void test_revwalk_basic__sorting_modes(void) { git_oid id; + revwalk_basic_setup_walk(NULL); + git_oid_fromstr(&id, commit_head); cl_git_pass(test_walk(_walk, &id, GIT_SORT_TIME, commit_sorting_time, 1)); @@ -132,6 +151,8 @@ void test_revwalk_basic__glob_heads(void) int i = 0; git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_revwalk_push_glob(_walk, "heads")); while (git_revwalk_next(&oid, _walk) == 0) { @@ -147,12 +168,9 @@ void test_revwalk_basic__glob_heads_with_invalid(void) int i; git_oid oid; - test_revwalk_basic__cleanup(); + revwalk_basic_setup_walk("testrepo"); - _repo = cl_git_sandbox_init("testrepo"); cl_git_mkfile("testrepo/.git/refs/heads/garbage", "not-a-ref"); - - cl_git_pass(git_revwalk_new(&_walk, _repo)); cl_git_pass(git_revwalk_push_glob(_walk, "heads")); for (i = 0; !git_revwalk_next(&oid, _walk); ++i) @@ -160,8 +178,6 @@ void test_revwalk_basic__glob_heads_with_invalid(void) /* git log --branches --oneline | wc -l => 16 */ cl_assert_equal_i(16, i); - - cl_fixture_cleanup("testrepo"); } void test_revwalk_basic__push_head(void) @@ -169,6 +185,8 @@ void test_revwalk_basic__push_head(void) int i = 0; git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_revwalk_push_head(_walk)); while (git_revwalk_next(&oid, _walk) == 0) { @@ -184,6 +202,8 @@ void test_revwalk_basic__push_head_hide_ref(void) int i = 0; git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_revwalk_push_head(_walk)); cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test")); @@ -200,6 +220,8 @@ void test_revwalk_basic__push_head_hide_ref_nobase(void) int i = 0; git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_revwalk_push_head(_walk)); cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed")); @@ -215,12 +237,16 @@ void test_revwalk_basic__disallow_non_commit(void) { git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_oid_fromstr(&oid, "521d87c1ec3aef9824daf6d96cc0ae3710766d91")); cl_git_fail(git_revwalk_push(_walk, &oid)); } void test_revwalk_basic__push_range(void) { + revwalk_basic_setup_walk(NULL); + git_revwalk_reset(_walk); git_revwalk_sorting(_walk, 0); cl_git_pass(git_revwalk_push_range(_walk, "9fd738e~2..9fd738e")); From b4a4cf24a539ce07d86fed6835c98154fb40e723 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 22 Jul 2013 16:07:56 -0700 Subject: [PATCH 153/367] Add git_diff_patch_size() API This adds a new API to get the size in bytes of the diffs in a git_diff_patch object. --- include/git2/diff.h | 18 ++++++++++++++++++ src/diff_patch.c | 22 ++++++++++++++++++++-- tests-clar/diff/patch.c | 19 +++++++++++++++---- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 71a8b72bf..711967501 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -942,6 +942,24 @@ GIT_EXTERN(int) git_diff_patch_get_line_in_hunk( size_t hunk_idx, size_t line_of_hunk); +/** + * Look up size of patch diff data in bytes + * + * This returns the raw size of the patch data. This only includes the + * actual data from the lines of the diff, not the file or hunk headers. + * + * If you pass `include_context` as true (non-zero), this will be the size + * of all of the diff output; if you pass it as false (zero), this will + * only include the actual changed lines (as if `context_lines` was 0). + * + * @param patch A git_diff_patch representing changes to one file + * @param include_context Include context lines in size if non-zero + * @return The number of bytes of data + */ +GIT_EXTERN(size_t) git_diff_patch_size( + git_diff_patch *patch, + int include_context); + /** * Serialize the patch to text via callback. * diff --git a/src/diff_patch.c b/src/diff_patch.c index 1b4adac03..5febc883c 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -42,7 +42,7 @@ struct git_diff_patch { git_array_t(diff_patch_hunk) hunks; git_array_t(diff_patch_line) lines; size_t oldno, newno; - size_t content_size; + size_t content_size, context_size; git_pool flattened; }; @@ -806,6 +806,20 @@ notfound: return diff_error_outofrange(thing); } +size_t git_diff_patch_size(git_diff_patch *patch, int include_context) +{ + size_t out; + + assert(patch); + + out = patch->content_size; + + if (!include_context) + out -= patch->context_size; + + return out; +} + git_diff_list *git_diff_patch__diff(git_diff_patch *patch) { return patch->diff; @@ -934,7 +948,11 @@ static int diff_patch_line_cb( line->len = content_len; line->origin = line_origin; - patch->content_size += content_len; + patch->content_size += content_len + 1; /* +1 for line_origin */ + + if (line_origin == GIT_DIFF_LINE_CONTEXT || + line_origin == GIT_DIFF_LINE_CONTEXT_EOFNL) + patch->context_size += content_len + 1; /* do some bookkeeping so we can provide old/new line numbers */ diff --git a/tests-clar/diff/patch.c b/tests-clar/diff/patch.c index 3f14a0de7..51baadf2e 100644 --- a/tests-clar/diff/patch.c +++ b/tests-clar/diff/patch.c @@ -128,6 +128,9 @@ void test_diff_patch__to_string(void) cl_assert_equal_s(expected, text); + cl_assert_equal_sz(31, git_diff_patch_size(patch, 0)); + cl_assert_equal_sz(31, git_diff_patch_size(patch, 1)); + git__free(text); git_diff_patch_free(patch); git_diff_list_free(diff); @@ -409,6 +412,7 @@ void test_diff_patch__hunks_have_correct_line_numbers(void) static void check_single_patch_stats( git_repository *repo, size_t hunks, size_t adds, size_t dels, size_t ctxt, + size_t size_with_context, size_t size_without_context, const char *expected) { git_diff_list *diff; @@ -439,6 +443,13 @@ static void check_single_patch_stats( git__free(text); } + if (size_with_context) + cl_assert_equal_sz( + size_with_context, git_diff_patch_size(patch, 1)); + if (size_without_context) + cl_assert_equal_sz( + size_without_context, git_diff_patch_size(patch, 0)); + /* walk lines in hunk with basic sanity checks */ for (; hunks > 0; --hunks) { size_t i, max_i; @@ -495,14 +506,14 @@ void test_diff_patch__line_counts_with_eofnl(void) git_buf_consume(&content, end); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL); + check_single_patch_stats(g_repo, 1, 0, 1, 3, 0, 0, NULL); /* remove trailing whitespace */ git_buf_rtrim(&content); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL); + check_single_patch_stats(g_repo, 2, 1, 2, 6, 0, 0, NULL); /* add trailing whitespace */ @@ -514,7 +525,7 @@ void test_diff_patch__line_counts_with_eofnl(void) cl_git_pass(git_buf_putc(&content, '\n')); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL); + check_single_patch_stats(g_repo, 1, 1, 1, 3, 0, 0, NULL); /* no trailing whitespace as context line */ @@ -537,7 +548,7 @@ void test_diff_patch__line_counts_with_eofnl(void) cl_git_rewritefile("renames/songof7cities.txt", content.ptr); check_single_patch_stats( - g_repo, 1, 1, 1, 6, + g_repo, 1, 1, 1, 6, 349, 115, /* below is pasted output of 'git diff' with fn context removed */ "diff --git a/songof7cities.txt b/songof7cities.txt\n" "index 378a7d9..3d0154e 100644\n" From c05a55b056509d1146ab55ab1351298789f00751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 23 Jul 2013 09:40:19 +0200 Subject: [PATCH 154/367] Clean up some documentation clang's docparser highlighted these. --- include/git2/notes.h | 4 ++-- include/git2/pack.h | 4 ++-- include/git2/sys/index.h | 2 -- include/git2/sys/refs.h | 4 ++-- include/git2/transport.h | 2 +- src/fileops.h | 8 ++++---- src/path.h | 2 -- src/pqueue.h | 6 ++---- 8 files changed, 13 insertions(+), 19 deletions(-) diff --git a/include/git2/notes.h b/include/git2/notes.h index 7382904ad..76361633b 100644 --- a/include/git2/notes.h +++ b/include/git2/notes.h @@ -99,7 +99,7 @@ GIT_EXTERN(int) git_note_read( /** * Get the note message * - * @param note + * @param note the note * @return the note message */ GIT_EXTERN(const char *) git_note_message(const git_note *note); @@ -108,7 +108,7 @@ GIT_EXTERN(const char *) git_note_message(const git_note *note); /** * Get the note object OID * - * @param note + * @param note the note * @return the note object OID */ GIT_EXTERN(const git_oid *) git_note_oid(const git_note *note); diff --git a/include/git2/pack.h b/include/git2/pack.h index cc1f48add..976e39cb4 100644 --- a/include/git2/pack.h +++ b/include/git2/pack.h @@ -137,7 +137,7 @@ GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, git_packbuilder_for * Get the total number of objects the packbuilder will write out * * @param pb the packbuilder - * @return + * @return the number of objects in the packfile */ GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb); @@ -145,7 +145,7 @@ GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb); * Get the number of objects the packbuilder has already written out * * @param pb the packbuilder - * @return + * @return the number of objects which have already been written */ GIT_EXTERN(uint32_t) git_packbuilder_written(git_packbuilder *pb); diff --git a/include/git2/sys/index.h b/include/git2/sys/index.h index a32e07036..1a06a4df1 100644 --- a/include/git2/sys/index.h +++ b/include/git2/sys/index.h @@ -72,7 +72,6 @@ GIT_EXTERN(int) git_index_name_add(git_index *index, * Remove all filename conflict entries. * * @param index an existing index object - * @return 0 or an error code */ GIT_EXTERN(void) git_index_name_clear(git_index *index); @@ -168,7 +167,6 @@ GIT_EXTERN(int) git_index_reuc_remove(git_index *index, size_t n); * Remove all resolve undo entries from the index * * @param index an existing index object - * @return 0 or an error code */ GIT_EXTERN(void) git_index_reuc_clear(git_index *index); diff --git a/include/git2/sys/refs.h b/include/git2/sys/refs.h index 85963258c..dd95ca12c 100644 --- a/include/git2/sys/refs.h +++ b/include/git2/sys/refs.h @@ -16,7 +16,7 @@ * * @param name the reference name * @param oid the object id for a direct reference - * @param symbolic the target for a symbolic reference + * @param peel the first non-tag object's OID, or NULL * @return the created git_reference or NULL on error */ GIT_EXTERN(git_reference *) git_reference__alloc( @@ -28,7 +28,7 @@ GIT_EXTERN(git_reference *) git_reference__alloc( * Create a new symbolic reference. * * @param name the reference name - * @param symbolic the target for a symbolic reference + * @param target the target for a symbolic reference * @return the created git_reference or NULL on error */ GIT_EXTERN(git_reference *) git_reference__alloc_symbolic( diff --git a/include/git2/transport.h b/include/git2/transport.h index 21061fc78..1cc200eb4 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -110,7 +110,7 @@ GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new( * @param out The newly created credential object. * @param publickey The bytes of the public key. * @param publickey_len The length of the public key in bytes. - * @param sign_callback The callback method for authenticating. + * @param sign_fn The callback method for authenticating. * @param sign_data The abstract data sent to the sign_callback method. * @return 0 for success or an error code for failure */ diff --git a/src/fileops.h b/src/fileops.h index d23ebaffb..5adedfc57 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -244,7 +244,7 @@ extern mode_t git_futils_canonical_mode(mode_t raw_mode); * @param out buffer to populate with the mapping information. * @param fd open descriptor to configure the mapping from. * @param begin first byte to map, this should be page aligned. - * @param end number of bytes to map. + * @param len number of bytes to map. * @return * - 0 on success; * - -1 on error. @@ -278,7 +278,7 @@ extern void git_futils_mmap_free(git_map *map); /** * Find a "global" file (i.e. one in a user's home directory). * - * @param pathbuf buffer to write the full path into + * @param path 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 */ @@ -287,7 +287,7 @@ 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 path 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 */ @@ -296,7 +296,7 @@ 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 path 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 */ diff --git a/src/path.h b/src/path.h index ead4fa338..b2899e97f 100644 --- a/src/path.h +++ b/src/path.h @@ -175,7 +175,6 @@ extern bool git_path_contains(git_buf *dir, const char *item); * * @param parent Directory path that might contain subdir * @param subdir Subdirectory name to look for in parent - * @param append_if_exists If true, then subdir will be appended to the parent path if it does exist * @return true if subdirectory exists, false otherwise. */ extern bool git_path_contains_dir(git_buf *parent, const char *subdir); @@ -185,7 +184,6 @@ extern bool git_path_contains_dir(git_buf *parent, const char *subdir); * * @param dir Directory path that might contain file * @param file File name to look for in parent - * @param append_if_exists If true, then file will be appended to the path if it does exist * @return true if file exists, false otherwise. */ extern bool git_path_contains_file(git_buf *dir, const char *file); diff --git a/src/pqueue.h b/src/pqueue.h index ed7139285..9061f8279 100644 --- a/src/pqueue.h +++ b/src/pqueue.h @@ -48,7 +48,7 @@ typedef struct { * should be preallocated * @param cmppri the callback function to compare two nodes of the queue * - * @Return the handle or NULL for insufficent memory + * @return the handle or NULL for insufficent memory */ int git_pqueue_init(git_pqueue *q, size_t n, git_pqueue_cmp cmppri); @@ -83,8 +83,7 @@ int git_pqueue_insert(git_pqueue *q, void *d); /** * pop the highest-ranking item from the queue. - * @param p the queue - * @param d where to copy the entry to + * @param q the queue * @return NULL on error, otherwise the entry */ void *git_pqueue_pop(git_pqueue *q); @@ -93,7 +92,6 @@ void *git_pqueue_pop(git_pqueue *q); /** * access highest-ranking item without removing it. * @param q the queue - * @param d the entry * @return NULL on error, otherwise the entry */ void *git_pqueue_peek(git_pqueue *q); From 64061d4a14f77fdcf6ecc038ea6e9b02f14e03a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 23 Jul 2013 10:51:14 +0200 Subject: [PATCH 155/367] remote: fix git_remote_download() documentation The description of what the function does hasn't been true for quite a while. Change it to reflect the way it currently works. While here, remove an even older comment about missing features that have been implemented. --- include/git2/remote.h | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/include/git2/remote.h b/include/git2/remote.h index 13b04367c..fa8b378c6 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -25,13 +25,6 @@ GIT_BEGIN_DECL typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload); -/* - * TODO: This functions still need to be implemented: - * - _listcb/_foreach - * - _add - * - _rename - * - _del (needs support from config) - */ /** * Add a remote with the default fetch refspec to the repository's configuration. This @@ -255,13 +248,14 @@ GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction); GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload); /** - * Download the packfile + * Download and index the packfile * - * Negotiate what objects should be downloaded and download the - * packfile with those objects. The packfile is downloaded with a - * temporary filename, as it's final name is not known yet. If there - * was no packfile needed (all the objects were available locally), - * filename will be NULL and the function will return success. + * Connect to the remote if it hasn't been done yet, negotiate with + * the remote git which objects are missing, download and index the + * packfile. + * + * The .idx file will be created and both it and the packfile with be + * renamed to their final name. * * @param remote the remote to download from * @param progress_cb function to call with progress information. Be aware that From 197b8966dba18770e4b77a17173c8f354ac175e3 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 23 Jul 2013 14:34:31 -0700 Subject: [PATCH 156/367] Add hunk/file headers to git_diff_patch_size This allows git_diff_patch_size to account for hunk headers and file headers in the returned size. This required some refactoring of the code that is used to print file headers so that it could be invoked by the git_diff_patch_size API. Also this increases the test coverage and fixes an off-by-one bug in the size calculation when newline changes happen at the end of the file. --- include/git2/diff.h | 8 +++- src/diff.h | 7 ++++ src/diff_patch.c | 49 ++++++++++++++++++------ src/diff_print.c | 85 ++++++++++++++++++++++------------------- tests-clar/diff/patch.c | 66 ++++++++++++++++++-------------- 5 files changed, 133 insertions(+), 82 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 711967501..269eb773d 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -391,7 +391,7 @@ typedef enum { */ GIT_DIFF_LINE_FILE_HDR = 'F', GIT_DIFF_LINE_HUNK_HDR = 'H', - GIT_DIFF_LINE_BINARY = 'B' + GIT_DIFF_LINE_BINARY = 'B' /**< Deprecated, will not be returned */ } git_diff_line_t; /** @@ -954,11 +954,15 @@ GIT_EXTERN(int) git_diff_patch_get_line_in_hunk( * * @param patch A git_diff_patch representing changes to one file * @param include_context Include context lines in size if non-zero + * @param include_hunk_headers Include hunk header lines if non-zero + * @param include_file_headers Include file header lines if non-zero * @return The number of bytes of data */ GIT_EXTERN(size_t) git_diff_patch_size( git_diff_patch *patch, - int include_context); + int include_context, + int include_hunk_headers, + int include_file_headers); /** * Serialize the patch to text via callback. diff --git a/src/diff.h b/src/diff.h index d09a130bc..ff480324f 100644 --- a/src/diff.h +++ b/src/diff.h @@ -81,6 +81,13 @@ extern const char *git_diff_delta__path(const git_diff_delta *delta); extern bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta); +extern int git_diff_delta__format_file_header( + git_buf *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + int oid_strlen); + extern int git_diff__oid_for_file( git_repository *, const char *, uint16_t, git_off_t, git_oid *); diff --git a/src/diff_patch.c b/src/diff_patch.c index 5febc883c..6f8ea2d3d 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -42,7 +42,7 @@ struct git_diff_patch { git_array_t(diff_patch_hunk) hunks; git_array_t(diff_patch_line) lines; size_t oldno, newno; - size_t content_size, context_size; + size_t content_size, context_size, header_size; git_pool flattened; }; @@ -806,7 +806,11 @@ notfound: return diff_error_outofrange(thing); } -size_t git_diff_patch_size(git_diff_patch *patch, int include_context) +size_t git_diff_patch_size( + git_diff_patch *patch, + int include_context, + int include_hunk_headers, + int include_file_headers) { size_t out; @@ -817,6 +821,21 @@ size_t git_diff_patch_size(git_diff_patch *patch, int include_context) if (!include_context) out -= patch->context_size; + if (include_hunk_headers) + out += patch->header_size; + + if (include_file_headers) { + git_buf file_header = GIT_BUF_INIT; + + if (git_diff_delta__format_file_header( + &file_header, patch->delta, NULL, NULL, 0) < 0) + giterr_clear(); + else + out += git_buf_len(&file_header); + + git_buf_free(&file_header); + } + return out; } @@ -914,6 +933,8 @@ static int diff_patch_hunk_cb( hunk->header[header_len] = '\0'; hunk->header_len = header_len; + patch->header_size += header_len; + hunk->line_start = git_array_size(patch->lines); hunk->line_count = 0; @@ -934,6 +955,7 @@ static int diff_patch_line_cb( git_diff_patch *patch = payload; diff_patch_hunk *hunk; diff_patch_line *line; + const char *content_end = content + content_len; GIT_UNUSED(delta); GIT_UNUSED(range); @@ -948,38 +970,43 @@ static int diff_patch_line_cb( line->len = content_len; line->origin = line_origin; - patch->content_size += content_len + 1; /* +1 for line_origin */ - - if (line_origin == GIT_DIFF_LINE_CONTEXT || - line_origin == GIT_DIFF_LINE_CONTEXT_EOFNL) - patch->context_size += content_len + 1; - /* do some bookkeeping so we can provide old/new line numbers */ - for (line->lines = 0; content_len > 0; --content_len) { + line->lines = 0; + while (content < content_end) if (*content++ == '\n') ++line->lines; - } + + patch->content_size += content_len; switch (line_origin) { case GIT_DIFF_LINE_ADDITION: + patch->content_size += 1; case GIT_DIFF_LINE_DEL_EOFNL: line->oldno = -1; line->newno = patch->newno; patch->newno += line->lines; break; case GIT_DIFF_LINE_DELETION: + patch->content_size += 1; case GIT_DIFF_LINE_ADD_EOFNL: line->oldno = patch->oldno; line->newno = -1; patch->oldno += line->lines; break; - default: + case GIT_DIFF_LINE_CONTEXT: + patch->content_size += 1; + patch->context_size += 1; + case GIT_DIFF_LINE_CONTEXT_EOFNL: + patch->context_size += content_len; line->oldno = patch->oldno; line->newno = patch->newno; patch->oldno += line->lines; patch->newno += line->lines; break; + default: + assert(false); + break; } hunk->line_count++; diff --git a/src/diff_print.c b/src/diff_print.c index 0de548813..cdb813176 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -198,13 +198,13 @@ int git_diff_print_raw( return error; } -static int diff_print_oid_range(diff_print_info *pi, const git_diff_delta *delta) +static int diff_print_oid_range( + git_buf *out, const git_diff_delta *delta, int oid_strlen) { - git_buf *out = pi->buf; char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; - git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); - git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid); + git_oid_tostr(start_oid, oid_strlen, &delta->old_file.oid); + git_oid_tostr(end_oid, oid_strlen, &delta->new_file.oid); /* TODO: Match git diff more closely */ if (delta->old_file.mode == delta->new_file.mode) { @@ -228,35 +228,29 @@ static int diff_print_oid_range(diff_print_info *pi, const git_diff_delta *delta return 0; } -static int diff_print_patch_file( - const git_diff_delta *delta, float progress, void *data) +int git_diff_delta__format_file_header( + git_buf *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + int oid_strlen) { - diff_print_info *pi = data; - const char *oldpfx = pi->diff ? pi->diff->opts.old_prefix : NULL; const char *oldpath = delta->old_file.path; - const char *newpfx = pi->diff ? pi->diff->opts.new_prefix : NULL; const char *newpath = delta->new_file.path; - uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL; - - GIT_UNUSED(progress); - - if (S_ISDIR(delta->new_file.mode) || - delta->status == GIT_DELTA_UNMODIFIED || - delta->status == GIT_DELTA_IGNORED || - (delta->status == GIT_DELTA_UNTRACKED && - (opts_flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)) - return 0; if (!oldpfx) oldpfx = DIFF_OLD_PREFIX_DEFAULT; if (!newpfx) newpfx = DIFF_NEW_PREFIX_DEFAULT; + if (!oid_strlen) + oid_strlen = GIT_ABBREV_DEFAULT + 1; - git_buf_clear(pi->buf); - git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", - oldpfx, delta->old_file.path, newpfx, delta->new_file.path); + git_buf_clear(out); - if (diff_print_oid_range(pi, delta) < 0) + git_buf_printf(out, "diff --git %s%s %s%s\n", + oldpfx, oldpath, newpfx, newpath); + + if (diff_print_oid_range(out, delta, oid_strlen) < 0) return -1; if (git_oid_iszero(&delta->old_file.oid)) { @@ -269,31 +263,42 @@ static int diff_print_patch_file( } if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) { - git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath); - git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath); + git_buf_printf(out, "--- %s%s\n", oldpfx, oldpath); + git_buf_printf(out, "+++ %s%s\n", newpfx, newpath); + } else { + git_buf_printf( + out, "Binary files %s%s and %s%s differ\n", + oldpfx, oldpath, newpfx, newpath); } - if (git_buf_oom(pi->buf)) + return git_buf_oom(out) ? -1 : 0; +} + +static int diff_print_patch_file( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + const char *oldpfx = pi->diff ? pi->diff->opts.old_prefix : NULL; + const char *newpfx = pi->diff ? pi->diff->opts.new_prefix : NULL; + uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL; + + GIT_UNUSED(progress); + + if (S_ISDIR(delta->new_file.mode) || + delta->status == GIT_DELTA_UNMODIFIED || + delta->status == GIT_DELTA_IGNORED || + (delta->status == GIT_DELTA_UNTRACKED && + (opts_flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)) + return 0; + + if (git_diff_delta__format_file_header( + pi->buf, delta, oldpfx, newpfx, pi->oid_strlen) < 0) return -1; if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) return callback_error(); - if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) - return 0; - - git_buf_clear(pi->buf); - git_buf_printf( - pi->buf, "Binary files %s%s and %s%s differ\n", - oldpfx, oldpath, newpfx, newpath); - if (git_buf_oom(pi->buf)) - return -1; - - if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) - return callback_error(); - return 0; } diff --git a/tests-clar/diff/patch.c b/tests-clar/diff/patch.c index 51baadf2e..6a33fa990 100644 --- a/tests-clar/diff/patch.c +++ b/tests-clar/diff/patch.c @@ -128,8 +128,10 @@ void test_diff_patch__to_string(void) cl_assert_equal_s(expected, text); - cl_assert_equal_sz(31, git_diff_patch_size(patch, 0)); - cl_assert_equal_sz(31, git_diff_patch_size(patch, 1)); + cl_assert_equal_sz(31, git_diff_patch_size(patch, 0, 0, 0)); + cl_assert_equal_sz(31, git_diff_patch_size(patch, 1, 0, 0)); + cl_assert_equal_sz(31 + 16, git_diff_patch_size(patch, 1, 1, 0)); + cl_assert_equal_sz(strlen(expected), git_diff_patch_size(patch, 1, 1, 1)); git__free(text); git_diff_patch_free(patch); @@ -411,8 +413,7 @@ void test_diff_patch__hunks_have_correct_line_numbers(void) static void check_single_patch_stats( git_repository *repo, size_t hunks, - size_t adds, size_t dels, size_t ctxt, - size_t size_with_context, size_t size_without_context, + size_t adds, size_t dels, size_t ctxt, size_t *sizes, const char *expected) { git_diff_list *diff; @@ -441,14 +442,19 @@ static void check_single_patch_stats( cl_git_pass(git_diff_patch_to_str(&text, patch)); cl_assert_equal_s(expected, text); git__free(text); + + cl_assert_equal_sz( + strlen(expected), git_diff_patch_size(patch, 1, 1, 1)); } - if (size_with_context) - cl_assert_equal_sz( - size_with_context, git_diff_patch_size(patch, 1)); - if (size_without_context) - cl_assert_equal_sz( - size_without_context, git_diff_patch_size(patch, 0)); + if (sizes) { + if (sizes[0]) + cl_assert_equal_sz(sizes[0], git_diff_patch_size(patch, 0, 0, 0)); + if (sizes[1]) + cl_assert_equal_sz(sizes[1], git_diff_patch_size(patch, 1, 0, 0)); + if (sizes[2]) + cl_assert_equal_sz(sizes[2], git_diff_patch_size(patch, 1, 1, 0)); + } /* walk lines in hunk with basic sanity checks */ for (; hunks > 0; --hunks) { @@ -492,6 +498,23 @@ void test_diff_patch__line_counts_with_eofnl(void) git_buf content = GIT_BUF_INIT; const char *end; git_index *index; + const char *expected = + /* below is pasted output of 'git diff' with fn context removed */ + "diff --git a/songof7cities.txt b/songof7cities.txt\n" + "index 378a7d9..3d0154e 100644\n" + "--- a/songof7cities.txt\n" + "+++ b/songof7cities.txt\n" + "@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n" + " \n" + " To the sound of trumpets shall their seed restore my Cities\n" + " Wealthy and well-weaponed, that once more may I behold\n" + "-All the world go softly when it walks before my Cities,\n" + "+#All the world go softly when it walks before my Cities,\n" + " And the horses and the chariots fleeing from them as of old!\n" + " \n" + " -- Rudyard Kipling\n" + "\\ No newline at end of file\n"; + size_t expected_sizes[3] = { 115, 119 + 115 + 114, 119 + 115 + 114 + 71 }; g_repo = cl_git_sandbox_init("renames"); @@ -506,14 +529,14 @@ void test_diff_patch__line_counts_with_eofnl(void) git_buf_consume(&content, end); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - check_single_patch_stats(g_repo, 1, 0, 1, 3, 0, 0, NULL); + check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL, NULL); /* remove trailing whitespace */ git_buf_rtrim(&content); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - check_single_patch_stats(g_repo, 2, 1, 2, 6, 0, 0, NULL); + check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL, NULL); /* add trailing whitespace */ @@ -525,7 +548,7 @@ void test_diff_patch__line_counts_with_eofnl(void) cl_git_pass(git_buf_putc(&content, '\n')); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - check_single_patch_stats(g_repo, 1, 1, 1, 3, 0, 0, NULL); + check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL, NULL); /* no trailing whitespace as context line */ @@ -548,22 +571,7 @@ void test_diff_patch__line_counts_with_eofnl(void) cl_git_rewritefile("renames/songof7cities.txt", content.ptr); check_single_patch_stats( - g_repo, 1, 1, 1, 6, 349, 115, - /* below is pasted output of 'git diff' with fn context removed */ - "diff --git a/songof7cities.txt b/songof7cities.txt\n" - "index 378a7d9..3d0154e 100644\n" - "--- a/songof7cities.txt\n" - "+++ b/songof7cities.txt\n" - "@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n" - " \n" - " To the sound of trumpets shall their seed restore my Cities\n" - " Wealthy and well-weaponed, that once more may I behold\n" - "-All the world go softly when it walks before my Cities,\n" - "+#All the world go softly when it walks before my Cities,\n" - " And the horses and the chariots fleeing from them as of old!\n" - " \n" - " -- Rudyard Kipling\n" - "\\ No newline at end of file\n"); + g_repo, 1, 1, 1, 6, expected_sizes, expected); git_buf_free(&content); git_config_free(cfg); From df40f3981c312b03415e388663176b2a8315221a Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 23 Jul 2013 15:18:28 -0700 Subject: [PATCH 157/367] Make compact output more like core Git --- src/diff_print.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diff_print.c b/src/diff_print.c index cdb813176..f427baa36 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -98,12 +98,12 @@ static int diff_print_one_compact( if (delta->old_file.path != delta->new_file.path && strcomp(delta->old_file.path,delta->new_file.path) != 0) - git_buf_printf(out, "%c\t%s%c -> %s%c\n", code, + git_buf_printf(out, "%c\t%s%c %s%c\n", code, delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); else if (delta->old_file.mode != delta->new_file.mode && delta->old_file.mode != 0 && delta->new_file.mode != 0) - git_buf_printf(out, "%c\t%s%c (%o -> %o)\n", code, - delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode); + git_buf_printf(out, "%c\t%s%c %s%c\n", code, + delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); else if (old_suffix != ' ') git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); else From eb1c1707ab6a399734d9083152c05516af052412 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 23 Jul 2013 15:45:58 -0700 Subject: [PATCH 158/367] Restore GIT_DIFF_LINE_BINARY usage This restores the usage of GIT_DIFF_LINE_BINARY for the diff output line that reads "Binary files x and y differ" so that it can be optionally colorized independently of the file header. --- include/git2/diff.h | 2 +- src/diff_print.c | 75 +++++++++++++++++++++++++++++---------------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 269eb773d..c989ba4ee 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -391,7 +391,7 @@ typedef enum { */ GIT_DIFF_LINE_FILE_HDR = 'F', GIT_DIFF_LINE_HUNK_HDR = 'H', - GIT_DIFF_LINE_BINARY = 'B' /**< Deprecated, will not be returned */ + GIT_DIFF_LINE_BINARY = 'B' /**< For "Binary files x and y differ" */ } git_diff_line_t; /** diff --git a/src/diff_print.c b/src/diff_print.c index f427baa36..4ddd72443 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -228,31 +228,16 @@ static int diff_print_oid_range( return 0; } -int git_diff_delta__format_file_header( +static int diff_delta_format_with_paths( git_buf *out, const git_diff_delta *delta, const char *oldpfx, const char *newpfx, - int oid_strlen) + const char *template) { const char *oldpath = delta->old_file.path; const char *newpath = delta->new_file.path; - if (!oldpfx) - oldpfx = DIFF_OLD_PREFIX_DEFAULT; - if (!newpfx) - newpfx = DIFF_NEW_PREFIX_DEFAULT; - if (!oid_strlen) - oid_strlen = GIT_ABBREV_DEFAULT + 1; - - git_buf_clear(out); - - git_buf_printf(out, "diff --git %s%s %s%s\n", - oldpfx, oldpath, newpfx, newpath); - - if (diff_print_oid_range(out, delta, oid_strlen) < 0) - return -1; - if (git_oid_iszero(&delta->old_file.oid)) { oldpfx = ""; oldpath = "/dev/null"; @@ -262,14 +247,34 @@ int git_diff_delta__format_file_header( newpath = "/dev/null"; } - if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) { - git_buf_printf(out, "--- %s%s\n", oldpfx, oldpath); - git_buf_printf(out, "+++ %s%s\n", newpfx, newpath); - } else { - git_buf_printf( - out, "Binary files %s%s and %s%s differ\n", - oldpfx, oldpath, newpfx, newpath); - } + return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath); +} + +int git_diff_delta__format_file_header( + git_buf *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + int oid_strlen) +{ + if (!oldpfx) + oldpfx = DIFF_OLD_PREFIX_DEFAULT; + if (!newpfx) + newpfx = DIFF_NEW_PREFIX_DEFAULT; + if (!oid_strlen) + oid_strlen = GIT_ABBREV_DEFAULT + 1; + + git_buf_clear(out); + + git_buf_printf(out, "diff --git %s%s %s%s\n", + oldpfx, delta->old_file.path, newpfx, delta->new_file.path); + + if (diff_print_oid_range(out, delta, oid_strlen) < 0) + return -1; + + if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) + diff_delta_format_with_paths( + out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n"); return git_buf_oom(out) ? -1 : 0; } @@ -278,8 +283,10 @@ static int diff_print_patch_file( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; - const char *oldpfx = pi->diff ? pi->diff->opts.old_prefix : NULL; - const char *newpfx = pi->diff ? pi->diff->opts.new_prefix : NULL; + const char *oldpfx = + pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT; + const char *newpfx = + pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT; uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL; GIT_UNUSED(progress); @@ -299,6 +306,20 @@ static int diff_print_patch_file( git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) return callback_error(); + if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) + return 0; + + git_buf_clear(pi->buf); + + if (diff_delta_format_with_paths( + pi->buf, delta, oldpfx, newpfx, + "Binary files %s%s and %s%s differ\n") < 0) + return -1; + + if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY, + git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + return callback_error(); + return 0; } From 39a1a66242a480b880032f5a6a4e31ee77414d4c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 24 Jul 2013 13:09:07 -0700 Subject: [PATCH 159/367] Don't unload diff data unless loaded --- src/diff_file.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/diff_file.c b/src/diff_file.c index 9d06daafa..19bcf2d45 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -417,6 +417,9 @@ int git_diff_file_content__load(git_diff_file_content *fc) void git_diff_file_content__unload(git_diff_file_content *fc) { + if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0) + return; + if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) { git__free(fc->map.data); fc->map.data = ""; From 69c66b554e1072d8b6c63366834e15310fecaea7 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 24 Jul 2013 13:09:33 -0700 Subject: [PATCH 160/367] Don't do text diff unless content will be used --- src/diff_patch.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/diff_patch.c b/src/diff_patch.c index 1b4adac03..02a45cb1a 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -230,6 +230,10 @@ static int diff_patch_generate(git_diff_patch *patch, git_diff_output *output) if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) return 0; + /* if we are not looking at the hunks and lines, don't do the diff */ + if (!output->hunk_cb && !output->data_cb) + return 0; + if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 && (error = diff_patch_load(patch, output)) < 0) return error; From 18e9efc425bd617655782188056fe43ecdc673e4 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 24 Jul 2013 13:10:16 -0700 Subject: [PATCH 161/367] Don't check rename if file size difference is huge --- src/diff_tform.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/diff_tform.c b/src/diff_tform.c index ac5356a8c..ab43a3a14 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -446,6 +446,8 @@ static int similarity_calc( } blobsize = git_blob_rawsize(blob); + if (!file->size) + file->size = blobsize; if (!git__is_sizet(blobsize)) /* ? what to do ? */ blobsize = (size_t)-1; @@ -510,6 +512,13 @@ static int similarity_measure( return 0; } + /* check if file sizes too small or nowhere near each other */ + if (a_file->size > 127 && + b_file->size > 127 && + (a_file->size > (b_file->size << 4) || + b_file->size > (a_file->size << 4))) + return 0; + /* update signature cache if needed */ if (!cache[a_idx] && similarity_calc(diff, opts, a_idx, cache) < 0) return -1; From 427cc255dffc1a44f1e9a289067012370f3cc38d Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 24 Jul 2013 13:11:11 -0700 Subject: [PATCH 162/367] Use local variables in hash calc to avoid aliasing --- src/hashsig.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/hashsig.c b/src/hashsig.c index ab8d8b3f0..a11c4bee7 100644 --- a/src/hashsig.c +++ b/src/hashsig.c @@ -43,8 +43,8 @@ struct git_hashsig { int considered; }; -#define HEAP_LCHILD_OF(I) (((I)*2)+1) -#define HEAP_RCHILD_OF(I) (((I)*2)+2) +#define HEAP_LCHILD_OF(I) (((I)<<1)+1) +#define HEAP_RCHILD_OF(I) (((I)<<1)+2) #define HEAP_PARENT_OF(I) (((I)-1)>>1) static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp) @@ -152,8 +152,9 @@ static void hashsig_initial_window( hashsig_in_progress *prog) { hashsig_state state, shift_n; - int win_len; + int win_len, saw_lf = prog->saw_lf; const char *scan, *end; + char *window = &prog->window[0]; /* init until we have processed at least HASHSIG_HASH_WINDOW data */ @@ -170,7 +171,7 @@ static void hashsig_initial_window( while (scan < end && win_len < HASHSIG_HASH_WINDOW) { char ch = *scan++; - if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf)) + if (!hashsig_include_char(ch, sig->opt, &saw_lf)) continue; state = (state * HASHSIG_HASH_SHIFT + ch) & HASHSIG_HASH_MASK; @@ -180,7 +181,7 @@ static void hashsig_initial_window( else shift_n = (shift_n * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK; - prog->window[win_len++] = ch; + window[win_len++] = ch; } /* insert initial hash if we just finished */ @@ -194,6 +195,7 @@ static void hashsig_initial_window( prog->state = state; prog->win_len = win_len; prog->shift_n = shift_n; + prog->saw_lf = saw_lf; *data = scan; } @@ -206,22 +208,26 @@ static int hashsig_add_hashes( { const char *scan = data, *end = data + size; hashsig_state state, shift_n, rmv; + int win_pos, saw_lf; + char *window = &prog->window[0]; if (prog->win_len < HASHSIG_HASH_WINDOW) hashsig_initial_window(sig, &scan, size, prog); state = prog->state; shift_n = prog->shift_n; + saw_lf = prog->saw_lf; + win_pos = prog->win_pos; /* advance window, adding new chars and removing old */ for (; scan < end; ++scan) { char ch = *scan; - if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf)) + if (!hashsig_include_char(ch, sig->opt, &saw_lf)) continue; - rmv = shift_n * prog->window[prog->win_pos]; + rmv = shift_n * window[win_pos]; state = (state - rmv) & HASHSIG_HASH_MASK; state = (state * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK; @@ -231,11 +237,13 @@ static int hashsig_add_hashes( hashsig_heap_insert(&sig->maxs, (hashsig_t)state); sig->considered++; - prog->window[prog->win_pos] = ch; - prog->win_pos = (prog->win_pos + 1) % HASHSIG_HASH_WINDOW; + window[win_pos] = ch; + win_pos = (win_pos + 1) % HASHSIG_HASH_WINDOW; } - prog->state = state; + prog->state = state; + prog->saw_lf = saw_lf; + prog->win_pos = win_pos; return 0; } @@ -296,7 +304,7 @@ int git_hashsig_create_fromfile( const char *path, git_hashsig_option_t opts) { - char buf[4096]; + char buf[0x1000]; ssize_t buflen = 0; int error = 0, fd; hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT; From 397357a0480b341fce5e93d3f485504f2ae94082 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 24 Jul 2013 13:12:00 -0700 Subject: [PATCH 163/367] Add rename test that used to be really slow Before the optimization commits, this test used to take about 20 seconds to run on my machine. Afterwards, there is still a couple seconds of data setup, but the actual diff and rename detection runs in a fraction of a second. --- tests-clar/diff/rename.c | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 9efd9281c..ed58b7aa9 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -1122,3 +1122,60 @@ void test_diff_rename__unmodified_can_be_renamed(void) git_index_free(index); git_tree_free(tree); } + +void test_diff_rename__many_files(void) +{ + git_index *index; + git_tree *tree; + git_diff_list *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + git_buf b = GIT_BUF_INIT; + int i, j; + char tmp[64]; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); + + for (i = 0; i < 100; i += 2) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + + for (j = 0; j < i * 128; ++j) + git_buf_printf(&b, "more content %d\n", i); + + cl_git_mkfile(tmp, b.ptr); + cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); + } + git_buf_free(&b); + + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(51, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(52, exp.files); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(50, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(51, exp.files); + + git_diff_list_free(diff); + git_index_free(index); + git_tree_free(tree); +} From f5c4d0225157a6e15fc08f07aa77b5e8f52cdac5 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 24 Jul 2013 13:44:35 -0700 Subject: [PATCH 164/367] Fix incorrect comment --- src/diff_tform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diff_tform.c b/src/diff_tform.c index ab43a3a14..cabcd1f80 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -512,7 +512,7 @@ static int similarity_measure( return 0; } - /* check if file sizes too small or nowhere near each other */ + /* check if file sizes are nowhere near each other */ if (a_file->size > 127 && b_file->size > 127 && (a_file->size > (b_file->size << 4) || From 847c679309378748d2a3ac1c5a49331d2e64d4cf Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 25 Jul 2013 00:26:51 +0200 Subject: [PATCH 165/367] Allow Makefile.embed to be used when cross-compiling This allows libgit2 to be cross-compiled (e.g. when building native rugged binaries for windows from Linux or OS X). ``` CROSS_COMPILE=i686-w64-mingw32 make -f Makefile.embed ``` --- Makefile.embed | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/Makefile.embed b/Makefile.embed index 76b4d3cda..8b8970d76 100644 --- a/Makefile.embed +++ b/Makefile.embed @@ -1,15 +1,33 @@ -PLATFORM=$(shell uname -o) +PLATFORM=$(shell uname -s) + +ifneq (,$(CROSS_COMPILE)) + PREFIX=$(CROSS_COMPILE)- +else + PREFIX= +endif + +WIN32=0 +ifneq (,$(findstring MINGW32,$(PLATFORM))) + WIN32=1 +endif +ifneq (,$(findstring mingw,$(CROSS_COMPILE))) + WIN32=1 +endif rm=rm -f -AR=ar cq -RANLIB=ranlib +AR=$(PREFIX)ar cq +RANLIB=$(PREFIX)ranlib + LIBNAME=libgit2.a -ifeq ($(PLATFORM),Msys) + +ifeq ($(WIN32),1) CC=gcc else CC=cc endif +CC:=$(PREFIX)$(CC) + INCLUDES= -I. -Isrc -Iinclude -Ideps/http-parser -Ideps/zlib DEFINES= $(INCLUDES) -DNO_VIZ -DSTDC -DNO_GZIP -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(EXTRA_DEFINES) @@ -17,7 +35,7 @@ CFLAGS= -g $(DEFINES) -Wall -Wextra -O2 $(EXTRA_CFLAGS) SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) src/hash/hash_generic.c -ifeq ($(PLATFORM),Msys) +ifeq ($(WIN32),1) SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c INCLUDES += -Ideps/regex DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 From a5140f4dda66263a34080b11cfc34a49c9743100 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 24 Jul 2013 17:11:49 -0700 Subject: [PATCH 166/367] Fix rename detection for tree-to-tree diffs The performance improvements I introduced for rename detection were not able to run successfully for tree-to-tree diffs because the blob size was not known early enough and so the file signature always had to be calculated nonetheless. This change separates loading blobs into memory from calculating the signature. I can't avoid having to load the large blobs into memory, but by moving it forward, I'm able to avoid the signature calculation if the blob won't come into play for renames. --- src/diff_tform.c | 149 ++++++++++++++++++++++++++------------- tests-clar/diff/rename.c | 50 ++++++++++++- 2 files changed, 148 insertions(+), 51 deletions(-) diff --git a/src/diff_tform.c b/src/diff_tform.c index cabcd1f80..08b0d5c38 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -408,54 +408,90 @@ GIT_INLINE(git_diff_file *) similarity_get_file(git_diff_list *diff, size_t idx) return (idx & 1) ? &delta->new_file : &delta->old_file; } +typedef struct { + size_t idx; + git_iterator_type_t src; + git_repository *repo; + git_diff_file *file; + git_buf data; + git_blob *blob; + int loaded; +} similarity_info; + +static void similarity_init( + similarity_info *info, git_diff_list *diff, size_t file_idx) +{ + info->idx = file_idx; + info->src = (file_idx & 1) ? diff->new_src : diff->old_src; + info->repo = diff->repo; + info->file = similarity_get_file(diff, file_idx); + info->blob = NULL; + info->loaded = 0; + git_buf_init(&info->data, 0); +} + +static int similarity_load(similarity_info *info) +{ + int error = 0; + git_diff_file *file = info->file; + + if (info->src == GIT_ITERATOR_TYPE_WORKDIR) { + error = git_buf_joinpath( + &info->data, git_repository_workdir(info->repo), file->path); + + /* if path is not a regular file, just skip this item */ + if (!error && !git_path_isfile(info->data.ptr)) + git_buf_free(&info->data); + } else if (git_blob_lookup(&info->blob, info->repo, &file->oid) < 0) { + /* if lookup fails, just skip this item in similarity calc */ + giterr_clear(); + } else { + if (!file->size) + file->size = git_blob_rawsize(info->blob); + assert(file->size == git_blob_rawsize(info->blob)); + + info->data.size = (size_t)(git__is_sizet(file->size) ? file->size : -1); + info->data.ptr = (char *)git_blob_rawcontent(info->blob); + } + + info->loaded = 1; + + return error; +} + +static void similarity_unload(similarity_info *info) +{ + if (info->blob) + git_blob_free(info->blob); + else + git_buf_free(&info->data); + + info->loaded = 0; +} + static int similarity_calc( - git_diff_list *diff, + similarity_info *info, const git_diff_find_options *opts, - size_t file_idx, void **cache) { int error = 0; - git_diff_file *file = similarity_get_file(diff, file_idx); - git_iterator_type_t src = (file_idx & 1) ? diff->new_src : diff->old_src; - if (src == GIT_ITERATOR_TYPE_WORKDIR) { /* compute hashsig from file */ - git_buf path = GIT_BUF_INIT; + if (!info->loaded && (error = similarity_load(info)) < 0) + return error; + if (!info->data.size) + return 0; + + if (info->src == GIT_ITERATOR_TYPE_WORKDIR) { /* TODO: apply wd-to-odb filters to file data if necessary */ - if ((error = git_buf_joinpath( - &path, git_repository_workdir(diff->repo), file->path)) < 0) - return error; - - /* if path is not a regular file, just skip this item */ - if (git_path_isfile(path.ptr)) - error = opts->metric->file_signature( - &cache[file_idx], file, path.ptr, opts->metric->payload); - - 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 (git_blob_lookup(&blob, diff->repo, &file->oid) < 0) { - /* if lookup fails, just skip this item in similarity calc */ - giterr_clear(); - return 0; - } - - blobsize = git_blob_rawsize(blob); - if (!file->size) - file->size = blobsize; - if (!git__is_sizet(blobsize)) /* ? what to do ? */ - blobsize = (size_t)-1; - + error = opts->metric->file_signature( + &cache[info->idx], info->file, + info->data.ptr, opts->metric->payload); + } else { error = opts->metric->buffer_signature( - &cache[file_idx], file, git_blob_rawcontent(blob), - (size_t)blobsize, opts->metric->payload); - - git_blob_free(blob); + &cache[info->idx], info->file, + info->data.ptr, info->data.size, opts->metric->payload); } return error; @@ -478,6 +514,8 @@ static int similarity_measure( git_diff_file *a_file = similarity_get_file(diff, a_idx); git_diff_file *b_file = similarity_get_file(diff, b_idx); bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY); + int error = 0; + similarity_info a_info, b_info; *score = -1; @@ -512,26 +550,39 @@ static int similarity_measure( return 0; } + similarity_init(&a_info, diff, a_idx); + similarity_init(&b_info, diff, b_idx); + + if (!a_file->size && (error = similarity_load(&a_info)) < 0) + goto done; + if (!b_file->size && (error = similarity_load(&b_info)) < 0) + goto done; + /* check if file sizes are nowhere near each other */ if (a_file->size > 127 && b_file->size > 127 && (a_file->size > (b_file->size << 4) || b_file->size > (a_file->size << 4))) - return 0; + goto done; /* update signature cache if needed */ - if (!cache[a_idx] && similarity_calc(diff, opts, a_idx, cache) < 0) - return -1; - if (!cache[b_idx] && similarity_calc(diff, opts, b_idx, cache) < 0) - return -1; + if (!cache[a_idx] && (error = similarity_calc(&a_info, opts, cache)) < 0) + goto done; + if (!cache[b_idx] && (error = similarity_calc(&b_info, opts, cache)) < 0) + goto done; - /* some metrics may not wish to process this file (too big / too small) */ - if (!cache[a_idx] || !cache[b_idx]) - return 0; + /* calculate similarity provided that the metric choose to process + * both the a and b files (some may not if file is too big, etc). + */ + if (cache[a_idx] && cache[b_idx]) + error = opts->metric->similarity( + score, cache[a_idx], cache[b_idx], opts->metric->payload); - /* compare signatures */ - return opts->metric->similarity( - score, cache[a_idx], cache[b_idx], opts->metric->payload); +done: + similarity_unload(&a_info); + similarity_unload(&b_info); + + return error; } static int calc_self_similarity( diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index ed58b7aa9..79c89e362 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -1126,7 +1126,7 @@ void test_diff_rename__unmodified_can_be_renamed(void) void test_diff_rename__many_files(void) { git_index *index; - git_tree *tree; + git_tree *tree, *new_tree; git_diff_list *diff = NULL; diff_expects exp; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; @@ -1176,6 +1176,52 @@ void test_diff_rename__many_files(void) cl_assert_equal_i(51, exp.files); git_diff_list_free(diff); - git_index_free(index); + + { + git_object *parent; + git_signature *sig; + git_oid tree_id, commit_id; + git_reference *ref; + + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_tree_lookup(&new_tree, g_repo, &tree_id)); + + cl_git_pass(git_revparse_ext(&parent, &ref, g_repo, "HEAD")); + cl_git_pass(git_signature_new( + &sig, "Sm Test", "sm@tester.test", 1372350000, 480)); + + cl_git_pass(git_commit_create_v( + &commit_id, g_repo, git_reference_name(ref), sig, sig, + NULL, "yoyoyo", new_tree, 1, parent)); + + git_object_free(parent); + git_reference_free(ref); + git_signature_free(sig); + } + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree, new_tree, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(51, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(52, exp.files); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(50, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(51, exp.files); + + git_diff_list_free(diff); + + git_tree_free(new_tree); git_tree_free(tree); + git_index_free(index); } From effdbeb3239b777e2b19fc4944643fc7f2a768c3 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 24 Jul 2013 17:48:37 -0700 Subject: [PATCH 167/367] Make rename detection file size fix better The previous fix for checking file sizes with rename detection always loads the blob. In this version, if the odb backend can get the object header without loading the whole thing into memory, then we'll just use that, so that we can eliminate possible rename sources & targets without loading them. --- src/diff.h | 29 ++++++++++++ src/diff_file.c | 14 +----- src/diff_tform.c | 118 +++++++++++++++++++++++------------------------ 3 files changed, 90 insertions(+), 71 deletions(-) diff --git a/src/diff.h b/src/diff.h index d09a130bc..d1bec00c6 100644 --- a/src/diff.h +++ b/src/diff.h @@ -16,6 +16,7 @@ #include "iterator.h" #include "repository.h" #include "pool.h" +#include "odb.h" #define DIFF_OLD_PREFIX_DEFAULT "a/" #define DIFF_NEW_PREFIX_DEFAULT "b/" @@ -108,5 +109,33 @@ extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); extern int git_diff_find_similar__calc_similarity( int *score, void *siga, void *sigb, void *payload); +/* + * Sometimes a git_diff_file will have a zero size; this attempts to + * fill in the size without loading the blob if possible. If that is + * not possible, then it will return the git_odb_object that had to be + * loaded and the caller can use it or dispose of it as needed. + */ +GIT_INLINE(int) git_diff_file__resolve_zero_size( + git_diff_file *file, git_odb_object **odb_obj, git_repository *repo) +{ + int error; + git_odb *odb; + size_t len; + git_otype type; + + if ((error = git_repository_odb(&odb, repo)) < 0) + return error; + + error = git_odb__read_header_or_object( + odb_obj, &len, &type, odb, &file->oid); + + git_odb_free(odb); + + if (!error) + file->size = (git_off_t)len; + + return error; +} + #endif diff --git a/src/diff_file.c b/src/diff_file.c index 19bcf2d45..bcfef13cd 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -241,19 +241,9 @@ static int diff_file_content_load_blob(git_diff_file_content *fc) /* if we don't know size, try to peek at object header first */ if (!fc->file->size) { - git_odb *odb; - size_t len; - git_otype type; - - if (!(error = git_repository_odb__weakptr(&odb, fc->repo))) { - error = git_odb__read_header_or_object( - &odb_obj, &len, &type, odb, &fc->file->oid); - git_odb_free(odb); - } - if (error) + if ((error = git_diff_file__resolve_zero_size( + fc->file, &odb_obj, fc->repo)) < 0) return error; - - fc->file->size = len; } if (diff_file_content_binary_by_size(fc)) diff --git a/src/diff_tform.c b/src/diff_tform.c index 08b0d5c38..8fd2a4fe9 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -414,59 +414,26 @@ typedef struct { git_repository *repo; git_diff_file *file; git_buf data; + git_odb_object *odb_obj; git_blob *blob; - int loaded; } similarity_info; -static void similarity_init( +static int similarity_init( similarity_info *info, git_diff_list *diff, size_t file_idx) { info->idx = file_idx; info->src = (file_idx & 1) ? diff->new_src : diff->old_src; info->repo = diff->repo; info->file = similarity_get_file(diff, file_idx); + info->odb_obj = NULL; info->blob = NULL; - info->loaded = 0; git_buf_init(&info->data, 0); -} -static int similarity_load(similarity_info *info) -{ - int error = 0; - git_diff_file *file = info->file; + if (info->file->size > 0) + return 0; - if (info->src == GIT_ITERATOR_TYPE_WORKDIR) { - error = git_buf_joinpath( - &info->data, git_repository_workdir(info->repo), file->path); - - /* if path is not a regular file, just skip this item */ - if (!error && !git_path_isfile(info->data.ptr)) - git_buf_free(&info->data); - } else if (git_blob_lookup(&info->blob, info->repo, &file->oid) < 0) { - /* if lookup fails, just skip this item in similarity calc */ - giterr_clear(); - } else { - if (!file->size) - file->size = git_blob_rawsize(info->blob); - assert(file->size == git_blob_rawsize(info->blob)); - - info->data.size = (size_t)(git__is_sizet(file->size) ? file->size : -1); - info->data.ptr = (char *)git_blob_rawcontent(info->blob); - } - - info->loaded = 1; - - return error; -} - -static void similarity_unload(similarity_info *info) -{ - if (info->blob) - git_blob_free(info->blob); - else - git_buf_free(&info->data); - - info->loaded = 0; + return git_diff_file__resolve_zero_size( + info->file, &info->odb_obj, info->repo); } static int similarity_calc( @@ -475,28 +442,59 @@ static int similarity_calc( void **cache) { int error = 0; - - if (!info->loaded && (error = similarity_load(info)) < 0) - return error; - - if (!info->data.size) - return 0; + git_diff_file *file = info->file; if (info->src == GIT_ITERATOR_TYPE_WORKDIR) { + if ((error = git_buf_joinpath( + &info->data, git_repository_workdir(info->repo), file->path)) < 0) + return error; + + /* if path is not a regular file, just skip this item */ + if (!git_path_isfile(info->data.ptr)) + return 0; + /* TODO: apply wd-to-odb filters to file data if necessary */ error = opts->metric->file_signature( &cache[info->idx], info->file, info->data.ptr, opts->metric->payload); } else { - error = opts->metric->buffer_signature( - &cache[info->idx], info->file, - info->data.ptr, info->data.size, opts->metric->payload); + /* if we didn't initially know the size, we might have an odb_obj + * around from earlier, so convert that, otherwise load the blob now + */ + if (info->odb_obj != NULL) + error = git_object__from_odb_object( + (git_object **)&info->blob, info->repo, + info->odb_obj, GIT_OBJ_BLOB); + else + error = git_blob_lookup(&info->blob, info->repo, &file->oid); + + if (error < 0) { + /* if lookup fails, just skip this item in similarity calc */ + giterr_clear(); + } else { + size_t sz = (size_t)(git__is_sizet(file->size) ? file->size : -1); + + error = opts->metric->buffer_signature( + &cache[info->idx], info->file, + git_blob_rawcontent(info->blob), sz, opts->metric->payload); + } } return error; } +static void similarity_unload(similarity_info *info) +{ + if (info->odb_obj) + git_odb_object_free(info->odb_obj); + + if (info->blob) + git_blob_free(info->blob); + else + git_buf_free(&info->data); +} + #define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0) /* - score < 0 means files cannot be compared @@ -550,26 +548,28 @@ static int similarity_measure( return 0; } - similarity_init(&a_info, diff, a_idx); - similarity_init(&b_info, diff, b_idx); + memset(&a_info, 0, sizeof(a_info)); + memset(&b_info, 0, sizeof(b_info)); - if (!a_file->size && (error = similarity_load(&a_info)) < 0) - goto done; - if (!b_file->size && (error = similarity_load(&b_info)) < 0) - goto done; + /* set up similarity data (will try to update missing file sizes) */ + if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0) + return error; + if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0) + goto cleanup; /* check if file sizes are nowhere near each other */ if (a_file->size > 127 && b_file->size > 127 && (a_file->size > (b_file->size << 4) || b_file->size > (a_file->size << 4))) - goto done; + goto cleanup; /* update signature cache if needed */ if (!cache[a_idx] && (error = similarity_calc(&a_info, opts, cache)) < 0) - goto done; + goto cleanup; + if (!cache[b_idx] && (error = similarity_calc(&b_info, opts, cache)) < 0) - goto done; + goto cleanup; /* calculate similarity provided that the metric choose to process * both the a and b files (some may not if file is too big, etc). @@ -578,7 +578,7 @@ static int similarity_measure( error = opts->metric->similarity( score, cache[a_idx], cache[b_idx], opts->metric->payload); -done: +cleanup: similarity_unload(&a_info); similarity_unload(&b_info); From 3a2d48d5ee48c520fdc6b0cd65e1982db7751352 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Thu, 25 Jul 2013 14:54:19 +0200 Subject: [PATCH 168/367] Close p->mwf.fd only if necessary This fixes a regression introduced in revision 9d2f841a5d39fc25ce722a3904f6ebc9aa112222. Signed-off-by: Sven Strickroth --- src/pack.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pack.c b/src/pack.c index 497db38e8..d7e6a1e94 100644 --- a/src/pack.c +++ b/src/pack.c @@ -822,7 +822,7 @@ void git_packfile_free(struct git_pack_file *p) git_mwindow_free_all(&p->mwf); - if (p->mwf.fd != -1) + if (p->mwf.fd >= 0) p_close(p->mwf.fd); pack_index_free(p); @@ -905,7 +905,8 @@ static int packfile_open(struct git_pack_file *p) cleanup: giterr_set(GITERR_OS, "Invalid packfile '%s'", p->pack_name); - p_close(p->mwf.fd); + if (p->mwf.fd >= 0) + p_close(p->mwf.fd); p->mwf.fd = -1; git_mutex_unlock(&p->lock); From a16e41729d5dec4acd30302c4a217622de00d290 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 25 Jul 2013 12:27:39 -0700 Subject: [PATCH 169/367] Fix rename detection to use actual blob size The size data in the index may not reflect the actual size of the blob data from the ODB when content filtering comes into play. This commit fixes rename detection to use the actual blob size when calculating data signatures instead of the value from the index. Because of a misunderstanding on my part, I first converted the git_index_add_bypath API to use the post-filtered blob data size in creating the index entry. I backed that change out, but I kept the overall refactoring of that routine and the new internal git_blob__create_from_paths API because it eliminates an extra stat() call from the code that adds a file to the index. The existing tests actually cover this code path, at least when running on Windows, so at this point I'm not adding new tests to cover the changes. --- src/blob.c | 89 ++++++++++++++++++++++++++++-------------------- src/blob.h | 9 +++++ src/diff_tform.c | 8 ++++- src/index.c | 34 +++++------------- 4 files changed, 76 insertions(+), 64 deletions(-) diff --git a/src/blob.c b/src/blob.c index 2e4d5f479..0f1c97003 100644 --- a/src/blob.c +++ b/src/blob.c @@ -105,6 +105,7 @@ static int write_file_stream( static int write_file_filtered( git_oid *oid, + git_off_t *size, git_odb *odb, const char *full_path, git_vector *filters) @@ -123,8 +124,11 @@ static int write_file_filtered( git_buf_free(&source); /* Write the file to disk if it was properly filtered */ - if (!error) + if (!error) { + *size = dest.size; + error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB); + } git_buf_free(&dest); return error; @@ -152,21 +156,46 @@ static int write_symlink( return error; } -static int blob_create_internal(git_oid *oid, git_repository *repo, const char *content_path, const char *hint_path, bool try_load_filters) +int git_blob__create_from_paths( + git_oid *oid, + struct stat *out_st, + git_repository *repo, + const char *content_path, + const char *hint_path, + mode_t hint_mode, + bool try_load_filters) { int error; struct stat st; git_odb *odb = NULL; git_off_t size; + mode_t mode; + git_buf path = GIT_BUF_INIT; assert(hint_path || !try_load_filters); - if ((error = git_path_lstat(content_path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0) - return error; + if (!content_path) { + if (git_repository__ensure_not_bare(repo, "create blob from file") < 0) + return GIT_EBAREREPO; + + if (git_buf_joinpath( + &path, git_repository_workdir(repo), hint_path) < 0) + return -1; + + content_path = path.ptr; + } + + if ((error = git_path_lstat(content_path, &st)) < 0 || + (error = git_repository_odb(&odb, repo)) < 0) + goto done; + + if (out_st) + memcpy(out_st, &st, sizeof(st)); size = st.st_size; + mode = hint_mode ? hint_mode : st.st_mode; - if (S_ISLNK(st.st_mode)) { + if (S_ISLNK(hint_mode)) { error = write_symlink(oid, odb, content_path, (size_t)size); } else { git_vector write_filters = GIT_VECTOR_INIT; @@ -187,7 +216,8 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char * error = write_file_stream(oid, odb, content_path, size); } else { /* We need to apply one or more filters */ - error = write_file_filtered(oid, odb, content_path, &write_filters); + error = write_file_filtered( + oid, &size, odb, content_path, &write_filters); } git_filters_free(&write_filters); @@ -207,34 +237,21 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char * */ } +done: + git_odb_free(odb); + git_buf_free(&path); + return error; } -int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char *path) +int git_blob_create_fromworkdir( + git_oid *oid, git_repository *repo, const char *path) { - git_buf full_path = GIT_BUF_INIT; - const char *workdir; - int error; - - if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0) - return error; - - workdir = git_repository_workdir(repo); - - if (git_buf_joinpath(&full_path, workdir, path) < 0) { - git_buf_free(&full_path); - return -1; - } - - 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; + return git_blob__create_from_paths(oid, NULL, repo, NULL, path, 0, true); } -int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path) +int git_blob_create_fromdisk( + git_oid *oid, git_repository *repo, const char *path) { int error; git_buf full_path = GIT_BUF_INIT; @@ -251,8 +268,8 @@ int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *pat if (workdir && !git__prefixcmp(hintpath, workdir)) hintpath += strlen(workdir); - error = blob_create_internal( - oid, repo, git_buf_cstr(&full_path), hintpath, true); + error = git_blob__create_from_paths( + oid, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true); git_buf_free(&full_path); return error; @@ -272,12 +289,9 @@ int git_blob_create_fromchunks( git_filebuf file = GIT_FILEBUF_INIT; git_buf path = GIT_BUF_INIT; - if (git_buf_join_n( - &path, '/', 3, - git_repository_path(repo), - GIT_OBJECTS_DIR, - "streamed") < 0) - goto cleanup; + if (git_buf_joinpath( + &path, git_repository_path(repo), GIT_OBJECTS_DIR "streamed") < 0) + goto cleanup; content = git__malloc(BUFFER_SIZE); GITERR_CHECK_ALLOC(content); @@ -303,7 +317,8 @@ int git_blob_create_fromchunks( if (git_filebuf_flush(&file) < 0) goto cleanup; - error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL); + error = git_blob__create_from_paths( + oid, NULL, repo, file.path_lock, hintpath, 0, hintpath != NULL); cleanup: git_buf_free(&path); diff --git a/src/blob.h b/src/blob.h index 22e37cc3a..4cd9f1e0c 100644 --- a/src/blob.h +++ b/src/blob.h @@ -21,4 +21,13 @@ void git_blob__free(void *blob); int git_blob__parse(void *blob, git_odb_object *obj); int git_blob__getbuf(git_buf *buffer, git_blob *blob); +extern int git_blob__create_from_paths( + git_oid *out_oid, + struct stat *out_st, + git_repository *repo, + const char *full_path, + const char *hint_path, + mode_t hint_mode, + bool apply_filters); + #endif diff --git a/src/diff_tform.c b/src/diff_tform.c index 8fd2a4fe9..d4b8cf30a 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -473,7 +473,13 @@ static int similarity_calc( /* if lookup fails, just skip this item in similarity calc */ giterr_clear(); } else { - size_t sz = (size_t)(git__is_sizet(file->size) ? file->size : -1); + size_t sz; + + /* index size may not be actual blob size if filtered */ + if (file->size != git_blob_rawsize(info->blob)) + file->size = git_blob_rawsize(info->blob); + + sz = (size_t)(git__is_sizet(file->size) ? file->size : -1); error = opts->metric->buffer_signature( &cache[info->idx], info->file, diff --git a/src/index.c b/src/index.c index 0610eb5b9..cbdd43bdc 100644 --- a/src/index.c +++ b/src/index.c @@ -16,6 +16,7 @@ #include "iterator.h" #include "pathspec.h" #include "ignore.h" +#include "blob.h" #include "git2/odb.h" #include "git2/oid.h" @@ -604,42 +605,23 @@ int git_index_entry__cmp_icase(const void *a, const void *b) return strcasecmp(entry_a->path, entry_b->path); } -static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path) +static int index_entry_init( + git_index_entry **entry_out, git_index *index, const char *rel_path) { + int error = 0; git_index_entry *entry = NULL; struct stat st; git_oid oid; - const char *workdir; - git_buf full_path = GIT_BUF_INIT; - int error; if (INDEX_OWNER(index) == NULL) return create_index_error(-1, "Could not initialize index entry. " "Index is not backed up by an existing repository."); - workdir = git_repository_workdir(INDEX_OWNER(index)); - - if (!workdir) - return create_index_error(GIT_EBAREREPO, - "Could not initialize index entry. Repository is bare"); - - if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0) - return error; - - if ((error = git_path_lstat(full_path.ptr, &st)) < 0) { - git_buf_free(&full_path); - return error; - } - - git_buf_free(&full_path); /* done with full path */ - - /* There is no need to validate the rel_path here, since it will be - * immediately validated by the call to git_blob_create_fromfile. - */ - - /* write the blob to disk and get the oid */ - if ((error = git_blob_create_fromworkdir(&oid, INDEX_OWNER(index), rel_path)) < 0) + /* write the blob to disk and get the oid and stat info */ + error = git_blob__create_from_paths( + &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true); + if (error < 0) return error; entry = git__calloc(1, sizeof(git_index_entry)); From 8dd8aa480ba46863e9c7df40bb9695e88a0286ee Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 26 Jul 2013 10:28:57 -0700 Subject: [PATCH 170/367] Fix some warnings --- src/array.h | 11 ++++++----- src/blob.c | 2 +- src/diff_patch.c | 26 +++++++++++++------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/array.h b/src/array.h index 248010425..c25a1b29e 100644 --- a/src/array.h +++ b/src/array.h @@ -39,25 +39,26 @@ #define GITERR_CHECK_ARRAY(a) GITERR_CHECK_ALLOC((a).ptr) -typedef git_array_t(void) git_array_generic_t; +typedef git_array_t(char) git_array_generic_t; /* use a generic array for growth so this can return the new item */ -GIT_INLINE(void *) git_array_grow(git_array_generic_t *a, size_t item_size) +GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size) { + git_array_generic_t *a = _a; uint32_t new_size = (a->size < 8) ? 8 : a->asize * 3 / 2; - void *new_array = git__realloc(a->ptr, new_size * item_size); + char *new_array = git__realloc(a->ptr, new_size * item_size); if (!new_array) { git_array_clear(*a); return NULL; } else { a->ptr = new_array; a->asize = new_size; a->size++; - return (((char *)a->ptr) + (a->size - 1) * item_size); + return a->ptr + (a->size - 1) * item_size; } } #define git_array_alloc(a) \ ((a).size >= (a).asize) ? \ - git_array_grow((git_array_generic_t *)&(a), sizeof(*(a).ptr)) : \ + git_array_grow(&(a), sizeof(*(a).ptr)) : \ (a).ptr ? &(a).ptr[(a).size++] : NULL #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL) diff --git a/src/blob.c b/src/blob.c index 0f1c97003..5bb51f7cf 100644 --- a/src/blob.c +++ b/src/blob.c @@ -195,7 +195,7 @@ int git_blob__create_from_paths( size = st.st_size; mode = hint_mode ? hint_mode : st.st_mode; - if (S_ISLNK(hint_mode)) { + if (S_ISLNK(mode)) { error = write_symlink(oid, odb, content_path, (size_t)size); } else { git_vector write_filters = GIT_VECTOR_INIT; diff --git a/src/diff_patch.c b/src/diff_patch.c index 02a45cb1a..69bb08198 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -288,8 +288,8 @@ int git_diff_foreach( if (diff_required(diff, "git_diff_foreach") < 0) return -1; - diff_output_init((git_diff_output *)&xo, - &diff->opts, file_cb, hunk_cb, data_cb, payload); + diff_output_init( + &xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload); git_xdiff_init(&xo, &diff->opts); git_vector_foreach(&diff->deltas, idx, patch.delta) { @@ -300,10 +300,10 @@ int git_diff_foreach( if (!(error = diff_patch_init_from_diff(&patch, diff, idx))) { - error = diff_patch_file_callback(&patch, (git_diff_output *)&xo); + error = diff_patch_file_callback(&patch, &xo.output); if (!error) - error = diff_patch_generate(&patch, (git_diff_output *)&xo); + error = diff_patch_generate(&patch, &xo.output); git_diff_patch_free(&patch); } @@ -441,7 +441,7 @@ int git_diff_blobs( memset(&xo, 0, sizeof(xo)); diff_output_init( - (git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload); + &xo.output, opts, file_cb, hunk_cb, data_cb, payload); git_xdiff_init(&xo, opts); if (!old_path && new_path) @@ -452,7 +452,7 @@ int git_diff_blobs( error = diff_patch_from_blobs( &pd, &xo, old_blob, old_path, new_blob, new_path, opts); - git_diff_patch_free((git_diff_patch *)&pd); + git_diff_patch_free(&pd.patch); return error; } @@ -477,7 +477,7 @@ int git_diff_patch_from_blobs( memset(&xo, 0, sizeof(xo)); - diff_output_to_patch((git_diff_output *)&xo, &pd->patch); + diff_output_to_patch(&xo.output, &pd->patch); git_xdiff_init(&xo, opts); error = diff_patch_from_blobs( @@ -553,7 +553,7 @@ int git_diff_blob_to_buffer( memset(&xo, 0, sizeof(xo)); diff_output_init( - (git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload); + &xo.output, opts, file_cb, hunk_cb, data_cb, payload); git_xdiff_init(&xo, opts); if (!old_path && buf_path) @@ -564,7 +564,7 @@ int git_diff_blob_to_buffer( error = diff_patch_from_blob_and_buffer( &pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts); - git_diff_patch_free((git_diff_patch *)&pd); + git_diff_patch_free(&pd.patch); return error; } @@ -590,7 +590,7 @@ int git_diff_patch_from_blob_and_buffer( memset(&xo, 0, sizeof(xo)); - diff_output_to_patch((git_diff_output *)&xo, &pd->patch); + diff_output_to_patch(&xo.output, &pd->patch); git_xdiff_init(&xo, opts); error = diff_patch_from_blob_and_buffer( @@ -642,13 +642,13 @@ int git_diff_get_patch( if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0) return error; - diff_output_to_patch((git_diff_output *)&xo, patch); + diff_output_to_patch(&xo.output, patch); git_xdiff_init(&xo, &diff->opts); - error = diff_patch_file_callback(patch, (git_diff_output *)&xo); + error = diff_patch_file_callback(patch, &xo.output); if (!error) - error = diff_patch_generate(patch, (git_diff_output *)&xo); + error = diff_patch_generate(patch, &xo.output); if (!error) { /* if cumulative diff size is < 0.5 total size, flatten the patch */ From c3ae047361c0412ed371ef899b018e9c13d6ecf8 Mon Sep 17 00:00:00 2001 From: Brendan Macmillan Date: Sat, 27 Jul 2013 05:31:28 +1000 Subject: [PATCH 171/367] Fix -n bug; default to all ancestors --- examples/log.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/log.c b/examples/log.c index dbbc42914..413c211e4 100644 --- a/examples/log.c +++ b/examples/log.c @@ -265,6 +265,7 @@ int main(int argc, char *argv[]) memset(&opt, 0, sizeof(opt)); opt.max_parents = -1; + opt.limit = -1; for (i = 1; i < argc; ++i) { a = argv[i]; @@ -294,8 +295,8 @@ int main(int argc, char *argv[]) if (!match_int(&opt.limit, a + 1, 0)) usage("Invalid limit on number of commits", a); } else if (!strcmp(a, "-n")) { - if (i + 1 == argc || !match_int(&opt.limit, argv[i], 0)) - usage("Argument -n not followed by valid count", argv[i]); + if (i + 1 == argc || !match_int(&opt.limit, argv[i + 1], 0)) + usage("Argument -n not followed by valid count", argv[i + 1]); else ++i; } @@ -363,7 +364,7 @@ int main(int argc, char *argv[]) if (count++ < opt.skip) continue; - if (printed++ >= opt.limit) { + if (opt.limit != -1 && printed++ >= opt.limit) { git_commit_free(commit); break; } From f5254d78441f7fa1067a1c34d3925b72afcb024c Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Sat, 27 Jul 2013 20:15:06 +0200 Subject: [PATCH 172/367] Fix possible double close Signed-off-by: Sven Strickroth --- src/fileops.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fileops.c b/src/fileops.c index c01ff64db..7f8418d7a 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -222,6 +222,7 @@ int git_futils_writebuffer( if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) { giterr_set(GITERR_OS, "Could not write to '%s'", path); (void)p_close(fd); + return error; } if ((error = p_close(fd)) < 0) From a6837b5fc9465e0f271ccd2c0bb71aae78a10a64 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Wed, 31 Jul 2013 19:13:35 +0200 Subject: [PATCH 173/367] When building with MINGW, specify `__USE_MINGW_ANSI_STDIO`. This option is already present in the CMake config, but was missing from `Makefile.embed` and would cause all kinds of weird failures when compiling rugged on windows with the ruby devkit. --- Makefile.embed | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile.embed b/Makefile.embed index 8b8970d76..2f3b057c7 100644 --- a/Makefile.embed +++ b/Makefile.embed @@ -6,12 +6,12 @@ else PREFIX= endif -WIN32=0 +MINGW=0 ifneq (,$(findstring MINGW32,$(PLATFORM))) - WIN32=1 + MINGW=1 endif ifneq (,$(findstring mingw,$(CROSS_COMPILE))) - WIN32=1 + MINGW=1 endif rm=rm -f @@ -20,7 +20,7 @@ RANLIB=$(PREFIX)ranlib LIBNAME=libgit2.a -ifeq ($(WIN32),1) +ifeq ($(MINGW),1) CC=gcc else CC=cc @@ -35,10 +35,10 @@ CFLAGS= -g $(DEFINES) -Wall -Wextra -O2 $(EXTRA_CFLAGS) SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) src/hash/hash_generic.c -ifeq ($(WIN32),1) +ifeq ($(MINGW),1) SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c INCLUDES += -Ideps/regex - DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 + DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 -D__USE_MINGW_ANSI_STDIO=1 else SRCS += $(wildcard src/unix/*.c) CFLAGS += -fPIC From d730d3f4f0efb269dd760a3100ae86c460b8ba36 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 31 Jul 2013 16:40:42 -0700 Subject: [PATCH 174/367] Major rename detection changes After doing further profiling, I found that a lot of time was being spent attempting to insert hashes into the file hash signature when using the rolling hash because the rolling hash approach generates a hash per byte of the file instead of one per run/line of data. To optimize this, I decided to convert back to a run-based file signature algorithm which would be more like core Git. After changing this, a number of the existing tests started to fail. In some cases, this appears to have been because the test was coded to be too specific to the particular results of the file similarity metric and in some cases there appear to have been bugs in the core rename detection code where only by the coincidence of the file similarity scoring were the expected results being generated. This renames all the variables in the core rename detection code to be more consistent and hopefully easier to follow which made it a bit easier to reason about the behavior of that code and fix the problems that I was seeing. I think it's in better shape now. There are a couple of tests now that attempt to stress test the rename detection code and they are quite slow. Most of the time is spent setting up the test data on disk and in the index. When we roll out performance improvements for index insertion, it should also speed up these tests I hope. --- src/diff_tform.c | 219 ++++++++++++++++++++++---------------- src/hashsig.c | 208 ++++++++++++++++-------------------- src/util.h | 5 + tests-clar/clar_libgit2.h | 14 +++ tests-clar/core/buffer.c | 42 +++++--- tests-clar/diff/rename.c | 99 ++++++++++++----- 6 files changed, 335 insertions(+), 252 deletions(-) diff --git a/src/diff_tform.c b/src/diff_tform.c index d4b8cf30a..92c4036fb 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -436,7 +436,7 @@ static int similarity_init( info->file, &info->odb_obj, info->repo); } -static int similarity_calc( +static int similarity_sig( similarity_info *info, const git_diff_find_options *opts, void **cache) @@ -566,16 +566,19 @@ static int similarity_measure( /* check if file sizes are nowhere near each other */ if (a_file->size > 127 && b_file->size > 127 && - (a_file->size > (b_file->size << 4) || - b_file->size > (a_file->size << 4))) + (a_file->size > (b_file->size << 3) || + b_file->size > (a_file->size << 3))) goto cleanup; /* update signature cache if needed */ - if (!cache[a_idx] && (error = similarity_calc(&a_info, opts, cache)) < 0) - goto cleanup; - - if (!cache[b_idx] && (error = similarity_calc(&b_info, opts, cache)) < 0) - goto cleanup; + if (!cache[a_idx]) { + if ((error = similarity_sig(&a_info, opts, cache)) < 0) + goto cleanup; + } + if (!cache[b_idx]) { + if ((error = similarity_sig(&b_info, opts, cache)) < 0) + goto cleanup; + } /* calculate similarity provided that the metric choose to process * both the a and b files (some may not if file is too big, etc). @@ -759,25 +762,30 @@ int git_diff_find_similar( git_diff_list *diff, git_diff_find_options *given_opts) { - size_t i, j, sigcache_size; + size_t s, t; int error = 0, similarity; - git_diff_delta *from, *to; + git_diff_delta *src, *tgt; git_diff_find_options opts; - size_t num_srcs = 0, num_tgts = 0, tried_srcs = 0, tried_tgts = 0; + size_t num_deltas, num_srcs = 0, num_tgts = 0; + size_t tried_srcs = 0, tried_tgts = 0; size_t num_rewrites = 0, num_updates = 0, num_bumped = 0; void **sigcache; /* cache of similarity metric file signatures */ - diff_find_match *match_srcs = NULL, *match_tgts = NULL, *best_match; + diff_find_match *tgt2src = NULL; + diff_find_match *src2tgt = NULL; + diff_find_match *tgt2src_copy = NULL; + diff_find_match *best_match; git_diff_file swap; if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0) return error; + num_deltas = diff->deltas.length; + /* TODO: maybe abort if deltas.length > rename_limit ??? */ - if (!git__is_uint32(diff->deltas.length)) + if (!git__is_uint32(num_deltas)) return 0; - sigcache_size = diff->deltas.length * 2; /* keep size b/c diff may change */ - sigcache = git__calloc(sigcache_size, sizeof(void *)); + sigcache = git__calloc(num_deltas * 2, sizeof(void *)); GITERR_CHECK_ALLOC(sigcache); /* Label rename sources and targets @@ -785,11 +793,11 @@ int git_diff_find_similar( * This will also set self-similarity scores for MODIFIED files and * mark them for splitting if break-rewrites is enabled */ - git_vector_foreach(&diff->deltas, i, to) { - if (is_rename_source(diff, &opts, i, sigcache)) + git_vector_foreach(&diff->deltas, t, tgt) { + if (is_rename_source(diff, &opts, t, sigcache)) ++num_srcs; - if (is_rename_target(diff, &opts, i, sigcache)) + if (is_rename_target(diff, &opts, t, sigcache)) ++num_tgts; } @@ -797,10 +805,15 @@ int git_diff_find_similar( if (!num_srcs || !num_tgts) goto cleanup; - match_tgts = git__calloc(diff->deltas.length, sizeof(diff_find_match)); - GITERR_CHECK_ALLOC(match_tgts); - match_srcs = git__calloc(diff->deltas.length, sizeof(diff_find_match)); - GITERR_CHECK_ALLOC(match_srcs); + src2tgt = git__calloc(num_deltas, sizeof(diff_find_match)); + GITERR_CHECK_ALLOC(src2tgt); + tgt2src = git__calloc(num_deltas, sizeof(diff_find_match)); + GITERR_CHECK_ALLOC(tgt2src); + + if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { + tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match)); + GITERR_CHECK_ALLOC(tgt2src_copy); + } /* * Find best-fit matches for rename / copy candidates @@ -809,47 +822,61 @@ int git_diff_find_similar( find_best_matches: tried_tgts = num_bumped = 0; - git_vector_foreach(&diff->deltas, i, to) { + git_vector_foreach(&diff->deltas, t, tgt) { /* skip things that are not rename targets */ - if ((to->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) + if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) continue; tried_srcs = 0; - git_vector_foreach(&diff->deltas, j, from) { + git_vector_foreach(&diff->deltas, s, src) { /* skip things that are not rename sources */ - if ((from->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0) + if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0) continue; /* calculate similarity for this pair and find best match */ - if (i == j) + if (s == t) similarity = -1; /* don't measure self-similarity here */ else if ((error = similarity_measure( - &similarity, diff, &opts, sigcache, 2 * j, 2 * i + 1)) < 0) + &similarity, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0) goto cleanup; - /* if this pairing is better for the src and the tgt, keep it */ - if (similarity > 0 && - match_tgts[i].similarity < (uint32_t)similarity && - match_srcs[j].similarity < (uint32_t)similarity) + if (similarity < 0) + continue; + + /* is this a better rename? */ + if (tgt2src[t].similarity < (uint32_t)similarity && + src2tgt[s].similarity < (uint32_t)similarity) { - if (match_tgts[i].similarity > 0) { - match_tgts[match_srcs[j].idx].similarity = 0; - match_srcs[match_tgts[i].idx].similarity = 0; - ++num_bumped; + /* eject old mapping */ + if (src2tgt[s].similarity > 0) { + tgt2src[src2tgt[s].idx].similarity = 0; + num_bumped++; + } + if (tgt2src[t].similarity > 0) { + src2tgt[tgt2src[t].idx].similarity = 0; + num_bumped++; } - match_tgts[i].similarity = (uint32_t)similarity; - match_tgts[i].idx = (uint32_t)j; + /* write new mapping */ + tgt2src[t].idx = s; + tgt2src[t].similarity = (uint32_t)similarity; + src2tgt[s].idx = t; + src2tgt[s].similarity = (uint32_t)similarity; + } - match_srcs[j].similarity = (uint32_t)similarity; - match_srcs[j].idx = (uint32_t)i; + /* keep best absolute match for copies */ + if (tgt2src_copy != NULL && + tgt2src_copy[t].similarity < (uint32_t)similarity) + { + tgt2src_copy[t].idx = s; + tgt2src_copy[t].similarity = (uint32_t)similarity; } if (++tried_srcs >= num_srcs) break; - /* cap on maximum targets we'll examine (per "to" file) */ + /* cap on maximum targets we'll examine (per "tgt" file) */ if (tried_srcs > opts.rename_limit) break; } @@ -867,18 +894,21 @@ find_best_matches: tried_tgts = 0; - git_vector_foreach(&diff->deltas, i, to) { + git_vector_foreach(&diff->deltas, t, tgt) { /* skip things that are not rename targets */ - if ((to->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) + if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) continue; /* check if this delta was the target of a similarity */ - best_match = &match_tgts[i]; - if (!best_match->similarity) + if (tgt2src[t].similarity) + best_match = &tgt2src[t]; + else if (tgt2src_copy && tgt2src_copy[t].similarity) + best_match = &tgt2src_copy[t]; + else continue; - j = best_match->idx; - from = GIT_VECTOR_GET(&diff->deltas, j); + s = best_match->idx; + src = GIT_VECTOR_GET(&diff->deltas, s); /* possible scenarios: * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME @@ -888,101 +918,107 @@ find_best_matches: * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY */ - if (from->status == GIT_DELTA_DELETED) { + if (src->status == GIT_DELTA_DELETED) { - if (delta_is_new_only(to)) { + if (delta_is_new_only(tgt)) { if (best_match->similarity < opts.rename_threshold) continue; - delta_make_rename(to, from, best_match->similarity); + delta_make_rename(tgt, src, best_match->similarity); - from->flags |= GIT_DIFF_FLAG__TO_DELETE; + src->flags |= GIT_DIFF_FLAG__TO_DELETE; num_rewrites++; } else { - assert(delta_is_split(to)); + assert(delta_is_split(tgt)); if (best_match->similarity < opts.rename_from_rewrite_threshold) continue; - memcpy(&swap, &to->old_file, sizeof(swap)); + memcpy(&swap, &tgt->old_file, sizeof(swap)); - delta_make_rename(to, from, best_match->similarity); + delta_make_rename(tgt, src, best_match->similarity); num_rewrites--; - from->status = GIT_DELTA_DELETED; - memcpy(&from->old_file, &swap, sizeof(from->old_file)); - memset(&from->new_file, 0, sizeof(from->new_file)); - from->new_file.path = from->old_file.path; - from->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; + src->status = GIT_DELTA_DELETED; + memcpy(&src->old_file, &swap, sizeof(src->old_file)); + memset(&src->new_file, 0, sizeof(src->new_file)); + src->new_file.path = src->old_file.path; + src->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; num_updates++; } } - else if (delta_is_split(from)) { + else if (delta_is_split(src)) { - if (delta_is_new_only(to)) { + if (delta_is_new_only(tgt)) { if (best_match->similarity < opts.rename_threshold) continue; - delta_make_rename(to, from, best_match->similarity); + delta_make_rename(tgt, src, best_match->similarity); - from->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ? + src->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ? GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED; - memset(&from->old_file, 0, sizeof(from->old_file)); - from->old_file.path = from->new_file.path; - from->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; + memset(&src->old_file, 0, sizeof(src->old_file)); + src->old_file.path = src->new_file.path; + src->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; - from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; + src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; num_rewrites--; num_updates++; } else { - assert(delta_is_split(from)); + assert(delta_is_split(src)); if (best_match->similarity < opts.rename_from_rewrite_threshold) continue; - memcpy(&swap, &to->old_file, sizeof(swap)); + memcpy(&swap, &tgt->old_file, sizeof(swap)); - delta_make_rename(to, from, best_match->similarity); + delta_make_rename(tgt, src, best_match->similarity); num_rewrites--; num_updates++; - memcpy(&from->old_file, &swap, sizeof(from->old_file)); + memcpy(&src->old_file, &swap, sizeof(src->old_file)); /* if we've just swapped the new element into the correct * place, clear the SPLIT flag */ - if (match_tgts[j].idx == i && - match_tgts[j].similarity > + if (tgt2src[s].idx == t && + tgt2src[s].similarity > opts.rename_from_rewrite_threshold) { - - from->status = GIT_DELTA_RENAMED; - from->similarity = match_tgts[j].similarity; - match_tgts[j].similarity = 0; - from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; + src->status = GIT_DELTA_RENAMED; + src->similarity = tgt2src[s].similarity; + tgt2src[s].similarity = 0; + src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; num_rewrites--; } /* otherwise, if we just overwrote a source, update mapping */ - else if (j > i && match_srcs[i].similarity > 0) { - match_tgts[match_srcs[i].idx].idx = (uint32_t)j; + else if (s > t && src2tgt[t].similarity > 0) { + /* what used to be at src t is now at src s */ + tgt2src[src2tgt[t].idx].idx = (uint32_t)s; } num_updates++; } } - else if (delta_is_new_only(to)) { - if (!FLAG_SET(&opts, GIT_DIFF_FIND_COPIES) || - best_match->similarity < opts.copy_threshold) + else if (delta_is_new_only(tgt)) { + if (!FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) continue; - to->status = GIT_DELTA_COPIED; - to->similarity = best_match->similarity; - memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); + if (tgt2src_copy[t].similarity < opts.copy_threshold) + continue; + + /* always use best possible source for copy */ + best_match = &tgt2src_copy[t]; + src = GIT_VECTOR_GET(&diff->deltas, best_match->idx); + + tgt->status = GIT_DELTA_COPIED; + tgt->similarity = best_match->similarity; + memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file)); num_updates++; } @@ -998,12 +1034,13 @@ find_best_matches: FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES)); cleanup: - git__free(match_srcs); - git__free(match_tgts); + git__free(tgt2src); + git__free(src2tgt); + git__free(tgt2src_copy); - for (i = 0; i < sigcache_size; ++i) { - if (sigcache[i] != NULL) - opts.metric->free_signature(sigcache[i], opts.metric->payload); + for (t = 0; t < num_deltas * 2; ++t) { + if (sigcache[t] != NULL) + opts.metric->free_signature(sigcache[t], opts.metric->payload); } git__free(sigcache); diff --git a/src/hashsig.c b/src/hashsig.c index a11c4bee7..109f966ba 100644 --- a/src/hashsig.c +++ b/src/hashsig.c @@ -13,12 +13,15 @@ typedef uint64_t hashsig_state; #define HASHSIG_SCALE 100 -#define HASHSIG_HASH_WINDOW 32 -#define HASHSIG_HASH_START 0 +#define HASHSIG_MAX_RUN 80 +#define HASHSIG_HASH_START 0x012345678ABCDEF0LL #define HASHSIG_HASH_SHIFT 5 -#define HASHSIG_HASH_MASK 0x7FFFFFFF + +#define HASHSIG_HASH_MIX(S,CH) \ + (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH) #define HASHSIG_HEAP_SIZE ((1 << 7) - 1) +#define HASHSIG_HEAP_MIN_SIZE 4 typedef int (*hashsig_cmp)(const void *a, const void *b, void *); @@ -28,14 +31,6 @@ typedef struct { hashsig_t values[HASHSIG_HEAP_SIZE]; } hashsig_heap; -typedef struct { - hashsig_state state, shift_n; - char window[HASHSIG_HASH_WINDOW]; - int win_len, win_pos, saw_lf; -} hashsig_in_progress; - -#define HASHSIG_IN_PROGRESS_INIT { HASHSIG_HASH_START, 1, {0}, 0, 0, 1 } - struct git_hashsig { hashsig_heap mins; hashsig_heap maxs; @@ -115,142 +110,109 @@ static void hashsig_heap_sort(hashsig_heap *h) 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], NULL) > 0) { - h->size--; - h->values[0] = h->values[h->size]; - hashsig_heap_down(h, 0); - } - /* if heap is not full, insert new element */ if (h->size < h->asize) { h->values[h->size++] = val; hashsig_heap_up(h, h->size - 1); } + + /* if heap is full, pop top if new element should replace it */ + else if (h->cmp(&val, &h->values[0], NULL) > 0) { + h->size--; + h->values[0] = h->values[h->size]; + hashsig_heap_down(h, 0); + } + } -GIT_INLINE(bool) hashsig_include_char( - char ch, git_hashsig_option_t opt, int *saw_lf) +typedef struct { + int use_ignores; + uint8_t ignore_ch[256]; +} hashsig_in_progress; + +static void hashsig_in_progress_init( + hashsig_in_progress *prog, git_hashsig *sig) { - if ((opt & GIT_HASHSIG_IGNORE_WHITESPACE) && git__isspace(ch)) - return false; + int i; - if (opt & GIT_HASHSIG_SMART_WHITESPACE) { - if (ch == '\r' || (*saw_lf && git__isspace(ch))) - return false; - - *saw_lf = (ch == '\n'); + switch (sig->opt) { + case GIT_HASHSIG_IGNORE_WHITESPACE: + for (i = 0; i < 256; ++i) + prog->ignore_ch[i] = git__isspace_nonlf(i); + prog->use_ignores = 1; + break; + case GIT_HASHSIG_SMART_WHITESPACE: + for (i = 0; i < 256; ++i) + prog->ignore_ch[i] = git__isspace(i); + prog->use_ignores = 1; + break; + default: + memset(prog, 0, sizeof(*prog)); + break; } - - return true; } -static void hashsig_initial_window( - git_hashsig *sig, - const char **data, - size_t size, - hashsig_in_progress *prog) -{ - hashsig_state state, shift_n; - int win_len, saw_lf = prog->saw_lf; - const char *scan, *end; - char *window = &prog->window[0]; - - /* init until we have processed at least HASHSIG_HASH_WINDOW data */ - - if (prog->win_len >= HASHSIG_HASH_WINDOW) - return; - - state = prog->state; - win_len = prog->win_len; - shift_n = prog->shift_n; - - scan = *data; - end = scan + size; - - while (scan < end && win_len < HASHSIG_HASH_WINDOW) { - char ch = *scan++; - - if (!hashsig_include_char(ch, sig->opt, &saw_lf)) - continue; - - state = (state * HASHSIG_HASH_SHIFT + ch) & HASHSIG_HASH_MASK; - - if (!win_len) - shift_n = 1; - else - shift_n = (shift_n * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK; - - window[win_len++] = ch; - } - - /* insert initial hash if we just finished */ - - if (win_len == HASHSIG_HASH_WINDOW) { - hashsig_heap_insert(&sig->mins, (hashsig_t)state); - hashsig_heap_insert(&sig->maxs, (hashsig_t)state); - sig->considered = 1; - } - - prog->state = state; - prog->win_len = win_len; - prog->shift_n = shift_n; - prog->saw_lf = saw_lf; - - *data = scan; -} +#define HASHSIG_IN_PROGRESS_INIT { 1 } static int hashsig_add_hashes( git_hashsig *sig, - const char *data, + const uint8_t *data, size_t size, hashsig_in_progress *prog) { - const char *scan = data, *end = data + size; - hashsig_state state, shift_n, rmv; - int win_pos, saw_lf; - char *window = &prog->window[0]; + const uint8_t *scan = data, *end = data + size; + hashsig_state state = HASHSIG_HASH_START; + int use_ignores = prog->use_ignores, len; + uint8_t ch; - if (prog->win_len < HASHSIG_HASH_WINDOW) - hashsig_initial_window(sig, &scan, size, prog); + while (scan < end) { + state = HASHSIG_HASH_START; - state = prog->state; - shift_n = prog->shift_n; - saw_lf = prog->saw_lf; - win_pos = prog->win_pos; + for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) { + ch = *scan; - /* advance window, adding new chars and removing old */ + if (use_ignores) + for (; scan < end && git__isspace_nonlf(ch); ch = *scan) + ++scan; + else if (sig->opt != GIT_HASHSIG_NORMAL) + for (; scan < end && ch == '\r'; ch = *scan) + ++scan; - for (; scan < end; ++scan) { - char ch = *scan; + /* peek at next character to decide what to do next */ + if (sig->opt == GIT_HASHSIG_SMART_WHITESPACE) + use_ignores = (ch == '\n'); - if (!hashsig_include_char(ch, sig->opt, &saw_lf)) - continue; + if (scan >= end) + break; + ++scan; - rmv = shift_n * window[win_pos]; + /* check run terminator */ + if (ch == '\n' || ch == '\0') + break; - state = (state - rmv) & HASHSIG_HASH_MASK; - state = (state * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK; - state = (state + ch) & HASHSIG_HASH_MASK; + ++len; + HASHSIG_HASH_MIX(state, ch); + } - hashsig_heap_insert(&sig->mins, (hashsig_t)state); - hashsig_heap_insert(&sig->maxs, (hashsig_t)state); - sig->considered++; + if (len > 0) { + hashsig_heap_insert(&sig->mins, (hashsig_t)state); + hashsig_heap_insert(&sig->maxs, (hashsig_t)state); - window[win_pos] = ch; - win_pos = (win_pos + 1) % HASHSIG_HASH_WINDOW; + sig->considered++; + + while (scan < end && (*scan == '\n' || !*scan)) + ++scan; + } } - prog->state = state; - prog->saw_lf = saw_lf; - prog->win_pos = win_pos; + prog->use_ignores = use_ignores; return 0; } static int hashsig_finalize_hashes(git_hashsig *sig) { - if (sig->mins.size < HASHSIG_HEAP_SIZE) { + if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE) { giterr_set(GITERR_INVALID, "File too small for similarity signature calculation"); return GIT_EBUFS; @@ -282,11 +244,13 @@ int git_hashsig_create( git_hashsig_option_t opts) { int error; - hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT; + hashsig_in_progress prog; git_hashsig *sig = hashsig_alloc(opts); GITERR_CHECK_ALLOC(sig); - error = hashsig_add_hashes(sig, buf, buflen, &prog); + hashsig_in_progress_init(&prog, sig); + + error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog); if (!error) error = hashsig_finalize_hashes(sig); @@ -304,10 +268,10 @@ int git_hashsig_create_fromfile( const char *path, git_hashsig_option_t opts) { - char buf[0x1000]; + uint8_t buf[0x1000]; ssize_t buflen = 0; int error = 0, fd; - hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT; + hashsig_in_progress prog; git_hashsig *sig = hashsig_alloc(opts); GITERR_CHECK_ALLOC(sig); @@ -316,6 +280,8 @@ int git_hashsig_create_fromfile( return fd; } + hashsig_in_progress_init(&prog, sig); + while (!error) { if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) { if ((error = (int)buflen) < 0) @@ -370,6 +336,12 @@ static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b) int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b) { - return (hashsig_heap_compare(&a->mins, &b->mins) + - hashsig_heap_compare(&a->maxs, &b->maxs)) / 2; + /* if we have fewer than the maximum number of elements, then just use + * one array since the two arrays will be the same + */ + if (a->mins.size < HASHSIG_HEAP_SIZE) + return hashsig_heap_compare(&a->mins, &b->mins); + else + return (hashsig_heap_compare(&a->mins, &b->mins) + + hashsig_heap_compare(&a->maxs, &b->maxs)) / 2; } diff --git a/src/util.h b/src/util.h index a97c9bf39..ed9624770 100644 --- a/src/util.h +++ b/src/util.h @@ -294,6 +294,11 @@ GIT_INLINE(bool) git__isspace(int c) return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */); } +GIT_INLINE(bool) git__isspace_nonlf(int c) +{ + return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */); +} + GIT_INLINE(bool) git__iswildcard(int c) { return (c == '*' || c == '?' || c == '['); diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h index 5371b2864..8c8357e40 100644 --- a/tests-clar/clar_libgit2.h +++ b/tests-clar/clar_libgit2.h @@ -34,6 +34,20 @@ void cl_git_report_failure(int, const char *, int, const char *); #define cl_assert_equal_sz(sz1,sz2) cl_assert_equal_i((int)sz1, (int)(sz2)) +GIT_INLINE(void) clar__assert_in_range( + int lo, int val, int hi, + const char *file, int line, const char *err, int should_abort) +{ + if (lo > val || hi < val) { + char buf[128]; + snprintf(buf, sizeof(buf), "%d not in [%d,%d]", val, lo, hi); + clar__fail(file, line, err, buf, should_abort); + } +} + +#define cl_assert_in_range(L,V,H) \ + clar__assert_in_range((L),(V),(H),__FILE__,__LINE__,"Range check: " #V " in [" #L "," #H "]", 1) + /* * Some utility macros for building long strings */ diff --git a/tests-clar/core/buffer.c b/tests-clar/core/buffer.c index 3d8221e04..9d9628cfd 100644 --- a/tests-clar/core/buffer.c +++ b/tests-clar/core/buffer.c @@ -734,10 +734,11 @@ void test_core_buffer__classify_with_utf8(void) } #define SIMILARITY_TEST_DATA_1 \ - "test data\nright here\ninline\ntada\nneeds more data\nlots of data\n" \ - "is this enough?\nthere has to be enough data to fill the hash array!\n" \ - "Apparently 191 bytes is the minimum amount of data needed.\nHere goes!\n" \ - "Let's make sure we've got plenty to go with here.\n smile \n" + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "020\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ + "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ + "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" void test_core_buffer__similarity_metric(void) { @@ -761,15 +762,17 @@ void test_core_buffer__similarity_metric(void) cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1)); cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); cl_git_pass(git_buf_sets(&buf, - "Test data\nright here\ninline\ntada\nneeds more data\nlots of data\n" - "is this enough?\nthere has to be enough data to fill the hash array!\n" - "Apparently 191 bytes is the minimum amount of data needed.\nHere goes!\n" - "Let's make sure we've got plenty to go with here.\n smile \n")); + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "x020x\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ + "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ + "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" + )); cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); sim = git_hashsig_compare(a, b); - cl_assert(95 < sim && sim < 100); /* expect >95% similarity */ + cl_assert_in_range(95, sim, 100); /* expect >95% similarity */ git_hashsig_free(a); git_hashsig_free(b); @@ -779,12 +782,13 @@ void test_core_buffer__similarity_metric(void) cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1)); cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1 - "and if I add some more, it should still be pretty similar, yes?\n")); + "050\n051\n052\n053\n054\n055\n056\n057\n058\n059\n")); cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); sim = git_hashsig_compare(a, b); + /* 20% lines added ~= 10% lines changed */ - cl_assert(70 < sim && sim < 80); /* expect in the 70-80% similarity range */ + cl_assert_in_range(85, sim, 95); /* expect similarity around 90% */ git_hashsig_free(a); git_hashsig_free(b); @@ -794,15 +798,19 @@ void test_core_buffer__similarity_metric(void) cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1)); cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); cl_git_pass(git_buf_sets(&buf, - "test data\nright here\ninline\ntada\nneeds more data\nlots of data\n" - "is this enough?\nthere has to be enough data to fill the hash array!\n" - "okay, that's half the original\nwhat else can we add?\nmore data\n" - "one more line will complete this\nshort\nlines\ndon't\nmatter\n")); + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "020x\n021\n022\n023\n024\n" \ + "x25\nx26\nx27\nx28\nx29\n" \ + "x30\nx31\nx32\nx33\nx34\nx35\nx36\nx37\nx38\nx39\n" \ + "x40\nx41\nx42\nx43\nx44\nx45\nx46\nx47\nx48\nx49\n" + )); cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); sim = git_hashsig_compare(a, b); + /* 50% lines changed */ - cl_assert(40 < sim && sim < 60); /* expect in the 40-60% similarity range */ + cl_assert_in_range(40, sim, 60); /* expect in the 40-60% similarity range */ git_hashsig_free(a); git_hashsig_free(b); @@ -891,7 +899,7 @@ void test_core_buffer__similarity_metric_whitespace(void) if (i == j) cl_assert_equal_i(100, sim); else - cl_assert(sim < 30); /* expect pretty different */ + cl_assert_in_range(0, sim, 30); /* pretty different */ } else { cl_assert_equal_i(100, sim); } diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 79c89e362..20ee66288 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -236,6 +236,8 @@ void test_diff_rename__not_exact_match(void) &diff, g_repo, old_tree, new_tree, &diffopts)); opts.flags = GIT_DIFF_FIND_ALL; + opts.break_rewrite_threshold = 70; + cl_git_pass(git_diff_find_similar(diff, &opts)); memset(&exp, 0, sizeof(exp)); @@ -312,8 +314,8 @@ void test_diff_rename__not_exact_match(void) /* the default match algorithm is going to find the internal * whitespace differences in the lines of sixserving.txt to be - * significant enough that this will decide to split it into - * an ADD and a DELETE + * significant enough that this will decide to split it into an ADD + * and a DELETE */ memset(&exp, 0, sizeof(exp)); @@ -480,6 +482,7 @@ void test_diff_rename__working_directory_changes(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE; + opts.rename_threshold = 70; cl_git_pass(git_diff_find_similar(diff, &opts)); memset(&exp, 0, sizeof(exp)); @@ -1123,7 +1126,10 @@ void test_diff_rename__unmodified_can_be_renamed(void) git_tree_free(tree); } -void test_diff_rename__many_files(void) +#define ANOTHER_POEM \ +"OH, glorious are the guarded heights\nWhere guardian souls abide—\nSelf-exiled from our gross delights—\nAbove, beyond, outside:\nAn ampler arc their spirit swings—\nCommands a juster view—\nWe have their word for all these things,\nNo doubt their words are true.\n\nYet we, the bond slaves of our day,\nWhom dirt and danger press—\nCo-heirs of insolence, delay,\nAnd leagued unfaithfulness—\nSuch is our need must seek indeed\nAnd, having found, engage\nThe men who merely do the work\nFor which they draw the wage.\n\nFrom forge and farm and mine and bench,\nDeck, altar, outpost lone—\nMill, school, battalion, counter, trench,\nRail, senate, sheepfold, throne—\nCreation's cry goes up on high\nFrom age to cheated age:\n\"Send us the men who do the work\n\"For which they draw the wage!\"\n" + +static void test_with_many(size_t expected_new) { git_index *index; git_tree *tree, *new_tree; @@ -1131,9 +1137,6 @@ void test_diff_rename__many_files(void) diff_expects exp; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - git_buf b = GIT_BUF_INIT; - int i, j; - char tmp[64]; cl_git_pass(git_repository_index(&index, g_repo)); cl_git_pass( @@ -1142,18 +1145,6 @@ void test_diff_rename__many_files(void) cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); - - for (i = 0; i < 100; i += 2) { - snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); - - for (j = 0; j < i * 128; ++j) - git_buf_printf(&b, "more content %d\n", i); - - cl_git_mkfile(tmp, b.ptr); - cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); - } - git_buf_free(&b); - cl_git_pass(git_index_write(index)); cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); @@ -1162,8 +1153,8 @@ void test_diff_rename__many_files(void) cl_git_pass(git_diff_foreach( diff, diff_file_cb, NULL, NULL, &exp)); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(51, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(52, exp.files); + cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 2, exp.files); opts.flags = GIT_DIFF_FIND_ALL; cl_git_pass(git_diff_find_similar(diff, &opts)); @@ -1172,8 +1163,8 @@ void test_diff_rename__many_files(void) cl_git_pass(git_diff_foreach( diff, diff_file_cb, NULL, NULL, &exp)); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(50, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(51, exp.files); + cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 1, exp.files); git_diff_list_free(diff); @@ -1206,8 +1197,8 @@ void test_diff_rename__many_files(void) cl_git_pass(git_diff_foreach( diff, diff_file_cb, NULL, NULL, &exp)); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(51, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(52, exp.files); + cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 2, exp.files); opts.flags = GIT_DIFF_FIND_ALL; cl_git_pass(git_diff_find_similar(diff, &opts)); @@ -1216,8 +1207,8 @@ void test_diff_rename__many_files(void) cl_git_pass(git_diff_foreach( diff, diff_file_cb, NULL, NULL, &exp)); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(50, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(51, exp.files); + cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 1, exp.files); git_diff_list_free(diff); @@ -1225,3 +1216,59 @@ void test_diff_rename__many_files(void) git_tree_free(tree); git_index_free(index); } + +void test_diff_rename__many_files(void) +{ + git_index *index; + char tmp[64]; + int i, j; + git_buf b = GIT_BUF_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + + for (i = 0; i < 100; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + for (j = i * 256; j > 0; --j) + git_buf_printf(&b, "more content %d\n", i); + cl_git_mkfile(tmp, b.ptr); + } + + for (i = 0; i < 100; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); + } + + git_buf_free(&b); + git_index_free(index); + + test_with_many(100); +} + +void test_diff_rename__again_many_files(void) +{ + git_index *index; + char tmp[64]; + int i; + git_buf b = GIT_BUF_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + + git_buf_printf(&b, "%08d\n" ANOTHER_POEM "%08d\n" ANOTHER_POEM ANOTHER_POEM, 0, 0); + + for (i = 0; i < 2500; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + snprintf(b.ptr, 9, "%08d", i); + b.ptr[8] = '\n'; + cl_git_mkfile(tmp, b.ptr); + } + git_buf_free(&b); + + for (i = 0; i < 2500; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); + } + + git_index_free(index); + + test_with_many(2500); +} From a42c2a8c89189afbcfccbc17f798971c18c15de6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 31 Jul 2013 21:51:50 -0500 Subject: [PATCH 175/367] Rename test for multiple similar matches A rename test that illustrates a source matching multiple targets. --- tests-clar/diff/rename.c | 66 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 20ee66288..108b34621 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -871,6 +871,8 @@ void test_diff_rename__from_deleted_to_split(void) struct rename_expected { size_t len; + + unsigned int *status; const char **sources; const char **targets; @@ -885,7 +887,7 @@ int test_names_expected(const git_diff_delta *delta, float progress, void *p) cl_assert(expected->idx < expected->len); - cl_assert_equal_i(delta->status, GIT_DELTA_RENAMED); + cl_assert_equal_i(delta->status, expected->status[expected->idx]); cl_assert(git__strcmp(expected->sources[expected->idx], delta->old_file.path) == 0); @@ -907,9 +909,10 @@ void test_diff_rename__rejected_match_can_match_others(void) git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; git_buf one = GIT_BUF_INIT, two = GIT_BUF_INIT; + unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; const char *sources[] = { "Class1.cs", "Class2.cs" }; const char *targets[] = { "ClassA.cs", "ClassB.cs" }; - struct rename_expected expect = { 2, sources, targets }; + struct rename_expected expect = { 2, status, sources, targets }; char *ptr; opts.checkout_strategy = GIT_CHECKOUT_FORCE; @@ -991,9 +994,10 @@ void test_diff_rename__rejected_match_can_match_others_two(void) git_diff_list *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; const char *sources[] = { "a.txt", "b.txt" }; const char *targets[] = { "c.txt", "d.txt" }; - struct rename_expected expect = { 2, sources, targets }; + struct rename_expected expect = { 2, status, sources, targets }; opts.checkout_strategy = GIT_CHECKOUT_FORCE; @@ -1036,6 +1040,62 @@ void test_diff_rename__rejected_match_can_match_others_two(void) git_reference_free(selfsimilar); } +void test_diff_rename__rejected_match_can_match_others_three(void) +{ + git_reference *head, *selfsimilar; + git_index *index; + git_tree *tree; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_diff_list *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + /* Both cannot be renames from a.txt */ + unsigned int status[] = { GIT_DELTA_ADDED, GIT_DELTA_RENAMED }; + const char *sources[] = { "0001.txt", "a.txt" }; + const char *targets[] = { "0001.txt", "0002.txt" }; + struct rename_expected expect = { 2, status, sources, targets }; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_git_pass(git_reference_symbolic_set_target( + &selfsimilar, head, "refs/heads/renames_similar_two")); + cl_git_pass(git_checkout_head(g_repo, &opts)); + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(p_unlink("renames/a.txt")); + + cl_git_pass(git_index_remove_bypath(index, "a.txt")); + + write_similarity_file_two("renames/0001.txt", 7); + write_similarity_file_two("renames/0002.txt", 0); + + cl_git_pass(git_index_add_bypath(index, "0001.txt")); + cl_git_pass(git_index_add_bypath(index, "0002.txt")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass( + git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + cl_git_pass( + git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect)); + + cl_assert(expect.idx == expect.len); + + git_diff_list_free(diff); + git_tree_free(tree); + git_index_free(index); + git_reference_free(head); + git_reference_free(selfsimilar); +} + void test_diff_rename__case_changes_are_split(void) { git_index *index; From 7edb74d374f401cea50be143b63c7d2d141d18be Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sun, 4 Aug 2013 14:06:13 -0700 Subject: [PATCH 176/367] Update rename src map for any split src When using a rename source that is actually a to-be-split record, we have to update the best-fit mapping data in both the case where the target is also a split record and the case where the target is a simple added record. Before this commit, we were only doing the update when the target was itself a split record (and even in that case, the test was slightly wrong). --- src/diff_tform.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/diff_tform.c b/src/diff_tform.c index 92c4036fb..ba35d3c14 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -947,6 +947,11 @@ find_best_matches: src->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; num_updates++; + + if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { + /* what used to be at src t is now at src s */ + tgt2src[src2tgt[t].idx].idx = (uint32_t)s; + } } } @@ -996,7 +1001,7 @@ find_best_matches: num_rewrites--; } /* otherwise, if we just overwrote a source, update mapping */ - else if (s > t && src2tgt[t].similarity > 0) { + else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { /* what used to be at src t is now at src s */ tgt2src[src2tgt[t].idx].idx = (uint32_t)s; } From 0a38eb42ca207f7aeb9e48a172fdbbc587201eb1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 31 Jul 2013 22:36:50 -0500 Subject: [PATCH 177/367] Rename test for rename from rewrite A rename test that illustrates a rename from a rewrite. --- tests-clar/diff/rename.c | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 108b34621..b78122c7e 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -1096,6 +1096,56 @@ void test_diff_rename__rejected_match_can_match_others_three(void) git_reference_free(selfsimilar); } +void test_diff_rename__can_rename_from_rewrite(void) +{ + git_index *index; + git_tree *tree; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_diff_list *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; + const char *sources[] = { "ikeepsix.txt", "songof7cities.txt" }; + const char *targets[] = { "songof7cities.txt", "this-is-a-rename.txt" }; + struct rename_expected expect = { 2, status, sources, targets }; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(p_rename("renames/songof7cities.txt", "renames/this-is-a-rename.txt")); + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/songof7cities.txt")); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_add_bypath(index, "this-is-a-rename.txt")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass( + git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + findopts.flags |= GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + cl_git_pass( + git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect)); + + cl_assert(expect.idx == expect.len); + + git_diff_list_free(diff); + git_tree_free(tree); + git_index_free(index); +} + void test_diff_rename__case_changes_are_split(void) { git_index *index; @@ -1284,6 +1334,8 @@ void test_diff_rename__many_files(void) int i, j; git_buf b = GIT_BUF_INIT; +/* + cl_git_pass(git_repository_index(&index, g_repo)); for (i = 0; i < 100; i += 1) { @@ -1302,6 +1354,7 @@ void test_diff_rename__many_files(void) git_index_free(index); test_with_many(100); +*/ } void test_diff_rename__again_many_files(void) @@ -1311,6 +1364,8 @@ void test_diff_rename__again_many_files(void) int i; git_buf b = GIT_BUF_INIT; +/* + cl_git_pass(git_repository_index(&index, g_repo)); git_buf_printf(&b, "%08d\n" ANOTHER_POEM "%08d\n" ANOTHER_POEM ANOTHER_POEM, 0, 0); @@ -1331,4 +1386,5 @@ void test_diff_rename__again_many_files(void) git_index_free(index); test_with_many(2500); +*/ } From 31b42eacce29defe07bdc1fa50a21aa11b23ea83 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sun, 4 Aug 2013 14:09:44 -0700 Subject: [PATCH 178/367] Restore commented out tests This restores the commented out tests (even though they're slow) and fixes some trailing whitespace. --- tests-clar/diff/rename.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index b78122c7e..55555012a 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -871,7 +871,7 @@ void test_diff_rename__from_deleted_to_split(void) struct rename_expected { size_t len; - + unsigned int *status; const char **sources; const char **targets; @@ -1129,7 +1129,7 @@ void test_diff_rename__can_rename_from_rewrite(void) cl_git_pass( git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - + findopts.flags |= GIT_DIFF_FIND_AND_BREAK_REWRITES | GIT_DIFF_FIND_REWRITES | GIT_DIFF_FIND_RENAMES_FROM_REWRITES; @@ -1334,8 +1334,6 @@ void test_diff_rename__many_files(void) int i, j; git_buf b = GIT_BUF_INIT; -/* - cl_git_pass(git_repository_index(&index, g_repo)); for (i = 0; i < 100; i += 1) { @@ -1354,7 +1352,6 @@ void test_diff_rename__many_files(void) git_index_free(index); test_with_many(100); -*/ } void test_diff_rename__again_many_files(void) @@ -1364,8 +1361,6 @@ void test_diff_rename__again_many_files(void) int i; git_buf b = GIT_BUF_INIT; -/* - cl_git_pass(git_repository_index(&index, g_repo)); git_buf_printf(&b, "%08d\n" ANOTHER_POEM "%08d\n" ANOTHER_POEM ANOTHER_POEM, 0, 0); @@ -1386,5 +1381,4 @@ void test_diff_rename__again_many_files(void) git_index_free(index); test_with_many(2500); -*/ } From e8242022bc409b3cb7e234eabdd9bda05ae3a158 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 5 Aug 2013 09:59:02 -0700 Subject: [PATCH 179/367] Move slow tests to "stress" clar module Create a new section of clar tests "stress" that will default to being off where we can put slow tests that push the library for performance testing purposes. --- CMakeLists.txt | 2 +- tests-clar/diff/rename.c | 147 ----------------------------------- tests-clar/stress/diff.c | 164 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 148 deletions(-) create mode 100644 tests-clar/stress/diff.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c937ba93c..53f568ed3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -361,7 +361,7 @@ IF (BUILD_CLAR) ADD_CUSTOM_COMMAND( OUTPUT ${CLAR_PATH}/clar.suite - COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline . + COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline -xstress . DEPENDS ${SRC_TEST} WORKING_DIRECTORY ${CLAR_PATH} ) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 55555012a..5a35495f7 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -1235,150 +1235,3 @@ void test_diff_rename__unmodified_can_be_renamed(void) git_index_free(index); git_tree_free(tree); } - -#define ANOTHER_POEM \ -"OH, glorious are the guarded heights\nWhere guardian souls abide—\nSelf-exiled from our gross delights—\nAbove, beyond, outside:\nAn ampler arc their spirit swings—\nCommands a juster view—\nWe have their word for all these things,\nNo doubt their words are true.\n\nYet we, the bond slaves of our day,\nWhom dirt and danger press—\nCo-heirs of insolence, delay,\nAnd leagued unfaithfulness—\nSuch is our need must seek indeed\nAnd, having found, engage\nThe men who merely do the work\nFor which they draw the wage.\n\nFrom forge and farm and mine and bench,\nDeck, altar, outpost lone—\nMill, school, battalion, counter, trench,\nRail, senate, sheepfold, throne—\nCreation's cry goes up on high\nFrom age to cheated age:\n\"Send us the men who do the work\n\"For which they draw the wage!\"\n" - -static void test_with_many(size_t expected_new) -{ - git_index *index; - git_tree *tree, *new_tree; - git_diff_list *diff = NULL; - diff_expects exp; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, NULL, NULL, &exp)); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(expected_new + 2, exp.files); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, NULL, NULL, &exp)); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(expected_new + 1, exp.files); - - git_diff_list_free(diff); - - { - git_object *parent; - git_signature *sig; - git_oid tree_id, commit_id; - git_reference *ref; - - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_tree_lookup(&new_tree, g_repo, &tree_id)); - - cl_git_pass(git_revparse_ext(&parent, &ref, g_repo, "HEAD")); - cl_git_pass(git_signature_new( - &sig, "Sm Test", "sm@tester.test", 1372350000, 480)); - - cl_git_pass(git_commit_create_v( - &commit_id, g_repo, git_reference_name(ref), sig, sig, - NULL, "yoyoyo", new_tree, 1, parent)); - - git_object_free(parent); - git_reference_free(ref); - git_signature_free(sig); - } - - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, tree, new_tree, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, NULL, NULL, &exp)); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(expected_new + 2, exp.files); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, NULL, NULL, &exp)); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(expected_new + 1, exp.files); - - git_diff_list_free(diff); - - git_tree_free(new_tree); - git_tree_free(tree); - git_index_free(index); -} - -void test_diff_rename__many_files(void) -{ - git_index *index; - char tmp[64]; - int i, j; - git_buf b = GIT_BUF_INIT; - - cl_git_pass(git_repository_index(&index, g_repo)); - - for (i = 0; i < 100; i += 1) { - snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); - for (j = i * 256; j > 0; --j) - git_buf_printf(&b, "more content %d\n", i); - cl_git_mkfile(tmp, b.ptr); - } - - for (i = 0; i < 100; i += 1) { - snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); - cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); - } - - git_buf_free(&b); - git_index_free(index); - - test_with_many(100); -} - -void test_diff_rename__again_many_files(void) -{ - git_index *index; - char tmp[64]; - int i; - git_buf b = GIT_BUF_INIT; - - cl_git_pass(git_repository_index(&index, g_repo)); - - git_buf_printf(&b, "%08d\n" ANOTHER_POEM "%08d\n" ANOTHER_POEM ANOTHER_POEM, 0, 0); - - for (i = 0; i < 2500; i += 1) { - snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); - snprintf(b.ptr, 9, "%08d", i); - b.ptr[8] = '\n'; - cl_git_mkfile(tmp, b.ptr); - } - git_buf_free(&b); - - for (i = 0; i < 2500; i += 1) { - snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); - cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); - } - - git_index_free(index); - - test_with_many(2500); -} diff --git a/tests-clar/stress/diff.c b/tests-clar/stress/diff.c new file mode 100644 index 000000000..62ccd5ec7 --- /dev/null +++ b/tests-clar/stress/diff.c @@ -0,0 +1,164 @@ +#include "clar_libgit2.h" +#include "../diff/diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_stress_diff__initialize(void) +{ +} + +void test_stress_diff__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define ANOTHER_POEM \ +"OH, glorious are the guarded heights\nWhere guardian souls abide—\nSelf-exiled from our gross delights—\nAbove, beyond, outside:\nAn ampler arc their spirit swings—\nCommands a juster view—\nWe have their word for all these things,\nNo doubt their words are true.\n\nYet we, the bond slaves of our day,\nWhom dirt and danger press—\nCo-heirs of insolence, delay,\nAnd leagued unfaithfulness—\nSuch is our need must seek indeed\nAnd, having found, engage\nThe men who merely do the work\nFor which they draw the wage.\n\nFrom forge and farm and mine and bench,\nDeck, altar, outpost lone—\nMill, school, battalion, counter, trench,\nRail, senate, sheepfold, throne—\nCreation's cry goes up on high\nFrom age to cheated age:\n\"Send us the men who do the work\n\"For which they draw the wage!\"\n" + +static void test_with_many(size_t expected_new) +{ + git_index *index; + git_tree *tree, *new_tree; + git_diff_list *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 2, exp.files); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 1, exp.files); + + git_diff_list_free(diff); + + { + git_object *parent; + git_signature *sig; + git_oid tree_id, commit_id; + git_reference *ref; + + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_tree_lookup(&new_tree, g_repo, &tree_id)); + + cl_git_pass(git_revparse_ext(&parent, &ref, g_repo, "HEAD")); + cl_git_pass(git_signature_new( + &sig, "Sm Test", "sm@tester.test", 1372350000, 480)); + + cl_git_pass(git_commit_create_v( + &commit_id, g_repo, git_reference_name(ref), sig, sig, + NULL, "yoyoyo", new_tree, 1, parent)); + + git_object_free(parent); + git_reference_free(ref); + git_signature_free(sig); + } + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree, new_tree, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 2, exp.files); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 1, exp.files); + + git_diff_list_free(diff); + + git_tree_free(new_tree); + git_tree_free(tree); + git_index_free(index); +} + +void test_stress_diff__rename_big_files(void) +{ + git_index *index; + char tmp[64]; + int i, j; + git_buf b = GIT_BUF_INIT; + + g_repo = cl_git_sandbox_init("renames"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + for (i = 0; i < 100; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + for (j = i * 256; j > 0; --j) + git_buf_printf(&b, "more content %d\n", i); + cl_git_mkfile(tmp, b.ptr); + } + + for (i = 0; i < 100; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); + } + + git_buf_free(&b); + git_index_free(index); + + test_with_many(100); +} + +void test_stress_diff__rename_many_files(void) +{ + git_index *index; + char tmp[64]; + int i; + git_buf b = GIT_BUF_INIT; + + g_repo = cl_git_sandbox_init("renames"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + git_buf_printf(&b, "%08d\n" ANOTHER_POEM "%08d\n" ANOTHER_POEM ANOTHER_POEM, 0, 0); + + for (i = 0; i < 2500; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + snprintf(b.ptr, 9, "%08d", i); + b.ptr[8] = '\n'; + cl_git_mkfile(tmp, b.ptr); + } + git_buf_free(&b); + + for (i = 0; i < 2500; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); + } + + git_index_free(index); + + test_with_many(2500); +} From 9b7d02ff2d9b87d61778ee7ef5e2d43bf4c561f0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 5 Aug 2013 10:53:39 -0700 Subject: [PATCH 180/367] Update submodule documentation Fixes #1762 --- include/git2/submodule.h | 51 +++++++++++++++++++++++++++++----------- include/git2/types.h | 51 ++++++++++++++++++++++++++++++++++------ 2 files changed, 81 insertions(+), 21 deletions(-) diff --git a/include/git2/submodule.h b/include/git2/submodule.h index ba7a558d0..186f263f5 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -316,9 +316,10 @@ GIT_EXTERN(const git_oid *) git_submodule_head_id(git_submodule *submodule); GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule); /** - * Get the ignore rule for the submodule. + * Get the ignore rule that will be used for the submodule. * - * There are four ignore values: + * These values control the behavior of `git_submodule_status()` for this + * submodule. There are four ignore values: * * - **GIT_SUBMODULE_IGNORE_NONE** will consider any change to the contents * of the submodule from a clean checkout to be dirty, including the @@ -332,6 +333,13 @@ GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule); * - **GIT_SUBMODULE_IGNORE_ALL** means not to open the submodule repo. * The working directory will be consider clean so long as there is a * checked out version present. + * + * plus the special **GIT_SUBMODULE_IGNORE_RESET** which can be used with + * `git_submodule_set_ignore()` to revert to the on-disk setting. + * + * @param submodule The submodule to check + * @return The current git_submodule_ignore_t valyue what will be used for + * this submodule. */ GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore( git_submodule *submodule); @@ -339,15 +347,17 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore( /** * Set the ignore rule for the submodule. * - * This sets the ignore rule in memory for the submodule. This will be used - * for any following actions (such as `git_submodule_status()`) while the - * submodule is in memory. You should call `git_submodule_save()` if you - * want to persist the new ignore role. + * This sets the in-memory ignore rule for the submodule which will + * control the behavior of `git_submodule_status()`. * - * Calling this again with GIT_SUBMODULE_IGNORE_RESET or calling - * `git_submodule_reload()` will revert the rule to the value that was in - * the original config. + * To make changes persistent, call `git_submodule_save()` to write the + * value to disk (in the ".gitmodules" and ".git/config" files). * + * Call with `GIT_SUBMODULE_IGNORE_RESET` or call `git_submodule_reload()` + * to revert the in-memory rule to the value that is on disk. + * + * @param submodule The submodule to update + * @param ignore The new value for the ignore rule * @return old value for ignore */ GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore( @@ -355,7 +365,16 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore( git_submodule_ignore_t ignore); /** - * Get the update rule for the submodule. + * Get the update rule that will be used for the submodule. + * + * This value controls the behavior of the `git submodule update` command. + * There are four useful values documented with `git_submodule_update_t` + * plus the `GIT_SUBMODULE_UPDATE_RESET` which can be used to revert to + * the on-disk setting. + * + * @param submodule The submodule to check + * @return The current git_submodule_update_t value that will be used + * for this submodule. */ GIT_EXTERN(git_submodule_update_t) git_submodule_update( git_submodule *submodule); @@ -363,13 +382,17 @@ GIT_EXTERN(git_submodule_update_t) git_submodule_update( /** * Set the update rule for the submodule. * - * This sets the update rule in memory for the submodule. You should call - * `git_submodule_save()` if you want to persist the new update rule. + * The initial value comes from the ".git/config" setting of + * `submodule.$name.update` for this submodule (which is initialized from + * the ".gitmodules" file). Using this function sets the update rule in + * memory for the submodule. Call `git_submodule_save()` to write out the + * new update rule. * * Calling this again with GIT_SUBMODULE_UPDATE_RESET or calling - * `git_submodule_reload()` will revert the rule to the value that was in - * the original config. + * `git_submodule_reload()` will revert the rule to the on disk value. * + * @param submodule The submodule to update + * @param update The new value to use * @return old value for update */ GIT_EXTERN(git_submodule_update_t) git_submodule_set_update( diff --git a/include/git2/types.h b/include/git2/types.h index 1da0d3ed2..b500c986d 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -235,10 +235,29 @@ typedef int (*git_transfer_progress_callback)(const git_transfer_progress *stats typedef struct git_submodule git_submodule; /** - * Values that could be specified for the update rule of a submodule. + * Submodule update values * - * Use the RESET value if you have altered the in-memory update value via - * `git_submodule_set_update()` and wish to reset to the original default. + * These values represent settings for the `submodule.$name.update` + * configuration value which says how to handle `git submodule update` for + * this submodule. The value is usually set in the ".gitmodules" file and + * copied to ".git/config" when the submodule is initialized. + * + * You can override this setting on a per-submodule basis with + * `git_submodule_set_update()` and write the changed value to disk using + * `git_submodule_save()`. If you have overwritten the value, you can + * revert it by passing `GIT_SUBMODULE_UPDATE_RESET` to the set function. + * + * The values are: + * + * - GIT_SUBMODULE_UPDATE_RESET: reset to the on-disk value. + * - GIT_SUBMODULE_UPDATE_CHECKOUT: the default; when a submodule is + * updated, checkout the new detached HEAD to the submodule directory. + * - GIT_SUBMODULE_UPDATE_REBASE: update by rebasing the current checked + * out branch onto the commit from the superproject. + * - GIT_SUBMODULE_UPDATE_MERGE: update by merging the commit in the + * superproject into the current checkout out branch of the submodule. + * - GIT_SUBMODULE_UPDATE_NONE: do not update this submodule even when + * the commit in the superproject is updated. */ typedef enum { GIT_SUBMODULE_UPDATE_RESET = -1, @@ -249,11 +268,29 @@ typedef enum { } git_submodule_update_t; /** - * Values that could be specified for how closely to examine the - * working directory when getting submodule status. + * Submodule ignore values * - * Use the RESET value if you have altered the in-memory ignore value via - * `git_submodule_set_ignore()` and wish to reset to the original value. + * These values represent settings for the `submodule.$name.ignore` + * configuration value which says how deeply to look at the working + * directory when getting submodule status. + * + * You can override this value in memory on a per-submodule basis with + * `git_submodule_set_ignore()` and can write the changed value to disk + * with `git_submodule_save()`. If you have overwritten the value, you + * can revert to the on disk value by using `GIT_SUBMODULE_IGNORE_RESET`. + * + * The values are: + * + * - GIT_SUBMODULE_IGNORE_RESET: reset to the on-disk value. + * - GIT_SUBMODULE_IGNORE_NONE: don't ignore any change - i.e. even an + * untracked file, will mark the submodule as dirty. Ignored files are + * still ignored, of course. + * - GIT_SUBMODULE_IGNORE_UNTRACKED: ignore untracked files; only changes + * to tracked files, or the index or the HEAD commit will matter. + * - GIT_SUBMODULE_IGNORE_DIRTY: ignore changes in the working directory, + * only considering changes if the HEAD of submodule has moved from the + * value in the superproject. + * - GIT_SUBMODULE_IGNORE_ALL: never check if the submodule is dirty */ typedef enum { GIT_SUBMODULE_IGNORE_RESET = -1, /* reset to on-disk value */ From d85636190f127efa2ec4a6593124c037dfec0ba9 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Mon, 5 Aug 2013 11:41:39 -0700 Subject: [PATCH 181/367] Split UTF-16 and UTF-8 buffer sizes for win32 Also fixed up call-sites to use the correct buffer sizes, especially when converting to utf-8. --- src/fileops.c | 4 +-- src/path.c | 4 +-- src/transports/winhttp.c | 4 +-- src/win32/dir.c | 12 +++---- src/win32/dir.h | 2 +- src/win32/posix.h | 4 +-- src/win32/posix_w32.c | 66 +++++++++++++++++++-------------------- src/win32/utf-conv.c | 2 +- src/win32/utf-conv.h | 3 +- tests-clar/clar_libgit2.c | 22 ++++++------- 10 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 7f8418d7a..36fcc73df 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -58,9 +58,9 @@ int git_futils_creat_locked(const char *path, const mode_t mode) int fd; #ifdef GIT_WIN32 - wchar_t buf[GIT_WIN_PATH]; + wchar_t buf[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY | O_CLOEXEC, mode); #else diff --git a/src/path.c b/src/path.c index 6437979d5..ca0cc8c7c 100644 --- a/src/path.c +++ b/src/path.c @@ -486,14 +486,14 @@ bool git_path_is_empty_dir(const char *path) { git_buf pathbuf = GIT_BUF_INIT; HANDLE hFind = INVALID_HANDLE_VALUE; - wchar_t wbuf[GIT_WIN_PATH]; + wchar_t wbuf[GIT_WIN_PATH_UTF16]; WIN32_FIND_DATAW ffd; bool retval = true; if (!git_path_isdir(path)) return false; git_buf_printf(&pathbuf, "%s\\*", path); - git__utf8_to_16(wbuf, GIT_WIN_PATH, git_buf_cstr(&pathbuf)); + git__utf8_to_16(wbuf, GIT_WIN_PATH_UTF16, git_buf_cstr(&pathbuf)); hFind = FindFirstFileW(wbuf, &ffd); if (INVALID_HANDLE_VALUE == hFind) { diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 95e422dc0..6064f90d8 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -893,7 +893,7 @@ static int winhttp_connect( const char *url) { wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; - wchar_t host[GIT_WIN_PATH]; + wchar_t host[GIT_WIN_PATH_UTF16]; int32_t port; const char *default_port = "80"; int ret; @@ -920,7 +920,7 @@ static int winhttp_connect( return -1; /* Prepare host */ - git__utf8_to_16(host, GIT_WIN_PATH, t->host); + git__utf8_to_16(host, GIT_WIN_PATH_UTF16, t->host); /* Establish session */ t->session = WinHttpOpen( diff --git a/src/win32/dir.c b/src/win32/dir.c index 8c51d8378..b18c603f7 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -25,8 +25,8 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { - char filter[GIT_WIN_PATH]; - wchar_t filter_w[GIT_WIN_PATH]; + char filter[GIT_WIN_PATH_UTF8]; + wchar_t filter_w[GIT_WIN_PATH_UTF16]; git__DIR *new = NULL; if (!dir || !init_filter(filter, sizeof(filter), dir)) @@ -40,7 +40,7 @@ git__DIR *git__opendir(const char *dir) if (!new->dir) goto fail; - git__utf8_to_16(filter_w, GIT_WIN_PATH, filter); + git__utf8_to_16(filter_w, GIT_WIN_PATH_UTF16, filter); new->h = FindFirstFileW(filter_w, &new->f); if (new->h == INVALID_HANDLE_VALUE) { @@ -101,8 +101,8 @@ struct git__dirent *git__readdir(git__DIR *d) void git__rewinddir(git__DIR *d) { - char filter[GIT_WIN_PATH]; - wchar_t filter_w[GIT_WIN_PATH]; + char filter[GIT_WIN_PATH_UTF8]; + wchar_t filter_w[GIT_WIN_PATH_UTF16]; if (!d) return; @@ -116,7 +116,7 @@ void git__rewinddir(git__DIR *d) if (!init_filter(filter, sizeof(filter), d->dir)) return; - git__utf8_to_16(filter_w, GIT_WIN_PATH, filter); + git__utf8_to_16(filter_w, GIT_WIN_PATH_UTF16, filter); d->h = FindFirstFileW(filter_w, &d->f); if (d->h == INVALID_HANDLE_VALUE) diff --git a/src/win32/dir.h b/src/win32/dir.h index 7696d468e..a3e154972 100644 --- a/src/win32/dir.h +++ b/src/win32/dir.h @@ -11,7 +11,7 @@ struct git__dirent { int d_ino; - char d_name[261]; + char d_name[260*4+1]; }; typedef struct { diff --git a/src/win32/posix.h b/src/win32/posix.h index c49c2175c..753f35a2d 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -20,9 +20,9 @@ GIT_INLINE(int) p_link(const char *old, const char *new) GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH]; + wchar_t buf[GIT_WIN_PATH_UTF16]; GIT_UNUSED(mode); - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); return _wmkdir(buf); } diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 036632e2a..a96741d1c 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -16,8 +16,8 @@ int p_unlink(const char *path) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + wchar_t buf[GIT_WIN_PATH_UTF16]; + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); _wchmod(buf, 0666); return _wunlink(buf); } @@ -59,10 +59,10 @@ static int do_lstat( const char *file_name, struct stat *buf, int posix_enotdir) { WIN32_FILE_ATTRIBUTE_DATA fdata; - wchar_t fbuf[GIT_WIN_PATH], lastch; + wchar_t fbuf[GIT_WIN_PATH_UTF16], lastch; int flen; - flen = git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name); + flen = git__utf8_to_16(fbuf, GIT_WIN_PATH_UTF16, file_name); /* truncate trailing slashes */ for (; flen > 0; --flen) { @@ -108,10 +108,10 @@ static int do_lstat( * the length of the path pointed to, which we expect everywhere else */ if (S_ISLNK(fMode)) { - char target[GIT_WIN_PATH]; + char target[GIT_WIN_PATH_UTF8]; int readlink_result; - readlink_result = p_readlink(file_name, target, GIT_WIN_PATH); + readlink_result = p_readlink(file_name, target, GIT_WIN_PATH_UTF8); if (readlink_result == -1) return -1; @@ -165,7 +165,7 @@ int p_readlink(const char *link, char *target, size_t target_len) static fpath_func pGetFinalPath = NULL; HANDLE hFile; DWORD dwRet; - wchar_t link_w[GIT_WIN_PATH]; + wchar_t link_w[GIT_WIN_PATH_UTF16]; wchar_t* target_w; int error = 0; @@ -188,7 +188,7 @@ int p_readlink(const char *link, char *target, size_t target_len) } } - git__utf8_to_16(link_w, GIT_WIN_PATH, link); + git__utf8_to_16(link_w, GIT_WIN_PATH_UTF16, link); hFile = CreateFileW(link_w, // file to open GENERIC_READ, // open for reading @@ -254,10 +254,10 @@ int p_symlink(const char *old, const char *new) int p_open(const char *path, int flags, ...) { - wchar_t buf[GIT_WIN_PATH]; + wchar_t buf[GIT_WIN_PATH_UTF16]; mode_t mode = 0; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); if (flags & O_CREAT) { va_list arg_list; @@ -272,8 +272,8 @@ int p_open(const char *path, int flags, ...) int p_creat(const char *path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + wchar_t buf[GIT_WIN_PATH_UTF16]; + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } @@ -299,7 +299,7 @@ int p_getcwd(char *buffer_out, size_t size) int p_stat(const char* path, struct stat* buf) { - char target[GIT_WIN_PATH]; + char target[GIT_WIN_PATH_UTF8]; int error = 0; error = do_lstat(path, buf, 0); @@ -307,7 +307,7 @@ int p_stat(const char* path, struct stat* buf) /* We need not do this in a loop to unwind chains of symlinks since * p_readlink calls GetFinalPathNameByHandle which does it for us. */ if (error >= 0 && S_ISLNK(buf->st_mode) && - (error = p_readlink(path, target, GIT_WIN_PATH)) >= 0) + (error = p_readlink(path, target, GIT_WIN_PATH_UTF8)) >= 0) error = do_lstat(target, buf, 0); return error; @@ -315,23 +315,23 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + wchar_t buf[GIT_WIN_PATH_UTF16]; + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + wchar_t buf[GIT_WIN_PATH_UTF16]; + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); return _wchmod(buf, mode); } int p_rmdir(const char* path) { int error; - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + wchar_t buf[GIT_WIN_PATH_UTF16]; + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); error = _wrmdir(buf); @@ -347,24 +347,24 @@ int p_rmdir(const char* path) int p_hide_directory__w32(const char *path) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + wchar_t buf[GIT_WIN_PATH_UTF16]; + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; } char *p_realpath(const char *orig_path, char *buffer) { int ret; - wchar_t orig_path_w[GIT_WIN_PATH]; - wchar_t buffer_w[GIT_WIN_PATH]; + wchar_t orig_path_w[GIT_WIN_PATH_UTF16]; + wchar_t buffer_w[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(orig_path_w, GIT_WIN_PATH, orig_path); + git__utf8_to_16(orig_path_w, GIT_WIN_PATH_UTF16, orig_path); /* Implicitly use GetCurrentDirectory which can be a threading issue */ - ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH, buffer_w, NULL); + ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL); /* According to MSDN, a return value equals to zero means a failure. */ - if (ret == 0 || ret > GIT_WIN_PATH) + if (ret == 0 || ret > GIT_WIN_PATH_UTF16) buffer = NULL; else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { @@ -448,18 +448,18 @@ int p_setenv(const char* name, const char* value, int overwrite) int p_access(const char* path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + wchar_t buf[GIT_WIN_PATH_UTF16]; + git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); return _waccess(buf, mode); } int p_rename(const char *from, const char *to) { - wchar_t wfrom[GIT_WIN_PATH]; - wchar_t wto[GIT_WIN_PATH]; + wchar_t wfrom[GIT_WIN_PATH_UTF16]; + wchar_t wto[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(wfrom, GIT_WIN_PATH, from); - git__utf8_to_16(wto, GIT_WIN_PATH, to); + git__utf8_to_16(wfrom, GIT_WIN_PATH_UTF16, from); + git__utf8_to_16(wto, GIT_WIN_PATH_UTF16, to); return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; } diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index c06f3a8c2..78d277494 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -77,5 +77,5 @@ int git__utf8_to_16(wchar_t *dest, size_t length, const char *src) int git__utf16_to_8(char *out, const wchar_t *input) { - return WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH, NULL, NULL); + return WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH_UTF8, NULL, NULL); } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 6cc9205f7..9922e2969 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -10,7 +10,8 @@ #ifndef INCLUDE_git_utfconv_h__ #define INCLUDE_git_utfconv_h__ -#define GIT_WIN_PATH (260 + 1) +#define GIT_WIN_PATH_UTF16 (260 + 1) +#define GIT_WIN_PATH_UTF8 (260 * 4 + 1) int git__utf8_to_16(wchar_t *dest, size_t length, const char *src); int git__utf16_to_8(char *dest, const wchar_t *src); diff --git a/tests-clar/clar_libgit2.c b/tests-clar/clar_libgit2.c index de0e41bf7..34d54cd94 100644 --- a/tests-clar/clar_libgit2.c +++ b/tests-clar/clar_libgit2.c @@ -56,17 +56,17 @@ void cl_git_rewritefile(const char *filename, const char *new_content) char *cl_getenv(const char *name) { - wchar_t name_utf16[GIT_WIN_PATH]; + wchar_t name_utf16[GIT_WIN_PATH_UTF16]; DWORD alloc_len; wchar_t *value_utf16; char *value_utf8; - git__utf8_to_16(name_utf16, GIT_WIN_PATH, name); + git__utf8_to_16(name_utf16, GIT_WIN_PATH_UTF16, name); alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0); if (alloc_len <= 0) return NULL; - alloc_len = GIT_WIN_PATH; + alloc_len = GIT_WIN_PATH_UTF8; cl_assert(value_utf16 = git__calloc(alloc_len, sizeof(wchar_t))); GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len); @@ -81,13 +81,13 @@ char *cl_getenv(const char *name) int cl_setenv(const char *name, const char *value) { - wchar_t name_utf16[GIT_WIN_PATH]; - wchar_t value_utf16[GIT_WIN_PATH]; + wchar_t name_utf16[GIT_WIN_PATH_UTF16]; + wchar_t value_utf16[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(name_utf16, GIT_WIN_PATH, name); + git__utf8_to_16(name_utf16, GIT_WIN_PATH_UTF16, name); if (value) { - git__utf8_to_16(value_utf16, GIT_WIN_PATH, value); + git__utf8_to_16(value_utf16, GIT_WIN_PATH_UTF16, value); cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16)); } else { /* Windows XP returns 0 (failed) when passing NULL for lpValue when @@ -107,12 +107,12 @@ int cl_setenv(const char *name, const char *value) * the source is a directory, a child of the source). */ int cl_rename(const char *source, const char *dest) { - wchar_t source_utf16[GIT_WIN_PATH]; - wchar_t dest_utf16[GIT_WIN_PATH]; + wchar_t source_utf16[GIT_WIN_PATH_UTF16]; + wchar_t dest_utf16[GIT_WIN_PATH_UTF16]; unsigned retries = 1; - git__utf8_to_16(source_utf16, GIT_WIN_PATH, source); - git__utf8_to_16(dest_utf16, GIT_WIN_PATH, dest); + git__utf8_to_16(source_utf16, GIT_WIN_PATH_UTF16, source); + git__utf8_to_16(dest_utf16, GIT_WIN_PATH_UTF16, dest); while (!MoveFileW(source_utf16, dest_utf16)) { /* Only retry if the error is ERROR_ACCESS_DENIED; From f1af935b8918e9f9eae63219a434a7dedbff1c38 Mon Sep 17 00:00:00 2001 From: Nikolai Vladimirov Date: Mon, 5 Aug 2013 21:53:09 +0300 Subject: [PATCH 182/367] submodule: check alloc and name presense --- src/submodule.c | 5 ++++- tests-clar/resources/submodules/gitmodules | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/submodule.c b/src/submodule.c index b4e917561..40bda9a41 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -1025,6 +1025,7 @@ static int submodule_get( if (!git_strmap_valid_index(smcfg, pos)) { sm = submodule_alloc(repo, name); + GITERR_CHECK_ALLOC(sm); /* insert value at name - if another thread beats us to it, then use * their record and release our own. @@ -1101,8 +1102,10 @@ static int submodule_load_from_config( namestart = key + strlen("submodule."); property = strrchr(namestart, '.'); - if (property == NULL) + + if (!property || (property == namestart)) return 0; + property++; is_path = (strcasecmp(property, "path") == 0); diff --git a/tests-clar/resources/submodules/gitmodules b/tests-clar/resources/submodules/gitmodules index 1262f8bb0..2798b696c 100644 --- a/tests-clar/resources/submodules/gitmodules +++ b/tests-clar/resources/submodules/gitmodules @@ -1,3 +1,6 @@ [submodule "testrepo"] + path = testrepo + url = +[submodule ""] path = testrepo url = \ No newline at end of file From e38f0d69aba011d02ba5cabc648fee6c6f4dcc29 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 5 Aug 2013 14:06:41 -0500 Subject: [PATCH 183/367] Add rename from rewrites to status In git_diff_paired_foreach, temporarily resort the index->workdir diff list by index path so that we can track a rename in the workdir from head->index->workdir. --- include/git2/status.h | 5 +- src/diff.c | 77 +++++++++++++++------ src/status.c | 29 ++------ tests-clar/status/renames.c | 129 ++++++++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 45 deletions(-) diff --git a/include/git2/status.h b/include/git2/status.h index 2f7c06726..aa934d96b 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -107,7 +107,7 @@ typedef enum { * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection * should be processed between the head and the index and enables * the GIT_STATUS_INDEX_RENAMED as a possible status flag. - * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename + * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that rename * detection should be run between the index and the working directory * and enabled GIT_STATUS_WT_RENAMED as a possible status flag. * - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case @@ -116,6 +116,8 @@ typedef enum { * - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case * sensitivity for the file system and forces the output to be in * case-insensitive order + * - GIT_STATUS_OPT_RENAMES_FROM_REWRITES indicates that rename detection + * should include rewritten files * * Calling `git_status_foreach()` is like calling the extended version * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, @@ -134,6 +136,7 @@ typedef enum { GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9), GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10), + GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11), } git_status_opt_t; #define GIT_STATUS_OPT_DEFAULTS \ diff --git a/src/diff.c b/src/diff.c index e875d09b3..77dbbd8bc 100644 --- a/src/diff.c +++ b/src/diff.c @@ -258,6 +258,26 @@ int git_diff_delta__casecmp(const void *a, const void *b) return val ? val : ((int)da->status - (int)db->status); } +GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta) +{ + return delta->old_file.path ? + delta->old_file.path : delta->new_file.path; +} + +int git_diff_delta__i2w_cmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +int git_diff_delta__i2w_casecmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta) { @@ -1276,7 +1296,7 @@ int git_diff__paired_foreach( git_diff_delta *h2i, *i2w; size_t i, j, i_max, j_max; int (*strcomp)(const char *, const char *) = git__strcmp; - bool icase_mismatch; + bool h2i_icase, i2w_icase, icase_mismatch; i_max = head2idx ? head2idx->deltas.length : 0; j_max = idx2wd ? idx2wd->deltas.length : 0; @@ -1291,24 +1311,35 @@ int git_diff__paired_foreach( * Therefore the main thing we need to do here is make sure the diffs * are traversed in a compatible order. To do this, we temporarily * resort a mismatched diff to get the order correct. + * + * In order to traverse renames in the index->workdir, we need to + * ensure that we compare the index name on both sides, so we + * always sort by the old name in the i2w list. */ - icase_mismatch = - (head2idx != NULL && idx2wd != NULL && - ((head2idx->opts.flags ^ idx2wd->opts.flags) & GIT_DIFF_DELTAS_ARE_ICASE)); + h2i_icase = head2idx != NULL && + (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0; - /* force case-sensitive delta sort */ - if (icase_mismatch) { - if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); - git_vector_sort(&head2idx->deltas); - } else { - git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__cmp); - git_vector_sort(&idx2wd->deltas); - } + i2w_icase = idx2wd != NULL && + (idx2wd->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0; + + icase_mismatch = + (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); + + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); + git_vector_sort(&head2idx->deltas); } - else if (head2idx != NULL && head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) + + if (i2w_icase && !icase_mismatch) { strcomp = git__strcasecmp; + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp); + git_vector_sort(&idx2wd->deltas); + } else if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp); + git_vector_sort(&idx2wd->deltas); + } + for (i = 0, j = 0; i < i_max || j < j_max; ) { h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; @@ -1332,14 +1363,16 @@ int git_diff__paired_foreach( } /* restore case-insensitive delta sort */ - if (icase_mismatch) { - if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); - git_vector_sort(&head2idx->deltas); - } else { - git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp); - git_vector_sort(&idx2wd->deltas); - } + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); + git_vector_sort(&head2idx->deltas); + } + + /* restore idx2wd sort by new path */ + if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, + i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); + git_vector_sort(&idx2wd->deltas); } return 0; diff --git a/src/status.c b/src/status.c index ccb8d37da..b2353258b 100644 --- a/src/status.c +++ b/src/status.c @@ -225,24 +225,6 @@ static git_status_list *git_status_list_alloc(git_index *index) return status; } -/* -static int newfile_cmp(const void *a, const void *b) -{ - const git_diff_delta *delta_a = a; - const git_diff_delta *delta_b = b; - - return git__strcmp(delta_a->new_file.path, delta_b->new_file.path); -} - -static int newfile_casecmp(const void *a, const void *b) -{ - const git_diff_delta *delta_a = a; - const git_diff_delta *delta_b = b; - - return git__strcasecmp(delta_a->new_file.path, delta_b->new_file.path); -} -*/ - int git_status_list_new( git_status_list **out, git_repository *repo, @@ -251,7 +233,7 @@ int git_status_list_new( git_index *index = NULL; git_status_list *status = NULL; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts_i2w = GIT_DIFF_FIND_OPTIONS_INIT; + git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; @@ -284,6 +266,7 @@ int git_status_list_new( } diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE; + findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED; if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; @@ -300,7 +283,9 @@ int git_status_list_new( if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; - findopts_i2w.flags |= GIT_DIFF_FIND_FOR_UNTRACKED; + if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0) + findopt.flags = findopt.flags | GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES; if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { if ((error = git_diff_tree_to_index( @@ -308,7 +293,7 @@ int git_status_list_new( goto done; if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && - (error = git_diff_find_similar(status->head2idx, NULL)) < 0) + (error = git_diff_find_similar(status->head2idx, &findopt)) < 0) goto done; } @@ -318,7 +303,7 @@ int git_status_list_new( goto done; if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && - (error = git_diff_find_similar(status->idx2wd, &findopts_i2w)) < 0) + (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0) goto done; } diff --git a/tests-clar/status/renames.c b/tests-clar/status/renames.c index 80ff26020..836e65c88 100644 --- a/tests-clar/status/renames.c +++ b/tests-clar/status/renames.c @@ -153,6 +153,65 @@ void test_status_renames__head2index_two(void) git_index_free(index); } +void test_status_renames__head2index_no_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_MODIFIED, "sixserving.txt", "sixserving.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__head2index_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "sixserving.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); +} + void test_status_renames__index2workdir_one(void) { git_status_list *statuslist; @@ -197,6 +256,32 @@ void test_status_renames__index2workdir_two(void) git_status_list_free(statuslist); } +void test_status_renames__index2workdir_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED, "sixserving.txt", "ikeepsix.txt" }, + { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "sixserving.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); +} + void test_status_renames__both_one(void) { git_index *index; @@ -274,6 +359,50 @@ void test_status_renames__both_two(void) git_index_free(index); } + +void test_status_renames__both_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "songof7cities.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "ikeepsix.txt", "sixserving.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "sixserving.txt", "songof7cities.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "songof7cities.txt", "sixserving.txt"); + rename_file(g_repo, "_temp_.txt", "songof7cities.txt"); + + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_write(index)); + + rename_file(g_repo, "songof7cities.txt", "_temp_.txt"); + rename_file(g_repo, "ikeepsix.txt", "songof7cities.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 3); + git_status_list_free(statuslist); + + git_index_free(index); +} + void test_status_renames__both_casechange_one(void) { git_index *index; From 437224b4b912176ac0992aa56790142e3ba5bb8f Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 5 Aug 2013 21:46:32 -0700 Subject: [PATCH 184/367] More tests for ambiguous OIDs across packs The test coverage for ambiguous OIDs was pretty thin. This adds a bunch of new objects both in packs, across packs, and loose that match to 8 characters so that we can test various cases of ambiguous lookups. --- include/git2/odb.h | 2 +- tests-clar/odb/mixed.c | 64 ++++++++++++++++++ tests-clar/resources/duplicate.git/config | 2 +- .../0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea | Bin 0 -> 22 bytes ...a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx | Bin 0 -> 1184 bytes ...4896f0a0b9c9947b0927c57a5c03dcae052e3.pack | Bin 0 -> 249 bytes ...8eeacbd65cbd30a365d7564b45a468e8bd43d6.idx | Bin 0 -> 1268 bytes ...eeacbd65cbd30a365d7564b45a468e8bd43d6.pack | Bin 0 -> 369 bytes .../duplicate.git/refs/heads/dummy-marker.txt | 1 + 9 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tests-clar/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea create mode 100644 tests-clar/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx create mode 100644 tests-clar/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.pack create mode 100644 tests-clar/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.idx create mode 100644 tests-clar/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.pack create mode 100644 tests-clar/resources/duplicate.git/refs/heads/dummy-marker.txt diff --git a/include/git2/odb.h b/include/git2/odb.h index b64436c4d..b3e9a57a6 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -120,7 +120,7 @@ GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *i * @param db database to search for the object in. * @param short_id a prefix of the id of the object to read. * @param len the length of the prefix - * @return + * @return * - 0 if the object was read; * - GIT_ENOTFOUND if the object is not in the database. * - GIT_EAMBIGUOUS if the prefix is ambiguous (several objects match the prefix) diff --git a/tests-clar/odb/mixed.c b/tests-clar/odb/mixed.c index da0ed97d7..7f7120a6e 100644 --- a/tests-clar/odb/mixed.c +++ b/tests-clar/odb/mixed.c @@ -18,8 +18,72 @@ void test_odb_mixed__dup_oid(void) { const char hex[] = "ce013625030ba8dba906f756967f9e9ca394464a"; git_oid oid; git_odb_object *obj; + cl_git_pass(git_oid_fromstr(&oid, hex)); cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, GIT_OID_HEXSZ)); git_odb_object_free(obj); } +/* some known sha collisions of file content: + * 'aabqhq' and 'aaazvc' with prefix 'dea509d0' (+ '9' and + 'b') + * 'aaeufo' and 'aaaohs' with prefix '81b5bff5' (+ 'f' and + 'b') + * 'aafewy' and 'aaepta' with prefix '739e3c4c' + * 'aahsyn' and 'aadrjg' with prefix '0ddeaded' (+ '9' and + 'e') + */ + +void test_odb_mixed__dup_oid_prefix_0(void) { + char hex[10]; + git_oid oid; + git_odb_object *obj; + + /* ambiguous in the same pack file */ + + strncpy(hex, "dea509d0", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + + strncpy(hex, "dea509d09", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + strncpy(hex, "dea509d0b", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + /* ambiguous in different pack files */ + + strncpy(hex, "81b5bff5", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + + strncpy(hex, "81b5bff5b", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + strncpy(hex, "81b5bff5f", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + /* ambiguous in pack file and loose */ + + strncpy(hex, "0ddeaded", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + + strncpy(hex, "0ddeaded9", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + strncpy(hex, "0ddeadede", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); +} diff --git a/tests-clar/resources/duplicate.git/config b/tests-clar/resources/duplicate.git/config index 515f48362..a4ef456cb 100644 --- a/tests-clar/resources/duplicate.git/config +++ b/tests-clar/resources/duplicate.git/config @@ -1,5 +1,5 @@ [core] repositoryformatversion = 0 filemode = true - bare = false + bare = true logallrefupdates = true diff --git a/tests-clar/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea b/tests-clar/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea new file mode 100644 index 0000000000000000000000000000000000000000..47c2a631abd12d79b08f6d17d28995b531874f30 GIT binary patch literal 22 dcmbU|EC5~E2V?*M literal 0 HcmV?d00001 diff --git a/tests-clar/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx b/tests-clar/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx new file mode 100644 index 0000000000000000000000000000000000000000..acbed82b6fb447c68800127d296625d0bd8babe5 GIT binary patch literal 1184 zcmexg;-AdGz`z8=qhR=i03*;VaGC@!97;}bY>_WB(E(C5qj zV;8+%qP3xM>;A918|0+AWm^K8o=iQnT!Q)8qF&|NehG%CP_-*vig^o9-Z~>~d;e2W zQ)z6P{*pO8mnHtp$mps(8R{f#w`%6WrbBm(8dh^W<2?xU-%%jG3dB4>+`Q{U(mRiW o-}Wrc^4Vb@-d{a3Rh~m{*TbU9KNlW9x?i!bT~Y2+iQDl90Qwh9m;e9( literal 0 HcmV?d00001 diff --git a/tests-clar/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.pack b/tests-clar/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.pack new file mode 100644 index 0000000000000000000000000000000000000000..652b0c91fe9e5fd36849d0f6cd0656036636fba9 GIT binary patch literal 249 zcmVCOc7%Or9tpM|Mty(YM;*JdhET+|5JUd`T_1rFwv?5c$_mdFfcPQQK)1no@e6| zIC1v+9RJYg%l%^)y2ahQB32R&*5o;D_YP?t}S3J+gXHS^dywmgJit^ik z@N4)tF*R=8|Mg4zekX&zpm(#XuWn18-raEf_i=G|ednZ-e80utUOYFPzG33!drLVl zOh1>YCsz9>%VYVXhBI;Pbs(|Lr~4kH^5-lP*wg$ay(4f}cYEDyc`m_yv-SVFzl@#s z)Bb#I#f>(88>OC9dth@As5jMnpy6Tp@*S*ESDqBfO$o4aG7l)uWpO?*=l6Bd zU%w82&lYa1bXN2GEM!x4+)Q0XzSZD}%<|US2?94eJ7d3ZKDax6`V}tymGYS%GG6}F z-SxfX{PT@XvpF))=o~sbX=~b*#VbzzFS-zQTJ0J87B{1!g`5?0j7g z^7+uS%eDMLKa0gU-(>!qbWd={X#u5~EYs%H=>_|*`Q|_E_$OE1^hdW|FPLip1lIlLujBo%rnVwQi~0TD zt@+E1e5Y6c|Ij(-Wb5*@wPF1~7z1Y72AfyR@%Hi2I-~J~iGk}FQzeAIM$7vdkpG#f z5yIE?J^dWWzs*zw;d|?@2dRI~)B)z7(O7rpF%tu~BvXHy?SuDw%B>@k|J-f6n`zkA I^l)1f0Gki4jsO4v literal 0 HcmV?d00001 diff --git a/tests-clar/resources/duplicate.git/refs/heads/dummy-marker.txt b/tests-clar/resources/duplicate.git/refs/heads/dummy-marker.txt new file mode 100644 index 000000000..421376db9 --- /dev/null +++ b/tests-clar/resources/duplicate.git/refs/heads/dummy-marker.txt @@ -0,0 +1 @@ +dummy From 2d9f5b9f13107a4f59ea1c055620efeb603f2bab Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 7 Aug 2013 11:11:55 -0500 Subject: [PATCH 185/367] Parse config headers with quoted quotes Parse config headers that have the last quote on the line quoted instead of walking off the end. --- src/config_file.c | 11 +++++++++++ tests-clar/config/read.c | 21 ++++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/config_file.c b/src/config_file.c index 2b0732a13..570f286c8 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -792,6 +792,11 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con } switch (c) { + case 0: + set_parse_error(cfg, 0, "Unexpected end-of-line in section header"); + git_buf_free(&buf); + return -1; + case '"': ++quote_marks; continue; @@ -801,6 +806,12 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con switch (c) { case '"': + if (&line[rpos-1] == last_quote) { + set_parse_error(cfg, 0, "Missing closing quotation mark in section header"); + git_buf_free(&buf); + return -1; + } + case '\\': break; diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c index 9f943d0f6..a18dca89b 100644 --- a/tests-clar/config/read.c +++ b/tests-clar/config/read.c @@ -431,10 +431,10 @@ void test_config_read__simple_read_from_specific_level(void) git_config_free(cfg); } -static void clean_empty_config(void *unused) +static void clean_test_config(void *unused) { GIT_UNUSED(unused); - cl_fixture_cleanup("./empty"); + cl_fixture_cleanup("./testconfig"); } void test_config_read__can_load_and_parse_an_empty_config_file(void) @@ -442,10 +442,21 @@ void test_config_read__can_load_and_parse_an_empty_config_file(void) git_config *cfg; int i; - cl_set_cleanup(&clean_empty_config, NULL); - cl_git_mkfile("./empty", ""); - cl_git_pass(git_config_open_ondisk(&cfg, "./empty")); + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", ""); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); cl_assert_equal_i(GIT_ENOTFOUND, git_config_get_int32(&i, cfg, "nope.neither")); git_config_free(cfg); } + +void test_config_read__corrupt_header(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[sneaky ] \"quoted closing quote mark\\\""); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} From 8c8a54901071f7d8372ce901b098507d5a4a5804 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Tue, 6 Aug 2013 20:35:51 -0700 Subject: [PATCH 186/367] Add status test for long paths --- tests-clar/status/status_data.h | 4 ++++ tests-clar/status/worktree.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/tests-clar/status/status_data.h b/tests-clar/status/status_data.h index 3efa934ea..652fe4378 100644 --- a/tests-clar/status/status_data.h +++ b/tests-clar/status/status_data.h @@ -1,5 +1,9 @@ #include "status_helpers.h" +// A utf-8 string with 89 characters, but 267 bytes. +static const char *longname = "\xE5\x8F\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; + + /* entries for a plain copy of tests/resources/status */ static const char *entry_paths0[] = { diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 0e315cd60..10efd7473 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -865,3 +865,35 @@ void test_status_worktree__sorting_by_case(void) cl_assert_equal_i(0, counts.wrong_status_flags_count); cl_assert_equal_i(0, counts.wrong_sorted_path); } + +void test_status_worktree__long_filenames(void) +{ + char path[GIT_WIN_PATH_UTF8]; + const char *expected_paths[] = {path}; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; + + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + + // Create directory with amazingly long filename + sprintf(path, "empty_standard_repo/%s", longname); + cl_git_pass(git_futils_mkdir_r(path, NULL, 0777)); + sprintf(path, "empty_standard_repo/%s/foo", longname); + cl_git_mkfile(path, "dummy"); + + sprintf(path, "%s/foo", longname); + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + From 2984f3190e350099662b944dc45579bc194c65be Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 7 Aug 2013 05:55:12 -0700 Subject: [PATCH 187/367] Don't use win32-only macro in test code --- tests-clar/status/worktree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 10efd7473..be7398cb6 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -868,7 +868,7 @@ void test_status_worktree__sorting_by_case(void) void test_status_worktree__long_filenames(void) { - char path[GIT_WIN_PATH_UTF8]; + char path[260*4+1]; const char *expected_paths[] = {path}; const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; From c0c516935292732e56cd2f2ca1c0c471743f4adc Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Tue, 6 Aug 2013 21:05:03 -0700 Subject: [PATCH 188/367] Add long-file-name branch to test repo --- tests-clar/refs/list.c | 4 ++-- tests-clar/repo/iterator.c | 3 ++- .../6b/377958d8c6a4906e8573b53672a1a23a4e8ce6 | Bin 0 -> 167 bytes .../6b/9b767af9992b4abad5e24ffb1ba2d688ca602e | Bin 0 -> 41 bytes .../7b/2417a23b63e1fdde88c80e14b33247c6e5785a | Bin 0 -> 187 bytes .../testrepo/.gitted/refs/heads/long-file-name | 1 + tests-clar/revwalk/basic.c | 2 +- 7 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 tests-clar/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6 create mode 100644 tests-clar/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602e create mode 100644 tests-clar/resources/testrepo/.gitted/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785a create mode 100644 tests-clar/resources/testrepo/.gitted/refs/heads/long-file-name diff --git a/tests-clar/refs/list.c b/tests-clar/refs/list.c index c9c2af4a7..de5c0fd3d 100644 --- a/tests-clar/refs/list.c +++ b/tests-clar/refs/list.c @@ -36,7 +36,7 @@ void test_refs_list__all(void) /* We have exactly 12 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert_equal_i((int)ref_list.count, 13); + cl_assert_equal_i((int)ref_list.count, 14); git_strarray_free(&ref_list); } @@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten "144344043ba4d4a405da03de3844aa829ae8be0e\n"); cl_git_pass(git_reference_list(&ref_list, g_repo)); - cl_assert_equal_i((int)ref_list.count, 13); + cl_assert_equal_i((int)ref_list.count, 14); git_strarray_free(&ref_list); } diff --git a/tests-clar/repo/iterator.c b/tests-clar/repo/iterator.c index 11a7d2a23..1c513e9e7 100644 --- a/tests-clar/repo/iterator.c +++ b/tests-clar/repo/iterator.c @@ -906,6 +906,7 @@ void test_repo_iterator__fs2(void) static const char *expect_base[] = { "heads/br2", "heads/dir", + "heads/long-file-name", "heads/master", "heads/packed-test", "heads/subtrees", @@ -922,6 +923,6 @@ void test_repo_iterator__fs2(void) cl_git_pass(git_iterator_for_filesystem( &i, "testrepo/.git/refs", 0, NULL, NULL)); - expect_iterator_items(i, 11, expect_base, 11, expect_base); + expect_iterator_items(i, 12, expect_base, 12, expect_base); git_iterator_free(i); } diff --git a/tests-clar/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6 b/tests-clar/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6 new file mode 100644 index 0000000000000000000000000000000000000000..ee7c7817406f0a13b7e0d57b2bd87409403763bf GIT binary patch literal 167 zcmV;Y09gNc0hNwh3c@fD0R7G>_5w=Y4-iqW;0ZR#rY*EdX;Zwuy@J03GfZmBGIfwL zT>3@?E=nyNN)@dlQF12lljkIKR7z@>EE(qwvK2R~9e4-@BPNA`Feqb!H_|8Rino@O z<~}4zr7%4D*fw}mg-_q`h;WblbC~*g#9M011B7xWaN&f2D|ei;lb&y#{ literal 0 HcmV?d00001 diff --git a/tests-clar/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602e b/tests-clar/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602e new file mode 100644 index 0000000000000000000000000000000000000000..197685b8644b1d915a06f1fb2b572e89430d08e1 GIT binary patch literal 41 zcmV+^0M`F_0ZYosPf{>4Wk}3Ttjf$w?%$4He)a}FfcPQQ3!H%bn$g%SfOmF@NI2De~WFK%%kl}eMcVO zI|fyeRFs&PoDrXvnUktlQc=QSHvO9SOUI?QUN62aDtz+zSJ(!nGloV6K%kJ5nU@`3 zk{_R!S`JovAgKS^nHfD?4(GTZN*|o`r}wBy9@JErlI5ap2k*aFE|arAM`*r-Pngqx p!@W=?Py45jOau 16 */ - cl_assert_equal_i(16, i); + cl_assert_equal_i(17, i); } void test_revwalk_basic__push_head(void) From 75f98a95eee8a0efe8f9649ddc8a81c64d863ced Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 7 Aug 2013 06:12:27 -0700 Subject: [PATCH 189/367] Add checkout test for long file name --- tests-clar/checkout/tree.c | 19 +++++++++++++++++++ tests-clar/status/status_data.h | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index e4bfbce06..fff530cdd 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -677,3 +677,22 @@ void test_checkout_tree__target_directory_from_bare(void) cl_git_pass(git_futils_rmdir_r( "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); } + +void test_checkout_tree__extremely_long_file_name(void) +{ + // A utf-8 string with 83 characters, but 249 bytes. + const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; + char path[1024]; + + g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_revparse_single(&g_object, g_repo, "long-file-name")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + sprintf(path, "testrepo/%s.txt", longname); + cl_assert(git_path_exists(path)); + + git_object_free(g_object); + cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + cl_assert(!git_path_exists(path)); +} diff --git a/tests-clar/status/status_data.h b/tests-clar/status/status_data.h index 652fe4378..8ad4235fd 100644 --- a/tests-clar/status/status_data.h +++ b/tests-clar/status/status_data.h @@ -1,7 +1,7 @@ #include "status_helpers.h" -// A utf-8 string with 89 characters, but 267 bytes. -static const char *longname = "\xE5\x8F\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; +// A utf-8 string with 83 characters, but 249 bytes. +static const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; /* entries for a plain copy of tests/resources/status */ From 9c38f7a6523cdc87a897eccb6d83439987f99a4e Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 7 Aug 2013 13:22:41 -0700 Subject: [PATCH 190/367] Add typedefs for win32 utf-8 and utf-16 buffers ...and normalize the signatures of the two conversion functions. --- src/fileops.c | 4 +-- src/path.c | 4 +-- src/transports/winhttp.c | 8 +++--- src/win32/dir.c | 8 +++--- src/win32/findfile.c | 2 +- src/win32/posix.h | 4 +-- src/win32/posix_w32.c | 55 ++++++++++++++++++++------------------- src/win32/utf-conv.c | 8 +++--- src/win32/utf-conv.h | 7 +++-- tests-clar/clar_libgit2.c | 20 +++++++------- 10 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 36fcc73df..5e86d1a91 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -58,9 +58,9 @@ int git_futils_creat_locked(const char *path, const mode_t mode) int fd; #ifdef GIT_WIN32 - wchar_t buf[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 buf; - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git__utf8_to_16(buf, path); fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY | O_CLOEXEC, mode); #else diff --git a/src/path.c b/src/path.c index ca0cc8c7c..da7bd90a0 100644 --- a/src/path.c +++ b/src/path.c @@ -486,14 +486,14 @@ bool git_path_is_empty_dir(const char *path) { git_buf pathbuf = GIT_BUF_INIT; HANDLE hFind = INVALID_HANDLE_VALUE; - wchar_t wbuf[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 wbuf; WIN32_FIND_DATAW ffd; bool retval = true; if (!git_path_isdir(path)) return false; git_buf_printf(&pathbuf, "%s\\*", path); - git__utf8_to_16(wbuf, GIT_WIN_PATH_UTF16, git_buf_cstr(&pathbuf)); + git__utf8_to_16(wbuf, git_buf_cstr(&pathbuf)); hFind = FindFirstFileW(wbuf, &ffd); if (INVALID_HANDLE_VALUE == hFind) { diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 6064f90d8..fbf9b2f48 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -264,7 +264,7 @@ static int winhttp_stream_connect(winhttp_stream *s) if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0) goto on_error; - git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); + git__utf8_to_16(ct, git_buf_cstr(&buf)); if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { giterr_set(GITERR_OS, "Failed to add a header to the request"); @@ -593,7 +593,7 @@ replay: else snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); - git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8); + git__utf8_to_16(expected_content_type, expected_content_type_8); content_type_length = sizeof(content_type); if (!WinHttpQueryHeaders(s->request, @@ -893,7 +893,7 @@ static int winhttp_connect( const char *url) { wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; - wchar_t host[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 host; int32_t port; const char *default_port = "80"; int ret; @@ -920,7 +920,7 @@ static int winhttp_connect( return -1; /* Prepare host */ - git__utf8_to_16(host, GIT_WIN_PATH_UTF16, t->host); + git__utf8_to_16(host, t->host); /* Establish session */ t->session = WinHttpOpen( diff --git a/src/win32/dir.c b/src/win32/dir.c index b18c603f7..a638fce96 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -26,7 +26,7 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { char filter[GIT_WIN_PATH_UTF8]; - wchar_t filter_w[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 filter_w; git__DIR *new = NULL; if (!dir || !init_filter(filter, sizeof(filter), dir)) @@ -40,7 +40,7 @@ git__DIR *git__opendir(const char *dir) if (!new->dir) goto fail; - git__utf8_to_16(filter_w, GIT_WIN_PATH_UTF16, filter); + git__utf8_to_16(filter_w, filter); new->h = FindFirstFileW(filter_w, &new->f); if (new->h == INVALID_HANDLE_VALUE) { @@ -102,7 +102,7 @@ struct git__dirent *git__readdir(git__DIR *d) void git__rewinddir(git__DIR *d) { char filter[GIT_WIN_PATH_UTF8]; - wchar_t filter_w[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 filter_w; if (!d) return; @@ -116,7 +116,7 @@ void git__rewinddir(git__DIR *d) if (!init_filter(filter, sizeof(filter), d->dir)) return; - git__utf8_to_16(filter_w, GIT_WIN_PATH_UTF16, filter); + git__utf8_to_16(filter_w, filter); d->h = FindFirstFileW(filter_w, &d->f); if (d->h == INVALID_HANDLE_VALUE) diff --git a/src/win32/findfile.c b/src/win32/findfile.c index 9d9051bff..248173563 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -53,7 +53,7 @@ int git_win32__find_file( if (*filename == '/' || *filename == '\\') filename++; - git__utf8_to_16(file_utf16 + root->len - 1, alloc_len, filename); + git__utf8_to_16(file_utf16 + root->len - 1, filename); /* check access */ if (_waccess(file_utf16, F_OK) < 0) { diff --git a/src/win32/posix.h b/src/win32/posix.h index 753f35a2d..259ad572c 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -20,9 +20,9 @@ GIT_INLINE(int) p_link(const char *old, const char *new) GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 buf; GIT_UNUSED(mode); - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git__utf8_to_16(buf, path); return _wmkdir(buf); } diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index a96741d1c..4c696d0cd 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -16,8 +16,8 @@ int p_unlink(const char *path) { - wchar_t buf[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git_win_str_utf16 buf; + git__utf8_to_16(buf, path); _wchmod(buf, 0666); return _wunlink(buf); } @@ -59,10 +59,11 @@ static int do_lstat( const char *file_name, struct stat *buf, int posix_enotdir) { WIN32_FILE_ATTRIBUTE_DATA fdata; - wchar_t fbuf[GIT_WIN_PATH_UTF16], lastch; + git_win_str_utf16 fbuf; + wchar_t lastch; int flen; - flen = git__utf8_to_16(fbuf, GIT_WIN_PATH_UTF16, file_name); + flen = git__utf8_to_16(fbuf, file_name); /* truncate trailing slashes */ for (; flen > 0; --flen) { @@ -165,7 +166,7 @@ int p_readlink(const char *link, char *target, size_t target_len) static fpath_func pGetFinalPath = NULL; HANDLE hFile; DWORD dwRet; - wchar_t link_w[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 link_w; wchar_t* target_w; int error = 0; @@ -188,7 +189,7 @@ int p_readlink(const char *link, char *target, size_t target_len) } } - git__utf8_to_16(link_w, GIT_WIN_PATH_UTF16, link); + git__utf8_to_16(link_w, link); hFile = CreateFileW(link_w, // file to open GENERIC_READ, // open for reading @@ -254,10 +255,10 @@ int p_symlink(const char *old, const char *new) int p_open(const char *path, int flags, ...) { - wchar_t buf[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 buf; mode_t mode = 0; - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git__utf8_to_16(buf, path); if (flags & O_CREAT) { va_list arg_list; @@ -272,8 +273,8 @@ int p_open(const char *path, int flags, ...) int p_creat(const char *path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git_win_str_utf16 buf; + git__utf8_to_16(buf, path); return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } @@ -315,23 +316,23 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { - wchar_t buf[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git_win_str_utf16 buf; + git__utf8_to_16(buf, path); return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git_win_str_utf16 buf; + git__utf8_to_16(buf, path); return _wchmod(buf, mode); } int p_rmdir(const char* path) { int error; - wchar_t buf[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git_win_str_utf16 buf; + git__utf8_to_16(buf, path); error = _wrmdir(buf); @@ -347,18 +348,18 @@ int p_rmdir(const char* path) int p_hide_directory__w32(const char *path) { - wchar_t buf[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git_win_str_utf16 buf; + git__utf8_to_16(buf, path); return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; } char *p_realpath(const char *orig_path, char *buffer) { int ret; - wchar_t orig_path_w[GIT_WIN_PATH_UTF16]; - wchar_t buffer_w[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 orig_path_w; + git_win_str_utf16 buffer_w; - git__utf8_to_16(orig_path_w, GIT_WIN_PATH_UTF16, orig_path); + git__utf8_to_16(orig_path_w, orig_path); /* Implicitly use GetCurrentDirectory which can be a threading issue */ ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL); @@ -448,18 +449,18 @@ int p_setenv(const char* name, const char* value, int overwrite) int p_access(const char* path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH_UTF16]; - git__utf8_to_16(buf, GIT_WIN_PATH_UTF16, path); + git_win_str_utf16 buf; + git__utf8_to_16(buf, path); return _waccess(buf, mode); } int p_rename(const char *from, const char *to) { - wchar_t wfrom[GIT_WIN_PATH_UTF16]; - wchar_t wto[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 wfrom; + git_win_str_utf16 wto; - git__utf8_to_16(wfrom, GIT_WIN_PATH_UTF16, from); - git__utf8_to_16(wto, GIT_WIN_PATH_UTF16, to); + git__utf8_to_16(wfrom, from); + git__utf8_to_16(wto, to); return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; } diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index 78d277494..9c9686147 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -70,12 +70,12 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) } #endif -int git__utf8_to_16(wchar_t *dest, size_t length, const char *src) +int git__utf8_to_16(git_win_str_utf16 dest, const git_win_str_utf8 src) { - return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length); + return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, GIT_WIN_PATH_UTF16); } -int git__utf16_to_8(char *out, const wchar_t *input) +int git__utf16_to_8(git_win_str_utf8 dest, const git_win_str_utf16 src) { - return WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH_UTF8, NULL, NULL); + return WideCharToMultiByte(CP_UTF8, 0, src, -1, dest, GIT_WIN_PATH_UTF8, NULL, NULL); } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 9922e2969..d0b6fc825 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -13,8 +13,11 @@ #define GIT_WIN_PATH_UTF16 (260 + 1) #define GIT_WIN_PATH_UTF8 (260 * 4 + 1) -int git__utf8_to_16(wchar_t *dest, size_t length, const char *src); -int git__utf16_to_8(char *dest, const wchar_t *src); +typedef wchar_t git_win_str_utf16[GIT_WIN_PATH_UTF16]; +typedef char git_win_str_utf8[GIT_WIN_PATH_UTF8]; + +int git__utf8_to_16(git_win_str_utf16 dest, const git_win_str_utf8 src); +int git__utf16_to_8(git_win_str_utf8 dest, const git_win_str_utf16 src); #endif diff --git a/tests-clar/clar_libgit2.c b/tests-clar/clar_libgit2.c index 34d54cd94..869acd613 100644 --- a/tests-clar/clar_libgit2.c +++ b/tests-clar/clar_libgit2.c @@ -56,12 +56,12 @@ void cl_git_rewritefile(const char *filename, const char *new_content) char *cl_getenv(const char *name) { - wchar_t name_utf16[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 name_utf16; DWORD alloc_len; wchar_t *value_utf16; char *value_utf8; - git__utf8_to_16(name_utf16, GIT_WIN_PATH_UTF16, name); + git__utf8_to_16(name_utf16, name); alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0); if (alloc_len <= 0) return NULL; @@ -81,13 +81,13 @@ char *cl_getenv(const char *name) int cl_setenv(const char *name, const char *value) { - wchar_t name_utf16[GIT_WIN_PATH_UTF16]; - wchar_t value_utf16[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 name_utf16; + git_win_str_utf16 value_utf16; - git__utf8_to_16(name_utf16, GIT_WIN_PATH_UTF16, name); + git__utf8_to_16(name_utf16, name); if (value) { - git__utf8_to_16(value_utf16, GIT_WIN_PATH_UTF16, value); + git__utf8_to_16(value_utf16, value); cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16)); } else { /* Windows XP returns 0 (failed) when passing NULL for lpValue when @@ -107,12 +107,12 @@ int cl_setenv(const char *name, const char *value) * the source is a directory, a child of the source). */ int cl_rename(const char *source, const char *dest) { - wchar_t source_utf16[GIT_WIN_PATH_UTF16]; - wchar_t dest_utf16[GIT_WIN_PATH_UTF16]; + git_win_str_utf16 source_utf16; + git_win_str_utf16 dest_utf16; unsigned retries = 1; - git__utf8_to_16(source_utf16, GIT_WIN_PATH_UTF16, source); - git__utf8_to_16(dest_utf16, GIT_WIN_PATH_UTF16, dest); + git__utf8_to_16(source_utf16, source); + git__utf8_to_16(dest_utf16, dest); while (!MoveFileW(source_utf16, dest_utf16)) { /* Only retry if the error is ERROR_ACCESS_DENIED; From 2c0128ee79243d32e60f19e60acc2e297c1761d6 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 7 Aug 2013 19:29:33 -0700 Subject: [PATCH 191/367] Rename git_win_str_utf* to git_win32_path_utf* --- src/fileops.c | 2 +- src/path.c | 2 +- src/transports/winhttp.c | 2 +- src/win32/dir.c | 4 ++-- src/win32/posix.h | 2 +- src/win32/posix_w32.c | 28 ++++++++++++++-------------- src/win32/utf-conv.c | 4 ++-- src/win32/utf-conv.h | 8 ++++---- tests-clar/clar_libgit2.c | 10 +++++----- 9 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 5e86d1a91..25323d0c9 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -58,7 +58,7 @@ int git_futils_creat_locked(const char *path, const mode_t mode) int fd; #ifdef GIT_WIN32 - git_win_str_utf16 buf; + git_win32_path_utf16 buf; git__utf8_to_16(buf, path); fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | diff --git a/src/path.c b/src/path.c index da7bd90a0..72831c736 100644 --- a/src/path.c +++ b/src/path.c @@ -486,7 +486,7 @@ bool git_path_is_empty_dir(const char *path) { git_buf pathbuf = GIT_BUF_INIT; HANDLE hFind = INVALID_HANDLE_VALUE; - git_win_str_utf16 wbuf; + git_win32_path_utf16 wbuf; WIN32_FIND_DATAW ffd; bool retval = true; diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index fbf9b2f48..359ab5f5e 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -893,7 +893,7 @@ static int winhttp_connect( const char *url) { wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; - git_win_str_utf16 host; + git_win32_path_utf16 host; int32_t port; const char *default_port = "80"; int ret; diff --git a/src/win32/dir.c b/src/win32/dir.c index a638fce96..b1cb47c9b 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -26,7 +26,7 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { char filter[GIT_WIN_PATH_UTF8]; - git_win_str_utf16 filter_w; + git_win32_path_utf16 filter_w; git__DIR *new = NULL; if (!dir || !init_filter(filter, sizeof(filter), dir)) @@ -102,7 +102,7 @@ struct git__dirent *git__readdir(git__DIR *d) void git__rewinddir(git__DIR *d) { char filter[GIT_WIN_PATH_UTF8]; - git_win_str_utf16 filter_w; + git_win32_path_utf16 filter_w; if (!d) return; diff --git a/src/win32/posix.h b/src/win32/posix.h index 259ad572c..47f6ddeb0 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -20,7 +20,7 @@ GIT_INLINE(int) p_link(const char *old, const char *new) GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) { - git_win_str_utf16 buf; + git_win32_path_utf16 buf; GIT_UNUSED(mode); git__utf8_to_16(buf, path); return _wmkdir(buf); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 4c696d0cd..a7df424df 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -16,7 +16,7 @@ int p_unlink(const char *path) { - git_win_str_utf16 buf; + git_win32_path_utf16 buf; git__utf8_to_16(buf, path); _wchmod(buf, 0666); return _wunlink(buf); @@ -59,7 +59,7 @@ static int do_lstat( const char *file_name, struct stat *buf, int posix_enotdir) { WIN32_FILE_ATTRIBUTE_DATA fdata; - git_win_str_utf16 fbuf; + git_win32_path_utf16 fbuf; wchar_t lastch; int flen; @@ -166,7 +166,7 @@ int p_readlink(const char *link, char *target, size_t target_len) static fpath_func pGetFinalPath = NULL; HANDLE hFile; DWORD dwRet; - git_win_str_utf16 link_w; + git_win32_path_utf16 link_w; wchar_t* target_w; int error = 0; @@ -255,7 +255,7 @@ int p_symlink(const char *old, const char *new) int p_open(const char *path, int flags, ...) { - git_win_str_utf16 buf; + git_win32_path_utf16 buf; mode_t mode = 0; git__utf8_to_16(buf, path); @@ -273,7 +273,7 @@ int p_open(const char *path, int flags, ...) int p_creat(const char *path, mode_t mode) { - git_win_str_utf16 buf; + git_win32_path_utf16 buf; git__utf8_to_16(buf, path); return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } @@ -316,14 +316,14 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { - git_win_str_utf16 buf; + git_win32_path_utf16 buf; git__utf8_to_16(buf, path); return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { - git_win_str_utf16 buf; + git_win32_path_utf16 buf; git__utf8_to_16(buf, path); return _wchmod(buf, mode); } @@ -331,7 +331,7 @@ int p_chmod(const char* path, mode_t mode) int p_rmdir(const char* path) { int error; - git_win_str_utf16 buf; + git_win32_path_utf16 buf; git__utf8_to_16(buf, path); error = _wrmdir(buf); @@ -348,7 +348,7 @@ int p_rmdir(const char* path) int p_hide_directory__w32(const char *path) { - git_win_str_utf16 buf; + git_win32_path_utf16 buf; git__utf8_to_16(buf, path); return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; } @@ -356,8 +356,8 @@ int p_hide_directory__w32(const char *path) char *p_realpath(const char *orig_path, char *buffer) { int ret; - git_win_str_utf16 orig_path_w; - git_win_str_utf16 buffer_w; + git_win32_path_utf16 orig_path_w; + git_win32_path_utf16 buffer_w; git__utf8_to_16(orig_path_w, orig_path); @@ -449,15 +449,15 @@ int p_setenv(const char* name, const char* value, int overwrite) int p_access(const char* path, mode_t mode) { - git_win_str_utf16 buf; + git_win32_path_utf16 buf; git__utf8_to_16(buf, path); return _waccess(buf, mode); } int p_rename(const char *from, const char *to) { - git_win_str_utf16 wfrom; - git_win_str_utf16 wto; + git_win32_path_utf16 wfrom; + git_win32_path_utf16 wto; git__utf8_to_16(wfrom, from); git__utf8_to_16(wto, to); diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index 9c9686147..bb63b0f8d 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -70,12 +70,12 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) } #endif -int git__utf8_to_16(git_win_str_utf16 dest, const git_win_str_utf8 src) +int git__utf8_to_16(git_win32_path_utf16 dest, const git_win32_path_utf8 src) { return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, GIT_WIN_PATH_UTF16); } -int git__utf16_to_8(git_win_str_utf8 dest, const git_win_str_utf16 src) +int git__utf16_to_8(git_win32_path_utf8 dest, const git_win32_path_utf16 src) { return WideCharToMultiByte(CP_UTF8, 0, src, -1, dest, GIT_WIN_PATH_UTF8, NULL, NULL); } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index d0b6fc825..4e602291e 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -13,11 +13,11 @@ #define GIT_WIN_PATH_UTF16 (260 + 1) #define GIT_WIN_PATH_UTF8 (260 * 4 + 1) -typedef wchar_t git_win_str_utf16[GIT_WIN_PATH_UTF16]; -typedef char git_win_str_utf8[GIT_WIN_PATH_UTF8]; +typedef wchar_t git_win32_path_utf16[GIT_WIN_PATH_UTF16]; +typedef char git_win32_path_utf8[GIT_WIN_PATH_UTF8]; -int git__utf8_to_16(git_win_str_utf16 dest, const git_win_str_utf8 src); -int git__utf16_to_8(git_win_str_utf8 dest, const git_win_str_utf16 src); +int git__utf8_to_16(git_win32_path_utf16 dest, const git_win32_path_utf8 src); +int git__utf16_to_8(git_win32_path_utf8 dest, const git_win32_path_utf16 src); #endif diff --git a/tests-clar/clar_libgit2.c b/tests-clar/clar_libgit2.c index 869acd613..ebd034a08 100644 --- a/tests-clar/clar_libgit2.c +++ b/tests-clar/clar_libgit2.c @@ -56,7 +56,7 @@ void cl_git_rewritefile(const char *filename, const char *new_content) char *cl_getenv(const char *name) { - git_win_str_utf16 name_utf16; + git_win32_path_utf16 name_utf16; DWORD alloc_len; wchar_t *value_utf16; char *value_utf8; @@ -81,8 +81,8 @@ char *cl_getenv(const char *name) int cl_setenv(const char *name, const char *value) { - git_win_str_utf16 name_utf16; - git_win_str_utf16 value_utf16; + git_win32_path_utf16 name_utf16; + git_win32_path_utf16 value_utf16; git__utf8_to_16(name_utf16, name); @@ -107,8 +107,8 @@ int cl_setenv(const char *name, const char *value) * the source is a directory, a child of the source). */ int cl_rename(const char *source, const char *dest) { - git_win_str_utf16 source_utf16; - git_win_str_utf16 dest_utf16; + git_win32_path_utf16 source_utf16; + git_win32_path_utf16 dest_utf16; unsigned retries = 1; git__utf8_to_16(source_utf16, source); From d19bcb335256fddc5dd1289f1772d4d864b54ec0 Mon Sep 17 00:00:00 2001 From: Brodie Rao Date: Thu, 6 Jun 2013 14:49:14 -0700 Subject: [PATCH 192/367] odb_pack: handle duplicate objects from different packs This is based on 24634c6fd02b2240e4a93fad70a08220f8fb793a. This also corrects an issue with error codes being mixed up with the number of found objects. --- src/odb_pack.c | 41 +++++++----------- tests-clar/odb/mixed.c | 3 ++ .../duplicate.git/objects/info/packs | 1 + ...ef1aa326265de7d05018ee51acc0a8717fe1ea.idx | Bin 0 -> 1100 bytes ...f1aa326265de7d05018ee51acc0a8717fe1ea.pack | Bin 0 -> 47 bytes 5 files changed, 20 insertions(+), 25 deletions(-) create mode 100644 tests-clar/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idx create mode 100644 tests-clar/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack diff --git a/src/odb_pack.c b/src/odb_pack.c index eec79259b..43880612a 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -259,23 +259,26 @@ static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backen return git_odb__error_notfound("failed to find pack entry", oid); } -static unsigned pack_entry_find_prefix_inner( - struct git_pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - size_t len, - struct git_pack_file *last_found) +static int pack_entry_find_prefix( + struct git_pack_entry *e, + struct pack_backend *backend, + const git_oid *short_oid, + size_t len) { int error; size_t i; - unsigned found = 0; + git_oid found_full_oid = {{0}}; + bool found = false; + struct git_pack_file *last_found = backend->last_found; if (last_found) { error = git_pack_entry_find(e, last_found, short_oid, len); if (error == GIT_EAMBIGUOUS) return error; - if (!error) - found = 1; + if (!error) { + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; + } } for (i = 0; i < backend->packs.length; ++i) { @@ -289,28 +292,16 @@ static unsigned pack_entry_find_prefix_inner( if (error == GIT_EAMBIGUOUS) return error; if (!error) { - if (++found > 1) - break; + if (found && git_oid_cmp(&e->sha1, &found_full_oid)) + return git_odb__error_ambiguous("found multiple pack entries"); + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; backend->last_found = p; } } - return found; -} - -static int pack_entry_find_prefix( - struct git_pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - size_t len) -{ - struct git_pack_file *last_found = backend->last_found; - unsigned int found = pack_entry_find_prefix_inner(e, backend, short_oid, len, last_found); - if (!found) return git_odb__error_notfound("no matching pack entry for prefix", short_oid); - else if (found > 1) - return git_odb__error_ambiguous("found multiple pack entries"); else return 0; } diff --git a/tests-clar/odb/mixed.c b/tests-clar/odb/mixed.c index 7f7120a6e..dd4587831 100644 --- a/tests-clar/odb/mixed.c +++ b/tests-clar/odb/mixed.c @@ -16,11 +16,14 @@ void test_odb_mixed__cleanup(void) void test_odb_mixed__dup_oid(void) { const char hex[] = "ce013625030ba8dba906f756967f9e9ca394464a"; + const char short_hex[] = "ce01362"; git_oid oid; git_odb_object *obj; cl_git_pass(git_oid_fromstr(&oid, hex)); cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, GIT_OID_HEXSZ)); + cl_git_pass(git_oid_fromstrn(&oid, short_hex, sizeof(short_hex) - 1)); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, sizeof(short_hex) - 1)); git_odb_object_free(obj); } diff --git a/tests-clar/resources/duplicate.git/objects/info/packs b/tests-clar/resources/duplicate.git/objects/info/packs index 3696a7d36..d0fdf905e 100644 --- a/tests-clar/resources/duplicate.git/objects/info/packs +++ b/tests-clar/resources/duplicate.git/objects/info/packs @@ -1,2 +1,3 @@ P pack-e87994ad581c9af946de0eb890175c08cd005f38.pack +P pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack diff --git a/tests-clar/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idx b/tests-clar/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idx new file mode 100644 index 0000000000000000000000000000000000000000..9f78f6e0f7d243f298189b35f19af590295b7011 GIT binary patch literal 1100 zcmexg;-AdGz`z8=qhK@yMniz~5MTsq8S?lXqnRo*_lnyq*}jKOtDiS#@f0_&pedrD w(BQepBIF?NH7}mkQQqa%&l9+4fd?z5f_^jYoP9{cY0JI1iG5`Po literal 0 HcmV?d00001 diff --git a/tests-clar/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack b/tests-clar/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack new file mode 100644 index 0000000000000000000000000000000000000000..d1dd3b61af11b23c05d779832d773633a48462f5 GIT binary patch literal 47 zcmWG=boORoU|<4bMze}Jr#;S|Jo%i7fy0|g{vL~vgS^+gcveSwmsdYe Date: Sun, 17 Mar 2013 20:39:01 +0100 Subject: [PATCH 193/367] added new type and several functions to git_strmap This step is needed to easily add iterators to git_config_backend As well use these new git_strmap functions to implement foreach * git_strmap_iter * git_strmap_has_data(...) * git_strmap_begin(...) * git_strmap_end(...) * git_strmap_next(...) --- src/config_file.c | 5 +++-- src/strmap.c | 34 ++++++++++++++++++++++++++++++++++ src/strmap.h | 12 ++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/strmap.c diff --git a/src/config_file.c b/src/config_file.c index 570f286c8..088f6190d 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -270,7 +270,8 @@ static int file_foreach( } } - git_strmap_foreach(b->values, key, var, + git_strmap_iter iter = git_strmap_begin(b->values); + while (!(git_strmap_next(&key, (void**) &var, &iter, b->values) < 0)) { for (; var != NULL; var = next_var) { next_var = CVAR_LIST_NEXT(var); @@ -285,7 +286,7 @@ static int file_foreach( goto cleanup; } } - ); + } cleanup: if (regexp != NULL) diff --git a/src/strmap.c b/src/strmap.c new file mode 100644 index 000000000..1b07359d1 --- /dev/null +++ b/src/strmap.c @@ -0,0 +1,34 @@ +/* + * 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 "strmap.h" + +int git_strmap_next( + const char **key, + void **data, + git_strmap_iter* iter, + git_strmap *map) +{ + if (!map) + return GIT_ERROR; + + while (*iter != git_strmap_end(map)) { + if (!(git_strmap_has_data(map, *iter))) { + ++(*iter); + continue; + } + + *key = git_strmap_key(map, *iter); + *data = git_strmap_value_at(map, *iter); + + ++(*iter); + + return GIT_OK; + } + + return GIT_ITEROVER; +} diff --git a/src/strmap.h b/src/strmap.h index 44176a0fc..cb079b500 100644 --- a/src/strmap.h +++ b/src/strmap.h @@ -17,6 +17,7 @@ __KHASH_TYPE(str, const char *, void *); typedef khash_t(str) git_strmap; +typedef khiter_t git_strmap_iter; #define GIT__USE_STRMAP \ __KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) @@ -31,7 +32,9 @@ typedef khash_t(str) git_strmap; #define git_strmap_valid_index(h, idx) (idx != kh_end(h)) #define git_strmap_exists(h, k) (kh_get(str, h, k) != kh_end(h)) +#define git_strmap_has_data(h, idx) kh_exist(h, idx) +#define git_strmap_key(h, idx) kh_key(h, idx) #define git_strmap_value_at(h, idx) kh_val(h, idx) #define git_strmap_set_value_at(h, idx, v) kh_val(h, idx) = v #define git_strmap_delete_at(h, idx) kh_del(str, h, idx) @@ -61,4 +64,13 @@ typedef khash_t(str) git_strmap; #define git_strmap_foreach kh_foreach #define git_strmap_foreach_value kh_foreach_value +#define git_strmap_begin kh_begin +#define git_strmap_end kh_end + +int git_strmap_next( + const char **key, + void **data, + git_strmap_iter* iter, + git_strmap *map); + #endif From a603c191578f7b33720e36e95421fcd58bc7abe4 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Mon, 18 Mar 2013 21:02:36 +0100 Subject: [PATCH 194/367] replaced foreach() with non callback based iterations in git_config_backend new functions in struct git_config_backend: * iterator_new(...) * iterator_free(...) * next(...) The old callback based foreach style can still be used with `git_config_backend_foreach_match` --- include/git2/config.h | 20 +++++++++ include/git2/sys/config.h | 4 +- src/config.c | 46 +++++++++++++++++++- src/config_file.c | 92 +++++++++++++++++++++++---------------- src/config_file.h | 4 +- 5 files changed, 124 insertions(+), 42 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index 827d43544..f6fc74ee1 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -61,6 +61,7 @@ typedef struct { } git_config_entry; typedef int (*git_config_foreach_cb)(const git_config_entry *, void *); +typedef struct git_config_backend_iter* git_config_backend_iter; typedef enum { GIT_CVAR_FALSE = 0, @@ -535,6 +536,25 @@ GIT_EXTERN(int) git_config_parse_int32(int32_t *out, const char *value); GIT_EXTERN(int) git_config_parse_int64(int64_t *out, const char *value); +/** + * Perform an operation on each config variable in given config backend + * matching a regular expression. + * + * This behaviors like `git_config_foreach_match` except instead of all config + * entries it just enumerates through the given backend entry. + * + * @param backend where to get the variables from + * @param regexp regular expression to match against config names (can be NULL) + * @param callback the function to call on each variable + * @param payload the data to pass to the callback + */ +GIT_EXTERN(int) git_config_backend_foreach_match( + git_config_backend *backend, + const char *regexp, + int (*fn)(const git_config_entry *, void *), + void *data); + + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 11e59cf03..61dcce544 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -35,7 +35,9 @@ struct git_config_backend { int (*set)(struct git_config_backend *, const char *key, const char *value); int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_backend *, const char *key); - int (*foreach)(struct git_config_backend *, const char *, git_config_foreach_cb callback, void *payload); + int (*iterator_new)(git_config_backend_iter **, struct git_config_backend *); + void (*iterator_free)(git_config_backend_iter *); + int (*next)(git_config_backend_iter *, git_config_entry *, struct git_config_backend *); int (*refresh)(struct git_config_backend *); void (*free)(struct git_config_backend *); }; diff --git a/src/config.c b/src/config.c index 2a058549f..b421b3be1 100644 --- a/src/config.c +++ b/src/config.c @@ -321,6 +321,50 @@ int git_config_foreach( return git_config_foreach_match(cfg, NULL, cb, payload); } +int git_config_backend_foreach_match( + git_config_backend *backend, + const char *regexp, + int (*fn)(const git_config_entry *, void *), + void *data) +{ + git_config_entry entry; + git_config_backend_iter iter; + regex_t regex; + int result = 0; + + if (regexp != NULL) { + if ((result = regcomp(®ex, regexp, REG_EXTENDED)) < 0) { + giterr_set_regex(®ex, result); + regfree(®ex); + return -1; + } + } + + if (backend->iterator_new(&iter, backend) < 0) + return 0; + + while(!(backend->next(&iter, &entry, backend) < 0)) { + /* skip non-matching keys if regexp was provided */ + if (regexp && regexec(®ex, entry.name, 0, NULL, 0) != 0) + continue; + + /* abort iterator on non-zero return value */ + if (fn(&entry, data)) { + giterr_clear(); + result = GIT_EUSER; + goto cleanup; + } + } + +cleanup: + if (regexp != NULL) + regfree(®ex); + + backend->iterator_free(iter); + + return result; +} + int git_config_foreach_match( const git_config *cfg, const char *regexp, @@ -335,7 +379,7 @@ int git_config_foreach_match( for (i = 0; i < cfg->files.length && ret == 0; ++i) { internal = git_vector_get(&cfg->files, i); file = internal->file; - ret = file->foreach(file, regexp, cb, payload); + ret = git_config_backend_foreach_match(file, regexp, cb, payload); } return ret; diff --git a/src/config_file.c b/src/config_file.c index 088f6190d..ff8f8fc15 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -27,6 +27,12 @@ typedef struct cvar_t { git_config_entry *entry; } cvar_t; +typedef struct git_config_file_iter { + git_strmap_iter iter; + cvar_t* next; +} git_config_file_iter; + + #define CVAR_LIST_HEAD(list) ((list)->head) #define CVAR_LIST_TAIL(list) ((list)->tail) @@ -247,52 +253,60 @@ static void backend_free(git_config_backend *_backend) git__free(backend); } -static int file_foreach( - git_config_backend *backend, - const char *regexp, - int (*fn)(const git_config_entry *, void *), - void *data) +static int config_iterator_new( + git_config_backend_iter *iter, + struct git_config_backend* backend) { diskfile_backend *b = (diskfile_backend *)backend; - cvar_t *var, *next_var; - const char *key; - regex_t regex; - int result = 0; + git_config_file_iter **it= ((git_config_file_iter**) iter); - if (!b->values) - return 0; + if (!b->values || git_strmap_num_entries(b->values) < 1) + return -1; - if (regexp != NULL) { - if ((result = regcomp(®ex, regexp, REG_EXTENDED)) < 0) { - giterr_set_regex(®ex, result); - regfree(®ex); - return -1; - } + *it = git__calloc(1, sizeof(git_config_file_iter)); + GITERR_CHECK_ALLOC(it); + + (*it)->iter = git_strmap_begin(b->values); + (*it)->next = NULL; + + return 0; +} + +static void config_iterator_free( + git_config_backend_iter iter) +{ + git__free(iter); +} + +static int config_next( + git_config_backend_iter *iter, + git_config_entry* entry, + struct git_config_backend* backend) +{ + diskfile_backend *b = (diskfile_backend *)backend; + git_config_file_iter *it = *((git_config_file_iter**) iter); + int err; + cvar_t * var; + const char* key; + + if (it->next == NULL) { + err = git_strmap_next(&key, (void**) &var, &(it->iter), b->values); + } else { + key = it->next->entry->name; + var = it->next; } - git_strmap_iter iter = git_strmap_begin(b->values); - while (!(git_strmap_next(&key, (void**) &var, &iter, b->values) < 0)) { - for (; var != NULL; var = next_var) { - next_var = CVAR_LIST_NEXT(var); - - /* skip non-matching keys if regexp was provided */ - if (regexp && regexec(®ex, key, 0, NULL, 0) != 0) - continue; - - /* abort iterator on non-zero return value */ - if (fn(var->entry, data)) { - giterr_clear(); - result = GIT_EUSER; - goto cleanup; - } - } + if (err < 0) { + it->next = NULL; + return -1; } -cleanup: - if (regexp != NULL) - regfree(®ex); + entry->name = key; + entry->value = var->entry->value; + entry->level = var->entry->level; + it->next = CVAR_LIST_NEXT(var); - return result; + return 0; } static int config_set(git_config_backend *cfg, const char *name, const char *value) @@ -595,7 +609,9 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->parent.set = config_set; backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; - backend->parent.foreach = file_foreach; + backend->parent.iterator_new = config_iterator_new; + backend->parent.iterator_free = config_iterator_free; + backend->parent.next = config_next; backend->parent.refresh = config_refresh; backend->parent.free = backend_free; diff --git a/src/config_file.h b/src/config_file.h index 7445859c4..d4a1a4061 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -42,7 +42,7 @@ GIT_INLINE(int) git_config_file_foreach( int (*fn)(const git_config_entry *entry, void *data), void *data) { - return cfg->foreach(cfg, NULL, fn, data); + return git_config_backend_foreach_match(cfg, NULL, fn, data); } GIT_INLINE(int) git_config_file_foreach_match( @@ -51,7 +51,7 @@ GIT_INLINE(int) git_config_file_foreach_match( int (*fn)(const git_config_entry *entry, void *data), void *data) { - return cfg->foreach(cfg, regexp, fn, data); + return git_config_backend_foreach_match(cfg, regexp, fn, data); } extern int git_config_file_normalize_section(char *start, char *end); From 4d588d9713bb558e45a8bdf6c41d232bb592b814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 11:24:47 +0200 Subject: [PATCH 195/367] Don't typedef a pointer Make the iterator structure opaque and make sure it compiles. --- include/git2/config.h | 2 +- include/git2/sys/config.h | 2 +- src/config.c | 4 ++-- src/config.h | 5 +++++ src/config_file.c | 18 ++++++++++-------- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index f6fc74ee1..43bc19412 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -61,7 +61,7 @@ typedef struct { } git_config_entry; typedef int (*git_config_foreach_cb)(const git_config_entry *, void *); -typedef struct git_config_backend_iter* git_config_backend_iter; +typedef struct git_config_backend_iter git_config_backend_iter; typedef enum { GIT_CVAR_FALSE = 0, diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 61dcce544..f617623a9 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -37,7 +37,7 @@ struct git_config_backend { int (*del)(struct git_config_backend *, const char *key); int (*iterator_new)(git_config_backend_iter **, struct git_config_backend *); void (*iterator_free)(git_config_backend_iter *); - int (*next)(git_config_backend_iter *, git_config_entry *, struct git_config_backend *); + int (*next)(git_config_entry *, git_config_backend_iter *); int (*refresh)(struct git_config_backend *); void (*free)(struct git_config_backend *); }; diff --git a/src/config.c b/src/config.c index b421b3be1..2f800a896 100644 --- a/src/config.c +++ b/src/config.c @@ -328,7 +328,7 @@ int git_config_backend_foreach_match( void *data) { git_config_entry entry; - git_config_backend_iter iter; + git_config_backend_iter* iter; regex_t regex; int result = 0; @@ -343,7 +343,7 @@ int git_config_backend_foreach_match( if (backend->iterator_new(&iter, backend) < 0) return 0; - while(!(backend->next(&iter, &entry, backend) < 0)) { + while(!(backend->next(&entry, iter) < 0)) { /* skip non-matching keys if regexp was provided */ if (regexp && regexec(®ex, entry.name, 0, NULL, 0) != 0) continue; diff --git a/src/config.h b/src/config.h index c5c11ae14..ea150e968 100644 --- a/src/config.h +++ b/src/config.h @@ -24,6 +24,11 @@ struct git_config { git_vector files; }; +typedef struct { + git_config_backend *backend; + unsigned int flags; +} git_config_backend_iter; + extern int git_config_find_global_r(git_buf *global_config_path); extern int git_config_find_xdg_r(git_buf *system_config_path); extern int git_config_find_system_r(git_buf *system_config_path); diff --git a/src/config_file.c b/src/config_file.c index ff8f8fc15..ea571e9f8 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -28,6 +28,8 @@ typedef struct cvar_t { } cvar_t; typedef struct git_config_file_iter { + git_config_backend *backend; + unsigned int flags; git_strmap_iter iter; cvar_t* next; } git_config_file_iter; @@ -254,7 +256,7 @@ static void backend_free(git_config_backend *_backend) } static int config_iterator_new( - git_config_backend_iter *iter, + git_config_backend_iter **iter, struct git_config_backend* backend) { diskfile_backend *b = (diskfile_backend *)backend; @@ -266,6 +268,7 @@ static int config_iterator_new( *it = git__calloc(1, sizeof(git_config_file_iter)); GITERR_CHECK_ALLOC(it); + (*it)->backend = backend; (*it)->iter = git_strmap_begin(b->values); (*it)->next = NULL; @@ -273,18 +276,17 @@ static int config_iterator_new( } static void config_iterator_free( - git_config_backend_iter iter) + git_config_backend_iter* iter) { git__free(iter); } -static int config_next( - git_config_backend_iter *iter, - git_config_entry* entry, - struct git_config_backend* backend) +static int config_iterator_next( + git_config_entry *entry, + git_config_backend_iter *iter) { - diskfile_backend *b = (diskfile_backend *)backend; git_config_file_iter *it = *((git_config_file_iter**) iter); + diskfile_backend *b = (diskfile_backend *)it->backend; int err; cvar_t * var; const char* key; @@ -611,7 +613,7 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->parent.del = config_delete; backend->parent.iterator_new = config_iterator_new; backend->parent.iterator_free = config_iterator_free; - backend->parent.next = config_next; + backend->parent.next = config_iterator_next; backend->parent.refresh = config_refresh; backend->parent.free = backend_free; From 82ae6fcdba2aa0fb21f49b127b5f5f7a637ffc76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 11:55:47 +0200 Subject: [PATCH 196/367] config: compilation fixes --- src/config.h | 4 ++-- src/config_file.c | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/config.h b/src/config.h index ea150e968..2f7c96d7f 100644 --- a/src/config.h +++ b/src/config.h @@ -24,10 +24,10 @@ struct git_config { git_vector files; }; -typedef struct { +struct git_config_backend_iter { git_config_backend *backend; unsigned int flags; -} git_config_backend_iter; +}; extern int git_config_find_global_r(git_buf *global_config_path); extern int git_config_find_xdg_r(git_buf *system_config_path); diff --git a/src/config_file.c b/src/config_file.c index ea571e9f8..849ef0f6f 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -28,8 +28,7 @@ typedef struct cvar_t { } cvar_t; typedef struct git_config_file_iter { - git_config_backend *backend; - unsigned int flags; + git_config_backend_iter parent; git_strmap_iter iter; cvar_t* next; } git_config_file_iter; @@ -260,17 +259,17 @@ static int config_iterator_new( struct git_config_backend* backend) { diskfile_backend *b = (diskfile_backend *)backend; - git_config_file_iter **it= ((git_config_file_iter**) iter); + git_config_file_iter *it = git__calloc(1, sizeof(git_config_file_iter)); if (!b->values || git_strmap_num_entries(b->values) < 1) return -1; - *it = git__calloc(1, sizeof(git_config_file_iter)); GITERR_CHECK_ALLOC(it); - (*it)->backend = backend; - (*it)->iter = git_strmap_begin(b->values); - (*it)->next = NULL; + it->parent.backend = backend; + it->iter = git_strmap_begin(b->values); + it->next = NULL; + *iter = (git_config_backend_iter *) it; return 0; } @@ -285,9 +284,9 @@ static int config_iterator_next( git_config_entry *entry, git_config_backend_iter *iter) { - git_config_file_iter *it = *((git_config_file_iter**) iter); - diskfile_backend *b = (diskfile_backend *)it->backend; - int err; + git_config_file_iter *it = (git_config_file_iter *) iter; + diskfile_backend *b = (diskfile_backend *) it->parent.backend; + int err = 0; cvar_t * var; const char* key; From 84fec6f628b8c82a70c0aff5dc6a47d1cdb6fbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 13:14:35 +0200 Subject: [PATCH 197/367] config: saner iterator errors Really report an error in foreach if we fail to allocate the iterator, and don't fail if the config is emtpy. --- src/config.c | 6 ++++-- src/config_file.c | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/config.c b/src/config.c index 2f800a896..6c055e0e6 100644 --- a/src/config.c +++ b/src/config.c @@ -340,8 +340,10 @@ int git_config_backend_foreach_match( } } - if (backend->iterator_new(&iter, backend) < 0) - return 0; + if ((result = backend->iterator_new(&iter, backend)) < 0) { + iter = NULL; + return -1; + } while(!(backend->next(&entry, iter) < 0)) { /* skip non-matching keys if regexp was provided */ diff --git a/src/config_file.c b/src/config_file.c index 849ef0f6f..6eb51ac25 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -261,9 +261,6 @@ static int config_iterator_new( diskfile_backend *b = (diskfile_backend *)backend; git_config_file_iter *it = git__calloc(1, sizeof(git_config_file_iter)); - if (!b->values || git_strmap_num_entries(b->values) < 1) - return -1; - GITERR_CHECK_ALLOC(it); it->parent.backend = backend; From 4efa32903adf131631d283c914e0a5bf29c49e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 13:41:18 +0200 Subject: [PATCH 198/367] config: get_multivar -> get_multivar_foreach The plain function will return an iterator, so move this one out of the way. --- include/git2/config.h | 4 +-- include/git2/sys/config.h | 2 +- src/config.c | 4 +-- src/config_file.c | 4 +-- src/diff_driver.c | 4 +-- src/remote.c | 2 +- tests-clar/config/multivar.c | 44 ++++++++++++++++---------------- tests-clar/config/validkeyname.c | 2 +- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index 43bc19412..a0dc11bb1 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -328,7 +328,7 @@ GIT_EXTERN(int) git_config_get_bool(int *out, const git_config *cfg, const char GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, const char *name); /** - * Get each value of a multivar. + * Get each value of a multivar in a foreach callback * * The callback will be called on each variable found * @@ -339,7 +339,7 @@ GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, c * @param callback the function to be called on each value of the variable * @param payload opaque pointer to pass to the callback */ -GIT_EXTERN(int) git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload); +GIT_EXTERN(int) git_config_get_multivar_foreach(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload); /** * Set the value of an integer config variable in the config file diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index f617623a9..09c79638a 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -31,7 +31,7 @@ struct git_config_backend { /* Open means open the file/database and parse if necessary */ int (*open)(struct git_config_backend *, git_config_level_t level); int (*get)(const struct git_config_backend *, const char *key, const git_config_entry **entry); - int (*get_multivar)(struct git_config_backend *, const char *key, const char *regexp, git_config_foreach_cb callback, void *payload); + int (*get_multivar_foreach)(struct git_config_backend *, const char *key, const char *regexp, git_config_foreach_cb callback, void *payload); int (*set)(struct git_config_backend *, const char *key, const char *value); int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_backend *, const char *key); diff --git a/src/config.c b/src/config.c index 6c055e0e6..a4627c9ad 100644 --- a/src/config.c +++ b/src/config.c @@ -574,7 +574,7 @@ int git_config_get_entry(const git_config_entry **out, const git_config *cfg, co return config_error_notfound(name); } -int git_config_get_multivar( +int git_config_get_multivar_foreach( const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb cb, void *payload) { @@ -593,7 +593,7 @@ int git_config_get_multivar( continue; file = internal->file; - if (!(err = file->get_multivar(file, name, regexp, cb, payload))) + if (!(err = file->get_multivar_foreach(file, name, regexp, cb, payload))) ret = 0; else if (err != GIT_ENOTFOUND) return err; diff --git a/src/config_file.c b/src/config_file.c index 6eb51ac25..ed79624eb 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -413,7 +413,7 @@ static int config_get(const git_config_backend *cfg, const char *name, const git return 0; } -static int config_get_multivar( +static int config_get_multivar_foreach( git_config_backend *cfg, const char *name, const char *regex_str, @@ -603,7 +603,7 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->parent.open = config_open; backend->parent.get = config_get; - backend->parent.get_multivar = config_get_multivar; + backend->parent.get_multivar_foreach = config_get_multivar_foreach; backend->parent.set = config_set; backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; diff --git a/src/diff_driver.c b/src/diff_driver.c index e82dfa50d..bd5a8fbd9 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -187,7 +187,7 @@ static int git_diff_driver_load( git_buf_truncate(&name, namelen + strlen("diff..")); git_buf_put(&name, "xfuncname", strlen("xfuncname")); - if ((error = git_config_get_multivar( + if ((error = git_config_get_multivar_foreach( cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) { if (error != GIT_ENOTFOUND) goto done; @@ -196,7 +196,7 @@ static int git_diff_driver_load( git_buf_truncate(&name, namelen + strlen("diff..")); git_buf_put(&name, "funcname", strlen("funcname")); - if ((error = git_config_get_multivar( + if ((error = git_config_get_multivar_foreach( cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) { if (error != GIT_ENOTFOUND) goto done; diff --git a/src/remote.c b/src/remote.c index 158f3e938..003fadaa3 100644 --- a/src/remote.c +++ b/src/remote.c @@ -217,7 +217,7 @@ static int get_optional_config( return -1; if (cb != NULL) - error = git_config_get_multivar(config, key, NULL, cb, payload); + error = git_config_get_multivar_foreach(config, key, NULL, cb, payload); else error = git_config_get_string(payload, config, key); diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c index efc431502..390b24c6b 100644 --- a/tests-clar/config/multivar.c +++ b/tests-clar/config/multivar.c @@ -46,27 +46,27 @@ static int cb(const git_config_entry *entry, void *data) return 0; } -static void check_get_multivar( +static void check_get_multivar_foreach( git_config *cfg, int expected, int expected_patterned) { int n = 0; if (expected > 0) { - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); cl_assert_equal_i(expected, n); } else { cl_assert_equal_i(GIT_ENOTFOUND, - git_config_get_multivar(cfg, _name, NULL, cb, &n)); + git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); } n = 0; if (expected_patterned > 0) { - cl_git_pass(git_config_get_multivar(cfg, _name, "example", cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "example", cb, &n)); cl_assert_equal_i(expected_patterned, n); } else { cl_assert_equal_i(GIT_ENOTFOUND, - git_config_get_multivar(cfg, _name, "example", cb, &n)); + git_config_get_multivar_foreach(cfg, _name, "example", cb, &n)); } } @@ -75,31 +75,31 @@ void test_config_multivar__get(void) git_config *cfg; cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - check_get_multivar(cfg, 2, 1); + check_get_multivar_foreach(cfg, 2, 1); /* add another that has the _name entry */ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config9", GIT_CONFIG_LEVEL_SYSTEM, 1)); - check_get_multivar(cfg, 3, 2); + check_get_multivar_foreach(cfg, 3, 2); /* add another that does not have the _name entry */ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config0", GIT_CONFIG_LEVEL_GLOBAL, 1)); - check_get_multivar(cfg, 3, 2); + check_get_multivar_foreach(cfg, 3, 2); /* add another that does not have the _name entry at the end */ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config1", GIT_CONFIG_LEVEL_APP, 1)); - check_get_multivar(cfg, 3, 2); + check_get_multivar_foreach(cfg, 3, 2); /* drop original file */ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config2", GIT_CONFIG_LEVEL_LOCAL, 1)); - check_get_multivar(cfg, 1, 1); + check_get_multivar_foreach(cfg, 1, 1); /* drop other file with match */ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config3", GIT_CONFIG_LEVEL_SYSTEM, 1)); - check_get_multivar(cfg, 0, 0); + check_get_multivar_foreach(cfg, 0, 0); /* reload original file (add different place in order) */ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config11", GIT_CONFIG_LEVEL_SYSTEM, 1)); - check_get_multivar(cfg, 2, 1); + check_get_multivar_foreach(cfg, 2, 1); git_config_free(cfg); } @@ -113,11 +113,11 @@ void test_config_multivar__add(void) cl_git_pass(git_config_set_multivar(cfg, _name, "nonexistant", "git://git.otherplace.org/libgit2")); n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); cl_assert(n == 3); n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "otherplace", cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); cl_assert(n == 1); git_config_free(cfg); @@ -127,11 +127,11 @@ void test_config_multivar__add(void) cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); cl_assert(n == 3); n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "otherplace", cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); cl_assert(n == 1); git_config_free(cfg); @@ -147,7 +147,7 @@ void test_config_multivar__add_new(void) cl_git_pass(git_config_set_multivar(cfg, var, "", "variable")); n = 0; - cl_git_pass(git_config_get_multivar(cfg, var, NULL, cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, var, NULL, cb, &n)); cl_assert(n == 1); git_config_free(cfg); @@ -161,13 +161,13 @@ void test_config_multivar__replace(void) cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); cl_assert(n == 2); cl_git_pass(git_config_set_multivar(cfg, _name, "github", "git://git.otherplace.org/libgit2")); n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); cl_assert(n == 2); git_config_free(cfg); @@ -175,7 +175,7 @@ void test_config_multivar__replace(void) cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); cl_assert(n == 2); git_config_free(cfg); @@ -190,7 +190,7 @@ void test_config_multivar__replace_multiple(void) cl_git_pass(git_config_set_multivar(cfg, _name, "git://", "git://git.otherplace.org/libgit2")); n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "otherplace", cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); cl_assert(n == 2); git_config_free(cfg); @@ -198,7 +198,7 @@ void test_config_multivar__replace_multiple(void) cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "otherplace", cb, &n)); + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); cl_assert(n == 2); git_config_free(cfg); diff --git a/tests-clar/config/validkeyname.c b/tests-clar/config/validkeyname.c index 03c13d723..33699737b 100644 --- a/tests-clar/config/validkeyname.c +++ b/tests-clar/config/validkeyname.c @@ -28,7 +28,7 @@ static void assert_invalid_config_key_name(const char *name) GIT_EINVALIDSPEC); cl_git_fail_with(git_config_delete_entry(cfg, name), GIT_EINVALIDSPEC); - cl_git_fail_with(git_config_get_multivar(cfg, name, "*", NULL, NULL), + cl_git_fail_with(git_config_get_multivar_foreach(cfg, name, "*", NULL, NULL), GIT_EINVALIDSPEC); cl_git_fail_with(git_config_set_multivar(cfg, name, "*", "42"), GIT_EINVALIDSPEC); From eba7399251cfa95d9346b9b41ca78dc5d43a840d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 14:39:32 +0200 Subject: [PATCH 199/367] config: move next() and free() into the iterator Like we have in the references iterator, next and free belong in the iterator itself. --- include/git2/config.h | 2 +- include/git2/sys/config.h | 30 ++++++++++++-- src/config.c | 8 ++-- src/config.h | 5 --- src/config_file.c | 87 ++++++++++++++++++++------------------- 5 files changed, 76 insertions(+), 56 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index a0dc11bb1..950312548 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -61,7 +61,7 @@ typedef struct { } git_config_entry; typedef int (*git_config_foreach_cb)(const git_config_entry *, void *); -typedef struct git_config_backend_iter git_config_backend_iter; +typedef struct git_config_iterator git_config_iterator; typedef enum { GIT_CVAR_FALSE = 0, diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 09c79638a..45599dc69 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -20,6 +20,32 @@ */ GIT_BEGIN_DECL +/** + * Every iterator must have this struct as its first element, so the + * API can talk to it. You'd define your iterator as + * + * struct my_iterator { + * git_config_iterator parent; + * ... + * } + * + * and assign `iter->parent.backend` to your `git_config_backend`. + */ +struct git_config_iterator { + git_config_backend *backend; + unsigned int flags; + + /** + * Return the current entry and advance the iterator + */ + int (*next)(git_config_entry *entry, git_config_iterator *iter); + + /** + * Free the iterator + */ + void (*free)(git_config_iterator *iter); +}; + /** * Generic backend that implements the interface to * access a configuration file @@ -35,9 +61,7 @@ struct git_config_backend { int (*set)(struct git_config_backend *, const char *key, const char *value); int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_backend *, const char *key); - int (*iterator_new)(git_config_backend_iter **, struct git_config_backend *); - void (*iterator_free)(git_config_backend_iter *); - int (*next)(git_config_entry *, git_config_backend_iter *); + int (*iterator)(git_config_iterator **, struct git_config_backend *); int (*refresh)(struct git_config_backend *); void (*free)(struct git_config_backend *); }; diff --git a/src/config.c b/src/config.c index a4627c9ad..5bec0f040 100644 --- a/src/config.c +++ b/src/config.c @@ -328,7 +328,7 @@ int git_config_backend_foreach_match( void *data) { git_config_entry entry; - git_config_backend_iter* iter; + git_config_iterator* iter; regex_t regex; int result = 0; @@ -340,12 +340,12 @@ int git_config_backend_foreach_match( } } - if ((result = backend->iterator_new(&iter, backend)) < 0) { + if ((result = backend->iterator(&iter, backend)) < 0) { iter = NULL; return -1; } - while(!(backend->next(&entry, iter) < 0)) { + while(!(iter->next(&entry, iter) < 0)) { /* skip non-matching keys if regexp was provided */ if (regexp && regexec(®ex, entry.name, 0, NULL, 0) != 0) continue; @@ -362,7 +362,7 @@ cleanup: if (regexp != NULL) regfree(®ex); - backend->iterator_free(iter); + iter->free(iter); return result; } diff --git a/src/config.h b/src/config.h index 2f7c96d7f..c5c11ae14 100644 --- a/src/config.h +++ b/src/config.h @@ -24,11 +24,6 @@ struct git_config { git_vector files; }; -struct git_config_backend_iter { - git_config_backend *backend; - unsigned int flags; -}; - extern int git_config_find_global_r(git_buf *global_config_path); extern int git_config_find_xdg_r(git_buf *system_config_path); extern int git_config_find_system_r(git_buf *system_config_path); diff --git a/src/config_file.c b/src/config_file.c index ed79624eb..3e0c6cc0b 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -28,9 +28,9 @@ typedef struct cvar_t { } cvar_t; typedef struct git_config_file_iter { - git_config_backend_iter parent; + git_config_iterator parent; git_strmap_iter iter; - cvar_t* next; + cvar_t* next_var; } git_config_file_iter; @@ -254,8 +254,44 @@ static void backend_free(git_config_backend *_backend) git__free(backend); } +static void config_iterator_free( + git_config_iterator* iter) +{ + git__free(iter); +} + +static int config_iterator_next( + git_config_entry *entry, + git_config_iterator *iter) +{ + git_config_file_iter *it = (git_config_file_iter *) iter; + diskfile_backend *b = (diskfile_backend *) it->parent.backend; + int err = 0; + cvar_t * var; + const char* key; + + if (it->next_var == NULL) { + err = git_strmap_next(&key, (void**) &var, &(it->iter), b->values); + } else { + key = it->next_var->entry->name; + var = it->next_var; + } + + if (err < 0) { + it->next_var = NULL; + return -1; + } + + entry->name = key; + entry->value = var->entry->value; + entry->level = var->entry->level; + it->next_var = CVAR_LIST_NEXT(var); + + return 0; +} + static int config_iterator_new( - git_config_backend_iter **iter, + git_config_iterator **iter, struct git_config_backend* backend) { diskfile_backend *b = (diskfile_backend *)backend; @@ -265,44 +301,11 @@ static int config_iterator_new( it->parent.backend = backend; it->iter = git_strmap_begin(b->values); - it->next = NULL; - *iter = (git_config_backend_iter *) it; + it->next_var = NULL; - return 0; -} - -static void config_iterator_free( - git_config_backend_iter* iter) -{ - git__free(iter); -} - -static int config_iterator_next( - git_config_entry *entry, - git_config_backend_iter *iter) -{ - git_config_file_iter *it = (git_config_file_iter *) iter; - diskfile_backend *b = (diskfile_backend *) it->parent.backend; - int err = 0; - cvar_t * var; - const char* key; - - if (it->next == NULL) { - err = git_strmap_next(&key, (void**) &var, &(it->iter), b->values); - } else { - key = it->next->entry->name; - var = it->next; - } - - if (err < 0) { - it->next = NULL; - return -1; - } - - entry->name = key; - entry->value = var->entry->value; - entry->level = var->entry->level; - it->next = CVAR_LIST_NEXT(var); + it->parent.next = config_iterator_next; + it->parent.free = config_iterator_free; + *iter = (git_config_iterator *) it; return 0; } @@ -607,9 +610,7 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->parent.set = config_set; backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; - backend->parent.iterator_new = config_iterator_new; - backend->parent.iterator_free = config_iterator_free; - backend->parent.next = config_iterator_next; + backend->parent.iterator = config_iterator_new; backend->parent.refresh = config_refresh; backend->parent.free = backend_free; From 3a7ffc29c9416c5d182835c7f18c04437366f218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 16:18:07 +0200 Subject: [PATCH 200/367] config: initial multivar iterator --- include/git2/sys/config.h | 6 ++- src/config.c | 18 ++++++++ src/config_file.c | 90 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 45599dc69..477d47271 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -36,9 +36,10 @@ struct git_config_iterator { unsigned int flags; /** - * Return the current entry and advance the iterator + * Return the current entry and advance the iterator. The + * memory belongs to the library. */ - int (*next)(git_config_entry *entry, git_config_iterator *iter); + int (*next)(const git_config_entry *entry, git_config_iterator *iter); /** * Free the iterator @@ -58,6 +59,7 @@ struct git_config_backend { int (*open)(struct git_config_backend *, git_config_level_t level); int (*get)(const struct git_config_backend *, const char *key, const git_config_entry **entry); int (*get_multivar_foreach)(struct git_config_backend *, const char *key, const char *regexp, git_config_foreach_cb callback, void *payload); + int (*get_multivar)(git_config_iterator **, struct git_config_backend *, const char *name, const char *regexp); int (*set)(struct git_config_backend *, const char *key, const char *value); int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_backend *, const char *key); diff --git a/src/config.c b/src/config.c index 5bec0f040..77c558022 100644 --- a/src/config.c +++ b/src/config.c @@ -602,6 +602,24 @@ int git_config_get_multivar_foreach( return (ret == GIT_ENOTFOUND) ? config_error_notfound(name) : 0; } +struct config_multivar_iter { + git_config_iterator parent; +}; + +int git_config_get_multivar(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) +{ + struct config_multivar_iter *iter; + + iter = git__calloc(1, sizeof(struct config_multivar_iter)); + GITERR_CHECK_ALLOC(iter); + + /* get multivar from each */ + + *out = (git_config_iterator *) iter; + + return 0; +} + int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) { git_config_backend *file; diff --git a/src/config_file.c b/src/config_file.c index 3e0c6cc0b..5559bd406 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -416,6 +416,95 @@ static int config_get(const git_config_backend *cfg, const char *name, const git return 0; } +typedef struct { + git_config_iterator parent; + cvar_t *var; + regex_t regex; + int have_regex; +} foreach_iter; + +static void foreach_iter_free(git_config_iterator *_iter) +{ + foreach_iter *iter = (foreach_iter *) _iter; + + if (iter->have_regex) + regfree(&iter->regex); + + git__free(iter); +} + +static int foreach_iter_next(git_config_entry **out, git_config_iterator *_iter) +{ + foreach_iter *iter = (foreach_iter *) _iter; + + cvar_t* var = iter->var; + + if (var == NULL) + return GIT_ITEROVER; + + if (!iter->have_regex) { + *out = var->entry; + iter->var = var->next; + return 0; + } + + /* For the regex case, we must loop until we find something we like */ + do { + git_config_entry *entry = var->entry; + regex_t *regex = &iter->regex;; + if (regexec(regex, entry->value, 0, NULL, 0) == 0) { + *out = entry; + return 0; + } + } while(var != NULL); + + return GIT_ITEROVER; +} + +static int config_get_multivar(git_config_iterator **out, git_config_backend *_backend, + const char *name, const char *regexp) +{ + foreach_iter *iter; + diskfile_backend *b = (diskfile_backend *) _backend; + + char *key; + khiter_t pos; + int error = 0; + + if ((error = normalize_name(name, &key)) < 0) + return error; + + pos = git_strmap_lookup_index(b->values, key); + git__free(key); + + if (!git_strmap_valid_index(b->values, pos)) + return GIT_ENOTFOUND; + + iter = git__calloc(1, sizeof(foreach_iter)); + GITERR_CHECK_ALLOC(iter); + + iter->var = git_strmap_value_at(b->values, pos); + + if (regexp != NULL) { + int result; + + result = regcomp(&iter->regex, regexp, REG_EXTENDED); + if (result < 0) { + giterr_set_regex(&iter->regex, result); + regfree(&iter->regex); + return -1; + } + iter->have_regex = 1; + } + + iter->parent.free = foreach_iter_free; + iter->parent.next = foreach_iter_next; + + *out = (git_config_iterator *) iter; + + return 0; + } + static int config_get_multivar_foreach( git_config_backend *cfg, const char *name, @@ -607,6 +696,7 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->parent.open = config_open; backend->parent.get = config_get; backend->parent.get_multivar_foreach = config_get_multivar_foreach; + backend->parent.get_multivar = config_get_multivar; backend->parent.set = config_set; backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; From cca5df6376fd41fb4fbbb9f8a9ff87c38079dfd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 16:59:39 +0200 Subject: [PATCH 201/367] config: hopefully get the iterator to work on multivars --- include/git2/sys/config.h | 2 +- src/config.c | 81 ++++++++++++++++++++++++++++++++++++--- src/config_file.c | 9 +++-- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 477d47271..d5b450a6c 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -39,7 +39,7 @@ struct git_config_iterator { * Return the current entry and advance the iterator. The * memory belongs to the library. */ - int (*next)(const git_config_entry *entry, git_config_iterator *iter); + int (*next)(git_config_entry *entry, git_config_iterator *iter); /** * Free the iterator diff --git a/src/config.c b/src/config.c index 77c558022..f34d5dd29 100644 --- a/src/config.c +++ b/src/config.c @@ -602,18 +602,89 @@ int git_config_get_multivar_foreach( return (ret == GIT_ENOTFOUND) ? config_error_notfound(name) : 0; } -struct config_multivar_iter { +typedef struct { git_config_iterator parent; -}; + git_config_iterator *current; + const char *name; + const char *regexp; + const git_config *cfg; + size_t i; +} multivar_iter; + +static int find_next_backend(size_t *out, const git_config *cfg, size_t i) +{ + file_internal *internal; + + for (; i > 0; --i) { + internal = git_vector_get(&cfg->files, i - 1); + if (!internal || !internal->file) + continue; + + *out = i; + return 0; + } + + return -1; +} + +static int multivar_iter_next_empty(git_config_entry *entry, git_config_iterator *_iter) +{ + GIT_UNUSED(entry); + GIT_UNUSED(_iter); + + return GIT_ITEROVER; +} + +static int multivar_iter_next(git_config_entry *entry, git_config_iterator *_iter) +{ + multivar_iter *iter = (multivar_iter *) _iter; + git_config_iterator *current = iter->current; + file_internal *internal; + git_config_backend *backend; + size_t i; + int error; + + if (current != NULL && + (error = current->next(entry, current)) == 0) + return 0; + + if (error != GIT_ITEROVER) + return error; + + do { + if (find_next_backend(&i, iter->cfg, iter->i) < 0) + return GIT_ITEROVER; + + internal = git_vector_get(&iter->cfg->files, i - 1); + backend = internal->file; + if ((error = backend->get_multivar(&iter->current, backend, iter->name, iter->regexp)) < 0) + return -1; + + iter->i = i; + return iter->current->next(entry, iter->current); + + } while(1); + + return GIT_ITEROVER; +} int git_config_get_multivar(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) { - struct config_multivar_iter *iter; + multivar_iter *iter; + size_t i; - iter = git__calloc(1, sizeof(struct config_multivar_iter)); + iter = git__calloc(1, sizeof(multivar_iter)); GITERR_CHECK_ALLOC(iter); - /* get multivar from each */ + if (find_next_backend(&i, cfg, cfg->files.length) < 0) + iter->parent.next = multivar_iter_next_empty; + else + iter->parent.next = multivar_iter_next; + + iter->i = cfg->files.length; + iter->cfg = cfg; + iter->name = name; + iter->regexp = regexp; *out = (git_config_iterator *) iter; diff --git a/src/config_file.c b/src/config_file.c index 5559bd406..74b200073 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -433,7 +433,7 @@ static void foreach_iter_free(git_config_iterator *_iter) git__free(iter); } -static int foreach_iter_next(git_config_entry **out, git_config_iterator *_iter) +static int foreach_iter_next(git_config_entry *out, git_config_iterator *_iter) { foreach_iter *iter = (foreach_iter *) _iter; @@ -443,7 +443,9 @@ static int foreach_iter_next(git_config_entry **out, git_config_iterator *_iter) return GIT_ITEROVER; if (!iter->have_regex) { - *out = var->entry; + out->name = var->entry->name; + out->value = var->entry->value; + iter->var = var->next; return 0; } @@ -453,7 +455,8 @@ static int foreach_iter_next(git_config_entry **out, git_config_iterator *_iter) git_config_entry *entry = var->entry; regex_t *regex = &iter->regex;; if (regexec(regex, entry->value, 0, NULL, 0) == 0) { - *out = entry; + out->name = entry->name; + out->value = entry->value; return 0; } } while(var != NULL); From aaefbdeea205fbea2005113a9743c81721a42b36 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Thu, 8 Aug 2013 08:48:57 -0700 Subject: [PATCH 202/367] Discriminate path-specific and general UTF-X conversions --- src/fileops.c | 2 +- src/path.c | 2 +- src/transports/winhttp.c | 6 +++--- src/win32/dir.c | 6 +++--- src/win32/findfile.c | 4 ++-- src/win32/posix.h | 2 +- src/win32/posix_w32.c | 26 +++++++++++++------------- src/win32/utf-conv.c | 8 ++++---- src/win32/utf-conv.h | 17 ++++++++++++++--- tests-clar/clar_libgit2.c | 12 ++++++------ 10 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 25323d0c9..d62158db7 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -60,7 +60,7 @@ int git_futils_creat_locked(const char *path, const mode_t mode) #ifdef GIT_WIN32 git_win32_path_utf16 buf; - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY | O_CLOEXEC, mode); #else diff --git a/src/path.c b/src/path.c index 72831c736..d0cecc070 100644 --- a/src/path.c +++ b/src/path.c @@ -493,7 +493,7 @@ bool git_path_is_empty_dir(const char *path) if (!git_path_isdir(path)) return false; git_buf_printf(&pathbuf, "%s\\*", path); - git__utf8_to_16(wbuf, git_buf_cstr(&pathbuf)); + git__win32_path_utf8_to_16(wbuf, git_buf_cstr(&pathbuf)); hFind = FindFirstFileW(wbuf, &ffd); if (INVALID_HANDLE_VALUE == hFind) { diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 359ab5f5e..5c55c10f1 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -264,7 +264,7 @@ static int winhttp_stream_connect(winhttp_stream *s) if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0) goto on_error; - git__utf8_to_16(ct, git_buf_cstr(&buf)); + git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { giterr_set(GITERR_OS, "Failed to add a header to the request"); @@ -593,7 +593,7 @@ replay: else snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); - git__utf8_to_16(expected_content_type, expected_content_type_8); + git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8); content_type_length = sizeof(content_type); if (!WinHttpQueryHeaders(s->request, @@ -920,7 +920,7 @@ static int winhttp_connect( return -1; /* Prepare host */ - git__utf8_to_16(host, t->host); + git__win32_path_utf8_to_16(host, t->host); /* Establish session */ t->session = WinHttpOpen( diff --git a/src/win32/dir.c b/src/win32/dir.c index b1cb47c9b..85f793d62 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -40,7 +40,7 @@ git__DIR *git__opendir(const char *dir) if (!new->dir) goto fail; - git__utf8_to_16(filter_w, filter); + git__win32_path_utf8_to_16(filter_w, filter); new->h = FindFirstFileW(filter_w, &new->f); if (new->h == INVALID_HANDLE_VALUE) { @@ -80,7 +80,7 @@ int git__readdir_ext( if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) return -1; - git__utf16_to_8(entry->d_name, d->f.cFileName); + git__win32_path_utf16_to_8(entry->d_name, d->f.cFileName); entry->d_ino = 0; *result = entry; @@ -116,7 +116,7 @@ void git__rewinddir(git__DIR *d) if (!init_filter(filter, sizeof(filter), d->dir)) return; - git__utf8_to_16(filter_w, filter); + git__win32_path_utf8_to_16(filter_w, filter); d->h = FindFirstFileW(filter_w, &d->f); if (d->h == INVALID_HANDLE_VALUE) diff --git a/src/win32/findfile.c b/src/win32/findfile.c index 248173563..64b9bb537 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -27,7 +27,7 @@ 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__utf16_to_8(temp_utf8, GIT_PATH_MAX, path_utf16); git_path_mkposix(temp_utf8); return git_buf_sets(path_utf8, temp_utf8); @@ -53,7 +53,7 @@ int git_win32__find_file( if (*filename == '/' || *filename == '\\') filename++; - git__utf8_to_16(file_utf16 + root->len - 1, filename); + git__utf8_to_16(file_utf16 + root->len - 1, alloc_len - root->len, filename); /* check access */ if (_waccess(file_utf16, F_OK) < 0) { diff --git a/src/win32/posix.h b/src/win32/posix.h index 47f6ddeb0..7803c9c84 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -22,7 +22,7 @@ GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) { git_win32_path_utf16 buf; GIT_UNUSED(mode); - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); return _wmkdir(buf); } diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index a7df424df..d9a68f284 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -17,7 +17,7 @@ int p_unlink(const char *path) { git_win32_path_utf16 buf; - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); _wchmod(buf, 0666); return _wunlink(buf); } @@ -63,7 +63,7 @@ static int do_lstat( wchar_t lastch; int flen; - flen = git__utf8_to_16(fbuf, file_name); + flen = git__win32_path_utf8_to_16(fbuf, file_name); /* truncate trailing slashes */ for (; flen > 0; --flen) { @@ -189,7 +189,7 @@ int p_readlink(const char *link, char *target, size_t target_len) } } - git__utf8_to_16(link_w, link); + git__win32_path_utf8_to_16(link_w, link); hFile = CreateFileW(link_w, // file to open GENERIC_READ, // open for reading @@ -258,7 +258,7 @@ int p_open(const char *path, int flags, ...) git_win32_path_utf16 buf; mode_t mode = 0; - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); if (flags & O_CREAT) { va_list arg_list; @@ -274,7 +274,7 @@ int p_open(const char *path, int flags, ...) int p_creat(const char *path, mode_t mode) { git_win32_path_utf16 buf; - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } @@ -317,14 +317,14 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { git_win32_path_utf16 buf; - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { git_win32_path_utf16 buf; - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); return _wchmod(buf, mode); } @@ -332,7 +332,7 @@ int p_rmdir(const char* path) { int error; git_win32_path_utf16 buf; - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); error = _wrmdir(buf); @@ -349,7 +349,7 @@ int p_rmdir(const char* path) int p_hide_directory__w32(const char *path) { git_win32_path_utf16 buf; - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; } @@ -359,7 +359,7 @@ char *p_realpath(const char *orig_path, char *buffer) git_win32_path_utf16 orig_path_w; git_win32_path_utf16 buffer_w; - git__utf8_to_16(orig_path_w, orig_path); + git__win32_path_utf8_to_16(orig_path_w, orig_path); /* Implicitly use GetCurrentDirectory which can be a threading issue */ ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL); @@ -450,7 +450,7 @@ int p_setenv(const char* name, const char* value, int overwrite) int p_access(const char* path, mode_t mode) { git_win32_path_utf16 buf; - git__utf8_to_16(buf, path); + git__win32_path_utf8_to_16(buf, path); return _waccess(buf, mode); } @@ -459,8 +459,8 @@ int p_rename(const char *from, const char *to) git_win32_path_utf16 wfrom; git_win32_path_utf16 wto; - git__utf8_to_16(wfrom, from); - git__utf8_to_16(wto, to); + git__win32_path_utf8_to_16(wfrom, from); + git__win32_path_utf8_to_16(wto, to); return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; } diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index bb63b0f8d..623b0517a 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -70,12 +70,12 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) } #endif -int git__utf8_to_16(git_win32_path_utf16 dest, const git_win32_path_utf8 src) +int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src) { - return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, GIT_WIN_PATH_UTF16); + return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, dest_size); } -int git__utf16_to_8(git_win32_path_utf8 dest, const git_win32_path_utf16 src) +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) { - return WideCharToMultiByte(CP_UTF8, 0, src, -1, dest, GIT_WIN_PATH_UTF8, NULL, NULL); + return WideCharToMultiByte(CP_UTF8, 0, src, -1, dest, dest_size, NULL, NULL); } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 4e602291e..7290f1d1a 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -16,8 +16,19 @@ typedef wchar_t git_win32_path_utf16[GIT_WIN_PATH_UTF16]; typedef char git_win32_path_utf8[GIT_WIN_PATH_UTF8]; -int git__utf8_to_16(git_win32_path_utf16 dest, const git_win32_path_utf8 src); -int git__utf16_to_8(git_win32_path_utf8 dest, const git_win32_path_utf16 src); +// dest_size is the size of dest in wchar_t's +int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src); +// dest_size is the size of dest in char's +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); + +GIT_INLINE(int) git__win32_path_utf8_to_16(git_win32_path_utf16 dest, const git_win32_path_utf8 src) +{ + return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src); +} + +GIT_INLINE(int) git__win32_path_utf16_to_8(git_win32_path_utf8 dest, const git_win32_path_utf16 src) +{ + return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src); +} #endif - diff --git a/tests-clar/clar_libgit2.c b/tests-clar/clar_libgit2.c index ebd034a08..6c741accd 100644 --- a/tests-clar/clar_libgit2.c +++ b/tests-clar/clar_libgit2.c @@ -61,7 +61,7 @@ char *cl_getenv(const char *name) wchar_t *value_utf16; char *value_utf8; - git__utf8_to_16(name_utf16, name); + git__win32_path_utf8_to_16(name_utf16, name); alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0); if (alloc_len <= 0) return NULL; @@ -72,7 +72,7 @@ char *cl_getenv(const char *name) GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len); cl_assert(value_utf8 = git__malloc(alloc_len)); - git__utf16_to_8(value_utf8, value_utf16); + git__win32_path_utf16_to_8(value_utf8, value_utf16); git__free(value_utf16); @@ -84,10 +84,10 @@ int cl_setenv(const char *name, const char *value) git_win32_path_utf16 name_utf16; git_win32_path_utf16 value_utf16; - git__utf8_to_16(name_utf16, name); + git__win32_path_utf8_to_16(name_utf16, name); if (value) { - git__utf8_to_16(value_utf16, value); + git__win32_path_utf8_to_16(value_utf16, value); cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16)); } else { /* Windows XP returns 0 (failed) when passing NULL for lpValue when @@ -111,8 +111,8 @@ int cl_rename(const char *source, const char *dest) git_win32_path_utf16 dest_utf16; unsigned retries = 1; - git__utf8_to_16(source_utf16, source); - git__utf8_to_16(dest_utf16, dest); + git__win32_path_utf8_to_16(source_utf16, source); + git__win32_path_utf8_to_16(dest_utf16, dest); while (!MoveFileW(source_utf16, dest_utf16)) { /* Only retry if the error is ERROR_ACCESS_DENIED; From 57f31f058c29edfd7d6d810fcd4a964875e9d463 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 8 Aug 2013 11:05:00 -0500 Subject: [PATCH 203/367] Fixes to safely reading the index Avoid wrapping around extension size when reading, avoid walking off the end of the buffer when reading names. --- src/index.c | 7 ++++--- src/posix.h | 4 ++++ src/win32/mingw-compat.h | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/index.c b/src/index.c index cbdd43bdc..5f53f1e2f 100644 --- a/src/index.c +++ b/src/index.c @@ -1371,7 +1371,7 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) while (size) { git_index_reuc_entry *lost; - len = strlen(buffer) + 1; + len = p_strnlen(buffer, size) + 1; if (size <= len) return index_error_invalid("reading reuc entries"); @@ -1444,7 +1444,7 @@ static int read_conflict_names(git_index *index, const char *buffer, size_t size return -1; #define read_conflict_name(ptr) \ - len = strlen(buffer) + 1; \ + len = p_strnlen(buffer, size) + 1; \ if (size < len) \ return index_error_invalid("reading conflict name entries"); \ \ @@ -1571,7 +1571,8 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer total_size = dest.extension_size + sizeof(struct index_extension); - if (buffer_size < total_size || + if (dest.extension_size > total_size || + buffer_size < total_size || buffer_size - total_size < INDEX_FOOTER_SIZE) return 0; diff --git a/src/posix.h b/src/posix.h index 40bcc1ab0..ea97a1349 100644 --- a/src/posix.h +++ b/src/posix.h @@ -93,6 +93,10 @@ extern int p_gettimeofday(struct timeval *tv, struct timezone *tz); # include "unix/posix.h" #endif +#ifndef __MINGW32__ +# define p_strnlen strnlen +#endif + #ifdef NO_READDIR_R # include GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index 7b97b48db..97b1cb71b 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -19,6 +19,11 @@ # define S_IFLNK _S_IFLNK # define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) +GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { + const char *end = memchr(s, 0, maxlen); + return end ? (end - s) : maxlen; +} + #endif #endif /* INCLUDE_mingw_compat__ */ From a1f69452a22689ca2eede7669e79a3e7ab849cdd Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 8 Aug 2013 12:36:11 -0500 Subject: [PATCH 204/367] git_strndup fix when OOM --- src/util.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/util.h b/src/util.h index ed9624770..a784390c1 100644 --- a/src/util.h +++ b/src/util.h @@ -55,6 +55,9 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) ptr = (char*)git__malloc(length + 1); + if (!ptr) + return NULL; + if (length) memcpy(ptr, str, length); From 99dfb538addc06c2f40d29371c52dd43f0d6ceb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 17:57:59 +0200 Subject: [PATCH 205/367] config: working multivar iterator Implement the foreach version as a wrapper around the iterator. --- include/git2/config.h | 10 ++++ include/git2/sys/config.h | 2 +- src/config.c | 89 +++++++++++++++++++++++------------- src/config_file.c | 20 ++++---- tests-clar/config/multivar.c | 14 +++--- 5 files changed, 83 insertions(+), 52 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index 950312548..e1d34b997 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -341,6 +341,16 @@ GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, c */ GIT_EXTERN(int) git_config_get_multivar_foreach(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload); +/** + * Get each value of a multivar + * + * @param out pointer to store the iterator + * @param cfg where to look for the variable + * @param name the variable's name + * @param regexp regular expression to filter which variables we're + * interested in. Use NULL to indicate all + */ +GIT_EXTERN(int) git_config_get_multivar(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); /** * Set the value of an integer config variable in the config file * with the highest level (usually the local one). diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index d5b450a6c..e369fb8ab 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -39,7 +39,7 @@ struct git_config_iterator { * Return the current entry and advance the iterator. The * memory belongs to the library. */ - int (*next)(git_config_entry *entry, git_config_iterator *iter); + int (*next)(git_config_entry **entry, git_config_iterator *iter); /** * Free the iterator diff --git a/src/config.c b/src/config.c index f34d5dd29..63a386de3 100644 --- a/src/config.c +++ b/src/config.c @@ -327,7 +327,7 @@ int git_config_backend_foreach_match( int (*fn)(const git_config_entry *, void *), void *data) { - git_config_entry entry; + git_config_entry *entry; git_config_iterator* iter; regex_t regex; int result = 0; @@ -347,11 +347,11 @@ int git_config_backend_foreach_match( while(!(iter->next(&entry, iter) < 0)) { /* skip non-matching keys if regexp was provided */ - if (regexp && regexec(®ex, entry.name, 0, NULL, 0) != 0) + if (regexp && regexec(®ex, entry->name, 0, NULL, 0) != 0) continue; /* abort iterator on non-zero return value */ - if (fn(&entry, data)) { + if (fn(entry, data)) { giterr_clear(); result = GIT_EUSER; goto cleanup; @@ -578,35 +578,36 @@ int git_config_get_multivar_foreach( const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb cb, void *payload) { - file_internal *internal; - git_config_backend *file; - int ret = GIT_ENOTFOUND, err; - size_t i; + int err, found; + git_config_iterator *iter; + git_config_entry *entry; - /* - * This loop runs the "wrong" way 'round because we need to - * look at every value from the most general to most specific - */ - for (i = cfg->files.length; i > 0; --i) { - internal = git_vector_get(&cfg->files, i - 1); - if (!internal || !internal->file) - continue; - file = internal->file; + if ((err = git_config_get_multivar(&iter, cfg, name, regexp)) < 0) + return err; - if (!(err = file->get_multivar_foreach(file, name, regexp, cb, payload))) - ret = 0; - else if (err != GIT_ENOTFOUND) - return err; + found = 0; + while ((err = iter->next(&entry, iter)) == 0) { + found = 1; + if(cb(entry, payload)) { + iter->free(iter); + return GIT_EUSER; + } } - return (ret == GIT_ENOTFOUND) ? config_error_notfound(name) : 0; + if (err == GIT_ITEROVER) + err = 0; + + if (found == 0 && err == 0) + err = config_error_notfound(name); + + return err; } typedef struct { git_config_iterator parent; git_config_iterator *current; - const char *name; - const char *regexp; + char *name; + char *regexp; const git_config *cfg; size_t i; } multivar_iter; @@ -627,7 +628,7 @@ static int find_next_backend(size_t *out, const git_config *cfg, size_t i) return -1; } -static int multivar_iter_next_empty(git_config_entry *entry, git_config_iterator *_iter) +static int multivar_iter_next_empty(git_config_entry **entry, git_config_iterator *_iter) { GIT_UNUSED(entry); GIT_UNUSED(_iter); @@ -635,20 +636,21 @@ static int multivar_iter_next_empty(git_config_entry *entry, git_config_iterator return GIT_ITEROVER; } -static int multivar_iter_next(git_config_entry *entry, git_config_iterator *_iter) +static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter) { multivar_iter *iter = (multivar_iter *) _iter; git_config_iterator *current = iter->current; file_internal *internal; git_config_backend *backend; size_t i; - int error; + int error = 0; if (current != NULL && - (error = current->next(entry, current)) == 0) + (error = current->next(entry, current)) == 0) { return 0; + } - if (error != GIT_ITEROVER) + if (error < 0 && error != GIT_ITEROVER) return error; do { @@ -657,10 +659,15 @@ static int multivar_iter_next(git_config_entry *entry, git_config_iterator *_ite internal = git_vector_get(&iter->cfg->files, i - 1); backend = internal->file; - if ((error = backend->get_multivar(&iter->current, backend, iter->name, iter->regexp)) < 0) - return -1; + iter->i = i - 1; + + error = backend->get_multivar(&iter->current, backend, iter->name, iter->regexp); + if (error == GIT_ENOTFOUND) + continue; + + if (error < 0) + return error; - iter->i = i; return iter->current->next(entry, iter->current); } while(1); @@ -668,6 +675,15 @@ static int multivar_iter_next(git_config_entry *entry, git_config_iterator *_ite return GIT_ITEROVER; } +void multivar_iter_free(git_config_iterator *_iter) +{ + multivar_iter *iter = (multivar_iter *) _iter; + + git__free(iter->name); + git__free(iter->regexp); + git__free(iter); +} + int git_config_get_multivar(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) { multivar_iter *iter; @@ -676,6 +692,15 @@ int git_config_get_multivar(git_config_iterator **out, const git_config *cfg, co iter = git__calloc(1, sizeof(multivar_iter)); GITERR_CHECK_ALLOC(iter); + iter->name = git__strdup(name); + GITERR_CHECK_ALLOC(iter->name); + + if (regexp != NULL) { + iter->regexp = git__strdup(regexp); + GITERR_CHECK_ALLOC(iter->regexp); + } + + iter->parent.free = multivar_iter_free; if (find_next_backend(&i, cfg, cfg->files.length) < 0) iter->parent.next = multivar_iter_next_empty; else @@ -683,8 +708,6 @@ int git_config_get_multivar(git_config_iterator **out, const git_config *cfg, co iter->i = cfg->files.length; iter->cfg = cfg; - iter->name = name; - iter->regexp = regexp; *out = (git_config_iterator *) iter; diff --git a/src/config_file.c b/src/config_file.c index 74b200073..38cb9f8b8 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -261,7 +261,7 @@ static void config_iterator_free( } static int config_iterator_next( - git_config_entry *entry, + git_config_entry **entry, git_config_iterator *iter) { git_config_file_iter *it = (git_config_file_iter *) iter; @@ -282,9 +282,7 @@ static int config_iterator_next( return -1; } - entry->name = key; - entry->value = var->entry->value; - entry->level = var->entry->level; + *entry = var->entry; it->next_var = CVAR_LIST_NEXT(var); return 0; @@ -433,19 +431,18 @@ static void foreach_iter_free(git_config_iterator *_iter) git__free(iter); } -static int foreach_iter_next(git_config_entry *out, git_config_iterator *_iter) +static int foreach_iter_next(git_config_entry **out, git_config_iterator *_iter) { foreach_iter *iter = (foreach_iter *) _iter; cvar_t* var = iter->var; + if (var == NULL) return GIT_ITEROVER; if (!iter->have_regex) { - out->name = var->entry->name; - out->value = var->entry->value; - + *out = var->entry; iter->var = var->next; return 0; } @@ -455,10 +452,11 @@ static int foreach_iter_next(git_config_entry *out, git_config_iterator *_iter) git_config_entry *entry = var->entry; regex_t *regex = &iter->regex;; if (regexec(regex, entry->value, 0, NULL, 0) == 0) { - out->name = entry->name; - out->value = entry->value; + *out = entry; + iter->var = var->next; return 0; } + var = var->next; } while(var != NULL); return GIT_ITEROVER; @@ -550,7 +548,7 @@ static int config_get_multivar_foreach( if (regexec(®ex, var->entry->value, 0, NULL, 0) == 0) { /* early termination by the user is not an error; * just break and return successfully */ - if (fn(var->entry, data) < 0) + if (fn(var->entry, data)) break; } diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c index 390b24c6b..b7283b32f 100644 --- a/tests-clar/config/multivar.c +++ b/tests-clar/config/multivar.c @@ -114,11 +114,11 @@ void test_config_multivar__add(void) n = 0; cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert(n == 3); + cl_assert_equal_i(n, 3); n = 0; cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); - cl_assert(n == 1); + cl_assert_equal_i(n, 1); git_config_free(cfg); @@ -128,11 +128,11 @@ void test_config_multivar__add(void) n = 0; cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert(n == 3); + cl_assert_equal_i(n, 3); n = 0; cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); - cl_assert(n == 1); + cl_assert_equal_i(n, 1); git_config_free(cfg); } @@ -148,7 +148,7 @@ void test_config_multivar__add_new(void) cl_git_pass(git_config_set_multivar(cfg, var, "", "variable")); n = 0; cl_git_pass(git_config_get_multivar_foreach(cfg, var, NULL, cb, &n)); - cl_assert(n == 1); + cl_assert_equal_i(n, 1); git_config_free(cfg); } @@ -191,7 +191,7 @@ void test_config_multivar__replace_multiple(void) n = 0; cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); - cl_assert(n == 2); + cl_assert_equal_i(n, 2); git_config_free(cfg); @@ -199,7 +199,7 @@ void test_config_multivar__replace_multiple(void) n = 0; cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); - cl_assert(n == 2); + cl_assert_equal_i(n, 2); git_config_free(cfg); } From 1e96c9d5341e5f2b0e1af9a1088cc30d3ffb9a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 20:47:06 +0200 Subject: [PATCH 206/367] config: add _next() and _iterator_free() Make it look like the refs iterator API. --- include/git2/config.h | 17 +++++++++++++++++ src/config.c | 10 ++++++++++ tests-clar/config/multivar.c | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/include/git2/config.h b/include/git2/config.h index e1d34b997..b338e0c81 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -351,6 +351,23 @@ GIT_EXTERN(int) git_config_get_multivar_foreach(const git_config *cfg, const cha * interested in. Use NULL to indicate all */ GIT_EXTERN(int) git_config_get_multivar(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); + +/** + * Return the current entry and advance the iterator + * + * @param entry pointer to store the entry + * @param iter the iterator + * @return 0 or an error code. GIT_ITEROVER if the iteration has completed + */ +GIT_EXTERN(int) git_config_next(git_config_entry **entry, git_config_iterator *iter); + +/** + * Free a config iterator + * + * @param iter the iterator to free + */ +GIT_EXTERN(void) git_config_iterator_free(git_config_iterator *iter); + /** * Set the value of an integer config variable in the config file * with the highest level (usually the local one). diff --git a/src/config.c b/src/config.c index 63a386de3..83b101ded 100644 --- a/src/config.c +++ b/src/config.c @@ -727,6 +727,16 @@ 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_next(git_config_entry **entry, git_config_iterator *iter) +{ + return iter->next(entry, iter); +} + +void git_config_iterator_free(git_config_iterator *iter) +{ + iter->free(iter); +} + static int git_config__find_file_to_path( char *out, size_t outlen, int (*find)(git_buf *buf)) { diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c index b7283b32f..afb993c18 100644 --- a/tests-clar/config/multivar.c +++ b/tests-clar/config/multivar.c @@ -70,6 +70,22 @@ static void check_get_multivar_foreach( } } +static void check_get_multivar(git_config *cfg, int expected) +{ + git_config_iterator *iter; + git_config_entry *entry; + int n = 0; + + cl_git_pass(git_config_get_multivar(&iter, cfg, _name, NULL)); + + while (git_config_next(&entry, iter) == 0) + n++; + + cl_assert_equal_i(expected, n); + git_config_iterator_free(iter); + +} + void test_config_multivar__get(void) { git_config *cfg; @@ -101,6 +117,8 @@ void test_config_multivar__get(void) cl_git_pass(git_config_add_file_ondisk(cfg, "config/config11", GIT_CONFIG_LEVEL_SYSTEM, 1)); check_get_multivar_foreach(cfg, 2, 1); + check_get_multivar(cfg, 2); + git_config_free(cfg); } From a319ffaead9290bfe35a0f105ff17dacaf7b6e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 Aug 2013 21:00:33 +0200 Subject: [PATCH 207/367] config: fix leaks in the iterators --- src/config.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/config.c b/src/config.c index 83b101ded..42273cde8 100644 --- a/src/config.c +++ b/src/config.c @@ -594,6 +594,7 @@ int git_config_get_multivar_foreach( } } + iter->free(iter); if (err == GIT_ITEROVER) err = 0; @@ -661,6 +662,10 @@ static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_it backend = internal->file; iter->i = i - 1; + if (iter->current) + iter->current->free(current); + + iter->current = NULL; error = backend->get_multivar(&iter->current, backend, iter->name, iter->regexp); if (error == GIT_ENOTFOUND) continue; @@ -679,6 +684,9 @@ void multivar_iter_free(git_config_iterator *_iter) { multivar_iter *iter = (multivar_iter *) _iter; + if (iter->current) + iter->current->free(iter->current); + git__free(iter->name); git__free(iter->regexp); git__free(iter); From c57f668268744cbccb13c30095a0c1649fb18a63 Mon Sep 17 00:00:00 2001 From: Nikolai Vladimirov Date: Thu, 8 Aug 2013 21:17:32 +0300 Subject: [PATCH 208/367] config: allow empty string as value `git_config_set_string(config, "config.section", "")` fails when escaping the value. The buffer in `escape_value` is allocated without NULL-termination. And in case of empty string 0 is passed for buffer size in `git_buf_grow`. `git_buf_detach` returns NULL when the allocated size is 0 and that leads to an error return in `GITERR_CHECK_ALLOC` called after `escape_value` The change in `config_file.c` was suggested by Russell Belfer --- src/config_file.c | 3 +++ tests-clar/config/write.c | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/config_file.c b/src/config_file.c index 2b0732a13..605e2e99c 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1293,6 +1293,9 @@ static char *escape_value(const char *ptr) assert(ptr); len = strlen(ptr); + if (!len) + return git__calloc(1, sizeof(char)); + git_buf_grow(&buf, len); while (*ptr != '\0') { diff --git a/tests-clar/config/write.c b/tests-clar/config/write.c index d70612a97..57b02a7d9 100644 --- a/tests-clar/config/write.c +++ b/tests-clar/config/write.c @@ -242,3 +242,20 @@ void test_config_write__can_set_a_value_to_NULL(void) cl_git_sandbox_cleanup(); } + +void test_config_write__can_set_an_empty_value(void) +{ + git_repository *repository; + git_config *config; + const char * str; + + repository = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_config(&config, repository)); + + cl_git_pass(git_config_set_string(config, "core.somevar", "")); + cl_git_pass(git_config_get_string(&str, config, "core.somevar")); + cl_assert_equal_s(str, ""); + + git_config_free(config); + cl_git_sandbox_cleanup(); +} From 4ba64794aee983483eef659623a765d61311ded1 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 9 Aug 2013 10:52:35 -0700 Subject: [PATCH 209/367] Revert PR #1462 and provide alternative fix This rolls back the changes to fnmatch parsing from commit 2e40a60e847d6c128af23e24ea7a8efebd2427da except for the tests that were added. Instead this adds couple of new flags that can be passed in when attempting to parse an fnmatch pattern. Also, this changes the pathspec match logic to special case matching a filename with a '!' prefix against a negative pattern. This fixes the build. --- src/attr_file.c | 85 +++++++++++++++--------------------------- src/attr_file.h | 14 +++---- src/ignore.c | 4 +- src/pathspec.c | 13 ++++++- tests-clar/attr/file.c | 8 ++-- 5 files changed, 54 insertions(+), 70 deletions(-) diff --git a/src/attr_file.c b/src/attr_file.c index ca5f2137c..92702df98 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -79,13 +79,17 @@ int git_attr_file__parse_buffer( while (!error && *scan) { /* allocate rule if needed */ - if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) { - error = -1; - break; + if (!rule) { + if (!(rule = git__calloc(1, sizeof(git_attr_rule)))) { + error = -1; + break; + } + rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG | + GIT_ATTR_FNMATCH_ALLOWMACRO; } /* parse the next "pattern attr attr attr" line */ - if (!(error = git_attr_fnmatch__parse_gitattr_format( + if (!(error = git_attr_fnmatch__parse( &rule->match, attrs->pool, context, &scan)) && !(error = git_attr_assignment__parse( repo, attrs->pool, &rule->assigns, &scan))) @@ -337,54 +341,7 @@ void git_attr_path__free(git_attr_path *info) * GIT_ENOTFOUND if the fnmatch does not require matching, or * another error code there was an actual problem. */ -int git_attr_fnmatch__parse_gitattr_format( - git_attr_fnmatch *spec, - git_pool *pool, - const char *source, - const char **base) -{ - const char *pattern; - - assert(spec && base && *base); - - pattern = *base; - - while (git__isspace(*pattern)) pattern++; - if (!*pattern || *pattern == '#') { - *base = git__next_line(pattern); - return GIT_ENOTFOUND; - } - - if (*pattern == '[') { - if (strncmp(pattern, "[attr]", 6) == 0) { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; - pattern += 6; - } - /* else a character range like [a-e]* which is accepted */ - } - - if (*pattern == '!') { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; - pattern++; - } - - if (git_attr_fnmatch__parse_shellglob_format(spec, pool, - source, &pattern) < 0) - return -1; - - *base = pattern; - - return 0; -} - -/* - * Fills a spec for the purpose of pure pathspec matching, not - * related to a gitattribute file parsing. - * - * This will return 0 if the spec was filled out, or - * another error code there was an actual problem. - */ -int git_attr_fnmatch__parse_shellglob_format( +int git_attr_fnmatch__parse( git_attr_fnmatch *spec, git_pool *pool, const char *source, @@ -398,9 +355,30 @@ int git_attr_fnmatch__parse_shellglob_format( if (parse_optimized_patterns(spec, pool, *base)) return 0; - allow_space = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0; + spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING); + allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0); + pattern = *base; + while (git__isspace(*pattern)) pattern++; + if (!*pattern || *pattern == '#') { + *base = git__next_line(pattern); + return GIT_ENOTFOUND; + } + + if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) { + if (strncmp(pattern, "[attr]", 6) == 0) { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; + pattern += 6; + } + /* else a character range like [a-e]* which is accepted */ + } + + if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; + pattern++; + } + slash_count = 0; for (scan = pattern; *scan != '\0'; ++scan) { /* scan until (non-escaped) white space */ @@ -636,7 +614,6 @@ static void git_attr_rule__clear(git_attr_rule *rule) /* match.pattern is stored in a git_pool, so no need to free */ rule->match.pattern = NULL; rule->match.length = 0; - rule->match.flags = 0; } void git_attr_rule__free(git_attr_rule *rule) diff --git a/src/attr_file.h b/src/attr_file.h index afea1e115..3bc7c6cb8 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -28,6 +28,12 @@ #define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) #define GIT_ATTR_FNMATCH_ICASE (1U << 7) #define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8) +#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9) +#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10) + +#define GIT_ATTR_FNMATCH__INCOMING \ + (GIT_ATTR_FNMATCH_ALLOWSPACE | \ + GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO) extern const char *git_attr__true; extern const char *git_attr__false; @@ -115,13 +121,7 @@ extern uint32_t git_attr_file__name_hash(const char *name); * other utilities */ -extern int git_attr_fnmatch__parse_gitattr_format( - git_attr_fnmatch *spec, - git_pool *pool, - const char *source, - const char **base); - -extern int git_attr_fnmatch__parse_shellglob_format( +extern int git_attr_fnmatch__parse( git_attr_fnmatch *spec, git_pool *pool, const char *source, diff --git a/src/ignore.c b/src/ignore.c index 7d8280403..3c23158c2 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -37,9 +37,9 @@ static int parse_ignore_file( GITERR_CHECK_ALLOC(match); } - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; - if (!(error = git_attr_fnmatch__parse_gitattr_format( + if (!(error = git_attr_fnmatch__parse( match, ignores->pool, context, &scan))) { match->flags |= GIT_ATTR_FNMATCH_IGNORE; diff --git a/src/pathspec.c b/src/pathspec.c index 4266bb99e..1d7b71a74 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -83,9 +83,9 @@ int git_pathspec__vinit( if (!match) return -1; - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; - ret = git_attr_fnmatch__parse_shellglob_format(match, strpool, NULL, &pattern); + ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); if (ret == GIT_ENOTFOUND) { git__free(match); continue; @@ -160,6 +160,15 @@ static int pathspec_match_one( path[match->length] == '/') result = 0; + /* if we didn't match and this is a negative match, check for exact + * match of filename with leading '!' + */ + if (result == FNM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && + *path == '!' && + ctxt->strncomp(path + 1, match->pattern, match->length) == 0) + return 1; + if (result == 0) return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1; return -1; diff --git a/tests-clar/attr/file.c b/tests-clar/attr/file.c index 8866fd9bd..4eb1d22fe 100644 --- a/tests-clar/attr/file.c +++ b/tests-clar/attr/file.c @@ -49,7 +49,6 @@ void test_attr_file__match_variants(void) cl_assert(rule); cl_assert_equal_s("pat0", rule->match.pattern); cl_assert(rule->match.length == strlen("pat0")); - cl_assert(rule->match.flags == 0); cl_assert(rule->assigns.length == 1); assign = get_assign(rule,0); cl_assert_equal_s("attr0", assign->name); @@ -59,16 +58,16 @@ void test_attr_file__match_variants(void) rule = get_rule(1); cl_assert_equal_s("pat1", rule->match.pattern); cl_assert(rule->match.length == strlen("pat1")); - cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_NEGATIVE); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0); rule = get_rule(2); cl_assert_equal_s("pat2", rule->match.pattern); cl_assert(rule->match.length == strlen("pat2")); - cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_DIRECTORY); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY) != 0); rule = get_rule(3); cl_assert_equal_s("pat3dir/pat3file", rule->match.pattern); - cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_FULLPATH); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH) != 0); rule = get_rule(4); cl_assert_equal_s("pat4.*", rule->match.pattern); @@ -89,7 +88,6 @@ void test_attr_file__match_variants(void) rule = get_rule(8); cl_assert_equal_s("pat8 with spaces", rule->match.pattern); cl_assert(rule->match.length == strlen("pat8 with spaces")); - cl_assert(rule->match.flags == 0); rule = get_rule(9); cl_assert_equal_s("pat9", rule->match.pattern); From b7b77def931be50777884ddd35a8689df0ecbebd Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 9 Aug 2013 11:20:49 -0700 Subject: [PATCH 210/367] Match against file with leading ! was too broad --- src/pathspec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pathspec.c b/src/pathspec.c index 1d7b71a74..d56d03918 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -166,7 +166,8 @@ static int pathspec_match_one( if (result == FNM_NOMATCH && (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && *path == '!' && - ctxt->strncomp(path + 1, match->pattern, match->length) == 0) + ctxt->strncomp(path + 1, match->pattern, match->length) == 0 && + (!path[match->length + 1] || path[match->length + 1] == '/')) return 1; if (result == 0) From ba8b8c040744edb9bc832a67646e0126636753bb Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 7 Aug 2013 09:17:20 -0700 Subject: [PATCH 211/367] Improve building ignore file lists The routines to push and pop ignore files while traversing a directory had some issues. In particular, setting up the initial list would sometimes push an ignore file before it ought to be applied if the starting path was a directory containing an ignore file. Also, the pop function was not always matching the right part of the path and would fail to pop ignores from the list in some cases. This adds some tests that exercise a particular problematic case and then fixes the problems that I could find related to this. At some point, I'd like to isolate this ignore rule management code and rewrite it, but that's a larger project and right now, I'll opt to just try to fix the broken behaviors. --- src/ignore.c | 30 ++++++++----- src/ignore.h | 5 ++- src/iterator.c | 2 +- src/path.c | 2 +- tests-clar/status/ignore.c | 87 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 14 deletions(-) diff --git a/src/ignore.c b/src/ignore.c index 3c23158c2..ca63f0bd9 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -159,17 +159,26 @@ int git_ignore__push_dir(git_ignores *ign, const char *dir) { if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) return -1; - else - return push_ignore_file( - ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); + + return push_ignore_file( + ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); } int git_ignore__pop_dir(git_ignores *ign) { if (ign->ign_path.length > 0) { git_attr_file *file = git_vector_last(&ign->ign_path); - if (git__suffixcmp(ign->dir.ptr, file->key + 2) == 0) + size_t keylen = strlen(file->key); + + while (keylen && file->key[keylen] != '/') + keylen--; + keylen -= 1; /* because we will skip "0#" prefix */ + + if (ign->dir.size > keylen && + !memcmp(ign->dir.ptr + ign->dir.size - keylen, + file->key + 2, keylen)) git_vector_pop(&ign->ign_path); + git_buf_rtruncate_at_char(&ign->dir, '/'); } return 0; @@ -298,12 +307,9 @@ int git_ignore_path_is_ignored( path.full.size = (tail - path.full.ptr); path.is_dir = (tail == end) ? full_is_dir : true; - /* update ignores for new path fragment */ - if (path.basename == path.path) - error = git_ignore__for_path(repo, path.path, &ignores); - else - error = git_ignore__push_dir(&ignores, path.basename); - if (error < 0) + /* initialize ignores the first time through */ + if (path.basename == path.path && + (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) break; /* first process builtins - success means path was found */ @@ -327,6 +333,10 @@ int git_ignore_path_is_ignored( if (tail == end) break; + /* now add this directory to list of ignores */ + if ((error = git_ignore__push_dir(&ignores, path.path)) < 0) + break; + /* reinstate divider in path */ *tail = '/'; while (*tail == '/') tail++; diff --git a/src/ignore.h b/src/ignore.h index cc114b001..851c824bf 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -24,14 +24,15 @@ */ typedef struct { git_repository *repo; - git_buf dir; + git_buf dir; /* current directory reflected in ign_path */ git_attr_file *ign_internal; git_vector ign_path; git_vector ign_global; int ignore_case; } git_ignores; -extern int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ign); +extern int git_ignore__for_path( + git_repository *repo, const char *path, git_ignores *ign); extern int git_ignore__push_dir(git_ignores *ign, const char *dir); diff --git a/src/iterator.c b/src/iterator.c index 5917f63fd..bdc98d22b 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -1350,7 +1350,7 @@ int git_iterator_for_workdir_ext( wi->fi.update_entry_cb = workdir_iterator__update_entry; if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0 || - (error = git_ignore__for_path(repo, "", &wi->ignores)) < 0) + (error = git_ignore__for_path(repo, ".gitignore", &wi->ignores)) < 0) { git_iterator_free((git_iterator *)wi); return error; diff --git a/src/path.c b/src/path.c index 6437979d5..b81675b49 100644 --- a/src/path.c +++ b/src/path.c @@ -603,7 +603,7 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base) } /* call dirname if this is not a directory */ - if (!error && git_path_isdir(dir->ptr) == false) + if (!error) /* && git_path_isdir(dir->ptr) == false) */ error = git_path_dirname_r(dir, dir->ptr); if (!error) diff --git a/tests-clar/status/ignore.c b/tests-clar/status/ignore.c index 1b41fed1a..acdc8fb58 100644 --- a/tests-clar/status/ignore.c +++ b/tests-clar/status/ignore.c @@ -493,3 +493,90 @@ void test_status_ignore__filenames_with_special_prefixes_do_not_interfere_with_s git_buf_free(&file); } } + +void test_status_ignore__issue_1766_negated_ignores(void) +{ + int ignored = 0; + unsigned int status; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_futils_mkdir_r( + "empty_standard_repo/a", NULL, 0775)); + cl_git_mkfile( + "empty_standard_repo/a/.gitignore", "*\n!.gitignore\n"); + cl_git_mkfile( + "empty_standard_repo/a/ignoreme", "I should be ignored\n"); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "a/.gitignore")); + cl_assert(!ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "a/ignoreme")); + cl_assert(ignored); + + cl_git_pass(git_futils_mkdir_r( + "empty_standard_repo/b", NULL, 0775)); + cl_git_mkfile( + "empty_standard_repo/b/.gitignore", "*\n!.gitignore\n"); + cl_git_mkfile( + "empty_standard_repo/b/ignoreme", "I should be ignored\n"); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "b/.gitignore")); + cl_assert(!ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "b/ignoreme")); + cl_assert(ignored); + + /* shouldn't have changed results from first couple either */ + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "a/.gitignore")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "a/ignoreme")); + cl_assert(ignored); + + /* status should find the two ignore files and nothing else */ + + cl_git_pass(git_status_file(&status, g_repo, "a/.gitignore")); + cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); + + cl_git_pass(git_status_file(&status, g_repo, "a/ignoreme")); + cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); + + cl_git_pass(git_status_file(&status, g_repo, "b/.gitignore")); + cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); + + cl_git_pass(git_status_file(&status, g_repo, "b/ignoreme")); + cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); + + { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *paths[] = { + "a/.gitignore", + "a/ignoreme", + "b/.gitignore", + "b/ignoreme", + }; + static const unsigned int statuses[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_NEW, + GIT_STATUS_IGNORED, + }; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 4; + counts.expected_paths = paths; + counts.expected_statuses = statuses; + + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + } +} + From 3bc3ed80f476bcef24a508cadd45d4c341ef60c7 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 9 Aug 2013 10:06:23 -0700 Subject: [PATCH 212/367] Improve and comment git_ignore__pop_dir This just cleans up the improved logic for popping ignore dirs and documents why the complex behavior is needed. --- src/ignore.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/ignore.c b/src/ignore.c index ca63f0bd9..0c35d0431 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -168,15 +168,25 @@ int git_ignore__pop_dir(git_ignores *ign) { if (ign->ign_path.length > 0) { git_attr_file *file = git_vector_last(&ign->ign_path); - size_t keylen = strlen(file->key); + const char *start, *end, *scan; + size_t keylen; - while (keylen && file->key[keylen] != '/') - keylen--; - keylen -= 1; /* because we will skip "0#" prefix */ + /* - ign->dir looks something like "a/b" (or "a/b/c/d") + * - file->key looks something like "0#a/b/.gitignore + * + * We are popping the last directory off ign->dir. We also want to + * remove the file from the vector if the directory part of the key + * matches the ign->dir path. We need to test if the "a/b" part of + * the file key matches the path we are about to pop. + */ - if (ign->dir.size > keylen && - !memcmp(ign->dir.ptr + ign->dir.size - keylen, - file->key + 2, keylen)) + for (start = end = scan = &file->key[2]; *scan; ++scan) + if (*scan == '/') + end = scan; /* point 'end' to last '/' in key */ + keylen = (end - start) + 1; + + if (ign->dir.size >= keylen && + !memcmp(ign->dir.ptr + ign->dir.size - keylen, start, keylen)) git_vector_pop(&ign->ign_path); git_buf_rtruncate_at_char(&ign->dir, '/'); From aa0af72933424bcf778db29c63c3727b25052c27 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Sat, 10 Aug 2013 14:56:58 -0700 Subject: [PATCH 213/367] Fix 64-bit MSVC warnings --- src/win32/utf-conv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index 623b0517a..d4dbfbab9 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -72,10 +72,10 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src) { - return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, dest_size); + return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)dest_size); } int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) { - return WideCharToMultiByte(CP_UTF8, 0, src, -1, dest, dest_size, NULL, NULL); + return WideCharToMultiByte(CP_UTF8, 0, src, -1, dest, (int)dest_size, NULL, NULL); } From 0e26fca1daa11a3108960dee3cb555b4c482ad81 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Sat, 10 Aug 2013 15:11:19 -0700 Subject: [PATCH 214/367] Make utf-8 source strings unlimited --- src/win32/utf-conv.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 7290f1d1a..66422e17c 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -21,12 +21,12 @@ int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src); // dest_size is the size of dest in char's int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); -GIT_INLINE(int) git__win32_path_utf8_to_16(git_win32_path_utf16 dest, const git_win32_path_utf8 src) +GIT_INLINE(int) git__win32_path_utf8_to_16(git_win32_path_utf16 dest, const char *src) { return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src); } -GIT_INLINE(int) git__win32_path_utf16_to_8(git_win32_path_utf8 dest, const git_win32_path_utf16 src) +GIT_INLINE(int) git__win32_path_utf16_to_8(git_win32_path_utf8 dest, const wchar_t *src) { return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src); } From 5880962d90958bc37c08c21d37c9da480ef20e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 9 Aug 2013 09:05:19 +0200 Subject: [PATCH 215/367] config: introduce _iterator_new() As the name suggests, it iterates over all the entries --- include/git2/config.h | 11 ++++ src/config.c | 123 ++++++++++++++++++++++++++++++--------- src/config_file.c | 2 +- tests-clar/config/read.c | 30 ++++++++++ 4 files changed, 136 insertions(+), 30 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index b338e0c81..aed720fc8 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -452,6 +452,17 @@ GIT_EXTERN(int) git_config_foreach( git_config_foreach_cb callback, void *payload); +/** + * Iterate over all the config variables + * + * Use `git_config_next` to advance the iteration and + * `git_config_iterator_free` when done. + * + * @param out pointer to store the iterator + * @param cfg where to ge the variables from + */ +GIT_EXTERN(int) git_config_iterator_new(git_config_iterator **out, const git_config *cfg); + /** * Perform an operation on each config variable matching a regular expression. * diff --git a/src/config.c b/src/config.c index 42273cde8..f7ea6f795 100644 --- a/src/config.c +++ b/src/config.c @@ -315,6 +315,99 @@ int git_config_refresh(git_config *cfg) * Loop over all the variables */ +typedef struct { + git_config_iterator parent; + git_config_iterator *current; + const git_config *cfg; + size_t i; +} all_iter; + +static int find_next_backend(size_t *out, const git_config *cfg, size_t i) +{ + file_internal *internal; + + for (; i > 0; --i) { + internal = git_vector_get(&cfg->files, i - 1); + if (!internal || !internal->file) + continue; + + *out = i; + return 0; + } + + return -1; +} + +static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + file_internal *internal; + git_config_backend *backend; + size_t i; + int error = 0; + + if (iter->current != NULL && + (error = iter->current->next(entry, iter->current)) == 0) { + return 0; + } + + if (error < 0 && error != GIT_ITEROVER) + return error; + + do { + if (find_next_backend(&i, iter->cfg, iter->i) < 0) + return GIT_ITEROVER; + + internal = git_vector_get(&iter->cfg->files, i - 1); + backend = internal->file; + iter->i = i - 1; + + if (iter->current) + iter->current->free(iter->current); + + iter->current = NULL; + error = backend->iterator(&iter->current, backend); + if (error == GIT_ENOTFOUND) + continue; + + if (error < 0) + return error; + + return iter->current->next(entry, iter->current); + + } while(1); + + return GIT_ITEROVER; +} + +static void all_iter_free(git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + + if (iter->current) + iter->current->free(iter->current); + + git__free(iter); +} + +int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) +{ + all_iter *iter; + + iter = git__calloc(1, sizeof(all_iter)); + GITERR_CHECK_ALLOC(iter); + + iter->parent.free = all_iter_free; + iter->parent.next = all_iter_next; + + iter->i = cfg->files.length; + iter->cfg = cfg; + + *out = (git_config_iterator *) iter; + + return 0; +} + int git_config_foreach( const git_config *cfg, git_config_foreach_cb cb, void *payload) { @@ -613,30 +706,6 @@ typedef struct { size_t i; } multivar_iter; -static int find_next_backend(size_t *out, const git_config *cfg, size_t i) -{ - file_internal *internal; - - for (; i > 0; --i) { - internal = git_vector_get(&cfg->files, i - 1); - if (!internal || !internal->file) - continue; - - *out = i; - return 0; - } - - return -1; -} - -static int multivar_iter_next_empty(git_config_entry **entry, git_config_iterator *_iter) -{ - GIT_UNUSED(entry); - GIT_UNUSED(_iter); - - return GIT_ITEROVER; -} - static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter) { multivar_iter *iter = (multivar_iter *) _iter; @@ -695,7 +764,6 @@ void multivar_iter_free(git_config_iterator *_iter) int git_config_get_multivar(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) { multivar_iter *iter; - size_t i; iter = git__calloc(1, sizeof(multivar_iter)); GITERR_CHECK_ALLOC(iter); @@ -709,10 +777,7 @@ int git_config_get_multivar(git_config_iterator **out, const git_config *cfg, co } iter->parent.free = multivar_iter_free; - if (find_next_backend(&i, cfg, cfg->files.length) < 0) - iter->parent.next = multivar_iter_next_empty; - else - iter->parent.next = multivar_iter_next; + iter->parent.next = multivar_iter_next; iter->i = cfg->files.length; iter->cfg = cfg; diff --git a/src/config_file.c b/src/config_file.c index 38cb9f8b8..4e564a51d 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -279,7 +279,7 @@ static int config_iterator_next( if (err < 0) { it->next_var = NULL; - return -1; + return err; } *entry = var->entry; diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c index a18dca89b..2fb511d9d 100644 --- a/tests-clar/config/read.c +++ b/tests-clar/config/read.c @@ -245,6 +245,36 @@ void test_config_read__foreach(void) git_config_free(cfg); } +void test_config_read__iterator(void) +{ + git_config *cfg; + git_config_iterator *iter; + git_config_entry *entry; + int count, ret; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, 0)); + + count = 0; + cl_git_pass(git_config_iterator_new(&iter, cfg)); + + while ((ret = git_config_next(&entry, iter)) == 0) { + count++; + } + + cl_assert_equal_i(GIT_ITEROVER, ret); + cl_assert_equal_i(7, count); + + count = 3; + cl_git_pass(git_config_iterator_new(&iter, cfg)); + + git_config_iterator_free(iter); + git_config_free(cfg); +} + static int count_cfg_entries(const git_config_entry *entry, void *payload) { int *count = payload; From 54f3a572b4fac419008afa83da56c1b0daee1257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 9 Aug 2013 10:29:11 +0200 Subject: [PATCH 216/367] config: introduce a regex-filtering iterator --- include/git2/config.h | 12 ++++++++ src/config.c | 61 ++++++++++++++++++++++++++++++++++++++++ tests-clar/config/read.c | 33 ++++++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/include/git2/config.h b/include/git2/config.h index aed720fc8..28216467b 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -463,6 +463,18 @@ GIT_EXTERN(int) git_config_foreach( */ GIT_EXTERN(int) git_config_iterator_new(git_config_iterator **out, const git_config *cfg); +/** + * Iterate over all the config variables whose name matches a pattern + * + * Use `git_config_next` to advance the iteration and + * `git_config_iterator_free` when done. + * + * @param out pointer to store the iterator + * @param cfg where to ge the variables from + * @param regexp regular expression to match the names + */ +GIT_EXTERN(int) git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp); + /** * Perform an operation on each config variable matching a regular expression. * diff --git a/src/config.c b/src/config.c index f7ea6f795..9d809f8f1 100644 --- a/src/config.c +++ b/src/config.c @@ -319,6 +319,8 @@ typedef struct { git_config_iterator parent; git_config_iterator *current; const git_config *cfg; + regex_t regex; + int has_regex; size_t i; } all_iter; @@ -380,6 +382,27 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) return GIT_ITEROVER; } +static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter) +{ + int error; + all_iter *iter = (all_iter *) _iter; + + /* + * We use the "normal" function to grab the next one across + * backends and then apply the regex + */ + while ((error = all_iter_next(entry, _iter)) == 0) { + /* skip non-matching keys if regexp was provided */ + if (regexec(&iter->regex, (*entry)->name, 0, NULL, 0) != 0) + continue; + + /* and simply return if we like the entry's name */ + return 0; + } + + return error; +} + static void all_iter_free(git_config_iterator *_iter) { all_iter *iter = (all_iter *) _iter; @@ -390,6 +413,14 @@ static void all_iter_free(git_config_iterator *_iter) git__free(iter); } +static void all_iter_glob_free(git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + + regfree(&iter->regex); + all_iter_free(_iter); +} + int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) { all_iter *iter; @@ -408,6 +439,36 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) return 0; } +int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp) +{ + all_iter *iter; + int result; + + iter = git__calloc(1, sizeof(all_iter)); + GITERR_CHECK_ALLOC(iter); + + if (regexp != NULL) { + if ((result = regcomp(&iter->regex, regexp, REG_EXTENDED)) < 0) { + giterr_set_regex(&iter->regex, result); + regfree(&iter->regex); + return -1; + } + + iter->parent.next = all_iter_glob_next; + } else { + iter->parent.next = all_iter_next; + } + + iter->parent.free = all_iter_glob_free; + + iter->i = cfg->files.length; + iter->cfg = cfg; + + *out = (git_config_iterator *) iter; + + return 0; +} + int git_config_foreach( const git_config *cfg, git_config_foreach_cb cb, void *payload) { diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c index 2fb511d9d..395f1cfdb 100644 --- a/tests-clar/config/read.c +++ b/tests-clar/config/read.c @@ -265,6 +265,7 @@ void test_config_read__iterator(void) count++; } + git_config_iterator_free(iter); cl_assert_equal_i(GIT_ITEROVER, ret); cl_assert_equal_i(7, count); @@ -318,6 +319,38 @@ void test_config_read__foreach_match(void) git_config_free(cfg); } +static void check_glob_iter(git_config *cfg, const char *regexp, int expected) +{ + git_config_iterator *iter; + git_config_entry *entry; + int count, error; + + cl_git_pass(git_config_iterator_glob_new(&iter, cfg, regexp)); + + count = 0; + while ((error = git_config_next(&entry, iter)) == 0) + count++; + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(expected, count); + git_config_iterator_free(iter); +} + +void test_config_read__iterator_glob(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + check_glob_iter(cfg, "core.*", 3); + check_glob_iter(cfg, "remote\\.ab.*", 2); + check_glob_iter(cfg, ".*url$", 2); + check_glob_iter(cfg, ".*dummy.*", 2); + check_glob_iter(cfg, ".*nomatch.*", 0); + + git_config_free(cfg); +} + void test_config_read__whitespace_not_required_around_assignment(void) { git_config *cfg; From d8488b981c1410dd2de2f9fe99764bdae33ca607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 9 Aug 2013 10:37:35 +0200 Subject: [PATCH 217/367] config: implement _foreach and _foreach_match on top of the iterator directly Use a glob iterator instead of going through git_config_backend_foreach_match. This function is left as it's exposed in the API. --- src/config.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/config.c b/src/config.c index 9d809f8f1..3881d73dd 100644 --- a/src/config.c +++ b/src/config.c @@ -455,12 +455,12 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf } iter->parent.next = all_iter_glob_next; + iter->parent.free = all_iter_glob_free; } else { iter->parent.next = all_iter_next; + iter->parent.free = all_iter_free; } - iter->parent.free = all_iter_glob_free; - iter->i = cfg->files.length; iter->cfg = cfg; @@ -527,18 +527,27 @@ int git_config_foreach_match( git_config_foreach_cb cb, void *payload) { - int ret = 0; - size_t i; - file_internal *internal; - git_config_backend *file; + int error; + git_config_iterator *iter; + git_config_entry *entry; - for (i = 0; i < cfg->files.length && ret == 0; ++i) { - internal = git_vector_get(&cfg->files, i); - file = internal->file; - ret = git_config_backend_foreach_match(file, regexp, cb, payload); + if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0) + return error; + + while ((error = git_config_next(&entry, iter)) == 0) { + if(cb(entry, payload)) { + giterr_clear(); + error = GIT_EUSER; + break; + } } - return ret; + git_config_iterator_free(iter); + + if (error == GIT_ITEROVER) + error = 0; + + return error; } /************** From d8289b9fb416cfe4f83eeb38fe324c077bada3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 9 Aug 2013 11:03:13 +0200 Subject: [PATCH 218/367] config: handle empty backends when iterating --- src/config.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index 3881d73dd..061765ac0 100644 --- a/src/config.c +++ b/src/config.c @@ -375,7 +375,12 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) if (error < 0) return error; - return iter->current->next(entry, iter->current); + error = iter->current->next(entry, iter->current); + /* If this backend is empty, then keep going */ + if (error == GIT_ITEROVER) + continue; + + return error; } while(1); From 86c02614608eb7e9e78c09cdec69aeba689edae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 9 Aug 2013 11:05:02 +0200 Subject: [PATCH 219/367] config: deduplicate iterator creation When the glob iterator is passed NULL regexp, call the non-globbing iterator so we don't have to special-case which functions to call. --- src/config.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/config.c b/src/config.c index 061765ac0..ae4e4816a 100644 --- a/src/config.c +++ b/src/config.c @@ -449,23 +449,20 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf all_iter *iter; int result; + if (regexp == NULL) + return git_config_iterator_new(out, cfg); + iter = git__calloc(1, sizeof(all_iter)); GITERR_CHECK_ALLOC(iter); - if (regexp != NULL) { - if ((result = regcomp(&iter->regex, regexp, REG_EXTENDED)) < 0) { - giterr_set_regex(&iter->regex, result); - regfree(&iter->regex); - return -1; - } - - iter->parent.next = all_iter_glob_next; - iter->parent.free = all_iter_glob_free; - } else { - iter->parent.next = all_iter_next; - iter->parent.free = all_iter_free; + if ((result = regcomp(&iter->regex, regexp, REG_EXTENDED)) < 0) { + giterr_set_regex(&iter->regex, result); + regfree(&iter->regex); + return -1; } + iter->parent.next = all_iter_glob_next; + iter->parent.free = all_iter_glob_free; iter->i = cfg->files.length; iter->cfg = cfg; From 43e5dda70249ede020a57f3888356a0dcb96da48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 12 Aug 2013 11:21:57 +0200 Subject: [PATCH 220/367] config: get rid of a useless asignment --- src/config_file.c | 4 +--- src/strmap.c | 2 -- src/strmap.h | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/config_file.c b/src/config_file.c index 4e564a51d..7c22be424 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -268,12 +268,10 @@ static int config_iterator_next( diskfile_backend *b = (diskfile_backend *) it->parent.backend; int err = 0; cvar_t * var; - const char* key; if (it->next_var == NULL) { - err = git_strmap_next(&key, (void**) &var, &(it->iter), b->values); + err = git_strmap_next((void**) &var, &(it->iter), b->values); } else { - key = it->next_var->entry->name; var = it->next_var; } diff --git a/src/strmap.c b/src/strmap.c index 1b07359d1..b26a13d1f 100644 --- a/src/strmap.c +++ b/src/strmap.c @@ -8,7 +8,6 @@ #include "strmap.h" int git_strmap_next( - const char **key, void **data, git_strmap_iter* iter, git_strmap *map) @@ -22,7 +21,6 @@ int git_strmap_next( continue; } - *key = git_strmap_key(map, *iter); *data = git_strmap_value_at(map, *iter); ++(*iter); diff --git a/src/strmap.h b/src/strmap.h index cb079b500..8276ab468 100644 --- a/src/strmap.h +++ b/src/strmap.h @@ -68,7 +68,6 @@ typedef khiter_t git_strmap_iter; #define git_strmap_end kh_end int git_strmap_next( - const char **key, void **data, git_strmap_iter* iter, git_strmap *map); From e54cfb9b544eeac2924f45fc7752c2a7be709a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 12 Aug 2013 11:50:27 +0200 Subject: [PATCH 221/367] odb: free object data when id is ambiguous By the time we recognise this as an ambiguous id, the object's data has been loaded into memory. Free it when returning EABMIGUOUS. --- src/odb.c | 4 +++- tests-clar/odb/mixed.c | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/odb.c b/src/odb.c index 23eb4e12e..6969cf772 100644 --- a/src/odb.c +++ b/src/odb.c @@ -786,8 +786,10 @@ attempt_lookup: git__free(data); data = raw.data; - if (found && git_oid__cmp(&full_oid, &found_full_oid)) + if (found && git_oid__cmp(&full_oid, &found_full_oid)) { + git__free(raw.data); return git_odb__error_ambiguous("multiple matches for prefix"); + } found_full_oid = full_oid; found = true; diff --git a/tests-clar/odb/mixed.c b/tests-clar/odb/mixed.c index dd4587831..51970ceec 100644 --- a/tests-clar/odb/mixed.c +++ b/tests-clar/odb/mixed.c @@ -22,6 +22,7 @@ void test_odb_mixed__dup_oid(void) { cl_git_pass(git_oid_fromstr(&oid, hex)); cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, GIT_OID_HEXSZ)); + git_odb_object_free(obj); cl_git_pass(git_oid_fromstrn(&oid, short_hex, sizeof(short_hex) - 1)); cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, sizeof(short_hex) - 1)); git_odb_object_free(obj); From 7affc2f7dec48dc886e9838b102bdd42d06b1d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 11 Aug 2013 23:30:47 +0200 Subject: [PATCH 222/367] Include username in each credential type Key-based authentication also needs an username, so include it in each one. Also stop assuming a default username of "git" in the ssh transport which has no business making such a decision. --- include/git2/transport.h | 16 +++++++++++++++- src/transports/cred.c | 39 +++++++++++++++++++++++++++++++++++++++ src/transports/ssh.c | 22 ++++++++++++---------- 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/include/git2/transport.h b/include/git2/transport.h index 1cc200eb4..e61b10423 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -59,6 +59,7 @@ typedef int (*git_cred_sign_callback)(void *, ...); /* A ssh key file and passphrase */ typedef struct git_cred_ssh_keyfile_passphrase { git_cred parent; + char *username; char *publickey; char *privatekey; char *passphrase; @@ -67,12 +68,21 @@ typedef struct git_cred_ssh_keyfile_passphrase { /* A ssh public key and authentication callback */ typedef struct git_cred_ssh_publickey { git_cred parent; + char *username; char *publickey; - size_t publickey_len; + size_t publickey_len; void *sign_callback; void *sign_data; } git_cred_ssh_publickey; +/** + * Check whether a credential object contains username information. + * + * @param cred object to check + * @return 1 if the credential object has non-NULL username, 0 otherwise + */ +GIT_EXTERN(int) git_cred_has_username(git_cred *cred); + /** * Creates a new plain-text username and password credential object. * The supplied credential parameter will be internally duplicated. @@ -92,6 +102,7 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new( * The supplied credential parameter will be internally duplicated. * * @param out The newly created credential object. + * @param username username to use to authenticate * @param publickey The path to the public key of the credential. * @param privatekey The path to the private key of the credential. * @param passphrase The passphrase of the credential. @@ -99,6 +110,7 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new( */ GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new( git_cred **out, + const char *username, const char *publickey, const char *privatekey, const char *passphrase); @@ -108,6 +120,7 @@ GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new( * The supplied credential parameter will be internally duplicated. * * @param out The newly created credential object. + * @param username username to use to authenticate * @param publickey The bytes of the public key. * @param publickey_len The length of the public key in bytes. * @param sign_fn The callback method for authenticating. @@ -116,6 +129,7 @@ GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new( */ GIT_EXTERN(int) git_cred_ssh_publickey_new( git_cred **out, + const char *username, const char *publickey, size_t publickey_len, git_cred_sign_callback sign_fn, diff --git a/src/transports/cred.c b/src/transports/cred.c index a6727e902..35aaf4f91 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -9,6 +9,31 @@ #include "smart.h" #include "git2/cred_helpers.h" +int git_cred_has_username(git_cred *cred) +{ + int ret = 0; + + switch (cred->credtype) { + case GIT_CREDTYPE_USERPASS_PLAINTEXT: { + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + ret = !!c->username; + break; + } + case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: { + git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; + ret = !!c->username; + break; + } + case GIT_CREDTYPE_SSH_PUBLICKEY: { + git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; + ret = !!c->username; + break; + } + } + + return ret; +} + static void plaintext_free(struct git_cred *cred) { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; @@ -64,6 +89,7 @@ static void ssh_keyfile_passphrase_free(struct git_cred *cred) git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; + git__free(c->username); git__free(c->publickey); git__free(c->privatekey); @@ -82,6 +108,7 @@ static void ssh_publickey_free(struct git_cred *cred) { git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; + git__free(c->username); git__free(c->publickey); git__memzero(c, sizeof(*c)); @@ -90,6 +117,7 @@ static void ssh_publickey_free(struct git_cred *cred) int git_cred_ssh_keyfile_passphrase_new( git_cred **cred, + const char *username, const char *publickey, const char *privatekey, const char *passphrase) @@ -104,6 +132,11 @@ int git_cred_ssh_keyfile_passphrase_new( c->parent.credtype = GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE; c->parent.free = ssh_keyfile_passphrase_free; + if (username) { + c->username = git__strdup(username); + GITERR_CHECK_ALLOC(c->username); + } + c->privatekey = git__strdup(privatekey); GITERR_CHECK_ALLOC(c->privatekey); @@ -123,6 +156,7 @@ int git_cred_ssh_keyfile_passphrase_new( int git_cred_ssh_publickey_new( git_cred **cred, + const char *username, const char *publickey, size_t publickey_len, git_cred_sign_callback sign_callback, @@ -138,6 +172,11 @@ int git_cred_ssh_publickey_new( c->parent.credtype = GIT_CREDTYPE_SSH_PUBLICKEY; c->parent.free = ssh_publickey_free; + if (username) { + c->username = git__strdup(username); + GITERR_CHECK_ALLOC(c->username); + } + if (publickey_len > 0) { c->publickey = git__malloc(publickey_len); GITERR_CHECK_ALLOC(c->publickey); diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 7fb53bc3c..1258a8e68 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -17,7 +17,6 @@ #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) static const char prefix_ssh[] = "ssh://"; -static const char default_user[] = "git"; static const char cmd_uploadpack[] = "git-upload-pack"; static const char cmd_receivepack[] = "git-receive-pack"; @@ -214,11 +213,10 @@ static int git_ssh_extract_url_parts( if (at) { start = at+1; *username = git__substrdup(url, at - url); + GITERR_CHECK_ALLOC(*username); } else { - start = url; - *username = git__strdup(default_user); + *username = NULL; } - GITERR_CHECK_ALLOC(*username); *host = git__substrdup(start, colon - start); GITERR_CHECK_ALLOC(*host); @@ -237,19 +235,23 @@ static int _git_ssh_authenticate_session( switch (cred->credtype) { case GIT_CREDTYPE_USERPASS_PLAINTEXT: { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; - rc = libssh2_userauth_password(session, c->username, c->password); + user = c->username ? c->username : user; + rc = libssh2_userauth_password(session, user, c->password); break; } case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: { git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; + user = c->username ? c->username : user; rc = libssh2_userauth_publickey_fromfile( - session, user, c->publickey, c->privatekey, c->passphrase); + session, c->username, c->publickey, c->privatekey, c->passphrase); break; } case GIT_CREDTYPE_SSH_PUBLICKEY: { git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; + + user = c->username ? c->username : user; rc = libssh2_userauth_publickey( - session, user, (const unsigned char *)c->publickey, + session, c->username, (const unsigned char *)c->publickey, c->publickey_len, c->sign_callback, &c->sign_data); break; } @@ -351,9 +353,9 @@ static int _git_ssh_setup_conn( } assert(t->cred); - if (!user) { - user = git__strdup(default_user); - GITERR_CHECK_ALLOC(user); + if (!user && !git_cred_has_username(t->cred)) { + giterr_set_str(GITERR_NET, "Cannot authenticate without a username"); + goto on_error; } if (_git_ssh_session_create(&session, s->socket) < 0) From d10de8bd8dff0ebbb7d6684f5a7b9d3e1ec04667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 12 Aug 2013 12:07:33 +0200 Subject: [PATCH 223/367] CMake: finding libssh2 should be idempotent With the current code, running 'cmake .' in an already-configured directory causes the removal of ssh flags passed to the compiler, making it impossible to build with ssh support but by removing CMake's cache. Remove the check for LIBSSH2_LIBRARY and let CMake do the right thing wrt finding the library. --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 53f568ed3..1500a3a68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,15 +148,14 @@ ELSE() FILE(GLOB SRC_ZLIB deps/zlib/*.c deps/zlib/*.h) ENDIF() -IF(NOT LIBSSH2_LIBRARY) - FIND_PACKAGE(LIBSSH2 QUIET) -ENDIF() +FIND_PACKAGE(LIBSSH2 QUIET) IF (LIBSSH2_FOUND) ADD_DEFINITIONS(-DGIT_SSH) INCLUDE_DIRECTORIES(${LIBSSH2_INCLUDE_DIR}) SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES}) ENDIF() + # Platform specific compilation flags IF (MSVC) From 423e3b0c4838537608bede2c166abd3b589dd23c Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Mon, 12 Aug 2013 11:02:53 -0700 Subject: [PATCH 224/367] Update to clar 7bf638b80 --- tests-clar/clar.c | 2 +- tests-clar/clar/sandbox.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-clar/clar.c b/tests-clar/clar.c index fb10dd397..585af8a74 100644 --- a/tests-clar/clar.c +++ b/tests-clar/clar.c @@ -36,7 +36,7 @@ # define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) # define W_OK 02 # define S_ISDIR(x) ((x & _S_IFDIR) != 0) -# define snprint_eq(buf,sz,fmt,a,b) _snprintf_s(buf,sz,_TRUNCATE,fmt,a,b) +# define snprint_eq(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) # else # define snprint_eq snprintf # endif diff --git a/tests-clar/clar/sandbox.h b/tests-clar/clar/sandbox.h index 5622bfab7..1ca6fcae8 100644 --- a/tests-clar/clar/sandbox.h +++ b/tests-clar/clar/sandbox.h @@ -45,7 +45,7 @@ find_tmp_path(char *buffer, size_t length) #else DWORD env_len; - if ((env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length)) > 0 && + if ((env_len = GetEnvironmentVariable("CLAR_TMP", buffer, length)) > 0 && env_len < length) return 0; From abf3732728d0af42ed7217c7148509c8aa30a7e5 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 13 Aug 2013 09:15:39 +0200 Subject: [PATCH 225/367] windows: Path conversion with better semantics --- src/fileops.c | 4 +-- src/path.c | 4 +-- src/transports/winhttp.c | 4 +-- src/win32/dir.c | 10 ++++---- src/win32/dir.h | 2 +- src/win32/findfile.c | 12 ++++----- src/win32/posix_w32.c | 54 +++++++++++++++++++-------------------- src/win32/utf-conv.h | 7 +++-- tests-clar/clar_libgit2.c | 22 ++++++++-------- 9 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index d62158db7..cf77ad164 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -58,9 +58,9 @@ int git_futils_creat_locked(const char *path, const mode_t mode) int fd; #ifdef GIT_WIN32 - git_win32_path_utf16 buf; + git_win32_path buf; - git__win32_path_utf8_to_16(buf, path); + git__win32_path_from_c(buf, path); fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY | O_CLOEXEC, mode); #else diff --git a/src/path.c b/src/path.c index d0cecc070..cf9a3776a 100644 --- a/src/path.c +++ b/src/path.c @@ -486,14 +486,14 @@ bool git_path_is_empty_dir(const char *path) { git_buf pathbuf = GIT_BUF_INIT; HANDLE hFind = INVALID_HANDLE_VALUE; - git_win32_path_utf16 wbuf; + git_win32_path wbuf; WIN32_FIND_DATAW ffd; bool retval = true; if (!git_path_isdir(path)) return false; git_buf_printf(&pathbuf, "%s\\*", path); - git__win32_path_utf8_to_16(wbuf, git_buf_cstr(&pathbuf)); + git__win32_path_from_c(wbuf, git_buf_cstr(&pathbuf)); hFind = FindFirstFileW(wbuf, &ffd); if (INVALID_HANDLE_VALUE == hFind) { diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 5c55c10f1..6f182c94c 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -893,7 +893,7 @@ static int winhttp_connect( const char *url) { wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; - git_win32_path_utf16 host; + git_win32_path host; int32_t port; const char *default_port = "80"; int ret; @@ -920,7 +920,7 @@ static int winhttp_connect( return -1; /* Prepare host */ - git__win32_path_utf8_to_16(host, t->host); + git__win32_path_from_c(host, t->host); /* Establish session */ t->session = WinHttpOpen( diff --git a/src/win32/dir.c b/src/win32/dir.c index 85f793d62..f75b8d57e 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -26,7 +26,7 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { char filter[GIT_WIN_PATH_UTF8]; - git_win32_path_utf16 filter_w; + git_win32_path filter_w; git__DIR *new = NULL; if (!dir || !init_filter(filter, sizeof(filter), dir)) @@ -40,7 +40,7 @@ git__DIR *git__opendir(const char *dir) if (!new->dir) goto fail; - git__win32_path_utf8_to_16(filter_w, filter); + git__win32_path_from_c(filter_w, filter); new->h = FindFirstFileW(filter_w, &new->f); if (new->h == INVALID_HANDLE_VALUE) { @@ -80,7 +80,7 @@ int git__readdir_ext( if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) return -1; - git__win32_path_utf16_to_8(entry->d_name, d->f.cFileName); + git__win32_path_from_c(entry->d_name, d->f.cFileName); entry->d_ino = 0; *result = entry; @@ -102,7 +102,7 @@ struct git__dirent *git__readdir(git__DIR *d) void git__rewinddir(git__DIR *d) { char filter[GIT_WIN_PATH_UTF8]; - git_win32_path_utf16 filter_w; + git_win32_path filter_w; if (!d) return; @@ -116,7 +116,7 @@ void git__rewinddir(git__DIR *d) if (!init_filter(filter, sizeof(filter), d->dir)) return; - git__win32_path_utf8_to_16(filter_w, filter); + git__win32_path_from_c(filter_w, filter); d->h = FindFirstFileW(filter_w, &d->f); if (d->h == INVALID_HANDLE_VALUE) diff --git a/src/win32/dir.h b/src/win32/dir.h index a3e154972..3875ab159 100644 --- a/src/win32/dir.h +++ b/src/win32/dir.h @@ -11,7 +11,7 @@ struct git__dirent { int d_ino; - char d_name[260*4+1]; + char d_name[GIT_WIN_PATH_UTF8]; }; typedef struct { diff --git a/src/win32/findfile.c b/src/win32/findfile.c index 64b9bb537..a1c11fcfb 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -23,11 +23,11 @@ int git_win32__expand_path(struct git_win32__path *s_root, const wchar_t *templ) return s_root->len ? 0 : -1; } -static int win32_path_utf16_to_8(git_buf *path_utf8, const wchar_t *path_utf16) +static int win32_path_to_8(git_buf *path_utf8, const wchar_t *path) { char temp_utf8[GIT_PATH_MAX]; - git__utf16_to_8(temp_utf8, GIT_PATH_MAX, path_utf16); + git__utf16_to_8(temp_utf8, GIT_PATH_MAX, path); git_path_mkposix(temp_utf8); return git_buf_sets(path_utf8, temp_utf8); @@ -61,7 +61,7 @@ int git_win32__find_file( return GIT_ENOTFOUND; } - win32_path_utf16_to_8(path, file_utf16); + win32_path_to_8(path, file_utf16); git__free(file_utf16); return 0; @@ -113,7 +113,7 @@ static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe) /* replace "bin\\" or "cmd\\" with "etc\\" */ wcscpy(&root.path[root.len - 4], L"etc\\"); - win32_path_utf16_to_8(buf, root.path); + win32_path_to_8(buf, root.path); return 0; } } @@ -146,7 +146,7 @@ static int win32_find_git_in_registry( wcscat(path16.path, L"etc\\"); path16.len += 4; - win32_path_utf16_to_8(buf, path16.path); + win32_path_to_8(buf, path16.path); } RegCloseKey(hKey); @@ -168,7 +168,7 @@ static int win32_find_existing_dirs( path16.path[0] != L'%' && !_waccess(path16.path, F_OK)) { - win32_path_utf16_to_8(&buf, path16.path); + win32_path_to_8(&buf, path16.path); if (buf.size) git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index d9a68f284..437ded284 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -16,8 +16,8 @@ int p_unlink(const char *path) { - git_win32_path_utf16 buf; - git__win32_path_utf8_to_16(buf, path); + git_win32_path buf; + git__win32_path_from_c(buf, path); _wchmod(buf, 0666); return _wunlink(buf); } @@ -59,11 +59,11 @@ static int do_lstat( const char *file_name, struct stat *buf, int posix_enotdir) { WIN32_FILE_ATTRIBUTE_DATA fdata; - git_win32_path_utf16 fbuf; + git_win32_path fbuf; wchar_t lastch; int flen; - flen = git__win32_path_utf8_to_16(fbuf, file_name); + flen = git__win32_path_from_c(fbuf, file_name); /* truncate trailing slashes */ for (; flen > 0; --flen) { @@ -166,7 +166,7 @@ int p_readlink(const char *link, char *target, size_t target_len) static fpath_func pGetFinalPath = NULL; HANDLE hFile; DWORD dwRet; - git_win32_path_utf16 link_w; + git_win32_path link_w; wchar_t* target_w; int error = 0; @@ -189,7 +189,7 @@ int p_readlink(const char *link, char *target, size_t target_len) } } - git__win32_path_utf8_to_16(link_w, link); + git__win32_path_from_c(link_w, link); hFile = CreateFileW(link_w, // file to open GENERIC_READ, // open for reading @@ -255,10 +255,10 @@ int p_symlink(const char *old, const char *new) int p_open(const char *path, int flags, ...) { - git_win32_path_utf16 buf; + git_win32_path buf; mode_t mode = 0; - git__win32_path_utf8_to_16(buf, path); + git__win32_path_from_c(buf, path); if (flags & O_CREAT) { va_list arg_list; @@ -273,8 +273,8 @@ int p_open(const char *path, int flags, ...) int p_creat(const char *path, mode_t mode) { - git_win32_path_utf16 buf; - git__win32_path_utf8_to_16(buf, path); + git_win32_path buf; + git__win32_path_from_c(buf, path); return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } @@ -316,23 +316,23 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { - git_win32_path_utf16 buf; - git__win32_path_utf8_to_16(buf, path); + git_win32_path buf; + git__win32_path_from_c(buf, path); return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { - git_win32_path_utf16 buf; - git__win32_path_utf8_to_16(buf, path); + git_win32_path buf; + git__win32_path_from_c(buf, path); return _wchmod(buf, mode); } int p_rmdir(const char* path) { int error; - git_win32_path_utf16 buf; - git__win32_path_utf8_to_16(buf, path); + git_win32_path buf; + git__win32_path_from_c(buf, path); error = _wrmdir(buf); @@ -348,18 +348,18 @@ int p_rmdir(const char* path) int p_hide_directory__w32(const char *path) { - git_win32_path_utf16 buf; - git__win32_path_utf8_to_16(buf, path); + git_win32_path buf; + git__win32_path_from_c(buf, path); return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; } char *p_realpath(const char *orig_path, char *buffer) { int ret; - git_win32_path_utf16 orig_path_w; - git_win32_path_utf16 buffer_w; + git_win32_path orig_path_w; + git_win32_path buffer_w; - git__win32_path_utf8_to_16(orig_path_w, orig_path); + git__win32_path_from_c(orig_path_w, orig_path); /* Implicitly use GetCurrentDirectory which can be a threading issue */ ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL); @@ -449,18 +449,18 @@ int p_setenv(const char* name, const char* value, int overwrite) int p_access(const char* path, mode_t mode) { - git_win32_path_utf16 buf; - git__win32_path_utf8_to_16(buf, path); + git_win32_path buf; + git__win32_path_from_c(buf, path); return _waccess(buf, mode); } int p_rename(const char *from, const char *to) { - git_win32_path_utf16 wfrom; - git_win32_path_utf16 wto; + git_win32_path wfrom; + git_win32_path wto; - git__win32_path_utf8_to_16(wfrom, from); - git__win32_path_utf8_to_16(wto, to); + git__win32_path_from_c(wfrom, from); + git__win32_path_from_c(wto, to); return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 66422e17c..3cfb3514e 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -13,20 +13,19 @@ #define GIT_WIN_PATH_UTF16 (260 + 1) #define GIT_WIN_PATH_UTF8 (260 * 4 + 1) -typedef wchar_t git_win32_path_utf16[GIT_WIN_PATH_UTF16]; -typedef char git_win32_path_utf8[GIT_WIN_PATH_UTF8]; +typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; // dest_size is the size of dest in wchar_t's int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src); // dest_size is the size of dest in char's int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); -GIT_INLINE(int) git__win32_path_utf8_to_16(git_win32_path_utf16 dest, const char *src) +GIT_INLINE(int) git__win32_path_from_c(git_win32_path dest, const char *src) { return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src); } -GIT_INLINE(int) git__win32_path_utf16_to_8(git_win32_path_utf8 dest, const wchar_t *src) +GIT_INLINE(int) git__win32_path_to_c(char *dest, const git_win32_path *src) { return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src); } diff --git a/tests-clar/clar_libgit2.c b/tests-clar/clar_libgit2.c index 6c741accd..9e0fbfbac 100644 --- a/tests-clar/clar_libgit2.c +++ b/tests-clar/clar_libgit2.c @@ -56,12 +56,12 @@ void cl_git_rewritefile(const char *filename, const char *new_content) char *cl_getenv(const char *name) { - git_win32_path_utf16 name_utf16; + git_win32_path name_utf16; DWORD alloc_len; wchar_t *value_utf16; char *value_utf8; - git__win32_path_utf8_to_16(name_utf16, name); + git__win32_path_from_c(name_utf16, name); alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0); if (alloc_len <= 0) return NULL; @@ -72,7 +72,7 @@ char *cl_getenv(const char *name) GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len); cl_assert(value_utf8 = git__malloc(alloc_len)); - git__win32_path_utf16_to_8(value_utf8, value_utf16); + git__utf16_to_8(value_utf8, alloc_len, value_utf16); git__free(value_utf16); @@ -81,13 +81,13 @@ char *cl_getenv(const char *name) int cl_setenv(const char *name, const char *value) { - git_win32_path_utf16 name_utf16; - git_win32_path_utf16 value_utf16; + git_win32_path name_utf16; + git_win32_path value_utf16; - git__win32_path_utf8_to_16(name_utf16, name); + git__win32_path_from_c(name_utf16, name); if (value) { - git__win32_path_utf8_to_16(value_utf16, value); + git__win32_path_from_c(value_utf16, value); cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16)); } else { /* Windows XP returns 0 (failed) when passing NULL for lpValue when @@ -107,12 +107,12 @@ int cl_setenv(const char *name, const char *value) * the source is a directory, a child of the source). */ int cl_rename(const char *source, const char *dest) { - git_win32_path_utf16 source_utf16; - git_win32_path_utf16 dest_utf16; + git_win32_path source_utf16; + git_win32_path dest_utf16; unsigned retries = 1; - git__win32_path_utf8_to_16(source_utf16, source); - git__win32_path_utf8_to_16(dest_utf16, dest); + git__win32_path_from_c(source_utf16, source); + git__win32_path_from_c(dest_utf16, dest); while (!MoveFileW(source_utf16, dest_utf16)) { /* Only retry if the error is ERROR_ACCESS_DENIED; From 53d712dcb9d74979a3c678336c97e2f7564aadbe Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 13 Aug 2013 09:31:03 +0200 Subject: [PATCH 226/367] windows: Missing renames. --- src/win32/posix.h | 4 ++-- src/win32/utf-conv.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/win32/posix.h b/src/win32/posix.h index 7803c9c84..0d5effe08 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -20,9 +20,9 @@ GIT_INLINE(int) p_link(const char *old, const char *new) GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) { - git_win32_path_utf16 buf; + git_win32_path buf; GIT_UNUSED(mode); - git__win32_path_utf8_to_16(buf, path); + git__win32_path_from_c(buf, path); return _wmkdir(buf); } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 3cfb3514e..967db859e 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -25,7 +25,7 @@ GIT_INLINE(int) git__win32_path_from_c(git_win32_path dest, const char *src) return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src); } -GIT_INLINE(int) git__win32_path_to_c(char *dest, const git_win32_path *src) +GIT_INLINE(int) git__win32_path_to_c(char *dest, const git_win32_path src) { return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src); } From 345b6307be76e6f48e255f698d057c7ac5f9679d Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 13 Aug 2013 09:35:07 +0200 Subject: [PATCH 227/367] windows: Require order --- src/path.c | 1 - src/win32/dir.c | 3 +-- src/win32/posix.h | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/path.c b/src/path.c index cf9a3776a..c9716ef6a 100644 --- a/src/path.c +++ b/src/path.c @@ -8,7 +8,6 @@ #include "path.h" #include "posix.h" #ifdef GIT_WIN32 -#include "win32/dir.h" #include "win32/posix.h" #else #include diff --git a/src/win32/dir.c b/src/win32/dir.c index f75b8d57e..cd3c7de51 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -5,8 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ #define GIT__WIN32_NO_WRAP_DIR -#include "dir.h" -#include "utf-conv.h" +#include "posix.h" static int init_filter(char *filter, size_t n, const char *dir) { diff --git a/src/win32/posix.h b/src/win32/posix.h index 0d5effe08..b573f49af 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -9,6 +9,7 @@ #include "common.h" #include "utf-conv.h" +#include "dir.h" GIT_INLINE(int) p_link(const char *old, const char *new) { From 3948e86240e401adebe0d683709ddc32ecaf7f96 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 13 Aug 2013 09:38:37 +0200 Subject: [PATCH 228/367] windows: Fuck me --- src/win32/dir.c | 2 +- src/win32/posix.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/win32/dir.c b/src/win32/dir.c index cd3c7de51..65472dc80 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -79,7 +79,7 @@ int git__readdir_ext( if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) return -1; - git__win32_path_from_c(entry->d_name, d->f.cFileName); + git__win32_path_to_c(entry->d_name, d->f.cFileName); entry->d_ino = 0; *result = entry; diff --git a/src/win32/posix.h b/src/win32/posix.h index b573f49af..962877166 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -8,6 +8,7 @@ #define INCLUDE_posix__w32_h__ #include "common.h" +#include "../posix.h" #include "utf-conv.h" #include "dir.h" From 3869a171dd78e52436bcb779a04b910454932e55 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Thu, 8 Aug 2013 10:10:23 -0700 Subject: [PATCH 229/367] Fix mingw cross-compile build --- src/win32/utf-conv.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 967db859e..835dc75a3 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -6,6 +6,7 @@ */ #include +#include "common.h" #ifndef INCLUDE_git_utfconv_h__ #define INCLUDE_git_utfconv_h__ From e12618b1ecea0eda5bc23662c78b678ca5a27013 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Tue, 13 Aug 2013 09:22:53 -0700 Subject: [PATCH 230/367] Add some things to precompiled header --- src/win32/precompiled.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/win32/precompiled.h b/src/win32/precompiled.h index 5de7e6f34..cbfe98812 100644 --- a/src/win32/precompiled.h +++ b/src/win32/precompiled.h @@ -1,4 +1,5 @@ #include "git2.h" +#include "common.h" #include #include @@ -6,6 +7,8 @@ #include #include #include +#include +#include #include #include From d4cff0cb1caac8c2c2cabbab9d3301f99b5ee230 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 13 Aug 2013 09:40:32 -0700 Subject: [PATCH 231/367] Rename git__win32_path fns to git_win32_path --- src/fileops.c | 2 +- src/path.c | 2 +- src/transports/winhttp.c | 4 ++-- src/win32/dir.c | 6 +++--- src/win32/posix.h | 2 +- src/win32/posix_w32.c | 26 +++++++++++++------------- src/win32/utf-conv.h | 11 +++++++---- tests-clar/clar_libgit2.c | 10 +++++----- 8 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index cf77ad164..aea8075d5 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -60,7 +60,7 @@ int git_futils_creat_locked(const char *path, const mode_t mode) #ifdef GIT_WIN32 git_win32_path buf; - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY | O_CLOEXEC, mode); #else diff --git a/src/path.c b/src/path.c index c9716ef6a..9a4b8c413 100644 --- a/src/path.c +++ b/src/path.c @@ -492,7 +492,7 @@ bool git_path_is_empty_dir(const char *path) if (!git_path_isdir(path)) return false; git_buf_printf(&pathbuf, "%s\\*", path); - git__win32_path_from_c(wbuf, git_buf_cstr(&pathbuf)); + git_win32_path_from_c(wbuf, git_buf_cstr(&pathbuf)); hFind = FindFirstFileW(wbuf, &ffd); if (INVALID_HANDLE_VALUE == hFind) { diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 6f182c94c..8decd8d51 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -920,7 +920,7 @@ static int winhttp_connect( return -1; /* Prepare host */ - git__win32_path_from_c(host, t->host); + git_win32_path_from_c(host, t->host); /* Establish session */ t->session = WinHttpOpen( @@ -934,7 +934,7 @@ static int winhttp_connect( giterr_set(GITERR_OS, "Failed to init WinHTTP"); return -1; } - + /* Establish connection */ t->connection = WinHttpConnect( t->session, diff --git a/src/win32/dir.c b/src/win32/dir.c index 65472dc80..0ea5124db 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -39,7 +39,7 @@ git__DIR *git__opendir(const char *dir) if (!new->dir) goto fail; - git__win32_path_from_c(filter_w, filter); + git_win32_path_from_c(filter_w, filter); new->h = FindFirstFileW(filter_w, &new->f); if (new->h == INVALID_HANDLE_VALUE) { @@ -79,7 +79,7 @@ int git__readdir_ext( if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) return -1; - git__win32_path_to_c(entry->d_name, d->f.cFileName); + git_win32_path_to_c(entry->d_name, d->f.cFileName); entry->d_ino = 0; *result = entry; @@ -115,7 +115,7 @@ void git__rewinddir(git__DIR *d) if (!init_filter(filter, sizeof(filter), d->dir)) return; - git__win32_path_from_c(filter_w, filter); + git_win32_path_from_c(filter_w, filter); d->h = FindFirstFileW(filter_w, &d->f); if (d->h == INVALID_HANDLE_VALUE) diff --git a/src/win32/posix.h b/src/win32/posix.h index 962877166..5f924a026 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -24,7 +24,7 @@ GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) { git_win32_path buf; GIT_UNUSED(mode); - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); return _wmkdir(buf); } diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 437ded284..3a626f767 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -17,7 +17,7 @@ int p_unlink(const char *path) { git_win32_path buf; - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); _wchmod(buf, 0666); return _wunlink(buf); } @@ -63,7 +63,7 @@ static int do_lstat( wchar_t lastch; int flen; - flen = git__win32_path_from_c(fbuf, file_name); + flen = git_win32_path_from_c(fbuf, file_name); /* truncate trailing slashes */ for (; flen > 0; --flen) { @@ -189,7 +189,7 @@ int p_readlink(const char *link, char *target, size_t target_len) } } - git__win32_path_from_c(link_w, link); + git_win32_path_from_c(link_w, link); hFile = CreateFileW(link_w, // file to open GENERIC_READ, // open for reading @@ -258,7 +258,7 @@ int p_open(const char *path, int flags, ...) git_win32_path buf; mode_t mode = 0; - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); if (flags & O_CREAT) { va_list arg_list; @@ -274,7 +274,7 @@ int p_open(const char *path, int flags, ...) int p_creat(const char *path, mode_t mode) { git_win32_path buf; - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } @@ -317,14 +317,14 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { git_win32_path buf; - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { git_win32_path buf; - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); return _wchmod(buf, mode); } @@ -332,7 +332,7 @@ int p_rmdir(const char* path) { int error; git_win32_path buf; - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); error = _wrmdir(buf); @@ -349,7 +349,7 @@ int p_rmdir(const char* path) int p_hide_directory__w32(const char *path) { git_win32_path buf; - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; } @@ -359,7 +359,7 @@ char *p_realpath(const char *orig_path, char *buffer) git_win32_path orig_path_w; git_win32_path buffer_w; - git__win32_path_from_c(orig_path_w, orig_path); + git_win32_path_from_c(orig_path_w, orig_path); /* Implicitly use GetCurrentDirectory which can be a threading issue */ ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL); @@ -450,7 +450,7 @@ int p_setenv(const char* name, const char* value, int overwrite) int p_access(const char* path, mode_t mode) { git_win32_path buf; - git__win32_path_from_c(buf, path); + git_win32_path_from_c(buf, path); return _waccess(buf, mode); } @@ -459,8 +459,8 @@ int p_rename(const char *from, const char *to) git_win32_path wfrom; git_win32_path wto; - git__win32_path_from_c(wfrom, from); - git__win32_path_from_c(wto, to); + git_win32_path_from_c(wfrom, from); + git_win32_path_from_c(wto, to); return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 835dc75a3..1d008dba5 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -11,22 +11,25 @@ #ifndef INCLUDE_git_utfconv_h__ #define INCLUDE_git_utfconv_h__ +/* Maximum characters in a Windows path plus one for NUL byte */ #define GIT_WIN_PATH_UTF16 (260 + 1) + +/* Maximum bytes necessary to convert a full-length UTF16 path to UTF8 */ #define GIT_WIN_PATH_UTF8 (260 * 4 + 1) typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; -// dest_size is the size of dest in wchar_t's +/* dest_size is the size of dest in wchar_t's */ int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src); -// dest_size is the size of dest in char's +/* dest_size is the size of dest in char's */ int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); -GIT_INLINE(int) git__win32_path_from_c(git_win32_path dest, const char *src) +GIT_INLINE(int) git_win32_path_from_c(git_win32_path dest, const char *src) { return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src); } -GIT_INLINE(int) git__win32_path_to_c(char *dest, const git_win32_path src) +GIT_INLINE(int) git_win32_path_to_c(char *dest, const git_win32_path src) { return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src); } diff --git a/tests-clar/clar_libgit2.c b/tests-clar/clar_libgit2.c index 9e0fbfbac..305581ec2 100644 --- a/tests-clar/clar_libgit2.c +++ b/tests-clar/clar_libgit2.c @@ -61,7 +61,7 @@ char *cl_getenv(const char *name) wchar_t *value_utf16; char *value_utf8; - git__win32_path_from_c(name_utf16, name); + git_win32_path_from_c(name_utf16, name); alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0); if (alloc_len <= 0) return NULL; @@ -84,10 +84,10 @@ int cl_setenv(const char *name, const char *value) git_win32_path name_utf16; git_win32_path value_utf16; - git__win32_path_from_c(name_utf16, name); + git_win32_path_from_c(name_utf16, name); if (value) { - git__win32_path_from_c(value_utf16, value); + git_win32_path_from_c(value_utf16, value); cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16)); } else { /* Windows XP returns 0 (failed) when passing NULL for lpValue when @@ -111,8 +111,8 @@ int cl_rename(const char *source, const char *dest) git_win32_path dest_utf16; unsigned retries = 1; - git__win32_path_from_c(source_utf16, source); - git__win32_path_from_c(dest_utf16, dest); + git_win32_path_from_c(source_utf16, source); + git_win32_path_from_c(dest_utf16, dest); while (!MoveFileW(source_utf16, dest_utf16)) { /* Only retry if the error is ERROR_ACCESS_DENIED; From 841034a35ee34190fa1cc136acccfa1a4abaed39 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 13 Aug 2013 09:45:56 -0700 Subject: [PATCH 232/367] Reintroduce type for UTF8 win32 path conversions --- src/win32/dir.c | 2 +- src/win32/dir.h | 2 +- src/win32/posix_w32.c | 8 ++++---- src/win32/utf-conv.h | 9 +++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/win32/dir.c b/src/win32/dir.c index 0ea5124db..22050e875 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -24,7 +24,7 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { - char filter[GIT_WIN_PATH_UTF8]; + git_win32_path_as_utf8 filter; git_win32_path filter_w; git__DIR *new = NULL; diff --git a/src/win32/dir.h b/src/win32/dir.h index 3875ab159..60883ffa5 100644 --- a/src/win32/dir.h +++ b/src/win32/dir.h @@ -11,7 +11,7 @@ struct git__dirent { int d_ino; - char d_name[GIT_WIN_PATH_UTF8]; + git_win32_path_as_utf8 d_name; }; typedef struct { diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 3a626f767..2d5479404 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -109,10 +109,10 @@ static int do_lstat( * the length of the path pointed to, which we expect everywhere else */ if (S_ISLNK(fMode)) { - char target[GIT_WIN_PATH_UTF8]; + git_win32_path_as_utf8 target; int readlink_result; - readlink_result = p_readlink(file_name, target, GIT_WIN_PATH_UTF8); + readlink_result = p_readlink(file_name, target, sizeof(target)); if (readlink_result == -1) return -1; @@ -300,7 +300,7 @@ int p_getcwd(char *buffer_out, size_t size) int p_stat(const char* path, struct stat* buf) { - char target[GIT_WIN_PATH_UTF8]; + git_win32_path_as_utf8 target; int error = 0; error = do_lstat(path, buf, 0); @@ -308,7 +308,7 @@ int p_stat(const char* path, struct stat* buf) /* We need not do this in a loop to unwind chains of symlinks since * p_readlink calls GetFinalPathNameByHandle which does it for us. */ if (error >= 0 && S_ISLNK(buf->st_mode) && - (error = p_readlink(path, target, GIT_WIN_PATH_UTF8)) >= 0) + (error = p_readlink(path, target, sizeof(target))) >= 0) error = do_lstat(target, buf, 0); return error; diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 1d008dba5..3af77580e 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -4,13 +4,12 @@ * 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_git_utfconv_h__ +#define INCLUDE_git_utfconv_h__ #include #include "common.h" -#ifndef INCLUDE_git_utfconv_h__ -#define INCLUDE_git_utfconv_h__ - /* Maximum characters in a Windows path plus one for NUL byte */ #define GIT_WIN_PATH_UTF16 (260 + 1) @@ -19,6 +18,8 @@ typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; +typedef char git_win32_path_as_utf8[GIT_WIN_PATH_UTF8]; + /* dest_size is the size of dest in wchar_t's */ int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src); /* dest_size is the size of dest in char's */ @@ -29,7 +30,7 @@ GIT_INLINE(int) git_win32_path_from_c(git_win32_path dest, const char *src) return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src); } -GIT_INLINE(int) git_win32_path_to_c(char *dest, const git_win32_path src) +GIT_INLINE(int) git_win32_path_to_c(git_win32_path_as_utf8 dest, const wchar_t *src) { return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src); } From ee0656012c213a9589c7a0892f3e4a11caebc664 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 13 Aug 2013 09:53:56 -0700 Subject: [PATCH 233/367] Minor win32 fixes and improvements This is just a bunch of small fixes that I noticed while looking at the UTF8 and UTF16 path stuff. It fixes a slowdown in looking for an empty directory (not exiting loop asap), makes the dir name in the git__DIR structure be a GIT_FLEX_ARRAY to save an allocation, and fixes some slightly odd assumptions in the cl_getenv helper. --- src/path.c | 2 ++ src/win32/dir.c | 21 ++++++++------------- src/win32/dir.h | 2 +- src/win32/posix_w32.c | 4 ++-- tests-clar/clar_libgit2.c | 5 +++-- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/path.c b/src/path.c index 9a4b8c413..d64a5b33e 100644 --- a/src/path.c +++ b/src/path.c @@ -497,12 +497,14 @@ bool git_path_is_empty_dir(const char *path) hFind = FindFirstFileW(wbuf, &ffd); if (INVALID_HANDLE_VALUE == hFind) { giterr_set(GITERR_OS, "Couldn't open '%s'", path); + git_buf_free(&pathbuf); return false; } do { if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) { retval = false; + break; } } while (FindNextFileW(hFind, &ffd) != 0); diff --git a/src/win32/dir.c b/src/win32/dir.c index 22050e875..b03c1d556 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -27,33 +27,29 @@ git__DIR *git__opendir(const char *dir) git_win32_path_as_utf8 filter; git_win32_path filter_w; git__DIR *new = NULL; + size_t dirlen; if (!dir || !init_filter(filter, sizeof(filter), dir)) return NULL; - new = git__calloc(1, sizeof(*new)); + dirlen = strlen(dir); + + new = git__calloc(sizeof(*new) + dirlen + 1, 1); if (!new) return NULL; - - new->dir = git__strdup(dir); - if (!new->dir) - goto fail; + memcpy(new->dir, dir, dirlen); git_win32_path_from_c(filter_w, filter); new->h = FindFirstFileW(filter_w, &new->f); if (new->h == INVALID_HANDLE_VALUE) { giterr_set(GITERR_OS, "Could not open directory '%s'", dir); - goto fail; + git__free(new); + return NULL; } new->first = 1; return new; - -fail: - git__free(new->dir); - git__free(new); - return NULL; } int git__readdir_ext( @@ -133,8 +129,7 @@ int git__closedir(git__DIR *d) FindClose(d->h); d->h = INVALID_HANDLE_VALUE; } - git__free(d->dir); - d->dir = NULL; + git__free(d); return 0; } diff --git a/src/win32/dir.h b/src/win32/dir.h index 60883ffa5..24d48f6ba 100644 --- a/src/win32/dir.h +++ b/src/win32/dir.h @@ -18,8 +18,8 @@ typedef struct { HANDLE h; WIN32_FIND_DATAW f; struct git__dirent entry; - char *dir; int first; + char dir[GIT_FLEX_ARRAY]; } git__DIR; extern git__DIR *git__opendir(const char *); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 2d5479404..9d5b8d877 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -514,10 +514,10 @@ p_gmtime_r (const time_t *timer, struct tm *result) #else #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL #endif - + #ifndef _TIMEZONE_DEFINED #define _TIMEZONE_DEFINED -struct timezone +struct timezone { int tz_minuteswest; /* minutes W of Greenwich */ int tz_dsttime; /* type of dst correction */ diff --git a/tests-clar/clar_libgit2.c b/tests-clar/clar_libgit2.c index 305581ec2..bf35a68eb 100644 --- a/tests-clar/clar_libgit2.c +++ b/tests-clar/clar_libgit2.c @@ -66,12 +66,13 @@ char *cl_getenv(const char *name) if (alloc_len <= 0) return NULL; - alloc_len = GIT_WIN_PATH_UTF8; cl_assert(value_utf16 = git__calloc(alloc_len, sizeof(wchar_t))); GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len); - cl_assert(value_utf8 = git__malloc(alloc_len)); + alloc_len = alloc_len * 4 + 1; /* worst case UTF16->UTF8 growth */ + cl_assert(value_utf8 = git__calloc(alloc_len, 1)); + git__utf16_to_8(value_utf8, alloc_len, value_utf16); git__free(value_utf16); From 0228a514294bcb9d23bf24c61199a1a3e34d4772 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 13 Aug 2013 10:20:25 -0700 Subject: [PATCH 234/367] Missed one path for path_as_utf8 type --- src/win32/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win32/dir.c b/src/win32/dir.c index b03c1d556..f7859b73f 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -96,7 +96,7 @@ struct git__dirent *git__readdir(git__DIR *d) void git__rewinddir(git__DIR *d) { - char filter[GIT_WIN_PATH_UTF8]; + git_win32_path_as_utf8 filter; git_win32_path filter_w; if (!d) From 9ccdb21155b3c9650acf58c55b2596c3503ea14d Mon Sep 17 00:00:00 2001 From: Brodie Rao Date: Tue, 13 Aug 2013 10:55:37 -0700 Subject: [PATCH 235/367] fileops: stat() before open()ing in git_futils_readbuffer_updated() This reverts refactoring done in 13224ea4aad9a1b3c9cc4c992ceaea9af623e047 that introduces a performance regression for NFS when reading files that don't exist. open() forces a cache invalidation on NFS, while stat()ing a file just uses the cache and is very quick. To give a specific example, say you have a repo with a thousand packed refs. Before this change, looking up every single one ould incur a thousand slow open() calls. With this change, it's a thousand fast stat() calls. --- src/fileops.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 7f8418d7a..e367ac244 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -147,6 +147,7 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) int git_futils_readbuffer_updated( git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated) { + int error = 0; git_file fd; struct stat st; bool changed = false; @@ -156,11 +157,15 @@ int git_futils_readbuffer_updated( if (updated != NULL) *updated = 0; - if ((fd = git_futils_open_ro(path)) < 0) - return fd; + if (p_stat(path, &st) < 0) { + error = errno; + giterr_set(GITERR_OS, "Failed to stat '%s'", path); + if (error == ENOENT || error == ENOTDIR) + return GIT_ENOTFOUND; + return -1; + } - if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { - p_close(fd); + if (S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path); return -1; } @@ -177,7 +182,6 @@ int git_futils_readbuffer_updated( changed = true; if (!changed) { - p_close(fd); return 0; } @@ -186,6 +190,9 @@ int git_futils_readbuffer_updated( if (size != NULL) *size = (size_t)st.st_size; + if ((fd = git_futils_open_ro(path)) < 0) + return fd; + if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) { p_close(fd); return -1; From af6dab7ef74cf96c165e39cafd1aac3a659a3e8a Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Tue, 13 Aug 2013 13:10:52 -0400 Subject: [PATCH 236/367] Respect GIT_SSL_NO_VERIFY and http.sslVerify --- src/remote.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/remote.c b/src/remote.c index 158f3e938..efd1d1260 100644 --- a/src/remote.c +++ b/src/remote.c @@ -81,6 +81,31 @@ static int ensure_remote_name_is_valid(const char *name) return error; } +static int get_check_cert(git_repository *repo) +{ + git_config *cfg; + const char *val; + int check_cert; + + assert(repo); + + /* Go through the possible sources for SSL verification settings, from + * most specific to least specific. */ + + /* GIT_SSL_NO_VERIFY environment variable */ + if ((val = getenv("GIT_SSL_NO_VERIFY")) && + !git_config_parse_bool(&check_cert, val)) + return !check_cert; + + /* http.sslVerify config setting */ + if (!git_repository_config__weakptr(&cfg, repo) && + !git_config_get_bool(&check_cert, cfg, "http.sslVerify")) + return check_cert; + + /* By default, we *DO* want to verify the certificate. */ + return 1; +} + static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) { git_remote *remote; @@ -94,7 +119,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n GITERR_CHECK_ALLOC(remote); remote->repo = repo; - remote->check_cert = 1; + remote->check_cert = (unsigned)get_check_cert(repo); remote->update_fetchhead = 1; if (git_vector_init(&remote->refs, 32, NULL) < 0) @@ -253,7 +278,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) GITERR_CHECK_ALLOC(remote); memset(remote, 0x0, sizeof(git_remote)); - remote->check_cert = 1; + remote->check_cert = (unsigned)get_check_cert(repo); remote->update_fetchhead = 1; remote->name = git__strdup(name); GITERR_CHECK_ALLOC(remote->name); From f4be8209afd3cc996667196a1e437aac21485691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 14 Aug 2013 00:45:05 +0200 Subject: [PATCH 237/367] config: don't special-case the multivar iterator Build it on top of the normal iterator instead, which lets use re-use a lot of code. --- include/git2/config.h | 2 +- include/git2/sys/config.h | 2 - src/config.c | 123 ++++++++++++--------- src/config.h | 3 + src/config_file.c | 201 +---------------------------------- tests-clar/config/multivar.c | 2 +- 6 files changed, 83 insertions(+), 250 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index 28216467b..f14415148 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -350,7 +350,7 @@ GIT_EXTERN(int) git_config_get_multivar_foreach(const git_config *cfg, const cha * @param regexp regular expression to filter which variables we're * interested in. Use NULL to indicate all */ -GIT_EXTERN(int) git_config_get_multivar(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); +GIT_EXTERN(int) git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); /** * Return the current entry and advance the iterator diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index e369fb8ab..7572ace51 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -58,8 +58,6 @@ struct git_config_backend { /* Open means open the file/database and parse if necessary */ int (*open)(struct git_config_backend *, git_config_level_t level); int (*get)(const struct git_config_backend *, const char *key, const git_config_entry **entry); - int (*get_multivar_foreach)(struct git_config_backend *, const char *key, const char *regexp, git_config_foreach_cb callback, void *payload); - int (*get_multivar)(git_config_iterator **, struct git_config_backend *, const char *name, const char *regexp); int (*set)(struct git_config_backend *, const char *key, const char *value); int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_backend *, const char *key); diff --git a/src/config.c b/src/config.c index ae4e4816a..c98d6a52d 100644 --- a/src/config.c +++ b/src/config.c @@ -747,7 +747,7 @@ int git_config_get_multivar_foreach( git_config_iterator *iter; git_config_entry *entry; - if ((err = git_config_get_multivar(&iter, cfg, name, regexp)) < 0) + if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0) return err; found = 0; @@ -771,92 +771,82 @@ int git_config_get_multivar_foreach( typedef struct { git_config_iterator parent; - git_config_iterator *current; + git_config_iterator *iter; char *name; - char *regexp; - const git_config *cfg; - size_t i; + regex_t regex; + int have_regex; } multivar_iter; static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter) { multivar_iter *iter = (multivar_iter *) _iter; - git_config_iterator *current = iter->current; - file_internal *internal; - git_config_backend *backend; - size_t i; int error = 0; - if (current != NULL && - (error = current->next(entry, current)) == 0) { - return 0; - } - - if (error < 0 && error != GIT_ITEROVER) - return error; - - do { - if (find_next_backend(&i, iter->cfg, iter->i) < 0) - return GIT_ITEROVER; - - internal = git_vector_get(&iter->cfg->files, i - 1); - backend = internal->file; - iter->i = i - 1; - - if (iter->current) - iter->current->free(current); - - iter->current = NULL; - error = backend->get_multivar(&iter->current, backend, iter->name, iter->regexp); - if (error == GIT_ENOTFOUND) + while ((error = iter->iter->next(entry, iter->iter)) == 0) { + if (git__strcmp(iter->name, (*entry)->name)) continue; - if (error < 0) - return error; + if (!iter->have_regex) + return 0; - return iter->current->next(entry, iter->current); + if (regexec(&iter->regex, (*entry)->value, 0, NULL, 0) == 0) + return 0; + } - } while(1); - - return GIT_ITEROVER; + return error; } void multivar_iter_free(git_config_iterator *_iter) { multivar_iter *iter = (multivar_iter *) _iter; - if (iter->current) - iter->current->free(iter->current); + iter->iter->free(iter->iter); git__free(iter->name); - git__free(iter->regexp); + regfree(&iter->regex); git__free(iter); } -int git_config_get_multivar(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) +int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) { - multivar_iter *iter; + multivar_iter *iter = NULL; + git_config_iterator *inner = NULL; + int error; + + if ((error = git_config_iterator_new(&inner, cfg)) < 0) + return error; iter = git__calloc(1, sizeof(multivar_iter)); GITERR_CHECK_ALLOC(iter); - iter->name = git__strdup(name); - GITERR_CHECK_ALLOC(iter->name); + if ((error = git_config__normalize_name(name, &iter->name)) < 0) + goto on_error; if (regexp != NULL) { - iter->regexp = git__strdup(regexp); - GITERR_CHECK_ALLOC(iter->regexp); + error = regcomp(&iter->regex, regexp, REG_EXTENDED); + if (error < 0) { + giterr_set_regex(&iter->regex, error); + error = -1; + regfree(&iter->regex); + goto on_error; + } + + iter->have_regex = 1; } + iter->iter = inner; iter->parent.free = multivar_iter_free; iter->parent.next = multivar_iter_next; - iter->i = cfg->files.length; - iter->cfg = cfg; - *out = (git_config_iterator *) iter; return 0; + +on_error: + + inner->free(inner); + git__free(iter); + return error; } int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) @@ -1125,6 +1115,41 @@ fail_parse: return -1; } +/* Take something the user gave us and make it nice for our hash function */ +int git_config__normalize_name(const char *in, char **out) +{ + char *name, *fdot, *ldot; + + assert(in && out); + + name = git__strdup(in); + GITERR_CHECK_ALLOC(name); + + fdot = strchr(name, '.'); + ldot = strrchr(name, '.'); + + if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1]) + goto invalid; + + /* Validate and downcase up to first dot and after last dot */ + if (git_config_file_normalize_section(name, fdot) < 0 || + git_config_file_normalize_section(ldot + 1, NULL) < 0) + goto invalid; + + /* If there is a middle range, make sure it doesn't have newlines */ + while (fdot < ldot) + if (*fdot++ == '\n') + goto invalid; + + *out = name; + return 0; + +invalid: + git__free(name); + giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in); + return GIT_EINVALIDSPEC; +} + struct rename_data { git_config *config; git_buf *name; diff --git a/src/config.h b/src/config.h index c5c11ae14..85db5e3e1 100644 --- a/src/config.h +++ b/src/config.h @@ -49,4 +49,7 @@ extern int git_config_rename_section( */ extern int git_config_file__ondisk(struct git_config_backend **out, const char *path); +extern int git_config__normalize_name(const char *in, char **out); + + #endif diff --git a/src/config_file.c b/src/config_file.c index 7c22be424..21dc22329 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -136,41 +136,6 @@ int git_config_file_normalize_section(char *start, char *end) return 0; } -/* Take something the user gave us and make it nice for our hash function */ -static int normalize_name(const char *in, char **out) -{ - char *name, *fdot, *ldot; - - assert(in && out); - - name = git__strdup(in); - GITERR_CHECK_ALLOC(name); - - fdot = strchr(name, '.'); - ldot = strrchr(name, '.'); - - if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1]) - goto invalid; - - /* Validate and downcase up to first dot and after last dot */ - if (git_config_file_normalize_section(name, fdot) < 0 || - git_config_file_normalize_section(ldot + 1, NULL) < 0) - goto invalid; - - /* If there is a middle range, make sure it doesn't have newlines */ - while (fdot < ldot) - if (*fdot++ == '\n') - goto invalid; - - *out = name; - return 0; - -invalid: - git__free(name); - giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in); - return GIT_EINVALIDSPEC; -} - static void free_vars(git_strmap *values) { cvar_t *var = NULL; @@ -314,7 +279,7 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val khiter_t pos; int rval, ret; - if ((rval = normalize_name(name, &key)) < 0) + if ((rval = git_config__normalize_name(name, &key)) < 0) return rval; /* @@ -397,7 +362,7 @@ static int config_get(const git_config_backend *cfg, const char *name, const git khiter_t pos; int error; - if ((error = normalize_name(name, &key)) < 0) + if ((error = git_config__normalize_name(name, &key)) < 0) return error; pos = git_strmap_lookup_index(b->values, key); @@ -412,162 +377,6 @@ static int config_get(const git_config_backend *cfg, const char *name, const git return 0; } -typedef struct { - git_config_iterator parent; - cvar_t *var; - regex_t regex; - int have_regex; -} foreach_iter; - -static void foreach_iter_free(git_config_iterator *_iter) -{ - foreach_iter *iter = (foreach_iter *) _iter; - - if (iter->have_regex) - regfree(&iter->regex); - - git__free(iter); -} - -static int foreach_iter_next(git_config_entry **out, git_config_iterator *_iter) -{ - foreach_iter *iter = (foreach_iter *) _iter; - - cvar_t* var = iter->var; - - - if (var == NULL) - return GIT_ITEROVER; - - if (!iter->have_regex) { - *out = var->entry; - iter->var = var->next; - return 0; - } - - /* For the regex case, we must loop until we find something we like */ - do { - git_config_entry *entry = var->entry; - regex_t *regex = &iter->regex;; - if (regexec(regex, entry->value, 0, NULL, 0) == 0) { - *out = entry; - iter->var = var->next; - return 0; - } - var = var->next; - } while(var != NULL); - - return GIT_ITEROVER; -} - -static int config_get_multivar(git_config_iterator **out, git_config_backend *_backend, - const char *name, const char *regexp) -{ - foreach_iter *iter; - diskfile_backend *b = (diskfile_backend *) _backend; - - char *key; - khiter_t pos; - int error = 0; - - if ((error = normalize_name(name, &key)) < 0) - return error; - - pos = git_strmap_lookup_index(b->values, key); - git__free(key); - - if (!git_strmap_valid_index(b->values, pos)) - return GIT_ENOTFOUND; - - iter = git__calloc(1, sizeof(foreach_iter)); - GITERR_CHECK_ALLOC(iter); - - iter->var = git_strmap_value_at(b->values, pos); - - if (regexp != NULL) { - int result; - - result = regcomp(&iter->regex, regexp, REG_EXTENDED); - if (result < 0) { - giterr_set_regex(&iter->regex, result); - regfree(&iter->regex); - return -1; - } - iter->have_regex = 1; - } - - iter->parent.free = foreach_iter_free; - iter->parent.next = foreach_iter_next; - - *out = (git_config_iterator *) iter; - - return 0; - } - -static int config_get_multivar_foreach( - git_config_backend *cfg, - const char *name, - const char *regex_str, - int (*fn)(const git_config_entry *, void *), - void *data) -{ - cvar_t *var; - diskfile_backend *b = (diskfile_backend *)cfg; - char *key; - khiter_t pos; - int error; - - if ((error = normalize_name(name, &key)) < 0) - return error; - - pos = git_strmap_lookup_index(b->values, key); - git__free(key); - - if (!git_strmap_valid_index(b->values, pos)) - return GIT_ENOTFOUND; - - var = git_strmap_value_at(b->values, pos); - - if (regex_str != NULL) { - regex_t regex; - int result; - - /* regex matching; build the regex */ - result = regcomp(®ex, regex_str, REG_EXTENDED); - if (result < 0) { - giterr_set_regex(®ex, result); - regfree(®ex); - return -1; - } - - /* and throw the callback only on the variables that - * match the regex */ - do { - if (regexec(®ex, var->entry->value, 0, NULL, 0) == 0) { - /* early termination by the user is not an error; - * just break and return successfully */ - if (fn(var->entry, data)) - break; - } - - var = var->next; - } while (var != NULL); - regfree(®ex); - } else { - /* no regex; go through all the variables */ - do { - /* early termination by the user is not an error; - * just break and return successfully */ - if (fn(var->entry, data) < 0) - break; - - var = var->next; - } while (var != NULL); - } - - return 0; -} - static int config_set_multivar( git_config_backend *cfg, const char *name, const char *regexp, const char *value) { @@ -581,7 +390,7 @@ static int config_set_multivar( assert(regexp); - if ((result = normalize_name(name, &key)) < 0) + if ((result = git_config__normalize_name(name, &key)) < 0) return result; pos = git_strmap_lookup_index(b->values, key); @@ -654,7 +463,7 @@ static int config_delete(git_config_backend *cfg, const char *name) int result; khiter_t pos; - if ((result = normalize_name(name, &key)) < 0) + if ((result = git_config__normalize_name(name, &key)) < 0) return result; pos = git_strmap_lookup_index(b->values, key); @@ -694,8 +503,6 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->parent.open = config_open; backend->parent.get = config_get; - backend->parent.get_multivar_foreach = config_get_multivar_foreach; - backend->parent.get_multivar = config_get_multivar; backend->parent.set = config_set; backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c index afb993c18..0d552d65e 100644 --- a/tests-clar/config/multivar.c +++ b/tests-clar/config/multivar.c @@ -76,7 +76,7 @@ static void check_get_multivar(git_config *cfg, int expected) git_config_entry *entry; int n = 0; - cl_git_pass(git_config_get_multivar(&iter, cfg, _name, NULL)); + cl_git_pass(git_config_multivar_iterator_new(&iter, cfg, _name, NULL)); while (git_config_next(&entry, iter) == 0) n++; From 67591c8cd8d55e6f3218f1a734385c845459e1ff Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Wed, 14 Aug 2013 10:28:01 +0200 Subject: [PATCH 238/367] sha1_lookup: do not use the "experimental" lookup mode --- src/pack.c | 5 ++++- src/sha1_lookup.c | 24 +++++++++++++++++++++++- src/sha1_lookup.h | 5 +++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/pack.c b/src/pack.c index d7e6a1e94..e7fb9f1ae 100644 --- a/src/pack.c +++ b/src/pack.c @@ -1110,8 +1110,11 @@ static int pack_entry_find_offset( short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); #endif - /* Use git.git lookup code */ +#ifdef GIT_USE_LOOKUP pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id); +#else + pos = sha1_position(index, stride, lo, hi, short_oid->id); +#endif if (pos >= 0) { /* An object matching exactly the oid was found */ diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index b7e66cc69..8aca86335 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -9,6 +9,7 @@ #include "sha1_lookup.h" #include "common.h" +#include "oid.h" /* * Conventional binary search loop looks like this: @@ -123,7 +124,7 @@ int sha1_entry_pos(const void *table, lov = (lov << 8) | lo_key[ofs_0+1]; kyv = (kyv << 8) | key[ofs_0+1]; } - assert(lov < hiv); + assert(lov <= hiv); if (kyv < lov) return -1 - lo; @@ -176,3 +177,24 @@ int sha1_entry_pos(const void *table, } while (lo < hi); return -((int)lo)-1; } + +int sha1_position(const void *table, + size_t stride, + unsigned lo, unsigned hi, + const unsigned char *key) +{ + do { + unsigned mi = (lo + hi) / 2; + int cmp = git_oid__cmp(table + mi * stride, (git_oid *)key); + + if (!cmp) + return mi; + + if (cmp > 0) + hi = mi; + else + lo = mi+1; + } while (lo < hi); + + return -((int)lo)-1; +} diff --git a/src/sha1_lookup.h b/src/sha1_lookup.h index 9a3537273..3799620c7 100644 --- a/src/sha1_lookup.h +++ b/src/sha1_lookup.h @@ -15,4 +15,9 @@ int sha1_entry_pos(const void *table, unsigned lo, unsigned hi, unsigned nr, const unsigned char *key); +int sha1_position(const void *table, + size_t stride, + unsigned lo, unsigned hi, + const unsigned char *key); + #endif From e2164da5eb2d76129b8dae0b5ea2f7a606324fba Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Wed, 14 Aug 2013 10:31:02 +0200 Subject: [PATCH 239/367] sha1_lookup: Hello my name is MSVC and how do I pointer --- src/sha1_lookup.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index 8aca86335..35149a18d 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -183,9 +183,11 @@ int sha1_position(const void *table, unsigned lo, unsigned hi, const unsigned char *key) { + const unsigned char *base = table; + do { unsigned mi = (lo + hi) / 2; - int cmp = git_oid__cmp(table + mi * stride, (git_oid *)key); + int cmp = git_oid__cmp((git_oid *)(base + mi * stride), (git_oid *)key); if (!cmp) return mi; From 59547ce77269e0667426327d9166aad0954bc686 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Wed, 14 Aug 2013 10:34:07 +0200 Subject: [PATCH 240/367] oid: Helper for old-school hashcmp --- src/oid.h | 23 +++++++++++++---------- src/sha1_lookup.c | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/oid.h b/src/oid.h index 077d0a4c8..cfe7ca1b2 100644 --- a/src/oid.h +++ b/src/oid.h @@ -9,17 +9,8 @@ #include "git2/oid.h" -/* - * Compare two oid structures. - * - * @param a first oid structure. - * @param b second oid structure. - * @return <0, 0, >0 if a < b, a == b, a > b. - */ -GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) +GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2) { - const unsigned char *sha1 = a->id; - const unsigned char *sha2 = b->id; int i; for (i = 0; i < GIT_OID_RAWSZ; i++, sha1++, sha2++) { @@ -30,4 +21,16 @@ GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) return 0; } +/* + * Compare two oid structures. + * + * @param a first oid structure. + * @param b second oid structure. + * @return <0, 0, >0 if a < b, a == b, a > b. + */ +GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) +{ + return git_oid__hashcmp(a->id, b->id); +} + #endif diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index 35149a18d..ce067caaa 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -187,7 +187,7 @@ int sha1_position(const void *table, do { unsigned mi = (lo + hi) / 2; - int cmp = git_oid__cmp((git_oid *)(base + mi * stride), (git_oid *)key); + int cmp = git_oid__hashcmp(base + mi * stride, key); if (!cmp) return mi; From 5be622fb563c89341a7dd5da4e65e2c225cb2c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 11 Aug 2013 01:37:44 +0200 Subject: [PATCH 241/367] Test SSH in travis Set up the ssh credentials so we are able to talk to localhost and issue git commands. Move to use a script, as the command list is getting somewhat long. While here, delay installing valgrind until we need it, as it and its dependencies are by far the largest downloads and this allows us to start compiling (and failing) faster and we only incur this cost when the test suite runs successfully. --- .travis.yml | 19 +++++-------------- CMakeLists.txt | 4 +++- script/cibuild.sh | 32 ++++++++++++++++++++++++++++++++ tests-clar/online/push.c | 10 ++++++++++ 4 files changed, 50 insertions(+), 15 deletions(-) create mode 100755 script/cibuild.sh diff --git a/.travis.yml b/.travis.yml index 0d5746f2e..71f8406fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ # Travis-CI Build for libgit2 # see travis-ci.org for details -# As CMake is not officially supported we use erlang VMs language: c compiler: @@ -18,25 +17,17 @@ matrix: - compiler: i586-mingw32msvc-gcc env: OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON" -# Make sure CMake is installed install: - - sudo apt-get update >/dev/null - - sudo apt-get -q install cmake valgrind + - sudo apt-get -qq update + - sudo apt-get -qq install cmake libssh2-1-dev openssh-client openssh-server -# Run the Build script +# Run the Build script and tests script: - - mkdir _temp - - git init --bare _temp/test.git - - git daemon --listen=localhost --export-all --enable=receive-pack --base-path=_temp _temp 2>/dev/null & - - export GITTEST_REMOTE_URL="git://localhost/test.git" - - mkdir _build - - cd _build - - cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS - - cmake --build . --target install - - ctest -V . + - script/cibuild.sh # Run Tests after_success: + - sudo apt-get -qq install valgrind - valgrind --leak-check=full --show-reachable=yes --suppressions=../libgit2_clar.supp ./libgit2_clar -ionline # Only watch the development branch diff --git a/CMakeLists.txt b/CMakeLists.txt index 1500a3a68..1c70ec2d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,7 +148,9 @@ ELSE() FILE(GLOB SRC_ZLIB deps/zlib/*.c deps/zlib/*.h) ENDIF() -FIND_PACKAGE(LIBSSH2 QUIET) +IF (NOT MINGW) + FIND_PACKAGE(LIBSSH2 QUIET) +ENDIF() IF (LIBSSH2_FOUND) ADD_DEFINITIONS(-DGIT_SSH) INCLUDE_DIRECTORIES(${LIBSSH2_INCLUDE_DIR}) diff --git a/script/cibuild.sh b/script/cibuild.sh new file mode 100755 index 000000000..722b3349d --- /dev/null +++ b/script/cibuild.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# Create a test repo which we can use for the online::push tests +mkdir $HOME/_temp +git init --bare $HOME/_temp/test.git +git daemon --listen=localhost --export-all --enable=receive-pack --base-path=$HOME/_temp $HOME/_temp 2>/dev/null & +export GITTEST_REMOTE_URL="git://localhost/test.git" + +mkdir _build +cd _build +cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS +cmake --build . --target install +ctest -V . + +# Now that we've tested the raw git protocol, let's set up ssh to we +# can do the push tests over it + +killall git-daemon +sudo start ssh +ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q +cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys +ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts + +export GITTEST_REMOTE_URL="ssh://localhost/$HOME/_temp/test.git" +export GITTEST_REMOTE_USER=$USER +export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa" +export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub" +export GITTEST_REMOTE_SSH_PASSPHRASE="" + +if [ -e ./libgit2_clar ]; then + ./libgit2_clar -sonline::push +fi diff --git a/tests-clar/online/push.c b/tests-clar/online/push.c index 5dc7974c7..8b9602b45 100644 --- a/tests-clar/online/push.c +++ b/tests-clar/online/push.c @@ -9,6 +9,10 @@ static git_repository *_repo; +static char *_remote_ssh_key; +static char *_remote_ssh_pubkey; +static char *_remote_ssh_passphrase; + static char *_remote_url; static char *_remote_user; static char *_remote_pass; @@ -42,6 +46,9 @@ static int cred_acquire_cb( *((bool*)payload) = true; + if (GIT_CREDTYPE_SSH_PUBLICKEY & allowed_types) + return git_cred_ssh_keyfile_passphrase_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase); + if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 || git_cred_userpass_plaintext_new(cred, _remote_user, _remote_pass) < 0) return -1; @@ -277,6 +284,9 @@ void test_online_push__initialize(void) _remote_url = cl_getenv("GITTEST_REMOTE_URL"); _remote_user = cl_getenv("GITTEST_REMOTE_USER"); _remote_pass = cl_getenv("GITTEST_REMOTE_PASS"); + _remote_ssh_key = cl_getenv("GITTEST_REMOTE_SSH_KEY"); + _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); + _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); _remote = NULL; if (_remote_url) { From 0b9ebb54ff9aab86bb858e1d2952efa4dcdafefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 14 Aug 2013 11:18:05 +0200 Subject: [PATCH 242/367] remote: relax the url rules Accept any value for the remote's url, including an empty string which we used to reject as invalid configuration. This is not quite what git does (although it has its own problems with such configurations) and it makes it harder to fix the issue, by not letting the user modify it. As we already need to check for a valid URL when we try to connect to the network, let that perform the check, as we don't need to do it anywhere else. --- src/remote.c | 6 ------ tests-clar/network/remote/remotes.c | 10 ++++++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/remote.c b/src/remote.c index 158f3e938..0dda196df 100644 --- a/src/remote.c +++ b/src/remote.c @@ -272,12 +272,6 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0) goto cleanup; - if (strlen(val) == 0) { - giterr_set(GITERR_INVALID, "Malformed remote '%s' - missing URL", name); - error = -1; - goto cleanup; - } - remote->repo = repo; remote->url = git__strdup(val); GITERR_CHECK_ALLOC(remote->url); diff --git a/tests-clar/network/remote/remotes.c b/tests-clar/network/remote/remotes.c index 3c4fa96fa..dec646526 100644 --- a/tests-clar/network/remote/remotes.c +++ b/tests-clar/network/remote/remotes.c @@ -361,13 +361,15 @@ void test_network_remote_remotes__tagopt(void) git_config_free(cfg); } -void test_network_remote_remotes__cannot_load_with_an_empty_url(void) +void test_network_remote_remotes__can_load_with_an_empty_url(void) { git_remote *remote = NULL; - cl_git_fail(git_remote_load(&remote, _repo, "empty-remote-url")); - cl_assert(giterr_last()->klass == GITERR_INVALID); - cl_assert_equal_p(remote, NULL); + cl_git_pass(git_remote_load(&remote, _repo, "empty-remote-url")); + + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH)); + + git_remote_free(remote); } void test_network_remote_remotes__check_structure_version(void) From c87bf86cd739c30ab430a789e9eb0dfb1b1d78b7 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 14 Aug 2013 10:58:02 -0700 Subject: [PATCH 243/367] Commit 7affc2f7 removed var initialization That commit accidentally removed the initialization of the "start" variable giving undefined results for the host extraction from the url input. --- src/transports/ssh.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 1258a8e68..e0126a8fb 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -215,6 +215,7 @@ static int git_ssh_extract_url_parts( *username = git__substrdup(url, at - url); GITERR_CHECK_ALLOC(*username); } else { + start = url; *username = NULL; } From 1e94df08dad9437164ac7727f23a51591b7c42f4 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Thu, 15 Aug 2013 00:09:46 +0200 Subject: [PATCH 244/367] sha1-lookup: This assert was correct --- src/sha1_lookup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index ce067caaa..cdcadfaa9 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -124,7 +124,7 @@ int sha1_entry_pos(const void *table, lov = (lov << 8) | lo_key[ofs_0+1]; kyv = (kyv << 8) | key[ofs_0+1]; } - assert(lov <= hiv); + assert(lov < hiv); if (kyv < lov) return -1 - lo; From 899ec41fa18a13db50ae3c3fd1c4bb4045364e6d Mon Sep 17 00:00:00 2001 From: Evan Hanson Date: Thu, 15 Aug 2013 16:25:48 +1200 Subject: [PATCH 245/367] revparse: Free left side of invalid range revspecs This fixes a small memory leak in git_revparse where early returns on errors from git_revparse_single cause a free() on the (reallocated) left side of the revspec to be skipped. --- src/revparse.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/revparse.c b/src/revparse.c index d21f08b53..2fdad0049 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -912,13 +912,9 @@ int git_revparse( rstr++; } - if ((error = git_revparse_single(&revspec->from, repo, lstr)) < 0) { - return error; - } - - if ((error = git_revparse_single(&revspec->to, repo, rstr)) < 0) { - return error; - } + error = git_revparse_single(&revspec->from, repo, lstr); + if (error == 0) + error = git_revparse_single(&revspec->to, repo, rstr); git__free((void*)lstr); } else { From 1616fa68e520dbd29b839335065599f8a2b93a6f Mon Sep 17 00:00:00 2001 From: Evan Hanson Date: Thu, 15 Aug 2013 17:25:05 +1200 Subject: [PATCH 246/367] revparse: Use more idiomatic error value test --- src/revparse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/revparse.c b/src/revparse.c index 2fdad0049..b84f0037f 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -913,7 +913,7 @@ int git_revparse( } error = git_revparse_single(&revspec->from, repo, lstr); - if (error == 0) + if (!error) error = git_revparse_single(&revspec->to, repo, rstr); git__free((void*)lstr); From 376e6c9f96ffcd572ba974c9cc4d13b4f1e31474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 15 Aug 2013 13:48:35 +0200 Subject: [PATCH 247/367] odb: wrap the stream reading and writing functions This is in preparation for moving the hashing to the frontend, which requires us to handle the incoming data before passing it to the backend's stream. --- include/git2/odb.h | 49 ++++++++++++++++++++++++++++------- src/blob.c | 14 +++++----- src/odb.c | 26 ++++++++++++++++--- src/tag.c | 6 ++--- src/transports/local.c | 6 ++--- tests-clar/object/raw/write.c | 6 ++--- 6 files changed, 78 insertions(+), 29 deletions(-) diff --git a/include/git2/odb.h b/include/git2/odb.h index b3e9a57a6..3e93a932c 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -219,16 +219,9 @@ GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size * The type and final length of the object must be specified * when opening the stream. * - * The returned stream will be of type `GIT_STREAM_WRONLY` and - * will have the following methods: - * - * - stream->write: write `n` bytes into the stream - * - stream->finalize_write: close the stream and store the object in - * the odb - * - stream->free: free the stream - * - * The streaming write won't be effective until `stream->finalize_write` - * is called and returns without an error + * The returned stream will be of type `GIT_STREAM_WRONLY`, and it + * won't be effective until `git_odb_stream_finalize_write` is called + * and returns without an error * * The stream must always be free'd or will leak memory. * @@ -242,6 +235,42 @@ GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size */ GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, size_t size, git_otype type); +/** + * Write to an odb stream + * + * @param stream the stream + * @param buffer the data to write + * @param len the buffer's length + * @return 0 if the write succeeded; error code otherwise + */ +GIT_EXTERN(int) git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len); + +/** + * Finish writing to an odb stream + * + * The object will take its final name and will be available to the + * odb. + * + * @param out pointer to store the resulting object's id + * @param stream the stream + * @return 0 on success; an error code otherwise + */ +GIT_EXTERN(int) git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream); + +/** + * Read from an odb stream + * + * Most backends don't implement streaming reads + */ +GIT_EXTERN(int) git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len); + +/** + * Free an odb stream + * + * @param stream the stream to free + */ +GIT_EXTERN(void) git_odb_stream_free(git_odb_stream *stream); + /** * Open a stream to read an object from the ODB * diff --git a/src/blob.c b/src/blob.c index 5bb51f7cf..6a289f43b 100644 --- a/src/blob.c +++ b/src/blob.c @@ -60,10 +60,10 @@ int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *b (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0) return error; - if ((error = stream->write(stream, buffer, len)) == 0) - error = stream->finalize_write(oid, stream); + if ((error = git_odb_stream_write(stream, buffer, len)) == 0) + error = git_odb_stream_finalize_write(oid, stream); - stream->free(stream); + git_odb_stream_free(stream); return error; } @@ -80,12 +80,12 @@ static int write_file_stream( return error; if ((fd = git_futils_open_ro(path)) < 0) { - stream->free(stream); + git_odb_stream_free(stream); return -1; } while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { - error = stream->write(stream, buffer, read_len); + error = git_odb_stream_write(stream, buffer, read_len); written += read_len; } @@ -97,9 +97,9 @@ static int write_file_stream( } if (!error) - error = stream->finalize_write(oid, stream); + error = git_odb_stream_finalize_write(oid, stream); - stream->free(stream); + git_odb_stream_free(stream); return error; } diff --git a/src/odb.c b/src/odb.c index 6969cf772..158159662 100644 --- a/src/odb.c +++ b/src/odb.c @@ -864,9 +864,9 @@ int git_odb_write( if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0) return error; - stream->write(stream, data, len); - error = stream->finalize_write(oid, stream); - stream->free(stream); + git_odb_stream_write(stream, data, len); + error = git_odb_stream_finalize_write(oid, stream); + git_odb_stream_free(stream); return error; } @@ -904,6 +904,26 @@ int git_odb_open_wstream( return error; } +int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) +{ + return stream->write(stream, buffer, len); +} + +int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) +{ + return stream->finalize_write(out, stream); +} + +int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len) +{ + return stream->read(stream, buffer, len); +} + +void git_odb_stream_free(git_odb_stream *stream) +{ + stream->free(stream); +} + int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) { size_t i, reads = 0; diff --git a/src/tag.c b/src/tag.c index 71f4c1eb1..31a3c8b80 100644 --- a/src/tag.c +++ b/src/tag.c @@ -366,10 +366,10 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu if (git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG) < 0) return -1; - stream->write(stream, buffer, strlen(buffer)); + git_odb_stream_write(stream, buffer, strlen(buffer)); - error = stream->finalize_write(oid, stream); - stream->free(stream); + error = git_odb_stream_finalize_write(oid, stream); + git_odb_stream_free(stream); if (error < 0) { git_buf_free(&ref_name); diff --git a/src/transports/local.c b/src/transports/local.c index a9da8146c..8a75de727 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -287,9 +287,9 @@ static int local_push_copy_object( odb_obj_size, odb_obj_type)) < 0) goto on_error; - if (odb_stream->write(odb_stream, (char *)git_odb_object_data(odb_obj), + if (git_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) { + git_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 " @@ -298,7 +298,7 @@ static int local_push_copy_object( error = -1; } - odb_stream->free(odb_stream); + git_odb_stream_free(odb_stream); on_error: git_odb_object_free(odb_obj); diff --git a/tests-clar/object/raw/write.c b/tests-clar/object/raw/write.c index 9709c0302..273f08f2c 100644 --- a/tests-clar/object/raw/write.c +++ b/tests-clar/object/raw/write.c @@ -31,9 +31,9 @@ static void streaming_write(git_oid *oid, git_odb *odb, git_rawobj *raw) int error; cl_git_pass(git_odb_open_wstream(&stream, odb, raw->len, raw->type)); - stream->write(stream, raw->data, raw->len); - error = stream->finalize_write(oid, stream); - stream->free(stream); + git_odb_stream_write(stream, raw->data, raw->len); + error = git_odb_stream_finalize_write(oid, stream); + git_odb_stream_free(stream); cl_git_pass(error); } From 8380b39a6761efa360398279083a65064d6770d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 15 Aug 2013 14:29:39 +0200 Subject: [PATCH 248/367] odb: perform the stream hashing in the frontend Hash the data as it's coming into the stream and tell the backend what its name is when finalizing the write. This makes it consistent with the way a plain git_odb_write() performs the write. --- include/git2/odb_backend.h | 3 +++ src/odb.c | 21 +++++++++++++++++++++ src/odb_loose.c | 4 +--- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index af1e3e5b9..d004238a4 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -65,10 +65,13 @@ typedef enum { GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY), } git_odb_stream_t; +typedef struct git_hash_ctx git_hash_ctx; + /** A stream to read/write from a backend */ struct git_odb_stream { git_odb_backend *backend; unsigned int mode; + git_hash_ctx *hash_ctx; int (*read)(git_odb_stream *stream, char *buffer, size_t len); int (*write)(git_odb_stream *stream, const char *buffer, size_t len); diff --git a/src/odb.c b/src/odb.c index 158159662..b7f64dfc9 100644 --- a/src/odb.c +++ b/src/odb.c @@ -871,11 +871,21 @@ int git_odb_write( return error; } +static void hash_header(git_hash_ctx *ctx, size_t size, git_otype type) +{ + char header[64]; + int hdrlen; + + hdrlen = git_odb__format_object_header(header, sizeof(header), size, type); + git_hash_update(ctx, header, hdrlen); +} + int git_odb_open_wstream( git_odb_stream **stream, git_odb *db, size_t size, git_otype type) { size_t i, writes = 0; int error = GIT_ERROR; + git_hash_ctx *ctx; assert(stream && db); @@ -901,16 +911,26 @@ int git_odb_open_wstream( if (error < 0 && !writes) error = git_odb__error_unsupported_in_backend("write object"); + ctx = git__malloc(sizeof(git_hash_ctx)); + GITERR_CHECK_ALLOC(ctx); + + + git_hash_ctx_init(ctx); + hash_header(ctx, size, type); + (*stream)->hash_ctx = ctx; + return error; } int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) { + git_hash_update(stream->hash_ctx, buffer, len); return stream->write(stream, buffer, len); } int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) { + git_hash_final(out, stream->hash_ctx); return stream->finalize_write(out, stream); } @@ -921,6 +941,7 @@ int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len) void git_odb_stream_free(git_odb_stream *stream) { + git__free(stream->hash_ctx); stream->free(stream); } diff --git a/src/odb_loose.c b/src/odb_loose.c index 76ed8e232..a0c2c8c4c 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -776,8 +776,7 @@ static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) git_buf final_path = GIT_BUF_INIT; int error = 0; - if (git_filebuf_hash(oid, &stream->fbuf) < 0 || - object_file_name(&final_path, backend, oid) < 0 || + if (object_file_name(&final_path, backend, oid) < 0 || object_mkdir(&final_path, backend) < 0) error = -1; /* @@ -848,7 +847,6 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_ if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 || git_filebuf_open(&stream->fbuf, tmp_path.ptr, - GIT_FILEBUF_HASH_CONTENTS | GIT_FILEBUF_TEMPORARY | (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0 || stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0) From d4e6cf0cd05540b373bfcb8908e4bc8f8f72c73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 15 Aug 2013 14:32:47 +0200 Subject: [PATCH 249/367] odb: remove a duplicate object header formatting function --- src/odb_loose.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/odb_loose.c b/src/odb_loose.c index a0c2c8c4c..35f53fb7d 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -809,17 +809,6 @@ static void loose_backend__stream_free(git_odb_stream *_stream) git__free(stream); } -static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) -{ - const char *type_str = git_object_type2string(obj_type); - int len = snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len); - - assert(len > 0); /* otherwise snprintf() is broken */ - assert(((size_t)len) < n); /* otherwise the caller is broken! */ - - return len+1; -} - static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type) { loose_backend *backend; @@ -833,7 +822,7 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_ backend = (loose_backend *)_backend; *stream_out = NULL; - hdrlen = format_object_header(hdr, sizeof(hdr), length, type); + hdrlen = git_odb__format_object_header(hdr, sizeof(hdr), length, type); stream = git__calloc(1, sizeof(loose_writestream)); GITERR_CHECK_ALLOC(stream); @@ -872,7 +861,7 @@ static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const v backend = (loose_backend *)_backend; /* prepare the header for the file */ - header_len = format_object_header(header, sizeof(header), len, type); + header_len = git_odb__format_object_header(header, sizeof(header), len, type); if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || git_filebuf_open(&fbuf, final_path.ptr, From 5ce6c1e917a4282455fef6c7fd2236a7fb68653a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 12 Aug 2013 16:15:36 +0200 Subject: [PATCH 250/367] push: handle tag chains correctly When dealing with a chain of tags, we need to enqueue each of them individually, which means we can't use `git_tag_peel` as that jumps over the intermediate tags. Do the peeling manually so we can look at each object and take the appropriate action. --- src/push.c | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/push.c b/src/push.c index 452d71789..eaaa46248 100644 --- a/src/push.c +++ b/src/push.c @@ -233,6 +233,37 @@ on_error: return error; } +/** + * Insert all tags until we find a non-tag object, which is returned + * in `out`. + */ +static int enqueue_tag(git_object **out, git_push *push, git_oid *id) +{ + git_object *obj = NULL, *target = NULL; + int error; + + if ((error = git_object_lookup(&obj, push->repo, id, GIT_OBJ_TAG)) < 0) + return error; + + while (git_object_type(obj) == GIT_OBJ_TAG) { + if ((error = git_packbuilder_insert(push->pb, git_object_id(obj), NULL)) < 0) + break; + + if ((error = git_tag_target(&target, (git_tag *) obj)) < 0) + break; + + git_object_free(obj); + obj = target; + } + + if (error < 0) + git_object_free(obj); + else + *out = obj; + + return error; +} + static int revwalk(git_vector *commits, git_push *push) { git_remote_head *head; @@ -265,21 +296,11 @@ static int revwalk(git_vector *commits, git_push *push) goto on_error; if (type == GIT_OBJ_TAG) { - git_tag *tag; git_object *target; - if (git_packbuilder_insert(push->pb, &spec->loid, NULL) < 0) + if ((error = enqueue_tag(&target, push, &spec->loid)) < 0) goto on_error; - if (git_tag_lookup(&tag, push->repo, &spec->loid) < 0) - goto on_error; - - if (git_tag_peel(&target, tag) < 0) { - git_tag_free(tag); - goto on_error; - } - git_tag_free(tag); - if (git_object_type(target) == GIT_OBJ_COMMIT) { if (git_revwalk_push(rw, git_object_id(target)) < 0) { git_object_free(target); From c9340df055d97ca55a3a358f7e02416671db6278 Mon Sep 17 00:00:00 2001 From: Martin Woodward Date: Fri, 16 Aug 2013 19:40:58 +0100 Subject: [PATCH 251/367] Give credit to PHP for the p_readlink function in posix_w32.c --- COPYING | 63 +++++++++++++++++++++++++++++++++++++++++++ src/win32/posix_w32.c | 9 +++++++ 2 files changed, 72 insertions(+) diff --git a/COPYING b/COPYING index d1ca4d401..f7e9f3af7 100644 --- a/COPYING +++ b/COPYING @@ -928,3 +928,66 @@ necessary. Here is a sample; alter the names: Ty Coon, President of Vice That's all there is to it! + +---------------------------------------------------------------------- + +Portions of src/win32/posix_w32.c are derrived from link_win32.c in PHP: + +-------------------------------------------------------------------- + The PHP License, version 3.01 +Copyright (c) 1999 - 2012 The PHP Group. All rights reserved. +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without +modification, is permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + 3. The name "PHP" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact group@php.net. + + 4. Products derived from this software may not be called "PHP", nor + may "PHP" appear in their name, without prior written permission + from group@php.net. You may indicate that your software works in + conjunction with PHP by saying "Foo for PHP" instead of calling + it "PHP Foo" or "phpfoo" + + 5. The PHP Group may publish revised and/or new versions of the + license from time to time. Each version will be given a + distinguishing version number. + Once covered code has been published under a particular version + of the license, you may always continue to use it under the terms + of that version. You may also choose to use such covered code + under the terms of any subsequent version of the license + published by the PHP Group. No one other than the PHP Group has + the right to modify the terms applicable to covered code created + under this License. + + 6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes PHP software, freely available from + ". + +THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND +ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP +DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------- + diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 9d5b8d877..57ebaa12e 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -160,6 +160,15 @@ int p_lstat_posixly(const char *filename, struct stat *buf) return do_lstat(filename, buf, 1); } + +/* + * Parts of the The p_readlink function are heavily inspired by the php + * readlink function in link_win32.c + * + * Copyright (c) 1999 - 2012 The PHP Group. All rights reserved. + * + * For details of the PHP license see http://www.php.net/license/3_01.txt + */ int p_readlink(const char *link, char *target, size_t target_len) { typedef DWORD (WINAPI *fpath_func)(HANDLE, LPWSTR, DWORD, DWORD); From ce23330fd636a99a25b3a6b7c81e63c424ae7d7c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 Aug 2013 14:34:51 -0700 Subject: [PATCH 252/367] Add new git_signature_default API using config This adds a new API for creating a signature that uses the config to look up "user.name" and "user.email". --- include/git2/signature.h | 13 +++++++++++++ src/signature.c | 19 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/include/git2/signature.h b/include/git2/signature.h index 00d19de66..2fa46d032 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -48,6 +48,19 @@ GIT_EXTERN(int) git_signature_new(git_signature **out, const char *name, const c */ GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const char *email); +/** + * Create a new action signature with default user and now timestamp. + * + * This looks up the user.name and user.email from the configuration and + * uses the current time as the timestamp, and creates a new signature + * based on that information. It will return GIT_ENOTFOUND if either the + * user.name or user.email are not set. + * + * @param out new signature + * @param repo repository pointer + * @return 0 on success, GIT_ENOTFOUND if config is missing, or error code + */ +GIT_EXTERN(int) git_signature_default(git_signature **out, git_repository *repo); /** * Create a copy of an existing signature. All internal strings are also diff --git a/src/signature.c b/src/signature.c index 0a34ccfaa..52ca2b375 100644 --- a/src/signature.c +++ b/src/signature.c @@ -74,7 +74,7 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema git_signature_free(p); return signature_error("Signature cannot have an empty name"); } - + p->when.time = time; p->when.offset = offset; @@ -129,6 +129,23 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema return 0; } +int git_signature_default(git_signature **out, git_repository *repo) +{ + int error; + git_config *cfg; + const char *user_name, *user_email; + + if ((error = git_repository_config(&cfg, repo)) < 0) + return error; + + if (!(error = git_config_get_string(&user_name, cfg, "user.name")) && + !(error = git_config_get_string(&user_email, cfg, "user.email"))) + error = git_signature_now(out, user_name, user_email); + + git_config_free(cfg); + return error; +} + int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender) { From 579d87c5d352232671dcfda43ec153883c01fc6f Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 Aug 2013 14:48:14 -0700 Subject: [PATCH 253/367] New test that inits repo and make commit --- tests-clar/repo/init.c | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 8cf73795f..02ea676fb 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -530,3 +530,52 @@ void test_repo_init__can_reinit_an_initialized_repository(void) git_repository_free(reinit); } + +void test_repo_init__init_with_initial_commit(void) +{ + git_index *index; + + cl_set_cleanup(&cleanup_repository, "committed"); + + /* Initialize the repository */ + cl_git_pass(git_repository_init(&_repo, "committed", 0)); + + /* Init will be automatically created when requested for a new repo */ + cl_git_pass(git_repository_index(&index, _repo)); + + /* Create a file so we can commit it + * + * If you are writing code outside the test suite, you can create this + * file any way that you like, such as: + * FILE *fp = fopen("committed/file.txt", "w"); + * fputs("some stuff\n", fp); + * fclose(fp); + * We like to use the help functions because they do error detection + * in a way that's easily compatible with our test suite. + */ + cl_git_mkfile("committed/file.txt", "some stuff\n"); + + /* Add file to the index */ + cl_git_pass(git_index_add_bypath(index, "file.txt")); + cl_git_pass(git_index_write(index)); + + /* Create a commit with the new contents of the index */ + { + git_signature *sig; + git_oid tree_id, commit_id; + git_tree *tree; + + cl_git_pass(git_signature_default(&sig, _repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); + + cl_git_pass(git_commit_create_v( + &commit_id, _repo, "HEAD", sig, sig, + NULL, "First", tree, 0)); + + git_tree_free(tree); + git_signature_free(sig); + } + + git_index_free(index); +} From 944c1589c25e011537dd9162265cedeab363a103 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 Aug 2013 14:49:38 -0700 Subject: [PATCH 254/367] Add example like "git init" --- examples/Makefile | 2 +- examples/init.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 examples/init.c diff --git a/examples/Makefile b/examples/Makefile index 95e46f0c6..d53ed8241 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -3,7 +3,7 @@ CC = gcc CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers LFLAGS = -L../build -lgit2 -lz -APPS = general showindex diff rev-list cat-file status log rev-parse +APPS = general showindex diff rev-list cat-file status log rev-parse init all: $(APPS) diff --git a/examples/init.c b/examples/init.c new file mode 100644 index 000000000..76b037a88 --- /dev/null +++ b/examples/init.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include + +static void fail(const char *msg, const char *arg) +{ + if (arg) + fprintf(stderr, "%s %s\n", msg, arg); + else + fprintf(stderr, "%s\n", msg); + exit(1); +} + +static void usage(const char *error, const char *arg) +{ + fprintf(stderr, "error: %s '%s'\n", error, arg); + fprintf(stderr, "usage: init [-q | --quiet] [--bare] " + "[--template=] [--shared[=perms]] \n"); + exit(1); +} + +static size_t is_prefixed(const char *arg, const char *pfx) +{ + size_t len = strlen(pfx); + return !strncmp(arg, pfx, len) ? len : 0; +} + +static uint32_t parse_shared(const char *shared) +{ + if (!strcmp(shared, "false") || !strcmp(shared, "umask")) + return GIT_REPOSITORY_INIT_SHARED_UMASK; + + else if (!strcmp(shared, "true") || !strcmp(shared, "group")) + return GIT_REPOSITORY_INIT_SHARED_GROUP; + + else if (!strcmp(shared, "all") || !strcmp(shared, "world") || + !strcmp(shared, "everybody")) + return GIT_REPOSITORY_INIT_SHARED_ALL; + + else if (shared[0] == '0') { + long val; + char *end = NULL; + val = strtol(shared + 1, &end, 8); + if (end == shared + 1 || *end != 0) + usage("invalid octal value for --shared", shared); + return (uint32_t)val; + } + + else + usage("unknown value for --shared", shared); + + return 0; +} + +static void create_initial_commit(git_repository *repo); + +int main(int argc, char *argv[]) +{ + git_repository *repo = NULL; + int no_options = 1, quiet = 0, bare = 0, initial_commit = 0, i; + uint32_t shared = GIT_REPOSITORY_INIT_SHARED_UMASK; + const char *template = NULL, *gitdir = NULL, *dir = NULL; + size_t pfxlen; + + git_threads_init(); + + /* Process arguments */ + + for (i = 1; i < argc; ++i) { + char *a = argv[i]; + + if (a[0] == '-') + no_options = 0; + + if (a[0] != '-') { + if (dir != NULL) + usage("extra argument", a); + dir = a; + } + else if (!strcmp(a, "-q") || !strcmp(a, "--quiet")) + quiet = 1; + else if (!strcmp(a, "--bare")) + bare = 1; + else if ((pfxlen = is_prefixed(a, "--template=")) > 0) + template = a + pfxlen; + else if (!strcmp(a, "--separate-git-dir")) + gitdir = argv[++i]; + else if ((pfxlen = is_prefixed(a, "--separate-git-dir=")) > 0) + gitdir = a + pfxlen; + else if (!strcmp(a, "--shared")) + shared = GIT_REPOSITORY_INIT_SHARED_GROUP; + else if ((pfxlen = is_prefixed(a, "--shared=")) > 0) + shared = parse_shared(a + pfxlen); + else if (!strcmp(a, "--initial-commit")) + initial_commit = 1; + else + usage("unknown option", a); + } + + if (!dir) + usage("must specify directory to init", NULL); + + /* Initialize repository */ + + if (no_options) { + /* No options were specified, so let's demonstrate the default + * simple case of git_repository_init() API usage... + */ + + if (git_repository_init(&repo, dir, 0) < 0) + fail("Could not initialize repository", dir); + } + else { + /* Some command line options were specified, so we'll use the + * extended init API to handle them + */ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + if (bare) + opts.flags |= GIT_REPOSITORY_INIT_BARE; + + if (template) { + opts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = template; + } + + if (gitdir) { + /* if you specified a separate git directory, then initialize + * the repository at that path and use the second path as the + * working directory of the repository (with a git-link file) + */ + opts.workdir_path = dir; + dir = gitdir; + } + + if (shared != 0) + opts.mode = shared; + + if (git_repository_init_ext(&repo, dir, &opts) < 0) + fail("Could not initialize repository", dir); + } + + if (!quiet) { + if (bare || gitdir) + dir = git_repository_path(repo); + else + dir = git_repository_workdir(repo); + + printf("Initialized empty Git repository in %s\n", dir); + } + + /* As an extension to the basic "git init" command, this example + * gives the option to create an empty initial commit. This is + * mostly to demonstrate what it takes to do that, but also some + * people like to have that empty base commit in their repo. + */ + if (initial_commit) { + create_initial_commit(repo); + printf("Created empty initial commit\n"); + } + + git_repository_free(repo); + git_threads_shutdown(); + + return 0; +} + +static void create_initial_commit(git_repository *repo) +{ + git_signature *sig; + git_index *index; + git_oid tree_id, commit_id; + git_tree *tree; + + /* First use the config to initialize a commit signature for the user */ + + if (git_signature_default(&sig, repo) < 0) + fail("Unable to create a commit signature.", + "Perhaps 'user.name' and 'user.email' are not set"); + + /* Now let's create an empty tree for this commit */ + + if (git_repository_index(&index, repo) < 0) + fail("Could not open repository index", NULL); + + /* Outside of this example, you could call git_index_add_bypath() + * here to put actual files into the index. For our purposes, we'll + * leave it empty for now. + */ + + if (git_index_write_tree(&tree_id, index) < 0) + fail("Unable to write initial tree from index", NULL); + + git_index_free(index); + + if (git_tree_lookup(&tree, repo, &tree_id) < 0) + fail("Could not look up initial tree", NULL); + + /* Ready to create the initial commit + * + * Normally creating a commit would involve looking up the current + * HEAD commit and making that be the parent of the initial commit, + * but here this is the first commit so there will be no parent. + */ + + if (git_commit_create_v( + &commit_id, repo, "HEAD", sig, sig, + NULL, "Initial commit", tree, 0) < 0) + fail("Could not create the initial commit", NULL); + + /* Clean up so we don't leak memory */ + + git_tree_free(tree); + git_signature_free(sig); +} From 0ea41445f4028460aefcd55cb37f10870ea9311c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 Aug 2013 15:03:15 -0700 Subject: [PATCH 255/367] Improve isolation of new test from user environs --- examples/init.c | 28 ++++++++++++++++++++++++++++ tests-clar/repo/init.c | 11 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/examples/init.c b/examples/init.c index 76b037a88..4a379c6e3 100644 --- a/examples/init.c +++ b/examples/init.c @@ -1,9 +1,27 @@ +/* + * This is a sample program that is similar to "git init". See the + * documentation for that (try "git help init") to understand what this + * program is emulating. + * + * This demonstrates using the libgit2 APIs to initialize a new repository. + * + * This also contains a special additional option that regular "git init" + * does not support which is "--initial-commit" to make a first empty commit. + * That is demonstrated in the "create_initial_commit" helper function. + * + * 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 #include #include #include #include +/* not actually good error handling */ static void fail(const char *msg, const char *arg) { if (arg) @@ -21,12 +39,14 @@ static void usage(const char *error, const char *arg) exit(1); } +/* simple string prefix test used in argument parsing */ static size_t is_prefixed(const char *arg, const char *pfx) { size_t len = strlen(pfx); return !strncmp(arg, pfx, len) ? len : 0; } +/* parse the tail of the --shared= argument */ static uint32_t parse_shared(const char *shared) { if (!strcmp(shared, "false") || !strcmp(shared, "umask")) @@ -54,8 +74,10 @@ static uint32_t parse_shared(const char *shared) return 0; } +/* forward declaration of helper to make an empty parent-less commit */ static void create_initial_commit(git_repository *repo); + int main(int argc, char *argv[]) { git_repository *repo = NULL; @@ -142,6 +164,8 @@ int main(int argc, char *argv[]) fail("Could not initialize repository", dir); } + /* Print a message to stdout like "git init" does */ + if (!quiet) { if (bare || gitdir) dir = git_repository_path(repo); @@ -167,6 +191,10 @@ int main(int argc, char *argv[]) return 0; } +/* Unlike regular "git init", this example shows how to create an initial + * empty commit in the repository. This is the helper function that does + * that. + */ static void create_initial_commit(git_repository *repo) { git_signature *sig; diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 02ea676fb..5076184b8 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -559,6 +559,17 @@ void test_repo_init__init_with_initial_commit(void) cl_git_pass(git_index_add_bypath(index, "file.txt")); cl_git_pass(git_index_write(index)); + /* Make sure we're ready to use git_signature_default :-) */ + { + git_config *cfg, *local; + cl_git_pass(git_repository_config(&cfg, _repo)); + cl_git_pass(git_config_open_level(&local, cfg, GIT_CONFIG_LEVEL_LOCAL)); + cl_git_pass(git_config_set_string(local, "user.name", "Test User")); + cl_git_pass(git_config_set_string(local, "user.email", "t@example.com")); + git_config_free(local); + git_config_free(cfg); + } + /* Create a commit with the new contents of the index */ { git_signature *sig; From fe0c6d4e712fa1bb072b8a847783bad47f3e91eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 17 Aug 2013 01:41:08 +0200 Subject: [PATCH 256/367] odb: make it clearer that the id is calculated in the frontend The frontend is in charge of calculating the id of the objects. Thus the backends should treat it as a read-only value. The positioning in the function signature made it seem as though it was an output parameter. Make the id const and move it from the front to behind the subject (backend or stream). --- include/git2/odb_backend.h | 2 +- include/git2/sys/odb_backend.h | 6 +----- src/odb.c | 8 ++++---- src/odb_loose.c | 4 ++-- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index d004238a4..5696ee07d 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -75,7 +75,7 @@ struct git_odb_stream { int (*read)(git_odb_stream *stream, char *buffer, size_t len); int (*write)(git_odb_stream *stream, const char *buffer, size_t len); - int (*finalize_write)(git_oid *oid_p, git_odb_stream *stream); + int (*finalize_write)(git_odb_stream *stream, const git_oid *oid); void (*free)(git_odb_stream *stream); }; diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index 3cd2734c0..2d06613d2 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -48,12 +48,8 @@ struct git_odb_backend { int (* read_header)( size_t *, git_otype *, git_odb_backend *, const git_oid *); - /* The writer may assume that the object - * has already been hashed and is passed - * in the first parameter. - */ int (* write)( - git_oid *, git_odb_backend *, const void *, size_t, git_otype); + git_odb_backend *, const git_oid *, const void *, size_t, git_otype); int (* writestream)( git_odb_stream **, git_odb_backend *, size_t, git_otype); diff --git a/src/odb.c b/src/odb.c index b7f64dfc9..d2be60f97 100644 --- a/src/odb.c +++ b/src/odb.c @@ -291,10 +291,10 @@ typedef struct { git_otype type; } fake_wstream; -static int fake_wstream__fwrite(git_oid *oid, git_odb_stream *_stream) +static int fake_wstream__fwrite(git_odb_stream *_stream, const git_oid *oid) { fake_wstream *stream = (fake_wstream *)_stream; - return _stream->backend->write(oid, _stream->backend, stream->buffer, stream->size, stream->type); + return _stream->backend->write(_stream->backend, oid, stream->buffer, stream->size, stream->type); } static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t len) @@ -851,7 +851,7 @@ int git_odb_write( continue; if (b->write != NULL) - error = b->write(oid, b, data, len, type); + error = b->write(b, oid, data, len, type); } if (!error || error == GIT_PASSTHROUGH) @@ -931,7 +931,7 @@ int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) { git_hash_final(out, stream->hash_ctx); - return stream->finalize_write(out, stream); + return stream->finalize_write(stream, out); } int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len) diff --git a/src/odb_loose.c b/src/odb_loose.c index 35f53fb7d..d1ca18edb 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -769,7 +769,7 @@ static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb return state.cb_error ? state.cb_error : error; } -static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) +static int loose_backend__stream_fwrite(git_odb_stream *_stream, const git_oid *oid) { loose_writestream *stream = (loose_writestream *)_stream; loose_backend *backend = (loose_backend *)_stream->backend; @@ -850,7 +850,7 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_ return !stream ? -1 : 0; } -static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type) +static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_otype type) { int error = 0, header_len; git_buf final_path = GIT_BUF_INIT; From 7a3764bee92456e5ee2ed41be9abb0ffb4165eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 17 Aug 2013 01:55:52 +0200 Subject: [PATCH 257/367] odb: document git_odb_stream Clarify the role of each function and in particular mention that there is no need for the backend or stream to worry about the object's id, as it will be given when `finalize_write` is called. --- include/git2/odb_backend.h | 26 +++++++++++++++++++++++++- include/git2/sys/odb_backend.h | 4 ++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index 5696ee07d..dca499583 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -67,15 +67,39 @@ typedef enum { typedef struct git_hash_ctx git_hash_ctx; -/** A stream to read/write from a backend */ +/** + * A stream to read/write from a backend. + * + * This represents a stream of data being written to or read from a + * backend. When writing, the frontend functions take care of + * calculating the object's id and all `finalize_write` needs to do is + * store the object with the id it is passed. + */ struct git_odb_stream { git_odb_backend *backend; unsigned int mode; git_hash_ctx *hash_ctx; + /** + * Write at most `len` bytes into `buffer` and advance the + * stream. + */ int (*read)(git_odb_stream *stream, char *buffer, size_t len); + + /** + * Write `len` bytes from `buffer` into the stream. + */ int (*write)(git_odb_stream *stream, const char *buffer, size_t len); + + /** + * Store the contents of the stream as an object with the id + * specified in `oid`. + */ int (*finalize_write)(git_odb_stream *stream, const git_oid *oid); + + /** + * Free the stream's memory. + */ void (*free)(git_odb_stream *stream); }; diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index 2d06613d2..31ffe1c33 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -48,6 +48,10 @@ struct git_odb_backend { int (* read_header)( size_t *, git_otype *, git_odb_backend *, const git_oid *); + /** + * Write an object into the backend. The id of the object has + * already been calculated and is passed in. + */ int (* write)( git_odb_backend *, const git_oid *, const void *, size_t, git_otype); From 090a07d29548ce69034b4be6ba043014226893c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 17 Aug 2013 02:12:04 +0200 Subject: [PATCH 258/367] odb: avoid hashing twice in and edge case If none of the backends support direct writes and we must stream the whole file, we already know what the object's id should be; so use the stream's functions directly, bypassing the frontend's hashing and overwriting of our existing id. --- src/odb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/odb.c b/src/odb.c index d2be60f97..21b46bf56 100644 --- a/src/odb.c +++ b/src/odb.c @@ -864,8 +864,8 @@ int git_odb_write( if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0) return error; - git_odb_stream_write(stream, data, len); - error = git_odb_stream_finalize_write(oid, stream); + stream->write(stream, data, len); + error = stream->finalize_write(stream, oid); git_odb_stream_free(stream); return error; From d19dd9cf73f1ebec135faf9fd6e702b09d3ee723 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 18 Aug 2013 23:38:51 +0200 Subject: [PATCH 259/367] odb: Straighten oid prefix handling --- src/odb_loose.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/odb_loose.c b/src/odb_loose.c index 76ed8e232..90e258793 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -641,10 +641,12 @@ static int loose_backend__read_prefix( { int error = 0; + assert(len <= GIT_OID_HEXSZ); + if (len < GIT_OID_MINPREFIXLEN) error = git_odb__error_ambiguous("prefix length too short"); - else if (len >= GIT_OID_HEXSZ) { + else if (len == GIT_OID_HEXSZ) { /* We can fall back to regular read method */ error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); if (!error) From 90a8ad63e3c861f15ad61379ed1355fd036ca6a7 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 19 Aug 2013 00:18:44 +0200 Subject: [PATCH 260/367] ci: Make Valgrind run on Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 71f8406fc..151060fb4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ script: # Run Tests after_success: - sudo apt-get -qq install valgrind - - valgrind --leak-check=full --show-reachable=yes --suppressions=../libgit2_clar.supp ./libgit2_clar -ionline + - valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline # Only watch the development branch branches: From 3d2768747548ec24b58ebdaa012a6b757e65f5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 19 Aug 2013 10:30:44 +0200 Subject: [PATCH 261/367] index: report when it's locked Report the index being locked with its own error code in order to be able to differentiate, as a locked index is typically the result of a crashed process or concurrent access, both of which often require user intervention to fix. --- include/git2/errors.h | 1 + src/filebuf.c | 10 +++++----- src/fileops.c | 2 +- src/index.c | 6 +++++- tests-clar/index/tests.c | 25 +++++++++++++++++++++++++ 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/include/git2/errors.h b/include/git2/errors.h index 2032a436a..0f0bddf07 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -32,6 +32,7 @@ typedef enum { GIT_ENONFASTFORWARD = -11, GIT_EINVALIDSPEC = -12, GIT_EMERGECONFLICT = -13, + GIT_ELOCKED = -14, GIT_PASSTHROUGH = -30, GIT_ITEROVER = -31, diff --git a/src/filebuf.c b/src/filebuf.c index 246ae34e7..714a32395 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -53,7 +53,7 @@ static int lock_file(git_filebuf *file, int flags) giterr_clear(); /* actual OS error code just confuses */ giterr_set(GITERR_OS, "Failed to lock file '%s' for writing", file->path_lock); - return -1; + return GIT_ELOCKED; } } @@ -66,7 +66,7 @@ static int lock_file(git_filebuf *file, int flags) } if (file->fd < 0) - return -1; + return file->fd; file->fd_is_open = true; @@ -197,7 +197,7 @@ static int write_deflate(git_filebuf *file, void *source, size_t len) int git_filebuf_open(git_filebuf *file, const char *path, int flags) { - int compression; + int compression, error = -1; size_t path_len; /* opening an already open buffer is a programming error; @@ -282,7 +282,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); /* open the file for locking */ - if (lock_file(file, flags) < 0) + if ((error = lock_file(file, flags)) < 0) goto cleanup; } @@ -290,7 +290,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) cleanup: git_filebuf_cleanup(file); - return -1; + return error; } int git_filebuf_hash(git_oid *oid, git_filebuf *file) diff --git a/src/fileops.c b/src/fileops.c index 3a5a53074..76119e02e 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -70,7 +70,7 @@ int git_futils_creat_locked(const char *path, const mode_t mode) if (fd < 0) { giterr_set(GITERR_OS, "Failed to create locked file '%s'", path); - return -1; + return errno == EEXIST ? GIT_ELOCKED : -1; } return fd; diff --git a/src/index.c b/src/index.c index 5f53f1e2f..17e43903f 100644 --- a/src/index.c +++ b/src/index.c @@ -498,8 +498,12 @@ int git_index_write(git_index *index) git_vector_sort(&index->reuc); if ((error = git_filebuf_open( - &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0) + &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0) { + if (error == GIT_ELOCKED) + giterr_set(GITERR_INDEX, "The index is locked. This might be due to a concurrrent or crashed process"); + return error; + } if ((error = write_index(index, &file)) < 0) { git_filebuf_cleanup(&file); diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index 1bc5e6a07..9090d4d8a 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -459,3 +459,28 @@ void test_index_tests__preserves_case(void) git_repository_free(repo); } +void test_index_tests__elocked(void) +{ + git_repository *repo; + git_index *index; + git_filebuf file = GIT_FILEBUF_INIT; + const git_error *err; + int error; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + /* Lock the index file so we fail to lock it */ + cl_git_pass(git_filebuf_open(&file, index->index_file_path, 0)); + error = git_index_write(index); + cl_assert_equal_i(GIT_ELOCKED, error); + + err = giterr_last(); + cl_assert_equal_i(err->klass, GITERR_INDEX); + + git_filebuf_cleanup(&file); + git_index_free(index); + git_repository_free(repo); +} From 6818080871db227c2586a102306832b41ea1c0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 19 Aug 2013 10:50:28 +0200 Subject: [PATCH 262/367] travis: really fail if the tests fail When implementing the ssh testing, the move to the script made it so the first test suite's exit code was ignored. Check whether the main tests fail and exit with an error in that case. --- script/cibuild.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/script/cibuild.sh b/script/cibuild.sh index 722b3349d..2b8d1af08 100755 --- a/script/cibuild.sh +++ b/script/cibuild.sh @@ -11,6 +11,11 @@ cd _build cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS cmake --build . --target install ctest -V . +ecode=$? + +if [ $ecode -ne 0 ]; then + exit $ecode +fi # Now that we've tested the raw git protocol, let's set up ssh to we # can do the push tests over it From 6d69fbce31c391c7a7739f8156b0d63f399c53bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 19 Aug 2013 13:04:05 +0200 Subject: [PATCH 263/367] Revparse does not handle refspecs --- src/revparse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/revparse.c b/src/revparse.c index b84f0037f..329b96dbc 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -129,7 +129,7 @@ static int revparse_lookup_object( if (error < 0 && error != GIT_ENOTFOUND) return error; - giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec); + giterr_set(GITERR_REFERENCE, "Revspec '%s' not found.", spec); return GIT_ENOTFOUND; } From 67c177ef26f25f4f7e5497393d075dde0f1b9415 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 19 Aug 2013 11:42:50 -0500 Subject: [PATCH 264/367] Don't expose git_hash_ctx since it's internal And doing so makes the mingw build choke. --- include/git2/odb_backend.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index dca499583..7b3c6a356 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -65,8 +65,6 @@ typedef enum { GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY), } git_odb_stream_t; -typedef struct git_hash_ctx git_hash_ctx; - /** * A stream to read/write from a backend. * @@ -78,7 +76,7 @@ typedef struct git_hash_ctx git_hash_ctx; struct git_odb_stream { git_odb_backend *backend; unsigned int mode; - git_hash_ctx *hash_ctx; + void *hash_ctx; /** * Write at most `len` bytes into `buffer` and advance the From 5875e8d21a1f2db8849e7babe7f0e2c7aee5a2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 19 Aug 2013 18:50:03 +0200 Subject: [PATCH 265/367] travis: exit on failure for anything related to building --- script/cibuild.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/script/cibuild.sh b/script/cibuild.sh index 2b8d1af08..aa4fa47aa 100755 --- a/script/cibuild.sh +++ b/script/cibuild.sh @@ -8,14 +8,9 @@ export GITTEST_REMOTE_URL="git://localhost/test.git" mkdir _build cd _build -cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS -cmake --build . --target install -ctest -V . -ecode=$? - -if [ $ecode -ne 0 ]; then - exit $ecode -fi +cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $? +cmake --build . --target install || exit $? +ctest -V . || exit $? # Now that we've tested the raw git protocol, let's set up ssh to we # can do the push tests over it From 86967cc579ee9e8c1a209a76c12605dd24dcf6cf Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 19 Aug 2013 16:44:17 -0500 Subject: [PATCH 266/367] Use time(2) to get the time We didn't use the added precision in gettimeofday, so remove it. This prevents us from having an unnecessary reimplementation on win32. --- src/date.c | 14 ++++++------ src/posix.h | 4 ---- src/win32/posix_w32.c | 50 ------------------------------------------- 3 files changed, 7 insertions(+), 61 deletions(-) diff --git a/src/date.c b/src/date.c index 48841e4f9..7849c2f02 100644 --- a/src/date.c +++ b/src/date.c @@ -823,15 +823,13 @@ static void pending_number(struct tm *tm, int *num) } static git_time_t approxidate_str(const char *date, - const struct timeval *tv, - int *error_ret) + time_t time_sec, + int *error_ret) { int number = 0; int touched = 0; struct tm tm = {0}, now; - time_t time_sec; - time_sec = tv->tv_sec; p_localtime_r(&time_sec, &tm); now = tm; @@ -861,7 +859,7 @@ static git_time_t approxidate_str(const char *date, int git__date_parse(git_time_t *out, const char *date) { - struct timeval tv; + time_t time_sec; git_time_t timestamp; int offset, error_ret=0; @@ -870,7 +868,9 @@ int git__date_parse(git_time_t *out, const char *date) return 0; } - p_gettimeofday(&tv, NULL); - *out = approxidate_str(date, &tv, &error_ret); + if (time(&time_sec) == -1) + return -1; + + *out = approxidate_str(date, time_sec, &error_ret); return error_ret; } diff --git a/src/posix.h b/src/posix.h index ea97a1349..de2aa0b73 100644 --- a/src/posix.h +++ b/src/posix.h @@ -71,16 +71,12 @@ typedef int GIT_SOCKET; #define p_localtime_r localtime_r #define p_gmtime_r gmtime_r -#define p_gettimeofday gettimeofday #else typedef SOCKET GIT_SOCKET; -struct timezone; extern struct tm * p_localtime_r (const time_t *timer, struct tm *result); extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result); -extern int p_gettimeofday(struct timeval *tv, struct timezone *tz); - #endif diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 57ebaa12e..087adcce3 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -518,56 +518,6 @@ p_gmtime_r (const time_t *timer, struct tm *result) return result; } -#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) -#define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64 -#else -#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL -#endif - -#ifndef _TIMEZONE_DEFINED -#define _TIMEZONE_DEFINED -struct timezone -{ - int tz_minuteswest; /* minutes W of Greenwich */ - int tz_dsttime; /* type of dst correction */ -}; -#endif - -int p_gettimeofday(struct timeval *tv, struct timezone *tz) -{ - FILETIME ft; - unsigned __int64 tmpres = 0; - static int tzflag; - - if (NULL != tv) - { - GetSystemTimeAsFileTime(&ft); - - tmpres |= ft.dwHighDateTime; - tmpres <<= 32; - tmpres |= ft.dwLowDateTime; - - /*converting file time to unix epoch*/ - tmpres /= 10; /*convert into microseconds*/ - tmpres -= DELTA_EPOCH_IN_MICROSECS; - tv->tv_sec = (long)(tmpres / 1000000UL); - tv->tv_usec = (long)(tmpres % 1000000UL); - } - - if (NULL != tz) - { - if (!tzflag) - { - _tzset(); - tzflag++; - } - tz->tz_minuteswest = _timezone / 60; - tz->tz_dsttime = _daylight; - } - - return 0; -} - int p_inet_pton(int af, const char* src, void* dst) { union { From 238b761491bee6b03f95fca9037b8dff8cced7e7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 16 Aug 2013 13:31:24 -0500 Subject: [PATCH 267/367] Fix p_inet_pton on windows p_inet_pton on Windows should set errno properly for callers. Rewrite p_inet_pton to handle error cases correctly and add test cases to exercise this function. --- src/win32/posix.h | 7 ++++ src/win32/posix_w32.c | 62 ++++++++++++++---------------- tests-clar/core/posix.c | 84 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 33 deletions(-) create mode 100644 tests-clar/core/posix.c diff --git a/src/win32/posix.h b/src/win32/posix.h index 5f924a026..24cba23e0 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -12,6 +12,13 @@ #include "utf-conv.h" #include "dir.h" +/* define some standard errnos that the runtime may be missing. for example, + * mingw lacks EAFNOSUPPORT. */ + +#ifndef EAFNOSUPPORT +# define EAFNOSUPPORT (INT_MAX-1) +#endif + GIT_INLINE(int) p_link(const char *old, const char *new) { GIT_UNUSED(old); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 087adcce3..2f490529c 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -518,44 +518,40 @@ p_gmtime_r (const time_t *timer, struct tm *result) return result; } -int p_inet_pton(int af, const char* src, void* dst) +int p_inet_pton(int af, const char *src, void *dst) { - union { - struct sockaddr_in6 sin6; - struct sockaddr_in sin; - } sa; - int srcsize; + struct sockaddr_storage sin; + void *addr; + int sin_len = sizeof(struct sockaddr_storage), addr_len; + int error = 0; - switch(af) - { - case AF_INET: - sa.sin.sin_family = AF_INET; - srcsize = (int)sizeof(sa.sin); - break; - case AF_INET6: - sa.sin6.sin6_family = AF_INET6; - srcsize = (int)sizeof(sa.sin6); - break; - default: - errno = WSAEPFNOSUPPORT; - return -1; - } - - if (WSAStringToAddress((LPSTR)src, af, NULL, (struct sockaddr *) &sa, &srcsize) != 0) - { - errno = WSAGetLastError(); + if (af == AF_INET) { + addr = &((struct sockaddr_in *)&sin)->sin_addr; + addr_len = sizeof(struct in_addr); + } else if (af == AF_INET6) { + addr = &((struct sockaddr_in6 *)&sin)->sin6_addr; + addr_len = sizeof(struct in6_addr); + } else { + errno = EAFNOSUPPORT; return -1; } - switch(af) - { - case AF_INET: - memcpy(dst, &sa.sin.sin_addr, sizeof(sa.sin.sin_addr)); - break; - case AF_INET6: - memcpy(dst, &sa.sin6.sin6_addr, sizeof(sa.sin6.sin6_addr)); - break; + if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) { + memcpy(dst, addr, addr_len); + return 1; } - return 1; + switch(WSAGetLastError()) { + case WSAEINVAL: + return 0; + case WSAEFAULT: + errno = ENOSPC; + return -1; + case WSA_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + return -1; + } + + errno = EINVAL; + return -1; } diff --git a/tests-clar/core/posix.c b/tests-clar/core/posix.c new file mode 100644 index 000000000..0d9443f92 --- /dev/null +++ b/tests-clar/core/posix.c @@ -0,0 +1,84 @@ +#ifndef _WIN32 +# include +#else +# include +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32") +# endif +#endif + +#include "clar_libgit2.h" +#include "posix.h" + +void test_core_posix__initialize(void) +{ +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + WSADATA wsd; + + cl_git_pass(WSAStartup(MAKEWORD(2,2), &wsd)); + cl_assert(LOBYTE(wsd.wVersion) == 2 && HIBYTE(wsd.wVersion) == 2); +#endif +} + +void test_core_posix__inet_pton(void) +{ + struct in_addr addr; + struct in6_addr addr6; + size_t i; + + struct in_addr_data { + const char *p; + const uint8_t n[4]; + }; + + struct in6_addr_data { + const char *p; + const uint8_t n[16]; + }; + + static struct in_addr_data in_addr_data[] = { + { "0.0.0.0", { 0, 0, 0, 0 } }, + { "10.42.101.8", { 10, 42, 101, 8 } }, + { "127.0.0.1", { 127, 0, 0, 1 } }, + { "140.177.10.12", { 140, 177, 10, 12 } }, + { "204.232.175.90", { 204, 232, 175, 90 } }, + { "255.255.255.255", { 255, 255, 255, 255 } }, + }; + + static struct in6_addr_data in6_addr_data[] = { + { "::", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { "::1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + { "0:0:0:0:0:0:0:1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + { "2001:db8:8714:3a90::12", { 0x20, 0x01, 0x0d, 0xb8, 0x87, 0x14, 0x3a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 } }, + { "fe80::f8ba:c2d6:86be:3645", { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xba, 0xc2, 0xd6, 0x86, 0xbe, 0x36, 0x45 } }, + { "::ffff:204.152.189.116", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x98, 0xbd, 0x74 } }, + }; + + /* Test some ipv4 addresses */ + for (i = 0; i < 6; i++) { + cl_assert(p_inet_pton(AF_INET, in_addr_data[i].p, &addr) == 1); + cl_assert(memcmp(&addr, in_addr_data[i].n, sizeof(struct in_addr)) == 0); + } + + /* Test some ipv6 addresses */ + for (i = 0; i < 6; i++) { + cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1); + cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0); + } + + /* Test some invalid strings */ + cl_assert(p_inet_pton(AF_INET, "", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "foo", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, " 127.0.0.1", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "bar", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "10.foo.bar.1", &addr) == 0); + + /* Test unsupported address families */ + cl_git_fail(p_inet_pton(12, "52.472", NULL)); /* AF_DECnet */ + cl_assert_equal_i(EAFNOSUPPORT, errno); + + cl_git_fail(p_inet_pton(5, "315.124", NULL)); /* AF_CHAOS */ + cl_assert_equal_i(EAFNOSUPPORT, errno); +} From 8255b497b625735acab527f15617daa19f796165 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 19 Aug 2013 17:49:12 -0500 Subject: [PATCH 268/367] Quiet down some warnings --- src/win32/mingw-compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index 97b1cb71b..fe0abfb54 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -21,7 +21,7 @@ GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { const char *end = memchr(s, 0, maxlen); - return end ? (end - s) : maxlen; + return end ? (size_t)(end - s) : maxlen; } #endif From c0b01b7572232bd6c0cd848e9b92ed8a8d678bcf Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 19 Aug 2013 18:46:26 -0500 Subject: [PATCH 269/367] Skip UTF-8 BOM in binary detection When a git_buf contains a UTF-8 BOM, the three bytes comprising that BOM are treated as unprintable characters. For a small git_buf, the three BOM characters overwhelm the printable characters. This is problematic when trying to check out a small file as the CR/LF filtering will not apply. --- src/buf_text.c | 6 ++++++ tests-clar/core/buffer.c | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/buf_text.c b/src/buf_text.c index 472339def..ecf592b51 100644 --- a/src/buf_text.c +++ b/src/buf_text.c @@ -170,8 +170,14 @@ int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings) bool git_buf_text_is_binary(const git_buf *buf) { const char *scan = buf->ptr, *end = buf->ptr + buf->size; + git_bom_t bom; int printable = 0, nonprintable = 0; + scan += git_buf_text_detect_bom(&bom, buf, 0); + + if (bom > GIT_BOM_UTF8) + return 1; + while (scan < end) { unsigned char c = *scan++; diff --git a/tests-clar/core/buffer.c b/tests-clar/core/buffer.c index 9d9628cfd..8a0b6711f 100644 --- a/tests-clar/core/buffer.c +++ b/tests-clar/core/buffer.c @@ -718,6 +718,8 @@ void test_core_buffer__classify_with_utf8(void) size_t data1len = 31; char *data2 = "Internal NUL!!!\000\n\nI see you!\n"; size_t data2len = 29; + char *data3 = "\xef\xbb\xbfThis is UTF-8 with a BOM.\n"; + size_t data3len = 20; git_buf b; b.ptr = data0; b.size = b.asize = data0len; @@ -731,6 +733,10 @@ void test_core_buffer__classify_with_utf8(void) b.ptr = data2; b.size = b.asize = data2len; cl_assert(git_buf_text_is_binary(&b)); cl_assert(git_buf_text_contains_nul(&b)); + + b.ptr = data3; b.size = b.asize = data3len; + cl_assert(!git_buf_text_is_binary(&b)); + cl_assert(!git_buf_text_contains_nul(&b)); } #define SIMILARITY_TEST_DATA_1 \ From 0f0f565507565520759bffc22976c583497ec01f Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 19 Aug 2013 10:42:48 -0700 Subject: [PATCH 270/367] Don't try to pack symbolic refs If there were symbolic refs among the loose refs then the code to create packed-refs would fail trying to parse the OID out of them (where Git just skips trying to pack them). This fixes it. --- src/refdb_fs.c | 11 ++++++++++- tests-clar/refs/pack.c | 30 ++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index acd82594b..658bebb61 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -316,6 +316,12 @@ static int loose_lookup_to_packfile( if (reference_read(&ref_file, NULL, backend->path, name, NULL) < 0) return -1; + /* skip symbolic refs */ + if (!git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF)) { + git_buf_free(&ref_file); + return 0; + } + git_buf_rtrim(&ref_file); name_len = strlen(name); @@ -355,6 +361,9 @@ static int _dirent_loose_load(void *data, git_buf *full_path) if (loose_lookup_to_packfile(&ref, backend, file_path) < 0) return -1; + if (!ref) + return 0; + git_strmap_insert2( backend->refcache.packfile, ref->name, ref, old_ref, err); if (err < 0) { @@ -460,7 +469,7 @@ static int loose_lookup( if (error < 0) goto done; - if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { + if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) { git_buf_rtrim(&ref_file); if ((target = loose_parse_symbolic(&ref_file)) == NULL) { diff --git a/tests-clar/refs/pack.c b/tests-clar/refs/pack.c index d8d5cc6d0..6019ed75a 100644 --- a/tests-clar/refs/pack.c +++ b/tests-clar/refs/pack.c @@ -32,7 +32,7 @@ static void packall(void) void test_refs_pack__empty(void) { - // create a packfile for an empty folder + /* create a packfile for an empty folder */ git_buf temp_path = GIT_BUF_INIT; cl_git_pass(git_buf_join_n(&temp_path, '/', 3, git_repository_path(g_repo), GIT_REFS_HEADS_DIR, "empty_dir")); @@ -44,7 +44,7 @@ void test_refs_pack__empty(void) void test_refs_pack__loose(void) { - // create a packfile from all the loose rn a repo + /* create a packfile from all the loose rn a repo */ git_reference *reference; git_buf temp_path = GIT_BUF_INIT; @@ -77,3 +77,29 @@ void test_refs_pack__loose(void) git_reference_free(reference); git_buf_free(&temp_path); } + +void test_refs_pack__symbolic(void) +{ + /* create a packfile from loose refs skipping symbolic refs */ + int i; + git_oid head; + git_reference *ref; + char name[128]; + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + /* make a bunch of references */ + + for (i = 0; i < 100; ++i) { + snprintf(name, sizeof(name), "refs/heads/symbolic-%03d", i); + cl_git_pass(git_reference_symbolic_create( + &ref, g_repo, name, "refs/heads/master", 0)); + git_reference_free(ref); + + snprintf(name, sizeof(name), "refs/heads/direct-%03d", i); + cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0)); + git_reference_free(ref); + } + + packall(); +} From 0b7cdc02637bcc8491153a476460c9feab33f8ee Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 20 Aug 2013 15:18:48 -0700 Subject: [PATCH 271/367] Add sorted cache data type This adds a convenient new data type for caching the contents of file in memory when each item in that file corresponds to a name and you need to both be able to lookup items by name and iterate over them in some sorted order. The new data type has locks in place to manage usage in a threaded environment. --- src/sortedcache.c | 297 +++++++++++++++++++++++++++++++++ src/sortedcache.h | 101 ++++++++++++ src/vector.h | 5 + tests-clar/core/sortedcache.c | 298 ++++++++++++++++++++++++++++++++++ 4 files changed, 701 insertions(+) create mode 100644 src/sortedcache.c create mode 100644 src/sortedcache.h create mode 100644 tests-clar/core/sortedcache.c diff --git a/src/sortedcache.c b/src/sortedcache.c new file mode 100644 index 000000000..6015d616d --- /dev/null +++ b/src/sortedcache.c @@ -0,0 +1,297 @@ +#include "sortedcache.h" + +GIT__USE_STRMAP; + +int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path) +{ + git_sortedcache *sc; + size_t pathlen; + + pathlen = path ? strlen(path) : 0; + + sc = git__calloc(sizeof(git_sortedcache) + pathlen + 1, 1); + GITERR_CHECK_ALLOC(sc); + + if (git_pool_init(&sc->pool, 1, 0) < 0 || + git_vector_init(&sc->items, 4, item_cmp) < 0 || + (sc->map = git_strmap_alloc()) == NULL) + goto fail; + + if (git_mutex_init(&sc->lock)) { + giterr_set(GITERR_OS, "Failed to initialize mutex"); + goto fail; + } + + sc->item_path_offset = item_path_offset; + sc->free_item = free_item; + sc->free_item_payload = free_item_payload; + GIT_REFCOUNT_INC(sc); + if (pathlen) + memcpy(sc->path, path, pathlen); + + *out = sc; + return 0; + +fail: + if (sc->map) + git_strmap_free(sc->map); + git_vector_free(&sc->items); + git_pool_clear(&sc->pool); + git__free(sc); + return -1; +} + +void git_sortedcache_incref(git_sortedcache *sc) +{ + GIT_REFCOUNT_INC(sc); +} + +static void sortedcache_clear(git_sortedcache *sc) +{ + git_strmap_clear(sc->map); + + if (sc->free_item) { + size_t i; + void *item; + + git_vector_foreach(&sc->items, i, item) { + sc->free_item(sc->free_item_payload, item); + } + } + + git_vector_clear(&sc->items); + + git_pool_clear(&sc->pool); +} + +static void sortedcache_free(git_sortedcache *sc) +{ + if (git_mutex_lock(&sc->lock) < 0) { + giterr_set(GITERR_OS, "Unable to acquire mutex lock for free"); + return; + } + + sortedcache_clear(sc); + + git_vector_free(&sc->items); + git_strmap_free(sc->map); + + git_mutex_unlock(&sc->lock); + git_mutex_free(&sc->lock); + + git__free(sc); +} + +void git_sortedcache_free(git_sortedcache *sc) +{ + if (!sc) + return; + GIT_REFCOUNT_DEC(sc, sortedcache_free); +} + +static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) +{ + git_sortedcache *sc = payload; + /* path will already have been copied by upsert */ + memcpy(tgt_item, src_item, sc->item_path_offset); + return 0; +} + +/* copy a sorted cache */ +int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload) +{ + git_sortedcache *tgt; + size_t i; + void *src_item, *tgt_item; + + if (!copy_item) { + copy_item = sortedcache_copy_item; + payload = src; + } + + if (git_sortedcache_new( + &tgt, src->item_path_offset, + src->free_item, src->free_item_payload, + src->items._cmp, src->path) < 0) + return -1; + + if (git_sortedcache_lock(src) < 0) { + git_sortedcache_free(tgt); + return -1; + } + + if (git_sortedcache_lock(tgt) < 0) + goto fail; + + git_vector_foreach(&src->items, i, src_item) { + if (git_sortedcache_upsert( + &tgt_item, tgt, ((char *)src_item) + src->item_path_offset) < 0) + goto fail; + if (copy_item(payload, tgt_item, src_item) < 0) + goto fail; + } + + git_sortedcache_unlock(tgt); + git_sortedcache_unlock(src); + + *out = tgt; + return 0; + +fail: + git_sortedcache_unlock(src); + git_sortedcache_free(tgt); + return -1; +} + +/* release all items in sorted cache */ +void git_sortedcache_clear(git_sortedcache *sc, bool lock) +{ + if (lock && git_mutex_lock(&sc->lock) < 0) { + giterr_set(GITERR_OS, "Unable to acquire mutex lock for clear"); + return; + } + + sortedcache_clear(sc); + + if (lock) + git_mutex_unlock(&sc->lock); +} + +/* check file stamp to see if reload is required */ +bool git_sortedcache_out_of_date(git_sortedcache *sc) +{ + return (git_futils_filestamp_check(&sc->stamp, sc->path) != 0); +} + +/* lock sortedcache while making modifications */ +int git_sortedcache_lock(git_sortedcache *sc) +{ + if (git_mutex_lock(&sc->lock) < 0) { + giterr_set(GITERR_OS, "Unable to acquire mutex lock"); + return -1; + } + return 0; +} + +/* unlock sorted cache when done with modifications */ +int git_sortedcache_unlock(git_sortedcache *sc) +{ + git_vector_sort(&sc->items); + git_mutex_unlock(&sc->lock); + return 0; +} + +/* if the file has changed, lock cache and load file contents into buf; + * returns <0 on error, >0 if file has not changed + */ +int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf) +{ + int error, fd; + + if ((error = git_sortedcache_lock(sc)) < 0) + return error; + + if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) + goto unlock; + + if (!git__is_sizet(sc->stamp.size)) { + giterr_set(GITERR_INVALID, "Unable to load file larger than size_t"); + error = -1; + goto unlock; + } + + if ((fd = git_futils_open_ro(sc->path)) < 0) { + error = fd; + goto unlock; + } + + if (buf) + error = git_futils_readbuffer_fd(buf, fd, (size_t)sc->stamp.size); + + (void)p_close(fd); + + if (error < 0) + goto unlock; + + return 1; /* return 1 -> file needs reload and was successfully loaded */ + +unlock: + git_sortedcache_unlock(sc); + return error; +} + +/* find and/or insert item, returning pointer to item data */ +int git_sortedcache_upsert( + void **out, git_sortedcache *sc, const char *key) +{ + int error = 0; + khiter_t pos; + void *item; + size_t keylen; + char *item_key; + + pos = git_strmap_lookup_index(sc->map, key); + if (git_strmap_valid_index(sc->map, pos)) { + item = git_strmap_value_at(sc->map, pos); + goto done; + } + + keylen = strlen(key); + item = git_pool_mallocz(&sc->pool, sc->item_path_offset + keylen + 1); + GITERR_CHECK_ALLOC(item); + + /* one strange thing is that even if the vector or hash table insert + * fail, there is no way to free the pool item so we just abandon it + */ + + item_key = ((char *)item) + sc->item_path_offset; + memcpy(item_key, key, keylen); + + pos = kh_put(str, sc->map, item_key, &error); + if (error < 0) + goto done; + + if (!error) + kh_key(sc->map, pos) = item_key; + kh_val(sc->map, pos) = item; + + error = git_vector_insert(&sc->items, item); + if (error < 0) + git_strmap_delete_at(sc->map, pos); + +done: + if (out) + *out = !error ? item : NULL; + return error; +} + +/* lookup item by key */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) +{ + khiter_t pos = git_strmap_lookup_index(sc->map, key); + if (git_strmap_valid_index(sc->map, pos)) + return git_strmap_value_at(sc->map, pos); + return NULL; +} + +/* find out how many items are in the cache */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc) +{ + return git_vector_length(&sc->items); +} + +/* lookup item by index */ +void *git_sortedcache_entry(const git_sortedcache *sc, size_t pos) +{ + return git_vector_get(&sc->items, pos); +} diff --git a/src/sortedcache.h b/src/sortedcache.h new file mode 100644 index 000000000..5d0d8f5a7 --- /dev/null +++ b/src/sortedcache.h @@ -0,0 +1,101 @@ +/* + * 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_sorted_cache_h__ +#define INCLUDE_sorted_cache_h__ + +#include "util.h" +#include "fileops.h" +#include "vector.h" +#include "thread-utils.h" +#include "pool.h" +#include "strmap.h" + +/* + * The purpose of this data structure is to cache the parsed contents of a + * file where each item in the file can be identified by a key string and + * you want to both look them up by name and traverse them in sorted + * order. Each item is assumed to itself end in a GIT_FLEX_ARRAY. + */ + +typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); + +typedef struct { + git_refcount rc; + git_mutex lock; + size_t item_path_offset; + git_sortedcache_free_item_fn free_item; + void *free_item_payload; + git_pool pool; + git_vector items; + git_strmap *map; + git_futils_filestamp stamp; + char path[GIT_FLEX_ARRAY]; +} git_sortedcache; + +/* create a new sortedcache + * + * even though every sortedcache stores items with a GIT_FLEX_ARRAY at + * the end containing their key string, you have to provide the item_cmp + * sorting function because the sorting function doesn't get a payload + * and therefore can't know the offset to the item key string. :-( + */ +int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, /* use offsetof() macro */ + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path); + +/* copy a sorted cache + * + * - copy_item can be NULL to memcpy + * - locks src while copying + */ +int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload); + +/* free sorted cache (first calling free_item callbacks) */ +void git_sortedcache_free(git_sortedcache *sc); + +/* increment reference count */ +void git_sortedcache_incref(git_sortedcache *sc); + +/* release all items in sorted cache - lock during clear if lock is true */ +void git_sortedcache_clear(git_sortedcache *sc, bool lock); + +/* check file stamp to see if reload is required */ +bool git_sortedcache_out_of_date(git_sortedcache *sc); + +/* lock sortedcache while making modifications */ +int git_sortedcache_lock(git_sortedcache *sc); + +/* unlock sorted cache when done with modifications */ +int git_sortedcache_unlock(git_sortedcache *sc); + +/* if the file has changed, lock cache and load file contents into buf; + * @return 0 if up-to-date, 1 if out-of-date, <0 on error + */ +int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf); + +/* find and/or insert item, returning pointer to item data - lock first */ +int git_sortedcache_upsert( + void **out, git_sortedcache *sc, const char *key); + +/* lookup item by key */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); + +/* find out how many items are in the cache */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc); + +/* lookup item by index */ +void *git_sortedcache_entry(const git_sortedcache *sc, size_t pos); + +#endif diff --git a/src/vector.h b/src/vector.h index 1bda9c93d..ad1c34ea1 100644 --- a/src/vector.h +++ b/src/vector.h @@ -55,6 +55,11 @@ GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position) #define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) +GIT_INLINE(size_t) git_vector_length(const git_vector *v) +{ + return v->length; +} + GIT_INLINE(void *) git_vector_last(const git_vector *v) { return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; diff --git a/tests-clar/core/sortedcache.c b/tests-clar/core/sortedcache.c new file mode 100644 index 000000000..f192af31d --- /dev/null +++ b/tests-clar/core/sortedcache.c @@ -0,0 +1,298 @@ +#include "clar_libgit2.h" +#include "sortedcache.h" + +static int name_only_cmp(const void *a, const void *b) +{ + return strcmp(a, b); +} + +void test_core_sortedcache__name_only(void) +{ + git_sortedcache *sc; + void *item; + + cl_git_pass(git_sortedcache_new( + &sc, 0, NULL, NULL, name_only_cmp, NULL)); + + cl_git_pass(git_sortedcache_lock(sc)); + cl_git_pass(git_sortedcache_upsert(&item, sc, "aaa")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "bbb")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "zzz")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "mmm")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "iii")); + cl_git_pass(git_sortedcache_unlock(sc)); + + cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); + cl_assert_equal_s("aaa", item); + cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); + cl_assert_equal_s("mmm", item); + cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); + cl_assert_equal_s("zzz", item); + cl_assert(git_sortedcache_lookup(sc, "qqq") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bbb", item); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("iii", item); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("mmm", item); + cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); + cl_assert_equal_s("zzz", item); + cl_assert(git_sortedcache_entry(sc, 5) == NULL); + + git_sortedcache_clear(sc, true); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + + git_sortedcache_free(sc); +} + +typedef struct { + int value; + char smaller_value; + char path[GIT_FLEX_ARRAY]; +} sortedcache_test_struct; + +static int sortedcache_test_struct_cmp(const void *a_, const void *b_) +{ + const sortedcache_test_struct *a = a_, *b = b_; + return strcmp(a->path, b->path); +} + +static void sortedcache_test_struct_free(void *payload, void *item_) +{ + sortedcache_test_struct *item = item_; + int *count = payload; + (*count)++; + item->smaller_value = 0; +} + +void test_core_sortedcache__in_memory(void) +{ + git_sortedcache *sc; + sortedcache_test_struct *item; + int free_count = 0; + + cl_git_pass(git_sortedcache_new( + &sc, offsetof(sortedcache_test_struct, path), + sortedcache_test_struct_free, &free_count, + sortedcache_test_struct_cmp, NULL)); + + cl_git_pass(git_sortedcache_lock(sc)); + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "aaa")); + item->value = 10; + item->smaller_value = 1; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "bbb")); + item->value = 20; + item->smaller_value = 2; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "zzz")); + item->value = 30; + item->smaller_value = 26; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "mmm")); + item->value = 40; + item->smaller_value = 14; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "iii")); + item->value = 50; + item->smaller_value = 9; + cl_git_pass(git_sortedcache_unlock(sc)); + + cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); + cl_assert_equal_s("mmm", item->path); + cl_assert_equal_i(40, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "abc") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bbb", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("iii", item->path); + cl_assert_equal_i(50, item->value); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("mmm", item->path); + cl_assert_equal_i(40, item->value); + cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_entry(sc, 5) == NULL); + + cl_assert_equal_i(0, free_count); + + git_sortedcache_clear(sc, true); + + cl_assert_equal_i(5, free_count); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + + free_count = 0; + + cl_git_pass(git_sortedcache_lock(sc)); + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "testing")); + item->value = 10; + item->smaller_value = 3; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "again")); + item->value = 20; + item->smaller_value = 1; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "final")); + item->value = 30; + item->smaller_value = 2; + cl_git_pass(git_sortedcache_unlock(sc)); + + cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "testing")) != NULL); + cl_assert_equal_s("testing", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "again")) != NULL); + cl_assert_equal_s("again", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "zzz") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("again", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(30, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("testing", item->path); + cl_assert_equal_i(10, item->value); + cl_assert(git_sortedcache_entry(sc, 3) == NULL); + + git_sortedcache_free(sc); + + cl_assert_equal_i(3, free_count); +} + +static void sortedcache_test_reload(git_sortedcache *sc) +{ + int count = 0; + git_buf buf = GIT_BUF_INIT; + char *scan, *after; + sortedcache_test_struct *item; + + cl_assert(git_sortedcache_lockandload(sc, &buf) > 0); + + git_sortedcache_clear(sc, false); /* clear once we already have lock */ + + for (scan = buf.ptr; *scan; scan = after + 1) { + int val = strtol(scan, &after, 0); + cl_assert(after > scan); + scan = after; + + for (scan = after; git__isspace(*scan); ++scan) /* find start */; + for (after = scan; *after && *after != '\n'; ++after) /* find eol */; + *after = '\0'; + + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, scan)); + + item->value = val; + item->smaller_value = (char)(count++); + } + + cl_git_pass(git_sortedcache_unlock(sc)); + + git_buf_free(&buf); +} + +void test_core_sortedcache__on_disk(void) +{ + git_sortedcache *sc; + sortedcache_test_struct *item; + int free_count = 0; + + cl_git_mkfile("cacheitems.txt", "10 abc\n20 bcd\n30 cde\n"); + + cl_git_pass(git_sortedcache_new( + &sc, offsetof(sortedcache_test_struct, path), + sortedcache_test_struct_free, &free_count, + sortedcache_test_struct_cmp, "cacheitems.txt")); + + /* should need to reload the first time */ + + sortedcache_test_reload(sc); + + /* test what we loaded */ + + cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "cde")) != NULL); + cl_assert_equal_s("cde", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bcd", item->path); + cl_assert_equal_i(20, item->value); + cl_assert(git_sortedcache_entry(sc, 3) == NULL); + + /* should not need to reload this time */ + + cl_assert_equal_i(0, git_sortedcache_lockandload(sc, NULL)); + + /* rewrite ondisk file and reload */ + + cl_assert_equal_i(0, free_count); + + cl_git_rewritefile( + "cacheitems.txt", "100 abc\n200 zzz\n500 aaa\n10 final\n"); + sortedcache_test_reload(sc); + + cl_assert_equal_i(3, free_count); + + /* test what we loaded */ + + cl_assert_equal_sz(4, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(100, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(10, item->value); + cl_assert(git_sortedcache_lookup(sc, "cde") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(500, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(200, item->value); + + git_sortedcache_free(sc); + + cl_assert_equal_i(7, free_count); +} + From a4977169e1e1920ed33f10a77e5dd8706f103f4f Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 21 Aug 2013 14:09:38 -0700 Subject: [PATCH 272/367] Add sortedcache APIs to lookup index and remove This adds two other APIs that I need to the sortedcache type. --- src/sortedcache.c | 59 +++++++++++++++++++++++++++++++++++ src/sortedcache.h | 7 +++++ tests-clar/core/sortedcache.c | 52 ++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) diff --git a/src/sortedcache.c b/src/sortedcache.c index 6015d616d..2efa3c4e9 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -295,3 +295,62 @@ void *git_sortedcache_entry(const git_sortedcache *sc, size_t pos) { return git_vector_get(&sc->items, pos); } + +struct sortedcache_magic_key { + size_t offset; + const char *key; +}; + +static int sortedcache_magic_cmp(const void *key, const void *value) +{ + const struct sortedcache_magic_key *magic = key; + const char *value_key = ((const char *)value) + magic->offset; + return strcmp(magic->key, value_key); +} + +/* lookup index of item by key */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key) +{ + struct sortedcache_magic_key magic; + + magic.offset = sc->item_path_offset; + magic.key = key; + + return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic); +} + +/* remove entry from cache */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos, bool lock) +{ + int error = 0; + char *item; + khiter_t mappos; + + if (lock && git_sortedcache_lock(sc) < 0) + return -1; + + /* because of pool allocation, this can't actually remove the item, + * but we can remove it from the items vector and the hash table. + */ + + if ((item = git_vector_get(&sc->items, pos)) == NULL) { + giterr_set(GITERR_INVALID, "Removing item out of range"); + error = GIT_ENOTFOUND; + goto done; + } + + (void)git_vector_remove(&sc->items, pos); + + mappos = git_strmap_lookup_index(sc->map, item + sc->item_path_offset); + git_strmap_delete_at(sc->map, mappos); + + if (sc->free_item) + sc->free_item(sc->free_item_payload, item); + +done: + if (lock) + git_sortedcache_unlock(sc); + return error; +} + diff --git a/src/sortedcache.h b/src/sortedcache.h index 5d0d8f5a7..f63ad645b 100644 --- a/src/sortedcache.h +++ b/src/sortedcache.h @@ -98,4 +98,11 @@ size_t git_sortedcache_entrycount(const git_sortedcache *sc); /* lookup item by index */ void *git_sortedcache_entry(const git_sortedcache *sc, size_t pos); +/* lookup index of item by key */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key); + +/* remove entry from cache */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos, bool lock); + #endif diff --git a/tests-clar/core/sortedcache.c b/tests-clar/core/sortedcache.c index f192af31d..91415943d 100644 --- a/tests-clar/core/sortedcache.c +++ b/tests-clar/core/sortedcache.c @@ -10,6 +10,7 @@ void test_core_sortedcache__name_only(void) { git_sortedcache *sc; void *item; + size_t pos; cl_git_pass(git_sortedcache_new( &sc, 0, NULL, NULL, name_only_cmp, NULL)); @@ -44,6 +45,15 @@ void test_core_sortedcache__name_only(void) cl_assert_equal_s("zzz", item); cl_assert(git_sortedcache_entry(sc, 5) == NULL); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "iii")); + cl_assert_equal_sz(2, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); + cl_assert_equal_sz(4, pos); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "abc")); + git_sortedcache_clear(sc, true); cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); @@ -182,6 +192,34 @@ void test_core_sortedcache__in_memory(void) cl_assert_equal_i(10, item->value); cl_assert(git_sortedcache_entry(sc, 3) == NULL); + { + size_t pos; + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "again")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_remove(sc, pos, true)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "again")); + + cl_assert_equal_sz(2, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "testing")); + cl_assert_equal_sz(1, pos); + cl_git_pass(git_sortedcache_remove(sc, pos, true)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "testing")); + + cl_assert_equal_sz(1, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_remove(sc, pos, true)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "final")); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + } + git_sortedcache_free(sc); cl_assert_equal_i(3, free_count); @@ -223,6 +261,7 @@ void test_core_sortedcache__on_disk(void) git_sortedcache *sc; sortedcache_test_struct *item; int free_count = 0; + size_t pos; cl_git_mkfile("cacheitems.txt", "10 abc\n20 bcd\n30 cde\n"); @@ -291,6 +330,19 @@ void test_core_sortedcache__on_disk(void) cl_assert_equal_s("zzz", item->path); cl_assert_equal_i(200, item->value); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "abc")); + cl_assert_equal_sz(1, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); + cl_assert_equal_sz(2, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); + cl_assert_equal_sz(3, pos); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "missing")); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "cde")); + git_sortedcache_free(sc); cl_assert_equal_i(7, free_count); From 24c71f14b4dcb696d9d87330322c12fd2c185317 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 21 Aug 2013 14:10:27 -0700 Subject: [PATCH 273/367] Add internal ref set_name fn instead of realloc The refdb_fs implementation calls realloc directly on a reference object when it wants to rename it. It is not a public object, so this doesn't mess with the immutability of references, but it does assume certain constraints on the reference representation. This commit wraps that assumption in an isolated API to isolate it. --- src/refs.c | 11 +++++++++++ src/refs.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/refs.c b/src/refs.c index c0e460cc3..e59b34aae 100644 --- a/src/refs.c +++ b/src/refs.c @@ -88,6 +88,17 @@ git_reference *git_reference__alloc( return ref; } +git_reference *git_reference__set_name( + git_reference *ref, const char *name) +{ + size_t namelen = strlen(name); + git_reference *rewrite = + git__realloc(ref, sizeof(git_reference) + namelen + 1); + if (rewrite != NULL) + memcpy(rewrite->name, name, namelen + 1); + return rewrite; +} + void git_reference_free(git_reference *reference) { if (reference == NULL) diff --git a/src/refs.h b/src/refs.h index f487ee3fc..4bb49d02a 100644 --- a/src/refs.h +++ b/src/refs.h @@ -61,6 +61,8 @@ struct git_reference { char name[0]; }; +git_reference *git_reference__set_name(git_reference *ref, const char *name); + 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); From fe3727408001f9708aad09562ffae5b82c0f7171 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 21 Aug 2013 16:26:32 -0700 Subject: [PATCH 274/367] Rewrite refdb_fs using git_sortedcache object This adds thread safety to the refdb_fs by using the new git_sortedcache object and also by relaxing the handling of some filesystem errors where the fs may be changed out from under us. This also adds some new threading tests that hammer on the refdb. --- src/refdb_fs.c | 830 +++++++++++++++---------------------- tests-clar/threads/refdb.c | 200 +++++++++ 2 files changed, 524 insertions(+), 506 deletions(-) create mode 100644 tests-clar/threads/refdb.c diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 658bebb61..876e84588 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -15,6 +15,7 @@ #include "refdb.h" #include "refdb_fs.h" #include "iterator.h" +#include "sortedcache.h" #include #include @@ -53,243 +54,149 @@ typedef struct refdb_fs_backend { git_repository *repo; char *path; - git_refcache refcache; + git_sortedcache *refcache; int peeling_mode; } refdb_fs_backend; -static int reference_read( - git_buf *file_content, - time_t *mtime, - const char *repo_path, - const char *ref_name, - int *updated) +static int packref_cmp(const void *a_, const void *b_) { - 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; + const struct packref *a = a_, *b = b_; + return strcmp(a->name, b->name); } -static int packed_parse_oid( - struct packref **ref_out, - const char **buffer_out, - const char *buffer_end) +static int packed_reload(refdb_fs_backend *backend) { - struct packref *ref = NULL; + int error; + git_buf packedrefs = GIT_BUF_INIT; + char *scan, *eof, *eol; - 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__calloc(1, 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_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; - - 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; - } - - tag_ref->flags |= PACKREF_HAS_PEEL; - *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); - } - - if (backend->path == NULL) + if (!backend->path) return 0; - result = reference_read(&packfile, &ref_cache->packfile_time, - backend->path, GIT_PACKEDREFS_FILE, &updated); + error = git_sortedcache_lockandload(backend->refcache, &packedrefs); /* - * 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 we can't find the packed-refs, clear table and return. + * Any other error just gets passed through. + * If no error, and file wasn't changed, 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 (error <= 0) { + if (error == GIT_ENOTFOUND) { + git_sortedcache_clear(backend->refcache, true); + giterr_clear(); + error = 0; + } + return error; } - if (result < 0) - return -1; + /* At this point, refresh the packed refs from the loaded buffer. */ - if (!updated) - return 0; + git_sortedcache_clear(backend->refcache, false); - /* - * 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; + scan = (char *)packedrefs.ptr; + eof = scan + packedrefs.size; backend->peeling_mode = PEELING_NONE; - if (buffer_start[0] == '#') { + if (*scan == '#') { static const char *traits_header = "# pack-refs with: "; - if (git__prefixcmp(buffer_start, traits_header) == 0) { - char *traits = (char *)buffer_start + strlen(traits_header); - char *traits_end = strchr(traits, '\n'); + if (git__prefixcmp(scan, traits_header) == 0) { + scan += strlen(traits_header); + eol = strchr(scan, '\n'); - if (traits_end == NULL) + if (!eol) goto parse_failed; + *eol = '\0'; - *traits_end = '\0'; - - if (strstr(traits, " fully-peeled ") != NULL) { + if (strstr(scan, " fully-peeled ") != NULL) { backend->peeling_mode = PEELING_FULL; - } else if (strstr(traits, " peeled ") != NULL) { + } else if (strstr(scan, " peeled ") != NULL) { backend->peeling_mode = PEELING_STANDARD; } - buffer_start = traits_end + 1; + scan = eol + 1; } } - while (buffer_start < buffer_end && buffer_start[0] == '#') { - buffer_start = strchr(buffer_start, '\n'); - if (buffer_start == NULL) + while (scan < eof && *scan == '#') { + if (!(eol = strchr(scan, '\n'))) goto parse_failed; - - buffer_start++; + scan = eol + 1; } - while (buffer_start < buffer_end) { - int err; - struct packref *ref = NULL; + while (scan < eof) { + struct packref *ref; + git_oid oid; - if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) + /* parse " \n" */ + + if (git_oid_fromstr(&oid, scan) < 0) goto parse_failed; + scan += GIT_OID_HEXSZ; - if (buffer_start[0] == '^') { - if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) + if (*scan++ != ' ') + goto parse_failed; + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + *eol = '\0'; + if (eol[-1] == '\r') + eol[-1] = '\0'; + + if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0) + goto parse_failed; + scan = eol + 1; + + git_oid_cpy(&ref->oid, &oid); + + /* look for optional "^\n" */ + + if (*scan == '^') { + if (git_oid_fromstr(&oid, scan + 1) < 0) goto parse_failed; - } else if (backend->peeling_mode == PEELING_FULL || - (backend->peeling_mode == PEELING_STANDARD && - git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) { - ref->flags |= PACKREF_CANNOT_PEEL; - } + scan += GIT_OID_HEXSZ + 1; - git_strmap_insert(ref_cache->packfile, ref->name, ref, err); - if (err < 0) - goto parse_failed; + if (scan < eof) { + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + scan = eol + 1; + } + + git_oid_cpy(&ref->peel, &oid); + ref->flags |= PACKREF_HAS_PEEL; + } + else if (backend->peeling_mode == PEELING_FULL || + (backend->peeling_mode == PEELING_STANDARD && + git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) + ref->flags |= PACKREF_CANNOT_PEEL; } - git_buf_free(&packfile); + git_sortedcache_unlock(backend->refcache); + git_buf_free(&packedrefs); + return 0; parse_failed: - git_strmap_free(ref_cache->packfile); - ref_cache->packfile = NULL; - git_buf_free(&packfile); + giterr_set(GITERR_REFERENCE, "Corrupted packed references file"); + + git_sortedcache_clear(backend->refcache, false); + git_sortedcache_unlock(backend->refcache); + git_buf_free(&packedrefs); + return -1; } -static int loose_parse_oid(git_oid *oid, const char *filename, git_buf *file_content) +static int loose_parse_oid( + git_oid *oid, const char *filename, git_buf *file_content) { - size_t len; - const char *str; + const char *str = git_buf_cstr(file_content); - len = git_buf_len(file_content); - if (len < GIT_OID_HEXSZ) + if (git_buf_len(file_content) < 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) + if (git_oid_fromstr(oid, str) < 0) goto corrupted; /* If the file is longer than 40 chars, the 41st must be a space */ @@ -302,77 +209,71 @@ corrupted: return -1; } -static int loose_lookup_to_packfile( - struct packref **ref_out, - refdb_fs_backend *backend, - const char *name) +static int loose_readbuffer(git_buf *buf, const char *base, const char *path) { - git_buf ref_file = GIT_BUF_INIT; - struct packref *ref = NULL; - size_t name_len; + int error; - *ref_out = NULL; + /* build full path to file */ + if ((error = git_buf_joinpath(buf, base, path)) < 0 || + (error = git_futils_readbuffer(buf, buf->ptr)) < 0) + git_buf_free(buf); - if (reference_read(&ref_file, NULL, backend->path, name, NULL) < 0) - return -1; - - /* skip symbolic refs */ - if (!git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF)) { - git_buf_free(&ref_file); - return 0; - } - - git_buf_rtrim(&ref_file); - - name_len = strlen(name); - ref = git__calloc(1, 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, name, &ref_file) < 0) { - git_buf_free(&ref_file); - git__free(ref); - return -1; - } - - ref->flags = PACKREF_WAS_LOOSE; - - *ref_out = ref; - git_buf_free(&ref_file); - return 0; + return error; } +static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) +{ + int error = 0; + git_buf ref_file = GIT_BUF_INIT; + struct packref *ref = NULL; + git_oid oid; + + /* if we fail to load the loose reference, assume someone changed + * the filesystem under us and skip it... + */ + if (loose_readbuffer(&ref_file, backend->path, name) < 0) { + giterr_clear(); + goto done; + } + + /* skip symbolic refs */ + if (!git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF)) + goto done; + + /* parse OID from file */ + if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0) + goto done; + + git_sortedcache_lock(backend->refcache); + + if (!(error = git_sortedcache_upsert( + (void **)&ref, backend->refcache, name))) { + + git_oid_cpy(&ref->oid, &oid); + ref->flags = PACKREF_WAS_LOOSE; + } + + git_sortedcache_unlock(backend->refcache); + +done: + git_buf_free(&ref_file); + return error; +} 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) + if (git__suffixcmp(full_path->ptr, ".lock") == 0) + return 0; + + if (git_path_isdir(full_path->ptr)) 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; - - if (!ref) - return 0; - - 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; + return loose_lookup_to_packfile(backend, file_path); } /* @@ -383,11 +284,8 @@ static int _dirent_loose_load(void *data, git_buf *full_path) */ static int packed_loadloose(refdb_fs_backend *backend) { + int error; 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; @@ -397,10 +295,11 @@ static int packed_loadloose(refdb_fs_backend *backend) * This will overwrite any old packed entries with their * updated loose versions */ - result = git_path_direach(&refs_path, _dirent_loose_load, backend); + error = git_path_direach(&refs_path, _dirent_loose_load, backend); + git_buf_free(&refs_path); - return result; + return error; } static int refdb_fs_backend__exists( @@ -408,23 +307,17 @@ static int refdb_fs_backend__exists( git_refdb_backend *_backend, const char *ref_name) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; git_buf ref_path = GIT_BUF_INIT; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - if (packed_load(backend) < 0) + if (packed_reload(backend) < 0 || + git_buf_joinpath(&ref_path, backend->path, ref_name) < 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; + *exists = git_path_isfile(ref_path.ptr) || + git_sortedcache_lookup(backend->refcache, ref_name); git_buf_free(&ref_path); return 0; @@ -456,69 +349,39 @@ static int loose_lookup( refdb_fs_backend *backend, const char *ref_name) { - const char *target; - git_oid oid; git_buf ref_file = GIT_BUF_INIT; int error = 0; if (out) *out = NULL; - error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL); + if ((error = loose_readbuffer(&ref_file, backend->path, ref_name)) < 0) + /* cannot read loose ref file - gah */; + else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) { + const char *target; - if (error < 0) - goto done; - - if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) { git_buf_rtrim(&ref_file); - if ((target = loose_parse_symbolic(&ref_file)) == NULL) { + if (!(target = loose_parse_symbolic(&ref_file))) error = -1; - goto done; - } - - if (out) + else if (out != NULL) *out = git_reference__alloc_symbolic(ref_name, target); } else { - if ((error = loose_parse_oid(&oid, ref_name, &ref_file)) < 0) - goto done; + git_oid oid; - if (out) + if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) && + out != NULL) *out = git_reference__alloc(ref_name, &oid, NULL); } - if (out && *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) +static int ref_error_notfound(const char *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; + giterr_set(GITERR_REFERENCE, "Reference '%s' not found", name); + return GIT_ENOTFOUND; } static int packed_lookup( @@ -526,18 +389,25 @@ static int packed_lookup( refdb_fs_backend *backend, const char *ref_name) { - struct packref *entry; - khiter_t pos; int error = 0; + struct packref *entry; - if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0) - return error; - - if ((*out = git_reference__alloc(ref_name, - &entry->oid, &entry->peel)) == NULL) + if (packed_reload(backend) < 0) return -1; - return 0; + git_sortedcache_lock(backend->refcache); + + entry = git_sortedcache_lookup(backend->refcache, ref_name); + if (!entry) { + error = ref_error_notfound(ref_name); + } else { + *out = git_reference__alloc(ref_name, &entry->oid, &entry->peel); + if (!*out) + error = -1; + } + + git_sortedcache_unlock(backend->refcache); + return error; } static int refdb_fs_backend__lookup( @@ -545,24 +415,22 @@ static int refdb_fs_backend__lookup( git_refdb_backend *_backend, const char *ref_name) { - refdb_fs_backend *backend; - int result; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + int error; - assert(_backend); + assert(backend); - backend = (refdb_fs_backend *)_backend; - - if ((result = loose_lookup(out, backend, ref_name)) == 0) + if (!(error = loose_lookup(out, backend, ref_name))) 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) { + if (error == GIT_ENOTFOUND) { giterr_clear(); - result = packed_lookup(out, backend, ref_name); + error = packed_lookup(out, backend, ref_name); } - return result; + return error; } typedef struct { @@ -573,8 +441,8 @@ typedef struct { git_pool pool; git_vector loose; - unsigned int loose_pos; - khiter_t packed_pos; + size_t loose_pos; + size_t packed_pos; } refdb_fs_iter; static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) @@ -589,7 +457,6 @@ static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) { int error = 0; - git_strmap *packfile = backend->refcache.packfile; git_buf path = GIT_BUF_INIT; git_iterator *fsit = NULL; const git_index_entry *entry = NULL; @@ -597,18 +464,18 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) if (!backend->path) /* do nothing if no path for loose refs */ return 0; - if (git_buf_printf(&path, "%s/refs", backend->path) < 0) - return -1; + if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 || + (error = git_iterator_for_filesystem( + &fsit, git_buf_cstr(&path), 0, NULL, NULL)) < 0) { + git_buf_free(&path); + return error; + } - if ((error = git_iterator_for_filesystem( - &fsit, git_buf_cstr(&path), 0, NULL, NULL)) < 0 || - (error = git_vector_init(&iter->loose, 8, NULL)) < 0 || - (error = git_buf_sets(&path, GIT_REFS_DIR)) < 0) - goto cleanup; + error = git_buf_sets(&path, GIT_REFS_DIR); - while (!git_iterator_advance(&entry, fsit)) { + while (!error && !git_iterator_advance(&entry, fsit)) { const char *ref_name; - khiter_t pos; + struct packref *ref; char *ref_dup; git_buf_truncate(&path, strlen(GIT_REFS_DIR)); @@ -619,26 +486,23 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0)) continue; - pos = git_strmap_lookup_index(packfile, ref_name); - if (git_strmap_valid_index(packfile, pos)) { - struct packref *ref = git_strmap_value_at(packfile, pos); + git_sortedcache_lock(backend->refcache); + ref = git_sortedcache_lookup(backend->refcache, ref_name); + if (ref) ref->flags |= PACKREF_SHADOWED; - } + git_sortedcache_unlock(backend->refcache); - if (!(ref_dup = git_pool_strdup(&iter->pool, ref_name))) { + ref_dup = git_pool_strdup(&iter->pool, ref_name); + if (!ref_dup) error = -1; - goto cleanup; - } - - if ((error = git_vector_insert(&iter->loose, ref_dup)) < 0) - goto cleanup; + else + error = git_vector_insert(&iter->loose, ref_dup); } -cleanup: git_iterator_free(fsit); git_buf_free(&path); - return 0; + return error; } static int refdb_fs_backend__iterator_next( @@ -646,7 +510,7 @@ static int refdb_fs_backend__iterator_next( { refdb_fs_iter *iter = (refdb_fs_iter *)_iter; refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; - git_strmap *packfile = backend->refcache.packfile; + struct packref *ref; while (iter->loose_pos < iter->loose.length) { const char *path = git_vector_get(&iter->loose, iter->loose_pos++); @@ -657,17 +521,12 @@ static int refdb_fs_backend__iterator_next( giterr_clear(); } - while (iter->packed_pos < kh_end(packfile)) { - struct packref *ref = NULL; + git_sortedcache_lock(backend->refcache); - while (!kh_exist(packfile, iter->packed_pos)) { - iter->packed_pos++; - if (iter->packed_pos == kh_end(packfile)) - return GIT_ITEROVER; - } - - ref = kh_val(packfile, iter->packed_pos); - iter->packed_pos++; + while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { + ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + if (!ref) /* stop now if another thread deleted refs and we past end */ + break; if (ref->flags & PACKREF_SHADOWED) continue; @@ -676,12 +535,11 @@ static int refdb_fs_backend__iterator_next( continue; *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); - if (*out == NULL) - return -1; - - return 0; + git_sortedcache_unlock(backend->refcache); + return (*out != NULL) ? 0 : -1; } + git_sortedcache_unlock(backend->refcache); return GIT_ITEROVER; } @@ -690,14 +548,10 @@ static int refdb_fs_backend__iterator_next_name( { refdb_fs_iter *iter = (refdb_fs_iter *)_iter; refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; - git_strmap *packfile = backend->refcache.packfile; while (iter->loose_pos < iter->loose.length) { const char *path = git_vector_get(&iter->loose, iter->loose_pos++); - if (git_strmap_exists(packfile, path)) - continue; - if (loose_lookup(NULL, backend, path) != 0) { giterr_clear(); continue; @@ -707,22 +561,25 @@ static int refdb_fs_backend__iterator_next_name( return 0; } - while (iter->packed_pos < kh_end(packfile)) { - while (!kh_exist(packfile, iter->packed_pos)) { - iter->packed_pos++; - if (iter->packed_pos == kh_end(packfile)) - return GIT_ITEROVER; - } + git_sortedcache_lock(backend->refcache); - *out = kh_key(packfile, iter->packed_pos); - iter->packed_pos++; + while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { + struct packref *ref = + git_sortedcache_entry(backend->refcache, iter->packed_pos++); + + if (ref->flags & PACKREF_SHADOWED) + continue; + + *out = ref->name; if (iter->glob && p_fnmatch(iter->glob, *out, 0) != 0) continue; + git_sortedcache_unlock(backend->refcache); return 0; } + git_sortedcache_unlock(backend->refcache); return GIT_ITEROVER; } @@ -730,18 +587,18 @@ static int refdb_fs_backend__iterator( git_reference_iterator **out, git_refdb_backend *_backend, const char *glob) { refdb_fs_iter *iter; - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - if (packed_load(backend) < 0) + if (packed_reload(backend) < 0) return -1; iter = git__calloc(1, sizeof(refdb_fs_iter)); GITERR_CHECK_ALLOC(iter); - if (git_pool_init(&iter->pool, 1, 0) < 0) + if (git_pool_init(&iter->pool, 1, 0) < 0 || + git_vector_init(&iter->loose, 8, NULL) < 0) goto fail; if (glob != NULL && @@ -786,15 +643,16 @@ static int reference_path_available( const char* old_ref, int force) { - struct packref *this_ref; + size_t i; - if (packed_load(backend) < 0) + if (packed_reload(backend) < 0) return -1; if (!force) { int exists; - if (refdb_fs_backend__exists(&exists, (git_refdb_backend *)backend, new_ref) < 0) + if (refdb_fs_backend__exists( + &exists, (git_refdb_backend *)backend, new_ref) < 0) return -1; if (exists) { @@ -805,14 +663,21 @@ static int reference_path_available( } } - git_strmap_foreach_value(backend->refcache.packfile, this_ref, { + git_sortedcache_lock(backend->refcache); + + for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { + struct packref *this_ref = + git_sortedcache_entry(backend->refcache, i); + if (!ref_is_available(old_ref, new_ref, this_ref->name)) { + git_sortedcache_unlock(backend->refcache); giterr_set(GITERR_REFERENCE, "The path to reference '%s' collides with an existing one", new_ref); return -1; } - }); + } + git_sortedcache_unlock(backend->refcache); return 0; } @@ -839,12 +704,9 @@ static int loose_write(refdb_fs_backend *backend, const git_reference *ref) 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_oid_nfmt(oid, sizeof(oid), &ref->target.oid); 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 { @@ -854,14 +716,6 @@ static int loose_write(refdb_fs_backend *backend, const git_reference *ref) 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. * @@ -914,9 +768,7 @@ static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref) 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; + git_oid_nfmt(oid, sizeof(oid), &ref->oid); /* * For references that peel to an object in the repo, we must @@ -930,8 +782,7 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file) */ if (ref->flags & PACKREF_HAS_PEEL) { char peel[GIT_OID_HEXSZ + 1]; - git_oid_fmt(peel, &ref->peel); - peel[GIT_OID_HEXSZ] = 0; + git_oid_nfmt(peel, sizeof(peel), &ref->peel); if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) return -1; @@ -954,16 +805,16 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file) * is well-written, because we are destructing references * here otherwise. */ -static int packed_remove_loose( - refdb_fs_backend *backend, - git_vector *packing_list) +static int packed_remove_loose(refdb_fs_backend *backend) { 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); + /* backend->refcache is already locked when this is called */ + + for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { + struct packref *ref = git_sortedcache_entry(backend->refcache, i); if ((ref->flags & PACKREF_WAS_LOOSE) == 0) continue; @@ -971,14 +822,13 @@ static int packed_remove_loose( 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 (git_path_exists(full_path.ptr) && 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; } @@ -1002,33 +852,14 @@ 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) + /* lock the cache to updates while we do this */ + if (git_sortedcache_lock(backend->refcache) < 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) + /* 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) @@ -1040,8 +871,9 @@ static int packed_write(refdb_fs_backend *backend) 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); + for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { + struct packref *ref = + git_sortedcache_entry(backend->refcache, i); if (packed_find_peel(backend, ref) < 0) goto cleanup_packfile; @@ -1057,16 +889,16 @@ static int packed_write(refdb_fs_backend *backend) /* 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; + if (packed_remove_loose(backend) < 0) + goto cleanup_memory; - { - struct stat st; - if (p_stat(pack_file_path.ptr, &st) == 0) - backend->refcache.packfile_time = st.st_mtime; - } + git_sortedcache_unlock(backend->refcache); + + /* update filestamp to latest value */ + if (git_futils_filestamp_check( + &backend->refcache->stamp, pack_file_path.ptr) < 0) + giterr_clear(); - git_vector_free(&packing_list); git_buf_free(&pack_file_path); /* we're good now */ @@ -1076,7 +908,7 @@ cleanup_packfile: git_filebuf_cleanup(&pack_file); cleanup_memory: - git_vector_free(&packing_list); + git_sortedcache_unlock(backend->refcache); git_buf_free(&pack_file_path); return -1; @@ -1087,11 +919,10 @@ static int refdb_fs_backend__write( const git_reference *ref, int force) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; int error; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); error = reference_path_available(backend, ref->name, NULL, force); if (error < 0) @@ -1104,17 +935,13 @@ static int refdb_fs_backend__delete( git_refdb_backend *_backend, const char *ref_name) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; git_buf loose_path = GIT_BUF_INIT; - struct packref *pack_ref; - khiter_t pack_ref_pos; + size_t pack_pos; int error = 0; bool loose_deleted = 0; - assert(_backend); - assert(ref_name); - - backend = (refdb_fs_backend *)_backend; + assert(backend && ref_name); /* If a loose reference exists, remove it from the filesystem */ if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0) @@ -1130,19 +957,23 @@ static int refdb_fs_backend__delete( if (error != 0) return error; + if (packed_reload(backend) < 0) + return -1; + /* If a packed reference exists, remove it from the packfile and repack */ - error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref_name); + if (git_sortedcache_lock(backend->refcache) < 0) + return -1; + + if (!(error = git_sortedcache_lookup_index( + &pack_pos, backend->refcache, ref_name))) + error = git_sortedcache_remove(backend->refcache, pack_pos, false); + + git_sortedcache_unlock(backend->refcache); if (error == GIT_ENOTFOUND) - return loose_deleted ? 0 : GIT_ENOTFOUND; + return loose_deleted ? 0 : ref_error_notfound(ref_name); - if (error == 0) { - git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos); - git__free(pack_ref); - error = packed_write(backend); - } - - return error; + return packed_write(backend); } static int refdb_fs_backend__rename( @@ -1152,53 +983,44 @@ static int refdb_fs_backend__rename( const char *new_name, int force) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; git_reference *old, *new; int error; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - error = reference_path_available(backend, new_name, old_name, force); - if (error < 0) + if ((error = reference_path_available( + backend, new_name, old_name, force)) < 0 || + (error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0) return error; - error = refdb_fs_backend__lookup(&old, _backend, old_name); - if (error < 0) - return error; - - error = refdb_fs_backend__delete(_backend, old_name); - if (error < 0) { + if ((error = refdb_fs_backend__delete(_backend, old_name)) < 0) { git_reference_free(old); return error; } - new = realloc(old, sizeof(git_reference) + strlen(new_name) + 1); - memcpy(new->name, new_name, strlen(new_name) + 1); + new = git_reference__set_name(old, new_name); + if (!new) { + git_reference_free(old); + return -1; + } - error = loose_write(backend, new); - if (error < 0) { + if ((error = loose_write(backend, new)) < 0 || out == NULL) { git_reference_free(new); return error; } - if (out) { - *out = new; - } else { - git_reference_free(new); - } - + *out = new; return 0; } static int refdb_fs_backend__compress(git_refdb_backend *_backend) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - if (packed_load(backend) < 0 || /* load the existing packfile */ + if (packed_reload(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; @@ -1206,29 +1028,13 @@ static int refdb_fs_backend__compress(git_refdb_backend *_backend) 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; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - refcache_free(&backend->refcache); + git_sortedcache_free(backend->refcache); git__free(backend->path); git__free(backend); } @@ -1252,7 +1058,7 @@ static int setup_namespace(git_buf *path, git_repository *repo) if (parts == NULL) return -1; - /** + /* * From `man gitnamespaces`: * namespaces which include a / will expand to a hierarchy * of namespaces; for example, GIT_NAMESPACE=foo/bar will store @@ -1269,7 +1075,7 @@ static int setup_namespace(git_buf *path, git_repository *repo) if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0) return -1; - /* Return the root of the namespaced path, i.e. without the trailing '/refs' */ + /* Return root of the namespaced path, i.e. without the trailing '/refs' */ git_buf_rtruncate_at_char(path, '/'); return 0; } @@ -1286,13 +1092,19 @@ int git_refdb_backend_fs( backend->repo = repository; - if (setup_namespace(&path, repository) < 0) { - git__free(backend); - return -1; - } + if (setup_namespace(&path, repository) < 0) + goto fail; backend->path = git_buf_detach(&path); + if (git_buf_joinpath(&path, backend->path, GIT_PACKEDREFS_FILE) < 0 || + git_sortedcache_new( + &backend->refcache, offsetof(struct packref, name), + NULL, NULL, packref_cmp, git_buf_cstr(&path)) < 0) + goto fail; + + git_buf_free(&path); + backend->parent.exists = &refdb_fs_backend__exists; backend->parent.lookup = &refdb_fs_backend__lookup; backend->parent.iterator = &refdb_fs_backend__iterator; @@ -1304,4 +1116,10 @@ int git_refdb_backend_fs( *backend_out = (git_refdb_backend *)backend; return 0; + +fail: + git_buf_free(&path); + git__free(backend->path); + git__free(backend); + return -1; } diff --git a/tests-clar/threads/refdb.c b/tests-clar/threads/refdb.c new file mode 100644 index 000000000..11e90a458 --- /dev/null +++ b/tests-clar/threads/refdb.c @@ -0,0 +1,200 @@ +#include "clar_libgit2.h" +#include "git2/refdb.h" +#include "refdb.h" + +static git_repository *g_repo; +static int g_expected = 0; + +void test_threads_refdb__initialize(void) +{ + g_repo = NULL; +} + +void test_threads_refdb__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +#define REPEAT 20 +#define THREADS 20 + +static void *iterate_refs(void *arg) +{ + git_reference_iterator *i; + git_reference *ref; + int count = 0, *id = arg; + + usleep(THREADS - *id); + + cl_git_pass(git_reference_iterator_new(&i, g_repo)); + + for (count = 0; !git_reference_next(&ref, i); ++count) { + cl_assert(ref != NULL); + git_reference_free(ref); + } + + if (g_expected > 0) + cl_assert_equal_i(g_expected, count); + + git_reference_iterator_free(i); + + return arg; +} + +void test_threads_refdb__iterator(void) +{ + int r, t; + git_thread th[THREADS]; + int id[THREADS]; + git_oid head; + git_reference *ref; + char name[128]; + git_refdb *refdb; + + g_repo = cl_git_sandbox_init("testrepo2"); + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + /* make a bunch of references */ + + for (r = 0; r < 200; ++r) { + snprintf(name, sizeof(name), "refs/heads/direct-%03d", r); + cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0)); + git_reference_free(ref); + } + + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + + g_expected = 206; + + for (r = 0; r < REPEAT; ++r) { + g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */ + + for (t = 0; t < THREADS; ++t) { + id[t] = t; + cl_git_pass(git_thread_create(&th[t], NULL, iterate_refs, &id[t])); + } + + for (t = 0; t < THREADS; ++t) { + cl_git_pass(git_thread_join(th[t], NULL)); + } + + memset(th, 0, sizeof(th)); + } +} + +static void *create_refs(void *arg) +{ + int *id = arg, i; + git_oid head; + char name[128]; + git_reference *ref[10]; + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + for (i = 0; i < 10; ++i) { + snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", *id, i); + cl_git_pass(git_reference_create(&ref[i], g_repo, name, &head, 0)); + + if (i == 5) { + git_refdb *refdb; + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + } + } + + for (i = 0; i < 10; ++i) + git_reference_free(ref[i]); + + return arg; +} + +static void *delete_refs(void *arg) +{ + int *id = arg, i; + git_reference *ref; + char name[128]; + + for (i = 0; i < 10; ++i) { + snprintf( + name, sizeof(name), "refs/heads/thread-%03d-%02d", (*id) & ~0x3, i); + + if (!git_reference_lookup(&ref, g_repo, name)) { + fprintf(stderr, "deleting %s\n", name); + cl_git_pass(git_reference_delete(ref)); + git_reference_delete(ref); + } + + if (i == 5) { + git_refdb *refdb; + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + } + } + + return arg; +} + +void test_threads_refdb__edit_while_iterate(void) +{ + int r, t; + git_thread th[THREADS]; + int id[THREADS]; + git_oid head; + git_reference *ref; + char name[128]; + git_refdb *refdb; + + g_repo = cl_git_sandbox_init("testrepo2"); + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + /* make a bunch of references */ + + for (r = 0; r < 50; ++r) { + snprintf(name, sizeof(name), "refs/heads/starter-%03d", r); + cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0)); + git_reference_free(ref); + } + + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + + g_expected = -1; + + g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */ + + for (t = 0; t < THREADS; ++t) { + void *(*fn)(void *arg); + + switch (t & 0x3) { + case 0: fn = create_refs; break; + case 1: fn = delete_refs; break; + default: fn = iterate_refs; break; + } + + id[t] = t; + cl_git_pass(git_thread_create(&th[t], NULL, fn, &id[t])); + } + + for (t = 0; t < THREADS; ++t) { + cl_git_pass(git_thread_join(th[t], NULL)); + } + + memset(th, 0, sizeof(th)); + + for (t = 0; t < THREADS; ++t) { + id[t] = t; + cl_git_pass(git_thread_create(&th[t], NULL, iterate_refs, &id[t])); + } + + for (t = 0; t < THREADS; ++t) { + cl_git_pass(git_thread_join(th[t], NULL)); + } +} From b37359aac5b02f59c07b453b53191432abc0f942 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 21 Aug 2013 16:50:03 -0700 Subject: [PATCH 275/367] Fix warnings when compiling without threads --- src/sortedcache.c | 2 ++ tests-clar/threads/refdb.c | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/sortedcache.c b/src/sortedcache.c index 2efa3c4e9..d7e74d3e5 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -176,6 +176,8 @@ bool git_sortedcache_out_of_date(git_sortedcache *sc) /* lock sortedcache while making modifications */ int git_sortedcache_lock(git_sortedcache *sc) { + GIT_UNUSED(sc); /* to prevent warning when compiled w/o threads */ + if (git_mutex_lock(&sc->lock) < 0) { giterr_set(GITERR_OS, "Unable to acquire mutex lock"); return -1; diff --git a/tests-clar/threads/refdb.c b/tests-clar/threads/refdb.c index 11e90a458..ffe437575 100644 --- a/tests-clar/threads/refdb.c +++ b/tests-clar/threads/refdb.c @@ -23,9 +23,7 @@ static void *iterate_refs(void *arg) { git_reference_iterator *i; git_reference *ref; - int count = 0, *id = arg; - - usleep(THREADS - *id); + int count = 0; cl_git_pass(git_reference_iterator_new(&i, g_repo)); @@ -75,12 +73,19 @@ void test_threads_refdb__iterator(void) for (t = 0; t < THREADS; ++t) { id[t] = t; +#ifdef GIT_THREADS cl_git_pass(git_thread_create(&th[t], NULL, iterate_refs, &id[t])); +#else + th[t] = t; + iterate_refs(&id[t]); +#endif } +#ifdef GIT_THREADS for (t = 0; t < THREADS; ++t) { cl_git_pass(git_thread_join(th[t], NULL)); } +#endif memset(th, 0, sizeof(th)); } @@ -124,7 +129,6 @@ static void *delete_refs(void *arg) name, sizeof(name), "refs/heads/thread-%03d-%02d", (*id) & ~0x3, i); if (!git_reference_lookup(&ref, g_repo, name)) { - fprintf(stderr, "deleting %s\n", name); cl_git_pass(git_reference_delete(ref)); git_reference_delete(ref); } @@ -180,9 +184,15 @@ void test_threads_refdb__edit_while_iterate(void) } id[t] = t; +#ifdef GIT_THREADS cl_git_pass(git_thread_create(&th[t], NULL, fn, &id[t])); +#else + th[t] = t; + fn(&id[t]); +#endif } +#ifdef GIT_THREADS for (t = 0; t < THREADS; ++t) { cl_git_pass(git_thread_join(th[t], NULL)); } @@ -197,4 +207,5 @@ void test_threads_refdb__edit_while_iterate(void) for (t = 0; t < THREADS; ++t) { cl_git_pass(git_thread_join(th[t], NULL)); } +#endif } From e8c5eb5537cc58fa29d4ed48ab9114238ba4cab0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 21 Aug 2013 22:44:56 -0700 Subject: [PATCH 276/367] No need to lock newly created tgt in copy --- src/sortedcache.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sortedcache.c b/src/sortedcache.c index d7e74d3e5..d0a8031c3 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -130,9 +130,6 @@ int git_sortedcache_copy( return -1; } - if (git_sortedcache_lock(tgt) < 0) - goto fail; - git_vector_foreach(&src->items, i, src_item) { if (git_sortedcache_upsert( &tgt_item, tgt, ((char *)src_item) + src->item_path_offset) < 0) @@ -141,7 +138,6 @@ int git_sortedcache_copy( goto fail; } - git_sortedcache_unlock(tgt); git_sortedcache_unlock(src); *out = tgt; From 3eecadcce583fd7e825e35e2b6f101071c2be613 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 21 Aug 2013 22:50:37 -0700 Subject: [PATCH 277/367] Improve comments on locking for sortedcache APIs --- src/sortedcache.h | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/sortedcache.h b/src/sortedcache.h index f63ad645b..c1c9d1341 100644 --- a/src/sortedcache.h +++ b/src/sortedcache.h @@ -62,22 +62,24 @@ int git_sortedcache_copy( int (*copy_item)(void *payload, void *tgt_item, void *src_item), void *payload); -/* free sorted cache (first calling free_item callbacks) */ +/* free sorted cache (first calling free_item callbacks) + * don't call on a locked collection - it may acquire a lock + */ void git_sortedcache_free(git_sortedcache *sc); -/* increment reference count */ +/* increment reference count - balance with call to free */ void git_sortedcache_incref(git_sortedcache *sc); -/* release all items in sorted cache - lock during clear if lock is true */ +/* release all items in sorted cache - lock during clear if `lock` is true */ void git_sortedcache_clear(git_sortedcache *sc, bool lock); /* check file stamp to see if reload is required */ bool git_sortedcache_out_of_date(git_sortedcache *sc); -/* lock sortedcache while making modifications */ +/* lock sortedcache during access or modification */ int git_sortedcache_lock(git_sortedcache *sc); -/* unlock sorted cache when done with modifications */ +/* unlock sorted cache when done */ int git_sortedcache_unlock(git_sortedcache *sc); /* if the file has changed, lock cache and load file contents into buf; @@ -85,24 +87,33 @@ int git_sortedcache_unlock(git_sortedcache *sc); */ int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf); -/* find and/or insert item, returning pointer to item data - lock first */ +/* find and/or insert item, returning pointer to item data + * should only call on locked collection + */ int git_sortedcache_upsert( void **out, git_sortedcache *sc, const char *key); -/* lookup item by key */ +/* lookup item by key + * should only call on locked collection if return value will be used + */ void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); /* find out how many items are in the cache */ size_t git_sortedcache_entrycount(const git_sortedcache *sc); -/* lookup item by index */ +/* lookup item by index + * should only call on locked collection if return value will be used + */ void *git_sortedcache_entry(const git_sortedcache *sc, size_t pos); -/* lookup index of item by key */ +/* lookup index of item by key + * if collection is not locked, there is no guarantee the returned index + * will be correct if it used to look up the item + */ int git_sortedcache_lookup_index( size_t *out, git_sortedcache *sc, const char *key); -/* remove entry from cache */ +/* remove entry from cache - lock during delete if `lock` is true */ int git_sortedcache_remove(git_sortedcache *sc, size_t pos, bool lock); #endif From 8d9a85d43aa6ed7a9fb15a2ac9e0f9ba1c33461e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 22 Aug 2013 11:40:53 -0700 Subject: [PATCH 278/367] Convert sortedcache to use rwlock This is the first use we have of pthread_rwlock_t in libgit2. Hopefully it won't cause any serious portability problems. --- src/refdb_fs.c | 131 +++++++++++++-------------- src/sortedcache.c | 161 +++++++++++++++++++--------------- src/sortedcache.h | 106 ++++++++++++++-------- src/thread-utils.h | 27 +++++- tests-clar/core/sortedcache.c | 31 ++++--- 5 files changed, 268 insertions(+), 188 deletions(-) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 876e84588..04516a5b0 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -172,7 +172,7 @@ static int packed_reload(refdb_fs_backend *backend) ref->flags |= PACKREF_CANNOT_PEEL; } - git_sortedcache_unlock(backend->refcache); + git_sortedcache_wunlock(backend->refcache); git_buf_free(&packedrefs); return 0; @@ -181,7 +181,7 @@ parse_failed: giterr_set(GITERR_REFERENCE, "Corrupted packed references file"); git_sortedcache_clear(backend->refcache, false); - git_sortedcache_unlock(backend->refcache); + git_sortedcache_wunlock(backend->refcache); git_buf_free(&packedrefs); return -1; @@ -244,7 +244,7 @@ static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0) goto done; - git_sortedcache_lock(backend->refcache); + git_sortedcache_wlock(backend->refcache); if (!(error = git_sortedcache_upsert( (void **)&ref, backend->refcache, name))) { @@ -253,7 +253,7 @@ static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) ref->flags = PACKREF_WAS_LOOSE; } - git_sortedcache_unlock(backend->refcache); + git_sortedcache_wunlock(backend->refcache); done: git_buf_free(&ref_file); @@ -317,7 +317,7 @@ static int refdb_fs_backend__exists( return -1; *exists = git_path_isfile(ref_path.ptr) || - git_sortedcache_lookup(backend->refcache, ref_name); + (git_sortedcache_lookup(backend->refcache, ref_name) != NULL); git_buf_free(&ref_path); return 0; @@ -395,7 +395,8 @@ static int packed_lookup( if (packed_reload(backend) < 0) return -1; - git_sortedcache_lock(backend->refcache); + if (git_sortedcache_rlock(backend->refcache) < 0) + return -1; entry = git_sortedcache_lookup(backend->refcache, ref_name); if (!entry) { @@ -406,7 +407,8 @@ static int packed_lookup( error = -1; } - git_sortedcache_unlock(backend->refcache); + git_sortedcache_runlock(backend->refcache); + return error; } @@ -486,11 +488,11 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0)) continue; - git_sortedcache_lock(backend->refcache); + git_sortedcache_rlock(backend->refcache); ref = git_sortedcache_lookup(backend->refcache, ref_name); if (ref) ref->flags |= PACKREF_SHADOWED; - git_sortedcache_unlock(backend->refcache); + git_sortedcache_runlock(backend->refcache); ref_dup = git_pool_strdup(&iter->pool, ref_name); if (!ref_dup) @@ -508,6 +510,7 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) static int refdb_fs_backend__iterator_next( git_reference **out, git_reference_iterator *_iter) { + int error = GIT_ITEROVER; refdb_fs_iter *iter = (refdb_fs_iter *)_iter; refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; struct packref *ref; @@ -521,7 +524,7 @@ static int refdb_fs_backend__iterator_next( giterr_clear(); } - git_sortedcache_lock(backend->refcache); + git_sortedcache_rlock(backend->refcache); while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); @@ -530,57 +533,56 @@ static int refdb_fs_backend__iterator_next( if (ref->flags & PACKREF_SHADOWED) continue; - if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0) continue; *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); - git_sortedcache_unlock(backend->refcache); - return (*out != NULL) ? 0 : -1; + error = (*out != NULL) ? 0 : -1; + break; } - git_sortedcache_unlock(backend->refcache); - return GIT_ITEROVER; + git_sortedcache_runlock(backend->refcache); + return error; } static int refdb_fs_backend__iterator_next_name( const char **out, git_reference_iterator *_iter) { + int error = GIT_ITEROVER; refdb_fs_iter *iter = (refdb_fs_iter *)_iter; refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; + struct packref *ref; while (iter->loose_pos < iter->loose.length) { const char *path = git_vector_get(&iter->loose, iter->loose_pos++); - if (loose_lookup(NULL, backend, path) != 0) { - giterr_clear(); - continue; + if (loose_lookup(NULL, backend, path) == 0) { + *out = path; + return 0; } - *out = path; - return 0; + giterr_clear(); } - git_sortedcache_lock(backend->refcache); + git_sortedcache_rlock(backend->refcache); while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { - struct packref *ref = - git_sortedcache_entry(backend->refcache, iter->packed_pos++); + ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + if (!ref) /* stop now if another thread deleted refs and we past end */ + break; if (ref->flags & PACKREF_SHADOWED) continue; - - *out = ref->name; - - if (iter->glob && p_fnmatch(iter->glob, *out, 0) != 0) + if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0) continue; - git_sortedcache_unlock(backend->refcache); - return 0; + *out = ref->name; + error = 0; + break; } - git_sortedcache_unlock(backend->refcache); - return GIT_ITEROVER; + git_sortedcache_runlock(backend->refcache); + return error; } static int refdb_fs_backend__iterator( @@ -658,26 +660,25 @@ static int reference_path_available( if (exists) { giterr_set(GITERR_REFERENCE, "Failed to write reference '%s': a reference with " - " that name already exists.", new_ref); + "that name already exists.", new_ref); return GIT_EEXISTS; } } - git_sortedcache_lock(backend->refcache); + git_sortedcache_rlock(backend->refcache); for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { - struct packref *this_ref = - git_sortedcache_entry(backend->refcache, i); + struct packref *ref = git_sortedcache_entry(backend->refcache, i); - if (!ref_is_available(old_ref, new_ref, this_ref->name)) { - git_sortedcache_unlock(backend->refcache); + if (ref && !ref_is_available(old_ref, new_ref, ref->name)) { + git_sortedcache_runlock(backend->refcache); giterr_set(GITERR_REFERENCE, - "The path to reference '%s' collides with an existing one", new_ref); + "Path to reference '%s' collides with existing one", new_ref); return -1; } } - git_sortedcache_unlock(backend->refcache); + git_sortedcache_runlock(backend->refcache); return 0; } @@ -816,7 +817,7 @@ static int packed_remove_loose(refdb_fs_backend *backend) for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { struct packref *ref = git_sortedcache_entry(backend->refcache, i); - if ((ref->flags & PACKREF_WAS_LOOSE) == 0) + if (!ref || !(ref->flags & PACKREF_WAS_LOOSE)) continue; if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0) @@ -849,67 +850,53 @@ static int packed_remove_loose(refdb_fs_backend *backend) */ static int packed_write(refdb_fs_backend *backend) { + git_sortedcache *refcache = backend->refcache; git_filebuf pack_file = GIT_FILEBUF_INIT; size_t i; - git_buf pack_file_path = GIT_BUF_INIT; /* lock the cache to updates while we do this */ - if (git_sortedcache_lock(backend->refcache) < 0) + if (git_sortedcache_wlock(refcache) < 0) return -1; /* 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; + if (git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0) < 0) + goto fail; /* 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; + goto fail; - for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { - struct packref *ref = - git_sortedcache_entry(backend->refcache, i); + for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) { + struct packref *ref = git_sortedcache_entry(refcache, i); if (packed_find_peel(backend, ref) < 0) - goto cleanup_packfile; + goto fail; if (packed_write_ref(ref, &pack_file) < 0) - goto cleanup_packfile; + goto fail; } /* 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; + goto fail; /* when and only when the packfile has been properly written, * we can go ahead and remove the loose refs */ if (packed_remove_loose(backend) < 0) - goto cleanup_memory; + goto fail; - git_sortedcache_unlock(backend->refcache); - - /* update filestamp to latest value */ - if (git_futils_filestamp_check( - &backend->refcache->stamp, pack_file_path.ptr) < 0) - giterr_clear(); - - git_buf_free(&pack_file_path); + git_sortedcache_updated(refcache); + git_sortedcache_wunlock(refcache); /* we're good now */ return 0; -cleanup_packfile: +fail: git_filebuf_cleanup(&pack_file); - -cleanup_memory: - git_sortedcache_unlock(backend->refcache); - git_buf_free(&pack_file_path); + git_sortedcache_wunlock(refcache); return -1; } @@ -961,14 +948,14 @@ static int refdb_fs_backend__delete( return -1; /* If a packed reference exists, remove it from the packfile and repack */ - if (git_sortedcache_lock(backend->refcache) < 0) + if (git_sortedcache_wlock(backend->refcache) < 0) return -1; if (!(error = git_sortedcache_lookup_index( &pack_pos, backend->refcache, ref_name))) - error = git_sortedcache_remove(backend->refcache, pack_pos, false); + error = git_sortedcache_remove(backend->refcache, pack_pos); - git_sortedcache_unlock(backend->refcache); + git_sortedcache_wunlock(backend->refcache); if (error == GIT_ENOTFOUND) return loose_deleted ? 0 : ref_error_notfound(ref_name); diff --git a/src/sortedcache.c b/src/sortedcache.c index d0a8031c3..c087dbbe9 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -23,13 +23,13 @@ int git_sortedcache_new( (sc->map = git_strmap_alloc()) == NULL) goto fail; - if (git_mutex_init(&sc->lock)) { - giterr_set(GITERR_OS, "Failed to initialize mutex"); + if (git_rwlock_init(&sc->lock)) { + giterr_set(GITERR_OS, "Failed to initialize lock"); goto fail; } - sc->item_path_offset = item_path_offset; - sc->free_item = free_item; + sc->item_path_offset = item_path_offset; + sc->free_item = free_item; sc->free_item_payload = free_item_payload; GIT_REFCOUNT_INC(sc); if (pathlen) @@ -52,6 +52,11 @@ void git_sortedcache_incref(git_sortedcache *sc) GIT_REFCOUNT_INC(sc); } +const char *git_sortedcache_path(git_sortedcache *sc) +{ + return sc->path; +} + static void sortedcache_clear(git_sortedcache *sc) { git_strmap_clear(sc->map); @@ -72,19 +77,17 @@ static void sortedcache_clear(git_sortedcache *sc) static void sortedcache_free(git_sortedcache *sc) { - if (git_mutex_lock(&sc->lock) < 0) { - giterr_set(GITERR_OS, "Unable to acquire mutex lock for free"); + /* acquire write lock to make sure everyone else is done */ + if (git_sortedcache_wlock(sc) < 0) return; - } sortedcache_clear(sc); - git_vector_free(&sc->items); git_strmap_free(sc->map); - git_mutex_unlock(&sc->lock); - git_mutex_free(&sc->lock); + git_sortedcache_wunlock(sc); + git_rwlock_free(&sc->lock); git__free(sc); } @@ -107,88 +110,88 @@ static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) int git_sortedcache_copy( git_sortedcache **out, git_sortedcache *src, + bool wlock, int (*copy_item)(void *payload, void *tgt_item, void *src_item), void *payload) { + int error = 0; git_sortedcache *tgt; size_t i; void *src_item, *tgt_item; + /* just use memcpy if no special copy fn is passed in */ if (!copy_item) { copy_item = sortedcache_copy_item; payload = src; } - if (git_sortedcache_new( + if ((error = git_sortedcache_new( &tgt, src->item_path_offset, src->free_item, src->free_item_payload, - src->items._cmp, src->path) < 0) - return -1; + src->items._cmp, src->path)) < 0) + return error; - if (git_sortedcache_lock(src) < 0) { + if (wlock && git_sortedcache_wlock(src) < 0) { git_sortedcache_free(tgt); return -1; } git_vector_foreach(&src->items, i, src_item) { - if (git_sortedcache_upsert( - &tgt_item, tgt, ((char *)src_item) + src->item_path_offset) < 0) - goto fail; - if (copy_item(payload, tgt_item, src_item) < 0) - goto fail; + char *path = ((char *)src_item) + src->item_path_offset; + + if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 || + (error = copy_item(payload, tgt_item, src_item)) < 0) + break; } - git_sortedcache_unlock(src); + if (wlock) + git_sortedcache_wunlock(src); + if (error) + git_sortedcache_free(tgt); - *out = tgt; - return 0; + *out = !error ? tgt : NULL; -fail: - git_sortedcache_unlock(src); - git_sortedcache_free(tgt); - return -1; -} - -/* release all items in sorted cache */ -void git_sortedcache_clear(git_sortedcache *sc, bool lock) -{ - if (lock && git_mutex_lock(&sc->lock) < 0) { - giterr_set(GITERR_OS, "Unable to acquire mutex lock for clear"); - return; - } - - sortedcache_clear(sc); - - if (lock) - git_mutex_unlock(&sc->lock); -} - -/* check file stamp to see if reload is required */ -bool git_sortedcache_out_of_date(git_sortedcache *sc) -{ - return (git_futils_filestamp_check(&sc->stamp, sc->path) != 0); + return error; } /* lock sortedcache while making modifications */ -int git_sortedcache_lock(git_sortedcache *sc) +int git_sortedcache_wlock(git_sortedcache *sc) { - GIT_UNUSED(sc); /* to prevent warning when compiled w/o threads */ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - if (git_mutex_lock(&sc->lock) < 0) { - giterr_set(GITERR_OS, "Unable to acquire mutex lock"); + if (git_rwlock_wrlock(&sc->lock) < 0) { + giterr_set(GITERR_OS, "Unable to acquire write lock on cache"); return -1; } return 0; } /* unlock sorted cache when done with modifications */ -int git_sortedcache_unlock(git_sortedcache *sc) +void git_sortedcache_wunlock(git_sortedcache *sc) { git_vector_sort(&sc->items); - git_mutex_unlock(&sc->lock); + git_rwlock_wrunlock(&sc->lock); +} + +/* lock sortedcache for read */ +int git_sortedcache_rlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + + if (git_rwlock_rdlock(&sc->lock) < 0) { + giterr_set(GITERR_OS, "Unable to acquire read lock on cache"); + return -1; + } return 0; } +/* unlock sorted cache when done reading */ +void git_sortedcache_runlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + git_rwlock_rdunlock(&sc->lock); +} + /* if the file has changed, lock cache and load file contents into buf; * returns <0 on error, >0 if file has not changed */ @@ -196,7 +199,7 @@ int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf) { int error, fd; - if ((error = git_sortedcache_lock(sc)) < 0) + if ((error = git_sortedcache_wlock(sc)) < 0) return error; if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) @@ -224,13 +227,33 @@ int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf) return 1; /* return 1 -> file needs reload and was successfully loaded */ unlock: - git_sortedcache_unlock(sc); + git_sortedcache_wunlock(sc); return error; } +void git_sortedcache_updated(git_sortedcache *sc) +{ + /* update filestamp to latest value */ + if (git_futils_filestamp_check(&sc->stamp, sc->path) < 0) + giterr_clear(); +} + +/* release all items in sorted cache */ +int git_sortedcache_clear(git_sortedcache *sc, bool wlock) +{ + if (wlock && git_sortedcache_wlock(sc) < 0) + return -1; + + sortedcache_clear(sc); + + if (wlock) + git_sortedcache_wunlock(sc); + + return 0; +} + /* find and/or insert item, returning pointer to item data */ -int git_sortedcache_upsert( - void **out, git_sortedcache *sc, const char *key) +int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) { int error = 0; khiter_t pos; @@ -246,7 +269,10 @@ int git_sortedcache_upsert( keylen = strlen(key); item = git_pool_mallocz(&sc->pool, sc->item_path_offset + keylen + 1); - GITERR_CHECK_ALLOC(item); + if (!item) { /* don't use GITERR_CHECK_ALLOC b/c of lock */ + error = -1; + goto done; + } /* one strange thing is that even if the vector or hash table insert * fail, there is no way to free the pool item so we just abandon it @@ -289,11 +315,16 @@ size_t git_sortedcache_entrycount(const git_sortedcache *sc) } /* lookup item by index */ -void *git_sortedcache_entry(const git_sortedcache *sc, size_t pos) +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos) { + /* make sure the items are sorted so this gets the correct item */ + if (!sc->items.sorted) + git_vector_sort(&sc->items); + return git_vector_get(&sc->items, pos); } +/* helper struct so bsearch callback can know offset + key value for cmp */ struct sortedcache_magic_key { size_t offset; const char *key; @@ -319,23 +350,18 @@ int git_sortedcache_lookup_index( } /* remove entry from cache */ -int git_sortedcache_remove(git_sortedcache *sc, size_t pos, bool lock) +int git_sortedcache_remove(git_sortedcache *sc, size_t pos) { - int error = 0; char *item; khiter_t mappos; - if (lock && git_sortedcache_lock(sc) < 0) - return -1; - /* because of pool allocation, this can't actually remove the item, * but we can remove it from the items vector and the hash table. */ if ((item = git_vector_get(&sc->items, pos)) == NULL) { giterr_set(GITERR_INVALID, "Removing item out of range"); - error = GIT_ENOTFOUND; - goto done; + return GIT_ENOTFOUND; } (void)git_vector_remove(&sc->items, pos); @@ -346,9 +372,6 @@ int git_sortedcache_remove(git_sortedcache *sc, size_t pos, bool lock) if (sc->free_item) sc->free_item(sc->free_item_payload, item); -done: - if (lock) - git_sortedcache_unlock(sc); - return error; + return 0; } diff --git a/src/sortedcache.h b/src/sortedcache.h index c1c9d1341..7d1cd2f14 100644 --- a/src/sortedcache.h +++ b/src/sortedcache.h @@ -25,7 +25,7 @@ typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); typedef struct { git_refcount rc; - git_mutex lock; + git_rwlock lock; size_t item_path_offset; git_sortedcache_free_item_fn free_item; void *free_item_payload; @@ -36,84 +36,120 @@ typedef struct { char path[GIT_FLEX_ARRAY]; } git_sortedcache; -/* create a new sortedcache +/* Create a new sortedcache * - * even though every sortedcache stores items with a GIT_FLEX_ARRAY at + * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at * the end containing their key string, you have to provide the item_cmp * sorting function because the sorting function doesn't get a payload * and therefore can't know the offset to the item key string. :-( */ int git_sortedcache_new( git_sortedcache **out, - size_t item_path_offset, /* use offsetof() macro */ + size_t item_path_offset, /* use offsetof(struct, path-field) macro */ git_sortedcache_free_item_fn free_item, void *free_item_payload, git_vector_cmp item_cmp, const char *path); -/* copy a sorted cache +/* Copy a sorted cache * - * - copy_item can be NULL to memcpy - * - locks src while copying + * - `copy_item` can be NULL to just use memcpy + * - if `wlock`, grabs write lock on `src` during copy and releases after */ int git_sortedcache_copy( git_sortedcache **out, git_sortedcache *src, + bool wlock, int (*copy_item)(void *payload, void *tgt_item, void *src_item), void *payload); -/* free sorted cache (first calling free_item callbacks) - * don't call on a locked collection - it may acquire a lock +/* Free sorted cache (first calling `free_item` callbacks) + * + * Don't call on a locked collection - it may acquire a write lock */ void git_sortedcache_free(git_sortedcache *sc); -/* increment reference count - balance with call to free */ +/* Increment reference count - balance with call to free */ void git_sortedcache_incref(git_sortedcache *sc); -/* release all items in sorted cache - lock during clear if `lock` is true */ -void git_sortedcache_clear(git_sortedcache *sc, bool lock); +/* Get the pathname associated with this cache at creation time */ +const char *git_sortedcache_path(git_sortedcache *sc); -/* check file stamp to see if reload is required */ -bool git_sortedcache_out_of_date(git_sortedcache *sc); +/* + * CACHE WRITE FUNCTIONS + * + * The following functions require you to have a writer lock to make the + * modification. Some of the functions take a `wlock` parameter and + * will optionally lock and unlock for you if that is passed as true. + * + */ -/* lock sortedcache during access or modification */ -int git_sortedcache_lock(git_sortedcache *sc); +/* Lock sortedcache for write */ +int git_sortedcache_wlock(git_sortedcache *sc); -/* unlock sorted cache when done */ -int git_sortedcache_unlock(git_sortedcache *sc); +/* Unlock sorted cache when done with write */ +void git_sortedcache_wunlock(git_sortedcache *sc); -/* if the file has changed, lock cache and load file contents into buf; +/* Lock cache and test if the file has changed. If the file has changed, + * then load the contents into `buf` otherwise unlock and return 0. + * * @return 0 if up-to-date, 1 if out-of-date, <0 on error */ int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf); -/* find and/or insert item, returning pointer to item data - * should only call on locked collection +/* Refresh file timestamp after write completes + * You should already be holding the write lock when you call this. + */ +void git_sortedcache_updated(git_sortedcache *sc); + +/* Release all items in sorted cache + * + * If `wlock` is true, grabs write lock and releases when done, otherwise + * you should already be holding a write lock when you call this. + */ +int git_sortedcache_clear(git_sortedcache *sc, bool wlock); + +/* Find and/or insert item, returning pointer to item data. + * You should already be holding the write lock when you call this. */ int git_sortedcache_upsert( void **out, git_sortedcache *sc, const char *key); -/* lookup item by key - * should only call on locked collection if return value will be used +/* Removes entry at pos from cache + * You should already be holding the write lock when you call this. */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos); + +/* + * CACHE READ FUNCTIONS + * + * The following functions access items in the cache. To prevent the + * results from being invalidated before they can be used, you should be + * holding either a read lock or a write lock when using these functions. + * + */ + +/* Lock sortedcache for read */ +int git_sortedcache_rlock(git_sortedcache *sc); + +/* Unlock sorted cache when done with read */ +void git_sortedcache_runlock(git_sortedcache *sc); + +/* Lookup item by key - returns NULL if not found */ void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); -/* find out how many items are in the cache */ +/* Get how many items are in the cache + * + * You can call this function without holding a lock, but be aware + * that it may change before you use it. + */ size_t git_sortedcache_entrycount(const git_sortedcache *sc); -/* lookup item by index - * should only call on locked collection if return value will be used - */ -void *git_sortedcache_entry(const git_sortedcache *sc, size_t pos); +/* Lookup item by index - returns NULL if out of range */ +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos); -/* lookup index of item by key - * if collection is not locked, there is no guarantee the returned index - * will be correct if it used to look up the item - */ +/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */ int git_sortedcache_lookup_index( size_t *out, git_sortedcache *sc, const char *key); -/* remove entry from cache - lock during delete if `lock` is true */ -int git_sortedcache_remove(git_sortedcache *sc, size_t pos, bool lock); - #endif diff --git a/src/thread-utils.h b/src/thread-utils.h index 04e02959f..ffcdb4ab0 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -41,7 +41,8 @@ typedef git_atomic git_atomic_ssize; #ifdef GIT_THREADS #define git_thread pthread_t -#define git_thread_create(thread, attr, start_routine, arg) pthread_create(thread, attr, start_routine, arg) +#define git_thread_create(thread, attr, start_routine, arg) \ + pthread_create(thread, attr, start_routine, arg) #define git_thread_kill(thread) pthread_cancel(thread) #define git_thread_exit(status) pthread_exit(status) #define git_thread_join(id, status) pthread_join(id, status) @@ -61,6 +62,17 @@ typedef git_atomic git_atomic_ssize; #define git_cond_signal(c) pthread_cond_signal(c) #define git_cond_broadcast(c) pthread_cond_broadcast(c) +/* Pthreads rwlock */ +#define git_rwlock pthread_rwlock_t +#define git_rwlock_init(a) pthread_rwlock_init(a, NULL) +#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) +#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a) +#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_free(a) pthread_rwlock_destroy(a) +#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER + + GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) { #if defined(GIT_WIN32) @@ -147,7 +159,7 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #else #define git_thread unsigned int -#define git_thread_create(thread, attr, start_routine, arg) (void)0 +#define git_thread_create(thread, attr, start_routine, arg) 0 #define git_thread_kill(thread) (void)0 #define git_thread_exit(status) (void)0 #define git_thread_join(id, status) (void)0 @@ -167,6 +179,17 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #define git_cond_signal(c) (void)0 #define git_cond_broadcast(c) (void)0 +/* Pthreads rwlock */ +#define git_rwlock unsigned int +#define git_rwlock_init(a) 0 +#define git_rwlock_rdlock(a) 0 +#define git_rwlock_rdunlock(a) (void)0 +#define git_rwlock_wrlock(a) 0 +#define git_rwlock_wrunlock(a) (void)0 +#define git_rwlock_free(a) (void)0 +#define GIT_RWLOCK_STATIC_INIT 0 + + GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) { a->val = val; diff --git a/tests-clar/core/sortedcache.c b/tests-clar/core/sortedcache.c index 91415943d..509d0df6b 100644 --- a/tests-clar/core/sortedcache.c +++ b/tests-clar/core/sortedcache.c @@ -15,13 +15,13 @@ void test_core_sortedcache__name_only(void) cl_git_pass(git_sortedcache_new( &sc, 0, NULL, NULL, name_only_cmp, NULL)); - cl_git_pass(git_sortedcache_lock(sc)); + cl_git_pass(git_sortedcache_wlock(sc)); cl_git_pass(git_sortedcache_upsert(&item, sc, "aaa")); cl_git_pass(git_sortedcache_upsert(&item, sc, "bbb")); cl_git_pass(git_sortedcache_upsert(&item, sc, "zzz")); cl_git_pass(git_sortedcache_upsert(&item, sc, "mmm")); cl_git_pass(git_sortedcache_upsert(&item, sc, "iii")); - cl_git_pass(git_sortedcache_unlock(sc)); + git_sortedcache_wunlock(sc); cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); @@ -95,7 +95,7 @@ void test_core_sortedcache__in_memory(void) sortedcache_test_struct_free, &free_count, sortedcache_test_struct_cmp, NULL)); - cl_git_pass(git_sortedcache_lock(sc)); + cl_git_pass(git_sortedcache_wlock(sc)); cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "aaa")); item->value = 10; item->smaller_value = 1; @@ -111,10 +111,12 @@ void test_core_sortedcache__in_memory(void) cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "iii")); item->value = 50; item->smaller_value = 9; - cl_git_pass(git_sortedcache_unlock(sc)); + git_sortedcache_wunlock(sc); cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); + cl_git_pass(git_sortedcache_rlock(sc)); + cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); cl_assert_equal_s("aaa", item->path); cl_assert_equal_i(10, item->value); @@ -126,6 +128,8 @@ void test_core_sortedcache__in_memory(void) cl_assert_equal_i(30, item->value); cl_assert(git_sortedcache_lookup(sc, "abc") == NULL); + cl_git_pass(git_sortedcache_rlock(sc)); /* grab more than one */ + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); cl_assert_equal_s("aaa", item->path); cl_assert_equal_i(10, item->value); @@ -143,6 +147,9 @@ void test_core_sortedcache__in_memory(void) cl_assert_equal_i(30, item->value); cl_assert(git_sortedcache_entry(sc, 5) == NULL); + git_sortedcache_runlock(sc); + git_sortedcache_runlock(sc); + cl_assert_equal_i(0, free_count); git_sortedcache_clear(sc, true); @@ -156,7 +163,7 @@ void test_core_sortedcache__in_memory(void) free_count = 0; - cl_git_pass(git_sortedcache_lock(sc)); + cl_git_pass(git_sortedcache_wlock(sc)); cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "testing")); item->value = 10; item->smaller_value = 3; @@ -166,7 +173,7 @@ void test_core_sortedcache__in_memory(void) cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "final")); item->value = 30; item->smaller_value = 2; - cl_git_pass(git_sortedcache_unlock(sc)); + git_sortedcache_wunlock(sc); cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); @@ -195,9 +202,11 @@ void test_core_sortedcache__in_memory(void) { size_t pos; + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "again")); cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_remove(sc, pos, true)); + cl_git_pass(git_sortedcache_remove(sc, pos)); cl_assert_equal_i( GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "again")); @@ -205,7 +214,7 @@ void test_core_sortedcache__in_memory(void) cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "testing")); cl_assert_equal_sz(1, pos); - cl_git_pass(git_sortedcache_remove(sc, pos, true)); + cl_git_pass(git_sortedcache_remove(sc, pos)); cl_assert_equal_i( GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "testing")); @@ -213,11 +222,13 @@ void test_core_sortedcache__in_memory(void) cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_remove(sc, pos, true)); + cl_git_pass(git_sortedcache_remove(sc, pos)); cl_assert_equal_i( GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "final")); cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + + git_sortedcache_wunlock(sc); } git_sortedcache_free(sc); @@ -251,7 +262,7 @@ static void sortedcache_test_reload(git_sortedcache *sc) item->smaller_value = (char)(count++); } - cl_git_pass(git_sortedcache_unlock(sc)); + git_sortedcache_wunlock(sc); git_buf_free(&buf); } From 2b6e1908476c95c84d3e3a62ac069f789156b070 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 22 Aug 2013 11:50:10 -0700 Subject: [PATCH 279/367] A bit of item alignment paranoia --- src/sortedcache.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sortedcache.c b/src/sortedcache.c index c087dbbe9..16dd6a7a1 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -258,7 +258,7 @@ int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) int error = 0; khiter_t pos; void *item; - size_t keylen; + size_t keylen, itemlen; char *item_key; pos = git_strmap_lookup_index(sc->map, key); @@ -267,9 +267,12 @@ int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) goto done; } - keylen = strlen(key); - item = git_pool_mallocz(&sc->pool, sc->item_path_offset + keylen + 1); - if (!item) { /* don't use GITERR_CHECK_ALLOC b/c of lock */ + keylen = strlen(key); + itemlen = sc->item_path_offset + keylen + 1; + itemlen = (itemlen + 7) & ~7; + + if ((item = git_pool_mallocz(&sc->pool, itemlen)) == NULL) { + /* don't use GITERR_CHECK_ALLOC b/c of lock */ error = -1; goto done; } From 972bb689c4a69ba5a5dfaeebacc7198622c4f051 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 22 Aug 2013 14:10:56 -0700 Subject: [PATCH 280/367] Add SRWLock implementation of rwlocks for Win32 --- src/thread-utils.h | 9 +++++++-- src/win32/pthread.c | 38 +++++++++++++++++++++++++++++++++++ src/win32/pthread.h | 14 ++++++++++++- tests-clar/core/sortedcache.c | 6 ++++-- 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/thread-utils.h b/src/thread-utils.h index ffcdb4ab0..819e24e7b 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -66,12 +66,17 @@ typedef git_atomic git_atomic_ssize; #define git_rwlock pthread_rwlock_t #define git_rwlock_init(a) pthread_rwlock_init(a, NULL) #define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) -#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_rdunlock(a) pthread_rwlock_rdunlock(a) #define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a) -#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_wrunlock(a) pthread_rwlock_wrunlock(a) #define git_rwlock_free(a) pthread_rwlock_destroy(a) #define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER +#ifndef GIT_WIN32 +#define pthread_rwlock_rdunlock pthread_rwlock_unlock +#define pthread_rwlock_wrunlock pthread_rwlock_unlock +#endif + GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) { diff --git a/src/win32/pthread.c b/src/win32/pthread.c index 2f263b3e0..41cb7a4c0 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -142,3 +142,41 @@ int pthread_num_processors_np(void) return n ? n : 1; } +int pthread_rwlock_init( + pthread_rwlock_t *GIT_RESTRICT lock, + const pthread_rwlockattr_t *GIT_RESTRICT attr) +{ + (void)attr; + InitializeSRWLock(lock); + return 0; +} + +int pthread_rwlock_rdlock(pthread_rwlock_t *lock) +{ + AcquireSRWLockShared(lock); + return 0; +} + +int pthread_rwlock_rdunlock(pthread_rwlock_t *lock) +{ + ReleaseSRWLockShared(lock); + return 0; +} + +int pthread_rwlock_wrlock(pthread_rwlock_t *lock) +{ + AcquireSRWLockExclusive(lock); + return 0; +} + +int pthread_rwlock_wrunlock(pthread_rwlock_t *lock) +{ + ReleaseSRWLockExclusive(lock); + return 0; +} + +int pthread_rwlock_destroy(pthread_rwlock_t *lock) +{ + (void)lock; + return 0; +} diff --git a/src/win32/pthread.h b/src/win32/pthread.h index 8277ecf6e..54e5286a6 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -19,11 +19,15 @@ typedef int pthread_mutexattr_t; typedef int pthread_condattr_t; typedef int pthread_attr_t; +typedef int pthread_rwlockattr_t; + typedef CRITICAL_SECTION pthread_mutex_t; typedef HANDLE pthread_t; typedef HANDLE pthread_cond_t; +typedef SRWLOCK pthread_rwlock_t; -#define PTHREAD_MUTEX_INITIALIZER {(void*)-1}; +#define PTHREAD_MUTEX_INITIALIZER {(void*)-1} +#define PTHREAD_RWLOCK_INITIALIZER SRWLOCK_INIT int pthread_create( pthread_t *GIT_RESTRICT, @@ -47,4 +51,12 @@ int pthread_cond_signal(pthread_cond_t *); int pthread_num_processors_np(void); +int pthread_rwlock_init( + pthread_rwlock_t *GIT_RESTRICT, const pthread_rwlockattr_t *GIT_RESTRICT); +int pthread_rwlock_rdlock(pthread_rwlock_t *); +int pthread_rwlock_rdunlock(pthread_rwlock_t *); +int pthread_rwlock_wrlock(pthread_rwlock_t *); +int pthread_rwlock_wrunlock(pthread_rwlock_t *); +int pthread_rwlock_destroy(pthread_rwlock_t *); + #endif diff --git a/tests-clar/core/sortedcache.c b/tests-clar/core/sortedcache.c index 509d0df6b..c1869bee0 100644 --- a/tests-clar/core/sortedcache.c +++ b/tests-clar/core/sortedcache.c @@ -128,7 +128,9 @@ void test_core_sortedcache__in_memory(void) cl_assert_equal_i(30, item->value); cl_assert(git_sortedcache_lookup(sc, "abc") == NULL); - cl_git_pass(git_sortedcache_rlock(sc)); /* grab more than one */ + /* not on Windows: + * cl_git_pass(git_sortedcache_rlock(sc)); -- grab more than one + */ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); cl_assert_equal_s("aaa", item->path); @@ -148,7 +150,7 @@ void test_core_sortedcache__in_memory(void) cl_assert(git_sortedcache_entry(sc, 5) == NULL); git_sortedcache_runlock(sc); - git_sortedcache_runlock(sc); + /* git_sortedcache_runlock(sc); */ cl_assert_equal_i(0, free_count); From eb868b1e98d7cea8796f9b92be04843a7f819e5e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 22 Aug 2013 14:34:21 -0700 Subject: [PATCH 281/367] Drop support for THREADSAFE on Windows XP This makes libgit2 require Windows Vista or newer if it is going to be compiled with the THREADSAFE option --- CMakeLists.txt | 7 ++++++- src/thread-utils.h | 14 +++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c70ec2d6..019777e78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -287,8 +287,13 @@ FILE(GLOB SRC_H include/git2.h include/git2/*.h include/git2/sys/*.h) # On Windows use specific platform sources IF (WIN32 AND NOT CYGWIN) - ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501) + ADD_DEFINITIONS(-DWIN32) FILE(GLOB SRC_OS src/win32/*.c src/win32/*.h) + IF (THREADSAFE) + ADD_DEFINITIONS(-D_WIN32_WINNT=0x0600) + ELSE() + ADD_DEFINITIONS(-D_WIN32_WINNT=0x0501) + ENDIF() ELSEIF (AMIGA) ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R) FILE(GLOB SRC_OS src/amiga/*.c src/amiga/*.h) diff --git a/src/thread-utils.h b/src/thread-utils.h index 819e24e7b..371dc0b26 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -40,6 +40,10 @@ typedef git_atomic git_atomic_ssize; #ifdef GIT_THREADS +#if defined(GIT_WIN32) && _WIN32_WINNT < 0x0600 +# error "Unsupported Windows version for thread support" +#endif + #define git_thread pthread_t #define git_thread_create(thread, attr, start_routine, arg) \ pthread_create(thread, attr, start_routine, arg) @@ -62,7 +66,15 @@ typedef git_atomic git_atomic_ssize; #define git_cond_signal(c) pthread_cond_signal(c) #define git_cond_broadcast(c) pthread_cond_broadcast(c) -/* Pthreads rwlock */ +/* Pthread (-ish) rwlock + * + * This differs from normal pthreads rwlocks in two ways: + * 1. Separate APIs for releasing read locks and write locks (as + * opposed to the pure POSIX API which only has one unlock fn) + * 2. You should not use recursive read locks (i.e. grabbing a read + * lock in a thread that already holds a read lock) because the + * Windows implementation doesn't support it + */ #define git_rwlock pthread_rwlock_t #define git_rwlock_init(a) pthread_rwlock_init(a, NULL) #define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) From b6ac07b51771641f3ae994c17f361fbd8bec36ef Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 22 Aug 2013 14:45:10 -0700 Subject: [PATCH 282/367] Trying to fix Win32 warnings --- src/cc-compat.h | 8 ++++++-- src/diff_tform.c | 6 +++--- src/sortedcache.c | 2 +- src/win32/pthread.h | 12 +++++++----- tests-clar/stress/diff.c | 2 +- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/cc-compat.h b/src/cc-compat.h index a5e4ce17e..37f1ea81e 100644 --- a/src/cc-compat.h +++ b/src/cc-compat.h @@ -54,8 +54,12 @@ #if defined (_MSC_VER) typedef unsigned char bool; -# define true 1 -# define false 0 +# ifndef true +# define true 1 +# endif +# ifndef false +# define false 0 +# endif #else # include #endif diff --git a/src/diff_tform.c b/src/diff_tform.c index ba35d3c14..6b8cf446e 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -859,9 +859,9 @@ find_best_matches: } /* write new mapping */ - tgt2src[t].idx = s; + tgt2src[t].idx = (uint32_t)s; tgt2src[t].similarity = (uint32_t)similarity; - src2tgt[s].idx = t; + src2tgt[s].idx = (uint32_t)t; src2tgt[s].similarity = (uint32_t)similarity; } @@ -869,7 +869,7 @@ find_best_matches: if (tgt2src_copy != NULL && tgt2src_copy[t].similarity < (uint32_t)similarity) { - tgt2src_copy[t].idx = s; + tgt2src_copy[t].idx = (uint32_t)s; tgt2src_copy[t].similarity = (uint32_t)similarity; } diff --git a/src/sortedcache.c b/src/sortedcache.c index 16dd6a7a1..33171c48d 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -271,7 +271,7 @@ int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) itemlen = sc->item_path_offset + keylen + 1; itemlen = (itemlen + 7) & ~7; - if ((item = git_pool_mallocz(&sc->pool, itemlen)) == NULL) { + if ((item = git_pool_mallocz(&sc->pool, (uint32_t)itemlen)) == NULL) { /* don't use GITERR_CHECK_ALLOC b/c of lock */ error = -1; goto done; diff --git a/src/win32/pthread.h b/src/win32/pthread.h index 54e5286a6..50d836247 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -30,15 +30,16 @@ typedef SRWLOCK pthread_rwlock_t; #define PTHREAD_RWLOCK_INITIALIZER SRWLOCK_INIT int pthread_create( - pthread_t *GIT_RESTRICT, - const pthread_attr_t *GIT_RESTRICT, + pthread_t *GIT_RESTRICT thread, + const pthread_attr_t *GIT_RESTRICT attr, void *(*start_routine)(void*), - void *__restrict); + void *GIT_RESTRICT arg); int pthread_join(pthread_t, void **); int pthread_mutex_init( - pthread_mutex_t *GIT_RESTRICT, const pthread_mutexattr_t *GIT_RESTRICT); + pthread_mutex_t *GIT_RESTRICT mutex, + const pthread_mutexattr_t *GIT_RESTRICT mutexattr); int pthread_mutex_destroy(pthread_mutex_t *); int pthread_mutex_lock(pthread_mutex_t *); int pthread_mutex_unlock(pthread_mutex_t *); @@ -52,7 +53,8 @@ int pthread_cond_signal(pthread_cond_t *); int pthread_num_processors_np(void); int pthread_rwlock_init( - pthread_rwlock_t *GIT_RESTRICT, const pthread_rwlockattr_t *GIT_RESTRICT); + pthread_rwlock_t *GIT_RESTRICT lock, + const pthread_rwlockattr_t *GIT_RESTRICT attr); int pthread_rwlock_rdlock(pthread_rwlock_t *); int pthread_rwlock_rdunlock(pthread_rwlock_t *); int pthread_rwlock_wrlock(pthread_rwlock_t *); diff --git a/tests-clar/stress/diff.c b/tests-clar/stress/diff.c index 62ccd5ec7..0524aa108 100644 --- a/tests-clar/stress/diff.c +++ b/tests-clar/stress/diff.c @@ -15,7 +15,7 @@ void test_stress_diff__cleanup(void) #define ANOTHER_POEM \ "OH, glorious are the guarded heights\nWhere guardian souls abide—\nSelf-exiled from our gross delights—\nAbove, beyond, outside:\nAn ampler arc their spirit swings—\nCommands a juster view—\nWe have their word for all these things,\nNo doubt their words are true.\n\nYet we, the bond slaves of our day,\nWhom dirt and danger press—\nCo-heirs of insolence, delay,\nAnd leagued unfaithfulness—\nSuch is our need must seek indeed\nAnd, having found, engage\nThe men who merely do the work\nFor which they draw the wage.\n\nFrom forge and farm and mine and bench,\nDeck, altar, outpost lone—\nMill, school, battalion, counter, trench,\nRail, senate, sheepfold, throne—\nCreation's cry goes up on high\nFrom age to cheated age:\n\"Send us the men who do the work\n\"For which they draw the wage!\"\n" -static void test_with_many(size_t expected_new) +static void test_with_many(int expected_new) { git_index *index; git_tree *tree, *new_tree; From 805755f49b0db5bc884f8929621ac61238b2c30e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 22 Aug 2013 15:44:34 -0700 Subject: [PATCH 283/367] Fix sortedcache docs and other feedback This converts an internal lock from a write lock to a read lock where write isn't needed, and also clarifies some doc things about where various locks are acquired and how various APIs are intended to be used. --- src/sortedcache.c | 8 ++++---- src/sortedcache.h | 35 ++++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/sortedcache.c b/src/sortedcache.c index 33171c48d..466e55dbe 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -110,7 +110,7 @@ static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) int git_sortedcache_copy( git_sortedcache **out, git_sortedcache *src, - bool wlock, + bool lock, int (*copy_item)(void *payload, void *tgt_item, void *src_item), void *payload) { @@ -131,7 +131,7 @@ int git_sortedcache_copy( src->items._cmp, src->path)) < 0) return error; - if (wlock && git_sortedcache_wlock(src) < 0) { + if (lock && git_sortedcache_rlock(src) < 0) { git_sortedcache_free(tgt); return -1; } @@ -144,8 +144,8 @@ int git_sortedcache_copy( break; } - if (wlock) - git_sortedcache_wunlock(src); + if (lock) + git_sortedcache_runlock(src); if (error) git_sortedcache_free(tgt); diff --git a/src/sortedcache.h b/src/sortedcache.h index 7d1cd2f14..5ebb116ed 100644 --- a/src/sortedcache.h +++ b/src/sortedcache.h @@ -16,9 +16,10 @@ /* * The purpose of this data structure is to cache the parsed contents of a - * file where each item in the file can be identified by a key string and - * you want to both look them up by name and traverse them in sorted - * order. Each item is assumed to itself end in a GIT_FLEX_ARRAY. + * file (a.k.a. the backing file) where each item in the file can be + * identified by a key string and you want to both look them up by name + * and traverse them in sorted order. Each item is assumed to itself end + * in a GIT_FLEX_ARRAY. */ typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); @@ -42,6 +43,16 @@ typedef struct { * the end containing their key string, you have to provide the item_cmp * sorting function because the sorting function doesn't get a payload * and therefore can't know the offset to the item key string. :-( + * + * @param out The allocated git_sortedcache + * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the + * struct - use offsetof(struct mine, key-field) to get this + * @param free_item Optional callback to free each item + * @param free_item_payload Optional payload passed to free_item callback + * @param item_cmp Compare the keys of two items + * @param path The path to the backing store file for this cache; this + * may be NULL. The cache makes it easy to load this and check + * if it has been modified since the last load and/or write. */ int git_sortedcache_new( git_sortedcache **out, @@ -54,12 +65,12 @@ int git_sortedcache_new( /* Copy a sorted cache * * - `copy_item` can be NULL to just use memcpy - * - if `wlock`, grabs write lock on `src` during copy and releases after + * - if `lock`, grabs read lock on `src` during copy and releases after */ int git_sortedcache_copy( git_sortedcache **out, git_sortedcache *src, - bool wlock, + bool lock, int (*copy_item)(void *payload, void *tgt_item, void *src_item), void *payload); @@ -90,8 +101,18 @@ int git_sortedcache_wlock(git_sortedcache *sc); /* Unlock sorted cache when done with write */ void git_sortedcache_wunlock(git_sortedcache *sc); -/* Lock cache and test if the file has changed. If the file has changed, - * then load the contents into `buf` otherwise unlock and return 0. +/* Lock cache and load backing file into a buffer. + * + * This grabs a write lock on the cache then looks at the modification + * time and size of the file on disk. + * + * If the file appears to have changed, this loads the file contents into + * the buffer and returns a positive value leaving the cache locked - the + * caller should parse the file content, update the cache as needed, then + * release the lock. NOTE: In this case, the caller MUST unlock the cache. + * + * If the file appears to be unchanged, then this automatically releases + * the lock on the cache, clears the buffer, and returns 0. * * @return 0 if up-to-date, 1 if out-of-date, <0 on error */ From 44d655318661affa2feb51e9d6d533bb16d7f2b5 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 19 Aug 2013 16:03:15 -0700 Subject: [PATCH 284/367] Fix comment --- tests-clar/refs/pack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-clar/refs/pack.c b/tests-clar/refs/pack.c index 6019ed75a..849a052aa 100644 --- a/tests-clar/refs/pack.c +++ b/tests-clar/refs/pack.c @@ -44,7 +44,7 @@ void test_refs_pack__empty(void) void test_refs_pack__loose(void) { - /* create a packfile from all the loose rn a repo */ + /* create a packfile from all the loose refs in a repo */ git_reference *reference; git_buf temp_path = GIT_BUF_INIT; From 9d85f00722c5161bda0727f5bb80d13d08ccb481 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Sat, 24 Aug 2013 17:39:15 +1000 Subject: [PATCH 285/367] fix tests on FreeBSD 238b761 introduced a test for posix behaviour, but on FreeBSD some of the structs and constants used aren't defined in . Include the appropriate headers to get the tests working again on FreeBSD. --- tests-clar/core/posix.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests-clar/core/posix.c b/tests-clar/core/posix.c index 0d9443f92..890e25d47 100644 --- a/tests-clar/core/posix.c +++ b/tests-clar/core/posix.c @@ -1,5 +1,7 @@ #ifndef _WIN32 # include +# include +# include #else # include # ifdef _MSC_VER From e52963080a1cc4c7cd7a921fd96d9656cc976022 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Sat, 24 Aug 2013 20:15:22 +1000 Subject: [PATCH 286/367] netops: remove duplicate include 9e9aee6 added an include to fix the build on FreeBSD. Sometime since then the same header is included ifndef _WIN32, so remove the duplicate include. --- src/netops.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/netops.c b/src/netops.c index 69179dd1c..803c2696a 100644 --- a/src/netops.c +++ b/src/netops.c @@ -19,10 +19,6 @@ # endif #endif -#ifdef __FreeBSD__ -# include -#endif - #ifdef GIT_SSL # include # include From 32614440561e2ccb33ee24462a120154e0009d08 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Sun, 25 Aug 2013 17:01:04 +1000 Subject: [PATCH 287/367] push: small documentation fix --- include/git2/push.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2/push.h b/include/git2/push.h index f92308144..ed6253afb 100644 --- a/include/git2/push.h +++ b/include/git2/push.h @@ -98,7 +98,7 @@ GIT_EXTERN(int) git_push_finish(git_push *push); * * @param push The push object * - * @return true if equal, false otherwise + * @return true if remote side successfully unpacked, false otherwise */ GIT_EXTERN(int) git_push_unpack_ok(git_push *push); From 504850cdf56190a61782abfec37f3533b42d769e Mon Sep 17 00:00:00 2001 From: Nikolai Vladimirov Date: Sun, 25 Aug 2013 15:59:50 +0300 Subject: [PATCH 288/367] refs: add git_reference_is_tag --- include/git2/refs.h | 9 +++++++++ src/refs.c | 11 +++++++++++ src/refs.h | 1 + tests-clar/refs/read.c | 16 ++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/include/git2/refs.h b/include/git2/refs.h index 205bfe59d..4871e9820 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -442,6 +442,15 @@ GIT_EXTERN(int) git_reference_is_branch(git_reference *ref); */ GIT_EXTERN(int) git_reference_is_remote(git_reference *ref); +/** + * Check if a reference is a tag + * + * @param ref A git reference + * + * @return 1 when the reference lives in the refs/tags + * namespace; 0 otherwise. + */ +GIT_EXTERN(int) git_reference_is_tag(git_reference *ref); typedef enum { GIT_REF_FORMAT_NORMAL = 0, diff --git a/src/refs.c b/src/refs.c index c0e460cc3..6cc937fda 100644 --- a/src/refs.c +++ b/src/refs.c @@ -952,6 +952,17 @@ int git_reference_is_remote(git_reference *ref) return git_reference__is_remote(ref->name); } +int git_reference__is_tag(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0; +} + +int git_reference_is_tag(git_reference *ref) +{ + assert(ref); + return git_reference__is_tag(ref->name); +} + static int peel_error(int error, git_reference *ref, const char* msg) { giterr_set( diff --git a/src/refs.h b/src/refs.h index f487ee3fc..cb75abbe5 100644 --- a/src/refs.h +++ b/src/refs.h @@ -67,6 +67,7 @@ int git_reference__update_terminal(git_repository *repo, const char *ref_name, c int git_reference__is_valid_name(const char *refname, unsigned int flags); int git_reference__is_branch(const char *ref_name); int git_reference__is_remote(const char *ref_name); +int git_reference__is_tag(const char *ref_name); /** * Lookup a reference by name and try to resolve to an OID. diff --git a/tests-clar/refs/read.c b/tests-clar/refs/read.c index afb6be008..35cf17e9e 100644 --- a/tests-clar/refs/read.c +++ b/tests-clar/refs/read.c @@ -255,6 +255,22 @@ void test_refs_read__can_determine_if_a_reference_is_a_local_branch(void) assert_is_branch("refs/tags/e90810b", false); } +static void assert_is_tag(const char *name, bool expected_tagness) +{ + git_reference *reference; + cl_git_pass(git_reference_lookup(&reference, g_repo, name)); + cl_assert_equal_i(expected_tagness, git_reference_is_tag(reference)); + git_reference_free(reference); +} + +void test_refs_read__can_determine_if_a_reference_is_a_tag(void) +{ + assert_is_tag("refs/tags/e90810b", true); + assert_is_tag("refs/tags/test", true); + assert_is_tag("refs/heads/packed", false); + assert_is_tag("refs/remotes/test/master", false); +} + void test_refs_read__invalid_name_returns_EINVALIDSPEC(void) { git_reference *reference; From 430953417f74dfcdbe030bafc069e1c07edceeb6 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 26 Aug 2013 14:56:31 -0700 Subject: [PATCH 289/367] Load SRWLock APIs at runtime This loads SRWLock APIs at runtime and in their absence (i.e. on Windows before Vista) falls back on a regular CRITICAL_SECTION that will not permit concurrent readers. --- CMakeLists.txt | 7 +--- src/global.c | 12 ++++-- src/hash/hash_win32.c | 3 +- src/thread-utils.h | 4 -- src/win32/pthread.c | 89 ++++++++++++++++++++++++++++++++++++++----- src/win32/pthread.h | 14 ++++++- 6 files changed, 103 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 019777e78..1c70ec2d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -287,13 +287,8 @@ FILE(GLOB SRC_H include/git2.h include/git2/*.h include/git2/sys/*.h) # On Windows use specific platform sources IF (WIN32 AND NOT CYGWIN) - ADD_DEFINITIONS(-DWIN32) + ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501) FILE(GLOB SRC_OS src/win32/*.c src/win32/*.h) - IF (THREADSAFE) - ADD_DEFINITIONS(-D_WIN32_WINNT=0x0600) - ELSE() - ADD_DEFINITIONS(-D_WIN32_WINNT=0x0501) - ENDIF() ELSEIF (AMIGA) ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R) FILE(GLOB SRC_OS src/amiga/*.c src/amiga/*.h) diff --git a/src/global.c b/src/global.c index a06d0c81f..b504e5e0a 100644 --- a/src/global.c +++ b/src/global.c @@ -71,18 +71,22 @@ int git_threads_init(void) GIT_MEMORY_BARRIER; + win32_pthread_initialize(); + return error; } void git_threads_shutdown(void) { + /* Shut down any subsystems that have global state */ + win32_pthread_shutdown(); + git_futils_dirs_free(); + git_hash_global_shutdown(); + TlsFree(_tls_index); _tls_init = 0; - git_mutex_free(&git__mwindow_mutex); - /* Shut down any subsystems that have global state */ - git_hash_global_shutdown(); - git_futils_dirs_free(); + git_mutex_free(&git__mwindow_mutex); } git_global_st *git__global_state(void) diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c index 43d54ca6d..6732f93d7 100644 --- a/src/hash/hash_win32.c +++ b/src/hash/hash_win32.c @@ -46,7 +46,8 @@ GIT_INLINE(int) hash_cng_prov_init(void) return -1; /* Load bcrypt.dll explicitly from the system directory */ - if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || dll_path_len > MAX_PATH || + if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || + dll_path_len > MAX_PATH || StringCchCat(dll_path, MAX_PATH, "\\") < 0 || StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) diff --git a/src/thread-utils.h b/src/thread-utils.h index 371dc0b26..914c1357d 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -40,10 +40,6 @@ typedef git_atomic git_atomic_ssize; #ifdef GIT_THREADS -#if defined(GIT_WIN32) && _WIN32_WINNT < 0x0600 -# error "Unsupported Windows version for thread support" -#endif - #define git_thread pthread_t #define git_thread_create(thread, attr, start_routine, arg) \ pthread_create(thread, attr, start_routine, arg) diff --git a/src/win32/pthread.c b/src/win32/pthread.c index 41cb7a4c0..8775f632a 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -127,9 +127,10 @@ int pthread_cond_signal(pthread_cond_t *cond) return 0; } -/* pthread_cond_broadcast is not implemented because doing so with just Win32 events - * is quite complicated, and no caller in libgit2 uses it yet. */ - +/* pthread_cond_broadcast is not implemented because doing so with just + * Win32 events is quite complicated, and no caller in libgit2 uses it + * yet. + */ int pthread_num_processors_np(void) { DWORD_PTR p, s; @@ -142,41 +143,111 @@ int pthread_num_processors_np(void) return n ? n : 1; } + +static HINSTANCE win32_kernel32_dll; + +typedef void (WINAPI *win32_srwlock_fn)(SRWLOCK *); + +static win32_srwlock_fn win32_srwlock_initialize; +static win32_srwlock_fn win32_srwlock_acquire_shared; +static win32_srwlock_fn win32_srwlock_release_shared; +static win32_srwlock_fn win32_srwlock_acquire_exclusive; +static win32_srwlock_fn win32_srwlock_release_exclusive; + int pthread_rwlock_init( pthread_rwlock_t *GIT_RESTRICT lock, const pthread_rwlockattr_t *GIT_RESTRICT attr) { (void)attr; - InitializeSRWLock(lock); + + if (win32_srwlock_initialize) + win32_srwlock_initialize(&lock->native.srwl); + else + InitializeCriticalSection(&lock->native.csec); + return 0; } int pthread_rwlock_rdlock(pthread_rwlock_t *lock) { - AcquireSRWLockShared(lock); + if (win32_srwlock_acquire_shared) + win32_srwlock_acquire_shared(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + return 0; } int pthread_rwlock_rdunlock(pthread_rwlock_t *lock) { - ReleaseSRWLockShared(lock); + if (win32_srwlock_release_shared) + win32_srwlock_release_shared(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + return 0; } int pthread_rwlock_wrlock(pthread_rwlock_t *lock) { - AcquireSRWLockExclusive(lock); + if (win32_srwlock_acquire_exclusive) + win32_srwlock_acquire_exclusive(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + return 0; } int pthread_rwlock_wrunlock(pthread_rwlock_t *lock) { - ReleaseSRWLockExclusive(lock); + if (win32_srwlock_release_exclusive) + win32_srwlock_release_exclusive(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + return 0; } int pthread_rwlock_destroy(pthread_rwlock_t *lock) { - (void)lock; + if (!win32_srwlock_initialize) + DeleteCriticalSection(&lock->native.csec); + git__memzero(lock, sizeof(*lock)); + return 0; +} + + +int win32_pthread_initialize(void) +{ + if (win32_kernel32_dll) + return 0; + + win32_kernel32_dll = LoadLibrary("Kernel32.dll"); + if (!win32_kernel32_dll) { + giterr_set(GITERR_OS, "Could not load Kernel32.dll!"); + return -1; + } + + win32_srwlock_initialize = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "InitializeSRWLock"); + win32_srwlock_acquire_shared = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "AcquireSRWLockShared"); + win32_srwlock_release_shared = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockShared"); + win32_srwlock_acquire_exclusive = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "AcquireSRWLockExclusive"); + win32_srwlock_release_exclusive = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockExclusive"); + + return 0; +} + +int win32_pthread_shutdown(void) +{ + if (win32_kernel32_dll) { + FreeLibrary(win32_kernel32_dll); + win32_kernel32_dll = NULL; + } + return 0; } diff --git a/src/win32/pthread.h b/src/win32/pthread.h index 50d836247..e84de471f 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -24,10 +24,17 @@ typedef int pthread_rwlockattr_t; typedef CRITICAL_SECTION pthread_mutex_t; typedef HANDLE pthread_t; typedef HANDLE pthread_cond_t; -typedef SRWLOCK pthread_rwlock_t; + +/* typedef struct { void *Ptr; } SRWLOCK; */ + +typedef struct { + union { + SRWLOCK srwl; + CRITICAL_SECTION csec; + } native; +} pthread_rwlock_t; #define PTHREAD_MUTEX_INITIALIZER {(void*)-1} -#define PTHREAD_RWLOCK_INITIALIZER SRWLOCK_INIT int pthread_create( pthread_t *GIT_RESTRICT thread, @@ -61,4 +68,7 @@ int pthread_rwlock_wrlock(pthread_rwlock_t *); int pthread_rwlock_wrunlock(pthread_rwlock_t *); int pthread_rwlock_destroy(pthread_rwlock_t *); +extern int win32_pthread_initialize(void); +extern int win32_pthread_shutdown(void); + #endif From 2f368a661c55b49a8f15905c221f9e76935bedd0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 26 Aug 2013 15:17:35 -0700 Subject: [PATCH 290/367] Fix MINGW SRWLock typedefs --- src/win32/mingw-compat.h | 2 ++ src/win32/pthread.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index fe0abfb54..b06dda209 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -24,6 +24,8 @@ GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { return end ? (size_t)(end - s) : maxlen; } +typedef struct { void *Ptr; } SRWLOCK; + #endif #endif /* INCLUDE_mingw_compat__ */ diff --git a/src/win32/pthread.h b/src/win32/pthread.h index e84de471f..679ebed23 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -25,8 +25,6 @@ typedef CRITICAL_SECTION pthread_mutex_t; typedef HANDLE pthread_t; typedef HANDLE pthread_cond_t; -/* typedef struct { void *Ptr; } SRWLOCK; */ - typedef struct { union { SRWLOCK srwl; From b83c92dd6fe11adae0ff29e1db381c31f0f88cb7 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 21 Aug 2013 13:16:17 +0200 Subject: [PATCH 291/367] remote: Assert proper GIT_DIRECTION_XXXX values --- src/remote.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/remote.c b/src/remote.c index 7677e56b2..14ed740a8 100644 --- a/src/remote.c +++ b/src/remote.c @@ -534,6 +534,8 @@ const char* git_remote__urlfordirection(git_remote *remote, int direction) { assert(remote); + assert(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); + if (direction == GIT_DIRECTION_FETCH) { return remote->url; } From 44bc0c6ac3b939d3dfc1102be77e82e00e919ae4 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 21 Aug 2013 13:20:17 +0200 Subject: [PATCH 292/367] remote: Warn the user when connecting with no url --- src/remote.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index 14ed740a8..948c755bb 100644 --- a/src/remote.c +++ b/src/remote.c @@ -558,8 +558,11 @@ int git_remote_connect(git_remote *remote, git_direction direction) t = remote->transport; url = git_remote__urlfordirection(remote, direction); - if (url == NULL ) + if (url == NULL ) { + giterr_set(GITERR_INVALID, + "Malformed remote '%s' - missing URL", remote->name); return -1; + } /* A transport could have been supplied in advance with * git_remote_set_transport */ From ece24ef7c4bb31eb2c715948bcf6dff6ed9d7dfc Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 21 Aug 2013 13:37:21 +0200 Subject: [PATCH 293/367] remote: Don't parse missing urls as empty strings --- src/remote.c | 2 +- tests-clar/network/remote/remotes.c | 6 ++++++ tests-clar/resources/testrepo.git/config | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/remote.c b/src/remote.c index 948c755bb..4bba1d57e 100644 --- a/src/remote.c +++ b/src/remote.c @@ -308,7 +308,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) if ((error = get_optional_config(config, &buf, NULL, (void *)&val)) < 0) goto cleanup; - if (val) { + if (val && strlen(val) > 0) { remote->pushurl = git__strdup(val); GITERR_CHECK_ALLOC(remote->pushurl); } diff --git a/tests-clar/network/remote/remotes.c b/tests-clar/network/remote/remotes.c index dec646526..e356526ed 100644 --- a/tests-clar/network/remote/remotes.c +++ b/tests-clar/network/remote/remotes.c @@ -367,8 +367,14 @@ void test_network_remote_remotes__can_load_with_an_empty_url(void) cl_git_pass(git_remote_load(&remote, _repo, "empty-remote-url")); + cl_assert(remote->url == NULL); + cl_assert(remote->pushurl == NULL); + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH)); + cl_assert(giterr_last() != NULL); + cl_assert(giterr_last()->klass == GITERR_INVALID); + git_remote_free(remote); } diff --git a/tests-clar/resources/testrepo.git/config b/tests-clar/resources/testrepo.git/config index 904a4e3f3..1264f6ea7 100644 --- a/tests-clar/resources/testrepo.git/config +++ b/tests-clar/resources/testrepo.git/config @@ -10,7 +10,7 @@ url = git://github.com/libgit2/libgit2 [remote "empty-remote-url"] url = - + pushurl = [remote "test_with_pushurl"] url = git://github.com/libgit2/fetchlibgit2 pushurl = git://github.com/libgit2/pushlibgit2 From c9ffa84bde45021c40623553822916fb3d13b20a Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 21 Aug 2013 16:04:25 +0200 Subject: [PATCH 294/367] remote: Relax the parsing logic even more In order to be loaded, a remote needs to be configured with at least a `url` or a `pushurl`. ENOTFOUND will be returned when trying to git_remote_load() a remote with neither of these entries defined. --- src/remote.c | 33 ++++++++++++++++++------ tests-clar/network/remote/remotes.c | 22 ++++++++++++++++ tests-clar/resources/testrepo.git/config | 4 +++ 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/remote.c b/src/remote.c index 4bba1d57e..7276daa39 100644 --- a/src/remote.c +++ b/src/remote.c @@ -233,7 +233,8 @@ static int refspec_cb(const git_config_entry *entry, void *payload) } static int get_optional_config( - git_config *config, git_buf *buf, git_config_foreach_cb cb, void *payload) + bool *found, git_config *config, git_buf *buf, + git_config_foreach_cb cb, void *payload) { int error = 0; const char *key = git_buf_cstr(buf); @@ -246,6 +247,9 @@ static int get_optional_config( else error = git_config_get_string(payload, config, key); + if (found) + *found = !error; + if (error == GIT_ENOTFOUND) { giterr_clear(); error = 0; @@ -265,6 +269,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) int error = 0; git_config *config; struct refspec_cb_data data; + bool optional_setting_found = false, found; assert(out && repo && name); @@ -294,21 +299,33 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) goto cleanup; } - if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0) + if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) goto cleanup; + optional_setting_found |= found; + remote->repo = repo; - remote->url = git__strdup(val); - GITERR_CHECK_ALLOC(remote->url); + + if (found && strlen(val) > 0) { + remote->url = git__strdup(val); + GITERR_CHECK_ALLOC(remote->url); + } val = NULL; git_buf_clear(&buf); git_buf_printf(&buf, "remote.%s.pushurl", name); - if ((error = get_optional_config(config, &buf, NULL, (void *)&val)) < 0) + if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) goto cleanup; - if (val && strlen(val) > 0) { + optional_setting_found |= found; + + if (!optional_setting_found) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + if (found && strlen(val) > 0) { remote->pushurl = git__strdup(val); GITERR_CHECK_ALLOC(remote->pushurl); } @@ -318,14 +335,14 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) git_buf_clear(&buf); git_buf_printf(&buf, "remote.%s.fetch", name); - if ((error = get_optional_config(config, &buf, refspec_cb, &data)) < 0) + if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) goto cleanup; data.fetch = false; git_buf_clear(&buf); git_buf_printf(&buf, "remote.%s.push", name); - if ((error = get_optional_config(config, &buf, refspec_cb, &data)) < 0) + if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) goto cleanup; if (download_tags_value(remote, config) < 0) diff --git a/tests-clar/network/remote/remotes.c b/tests-clar/network/remote/remotes.c index e356526ed..55b233eda 100644 --- a/tests-clar/network/remote/remotes.c +++ b/tests-clar/network/remote/remotes.c @@ -378,6 +378,28 @@ void test_network_remote_remotes__can_load_with_an_empty_url(void) git_remote_free(remote); } +void test_network_remote_remotes__can_load_with_only_an_empty_pushurl(void) +{ + git_remote *remote = NULL; + + cl_git_pass(git_remote_load(&remote, _repo, "empty-remote-pushurl")); + + cl_assert(remote->url == NULL); + cl_assert(remote->pushurl == NULL); + + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH)); + + git_remote_free(remote); +} + +void test_network_remote_remotes__returns_ENOTFOUND_when_neither_url_nor_pushurl(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with( + git_remote_load(&remote, _repo, "no-remote-url"), GIT_ENOTFOUND); +} + void test_network_remote_remotes__check_structure_version(void) { git_transport transport = GIT_TRANSPORT_INIT; diff --git a/tests-clar/resources/testrepo.git/config b/tests-clar/resources/testrepo.git/config index 1264f6ea7..dfab4eeb4 100644 --- a/tests-clar/resources/testrepo.git/config +++ b/tests-clar/resources/testrepo.git/config @@ -11,6 +11,10 @@ [remote "empty-remote-url"] url = pushurl = +[remote "empty-remote-pushurl"] + pushurl = +[remote "no-remote-url"] + fetch = [remote "test_with_pushurl"] url = git://github.com/libgit2/fetchlibgit2 pushurl = git://github.com/libgit2/pushlibgit2 From 191adce8751e728111a3b1c3e9b2c02fe9f5d775 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Tue, 27 Aug 2013 20:00:28 +0200 Subject: [PATCH 295/367] vector: Teach git_vector_uniq() to free while deduplicating --- src/util.h | 5 ++++- src/vector.c | 9 ++++++--- src/vector.h | 2 +- tests-clar/core/vector.c | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/util.h b/src/util.h index a784390c1..bd93b46b5 100644 --- a/src/util.h +++ b/src/util.h @@ -82,7 +82,10 @@ GIT_INLINE(void *) git__realloc(void *ptr, size_t size) return new_ptr; } -#define git__free(ptr) free(ptr) +GIT_INLINE(void) git__free(void *ptr) +{ + free(ptr); +} #define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \ ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2))) diff --git a/src/vector.c b/src/vector.c index 5ba2fab18..362e7b0c0 100644 --- a/src/vector.c +++ b/src/vector.c @@ -220,7 +220,7 @@ void git_vector_pop(git_vector *v) v->length--; } -void git_vector_uniq(git_vector *v) +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)) { git_vector_cmp cmp; size_t i, j; @@ -232,9 +232,12 @@ void git_vector_uniq(git_vector *v) cmp = v->_cmp ? v->_cmp : strict_comparison; for (i = 0, j = 1 ; j < v->length; ++j) - if (!cmp(v->contents[i], v->contents[j])) + if (!cmp(v->contents[i], v->contents[j])) { + if (git_free_cb) + git_free_cb(v->contents[i]); + v->contents[i] = v->contents[j]; - else + } else v->contents[++i] = v->contents[j]; v->length -= j - i - 1; diff --git a/src/vector.h b/src/vector.h index 1bda9c93d..cca846f43 100644 --- a/src/vector.h +++ b/src/vector.h @@ -71,7 +71,7 @@ int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void **old, void *new)); int git_vector_remove(git_vector *v, size_t idx); void git_vector_pop(git_vector *v); -void git_vector_uniq(git_vector *v); +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)); void git_vector_remove_matching( git_vector *v, int (*match)(const git_vector *v, size_t idx)); diff --git a/tests-clar/core/vector.c b/tests-clar/core/vector.c index c9e43a149..db52c004f 100644 --- a/tests-clar/core/vector.c +++ b/tests-clar/core/vector.c @@ -54,7 +54,7 @@ void test_core_vector__2(void) cl_git_pass(git_vector_insert(&x, ptrs[1])); cl_assert(x.length == 5); - git_vector_uniq(&x); + git_vector_uniq(&x, NULL); cl_assert(x.length == 2); git_vector_free(&x); From aec87f712fd1e84038d5d14b83a97d78e2e1b1ad Mon Sep 17 00:00:00 2001 From: nulltoken Date: Tue, 27 Aug 2013 19:14:18 +0200 Subject: [PATCH 296/367] remote: Make git_remote_list() detect pushurl --- src/remote.c | 6 ++++-- tests-clar/network/remote/remotes.c | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/remote.c b/src/remote.c index 7276daa39..10c1b5b81 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1109,10 +1109,10 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo) if (git_repository_config__weakptr(&cfg, repo) < 0) return -1; - if (git_vector_init(&list, 4, NULL) < 0) + if (git_vector_init(&list, 4, git__strcmp_cb) < 0) return -1; - if (regcomp(&preg, "^remote\\.(.*)\\.url$", REG_EXTENDED) < 0) { + if (regcomp(&preg, "^remote\\.(.*)\\.(push)?url$", REG_EXTENDED) < 0) { giterr_set(GITERR_OS, "Remote catch regex failed to compile"); return -1; } @@ -1137,6 +1137,8 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo) return error; } + git_vector_uniq(&list, git__free); + remotes_list->strings = (char **)list.contents; remotes_list->count = list.length; diff --git a/tests-clar/network/remote/remotes.c b/tests-clar/network/remote/remotes.c index 55b233eda..6e0eeeb05 100644 --- a/tests-clar/network/remote/remotes.c +++ b/tests-clar/network/remote/remotes.c @@ -243,13 +243,19 @@ void test_network_remote_remotes__list(void) git_config *cfg; cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 4); + cl_assert(list.count == 5); git_strarray_free(&list); cl_git_pass(git_repository_config(&cfg, _repo)); + + /* Create a new remote */ cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); + + /* Update a remote (previously without any url/pushurl entry) */ + cl_git_pass(git_config_set_string(cfg, "remote.no-remote-url.pushurl", "http://example.com")); + cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 5); + cl_assert(list.count == 7); git_strarray_free(&list); git_config_free(cfg); From f087bc245e9f3934d43c49f4034ee9b5638884dd Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 27 Aug 2013 12:08:55 -0700 Subject: [PATCH 297/367] Convert to our own SRWLOCK type on Win32 --- src/win32/mingw-compat.h | 2 -- src/win32/pthread.c | 2 +- src/win32/pthread.h | 4 +++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index b06dda209..fe0abfb54 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -24,8 +24,6 @@ GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { return end ? (size_t)(end - s) : maxlen; } -typedef struct { void *Ptr; } SRWLOCK; - #endif #endif /* INCLUDE_mingw_compat__ */ diff --git a/src/win32/pthread.c b/src/win32/pthread.c index 8775f632a..d50ace695 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -146,7 +146,7 @@ int pthread_num_processors_np(void) static HINSTANCE win32_kernel32_dll; -typedef void (WINAPI *win32_srwlock_fn)(SRWLOCK *); +typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *); static win32_srwlock_fn win32_srwlock_initialize; static win32_srwlock_fn win32_srwlock_acquire_shared; diff --git a/src/win32/pthread.h b/src/win32/pthread.h index 679ebed23..2ba2ca552 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -25,9 +25,11 @@ typedef CRITICAL_SECTION pthread_mutex_t; typedef HANDLE pthread_t; typedef HANDLE pthread_cond_t; +typedef struct { void *Ptr; } GIT_SRWLOCK; + typedef struct { union { - SRWLOCK srwl; + GIT_SRWLOCK srwl; CRITICAL_SECTION csec; } native; } pthread_rwlock_t; From 1ff3a094156baba11240f48924dd5e0c1d983d8e Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 27 Aug 2013 19:41:44 -0500 Subject: [PATCH 298/367] Improve win32 version check, no ipv6 tests on XP --- src/hash/hash_win32.c | 20 +------------------- src/transports/winhttp.c | 4 ++-- src/win32/error.c | 2 +- src/win32/version.h | 25 +++++++++++++++++++++---- tests-clar/core/posix.c | 19 ++++++++++++++++--- 5 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c index 43d54ca6d..362712e9a 100644 --- a/src/hash/hash_win32.c +++ b/src/hash/hash_win32.c @@ -20,29 +20,11 @@ static struct git_hash_prov hash_prov = {0}; /* Initialize CNG, if available */ GIT_INLINE(int) hash_cng_prov_init(void) { - OSVERSIONINFOEX version_test = {0}; - DWORD version_test_mask; - DWORDLONG version_condition_mask = 0; char dll_path[MAX_PATH]; DWORD dll_path_len, size_len; - return -1; - /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ - version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - version_test.dwMajorVersion = 6; - version_test.dwMinorVersion = 0; - version_test.wServicePackMajor = 1; - version_test.wServicePackMinor = 0; - - version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); - - VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); - - if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) + if (!git_has_win32_version(6, 0, 1)) return -1; /* Load bcrypt.dll explicitly from the system directory */ diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 8decd8d51..29d4ba619 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -511,7 +511,7 @@ replay: /* 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)) { + if (!git_has_win32_version(6, 1, 0)) { wchar_t *location; DWORD location_length; int redirect_cmp; @@ -989,7 +989,7 @@ static int winhttp_receivepack( { /* WinHTTP only supports Transfer-Encoding: chunked * on Windows Vista (NT 6.0) and higher. */ - s->chunked = git_has_win32_version(6, 0); + s->chunked = git_has_win32_version(6, 0, 0); if (s->chunked) s->parent.write = winhttp_stream_write_chunked; diff --git a/src/win32/error.c b/src/win32/error.c index a62a07e82..bc598ae32 100644 --- a/src/win32/error.c +++ b/src/win32/error.c @@ -47,7 +47,7 @@ char *git_win32_get_error_message(DWORD error_code) (LPWSTR)&lpMsgBuf, 0, NULL)) { /* Invalid code point check supported on Vista+ only */ - if (git_has_win32_version(6, 0)) + if (git_has_win32_version(6, 0, 0)) dwFlags = WC_ERR_INVALID_CHARS; else dwFlags = 0; diff --git a/src/win32/version.h b/src/win32/version.h index 483962b57..518b0a379 100644 --- a/src/win32/version.h +++ b/src/win32/version.h @@ -9,12 +9,29 @@ #include -GIT_INLINE(int) git_has_win32_version(int major, int minor) +GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack) { - WORD wVersion = LOWORD(GetVersion()); + OSVERSIONINFOEX version_test = {0}; + DWORD version_test_mask; + DWORDLONG version_condition_mask = 0; + + version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + version_test.dwMajorVersion = major; + version_test.dwMinorVersion = minor; + version_test.wServicePackMajor = service_pack; + version_test.wServicePackMinor = 0; - return LOBYTE(wVersion) > major || - (LOBYTE(wVersion) == major && HIBYTE(wVersion) >= minor); + version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); + + VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) + return 0; + + return 1; } #endif diff --git a/tests-clar/core/posix.c b/tests-clar/core/posix.c index 890e25d47..1cef937cd 100644 --- a/tests-clar/core/posix.c +++ b/tests-clar/core/posix.c @@ -24,6 +24,16 @@ void test_core_posix__initialize(void) #endif } +static bool supports_ipv6(void) +{ +#ifdef GIT_WIN32 + /* IPv6 is supported on Vista and newer */ + return git_has_win32_version(6, 0, 0); +#else + return 1; +#endif +} + void test_core_posix__inet_pton(void) { struct in_addr addr; @@ -65,9 +75,12 @@ void test_core_posix__inet_pton(void) } /* Test some ipv6 addresses */ - for (i = 0; i < 6; i++) { - cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1); - cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0); + if (supports_ipv6()) + { + for (i = 0; i < 6; i++) { + cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1); + cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0); + } } /* Test some invalid strings */ From 17c7fbf6d276443344c54f55800367b9837c0259 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 21 Aug 2013 14:07:53 -0500 Subject: [PATCH 299/367] Split rewrites, status doesn't return rewrites Ensure that we apply splits to rewrites, even if we're not interested in examining it closely for rename/copy detection. In keeping with core git, status should not display rewrites, it should simply show files as "modified". --- include/git2/diff.h | 3 +++ src/diff_tform.c | 6 ++++- src/status.c | 6 +++-- tests-clar/diff/rename.c | 49 +++++++++++++++++++++++++++++++++++++ tests-clar/status/renames.c | 41 +++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 3 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index c989ba4ee..596098574 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -454,6 +454,9 @@ typedef enum { GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 13), /** measure similarity only by comparing SHAs (fast and cheap) */ GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1 << 14), + + /** do not break rewrites unless they contribute to a rename */ + GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY = (1 << 15), } git_diff_find_t; /** diff --git a/src/diff_tform.c b/src/diff_tform.c index ba35d3c14..ca3c77187 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -799,6 +799,9 @@ int git_diff_find_similar( if (is_rename_target(diff, &opts, t, sigcache)) ++num_tgts; + + if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) + num_rewrites++; } /* if there are no candidate srcs or tgts, we're done */ @@ -1036,7 +1039,8 @@ find_best_matches: if (num_rewrites > 0 || num_updates > 0) error = apply_splits_and_deletes( diff, diff->deltas.length - num_rewrites, - FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES)); + FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) && + !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY)); cleanup: git__free(tgt2src); diff --git a/src/status.c b/src/status.c index b2353258b..4a0d65092 100644 --- a/src/status.c +++ b/src/status.c @@ -284,8 +284,10 @@ int git_status_list_new( diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0) - findopt.flags = findopt.flags | GIT_DIFF_FIND_AND_BREAK_REWRITES | - GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + findopt.flags = findopt.flags | + GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES | + GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY; if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { if ((error = git_diff_tree_to_index( diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 5a35495f7..ac3814d59 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -1235,3 +1235,52 @@ void test_diff_rename__unmodified_can_be_renamed(void) git_index_free(index); git_tree_free(tree); } + +void test_diff_rename__rewrite_on_single_file(void) +{ + git_index *index; + git_diff_list *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + + findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED | + GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_rewritefile("renames/ikeepsix.txt", + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_list_free(diff); + git_index_free(index); +} diff --git a/tests-clar/status/renames.c b/tests-clar/status/renames.c index 836e65c88..d72e563bf 100644 --- a/tests-clar/status/renames.c +++ b/tests-clar/status/renames.c @@ -403,6 +403,47 @@ void test_status_renames__both_rename_from_rewrite(void) git_index_free(index); } +void test_status_renames__rewrites_only_for_renames(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_rewritefile("renames/ikeepsix.txt", + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 1); + git_status_list_free(statuslist); + + git_index_free(index); +} + void test_status_renames__both_casechange_one(void) { git_index *index; From b2d3efcbce2d12cfa9736ab4f9283c91600a8a75 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 28 Aug 2013 09:31:32 -0700 Subject: [PATCH 300/367] Some documentation improvements --- include/git2/commit.h | 16 +++++++++++---- include/git2/revparse.h | 43 ++++++++++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/include/git2/commit.h b/include/git2/commit.h index fc0551be1..0eaf917bd 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -24,17 +24,24 @@ GIT_BEGIN_DECL /** * Lookup a commit object from a repository. * + * The returned object should be released with `git_commit_free` when no + * longer needed. + * * @param commit pointer to the looked up commit * @param repo the repo to use when locating the commit. * @param id identity of the commit to locate. If the object is * an annotated tag it will be peeled back to the commit. * @return 0 or an error code */ -GIT_EXTERN(int) git_commit_lookup(git_commit **commit, git_repository *repo, const git_oid *id); +GIT_EXTERN(int) git_commit_lookup( + git_commit **commit, git_repository *repo, const git_oid *id); /** - * Lookup a commit object from a repository, - * given a prefix of its identifier (short id). + * Lookup a commit object from a repository, given a prefix of its + * identifier (short id). + * + * The returned object should be released with `git_commit_free` when no + * longer needed. * * @see git_object_lookup_prefix * @@ -45,7 +52,8 @@ GIT_EXTERN(int) git_commit_lookup(git_commit **commit, git_repository *repo, con * @param len the length of the short identifier * @return 0 or an error code */ -GIT_EXTERN(int) git_commit_lookup_prefix(git_commit **commit, git_repository *repo, const git_oid *id, size_t len); +GIT_EXTERN(int) git_commit_lookup_prefix( + git_commit **commit, git_repository *repo, const git_oid *id, size_t len); /** * Close an open commit diff --git a/include/git2/revparse.h b/include/git2/revparse.h index 786a9da57..d170e1621 100644 --- a/include/git2/revparse.h +++ b/include/git2/revparse.h @@ -10,7 +10,6 @@ #include "common.h" #include "types.h" - /** * @file git2/revparse.h * @brief Git revision parsing routines @@ -21,27 +20,37 @@ GIT_BEGIN_DECL /** - * Find a single object, as specified by a revision string. See `man gitrevisions`, - * or http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for + * Find a single object, as specified by a revision string. + * + * See `man gitrevisions`, or + * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for * information on the syntax accepted. * + * The returned object should be released with `git_object_free` when no + * longer needed. + * * @param out pointer to output object * @param repo the repository to search in * @param spec the textual specification for an object * @return 0 on success, GIT_ENOTFOUND, GIT_EAMBIGUOUS, GIT_EINVALIDSPEC or an error code */ -GIT_EXTERN(int) git_revparse_single(git_object **out, git_repository *repo, const char *spec); +GIT_EXTERN(int) git_revparse_single( + git_object **out, git_repository *repo, const char *spec); /** - * Find a single object, as specified by a revision string. - * See `man gitrevisions`, - * or http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for + * Find a single object and intermediate reference by a revision string. + * + * See `man gitrevisions`, or + * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for * information on the syntax accepted. * * In some cases (`@{<-n>}` or `@{upstream}`), the expression may * point to an intermediate reference. When such expressions are being passed * in, `reference_out` will be valued as well. * + * The returned object should be released with `git_object_free` and the + * returned reference with `git_reference_free` when no longer needed. + * * @param object_out pointer to output object * @param reference_out pointer to output reference or NULL * @param repo the repository to search in @@ -76,25 +85,27 @@ typedef struct { git_object *from; /** The right element of the revspec; must be freed by the user */ git_object *to; - /** The intent of the revspec */ + /** The intent of the revspec (i.e. `git_revparse_mode_t` flags) */ unsigned int flags; } git_revspec; /** - * Parse a revision string for `from`, `to`, and intent. See `man gitrevisions` or - * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for information - * on the syntax accepted. + * Parse a revision string for `from`, `to`, and intent. * - * @param revspec Pointer to an user-allocated git_revspec struct where the result - * of the rev-parse will be stored + * See `man gitrevisions` or + * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for + * information on the syntax accepted. + * + * @param revspec Pointer to an user-allocated git_revspec struct where + * the result of the rev-parse will be stored * @param repo the repository to search in * @param spec the rev-parse spec to parse * @return 0 on success, GIT_INVALIDSPEC, GIT_ENOTFOUND, GIT_EAMBIGUOUS or an error code */ GIT_EXTERN(int) git_revparse( - git_revspec *revspec, - git_repository *repo, - const char *spec); + git_revspec *revspec, + git_repository *repo, + const char *spec); /** @} */ From 19b9a0920987f07d05040b3a067b1599e38f6013 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 28 Aug 2013 11:20:47 -0700 Subject: [PATCH 301/367] Add stddef include for sortedcache All use of sortedcache will need this header, so put it in the definition of the sortedcache API. --- src/sortedcache.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sortedcache.h b/src/sortedcache.h index 5ebb116ed..4cacad62b 100644 --- a/src/sortedcache.h +++ b/src/sortedcache.h @@ -14,6 +14,8 @@ #include "pool.h" #include "strmap.h" +#include + /* * The purpose of this data structure is to cache the parsed contents of a * file (a.k.a. the backing file) where each item in the file can be From 4ab6a759f63caa75ea716154887fc0a94de01b6e Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 28 Aug 2013 22:51:44 -0700 Subject: [PATCH 302/367] Fix incorrect precedence within git_repository_is_empty() Reverts part of 9146f1e57ec4f2b6fa293c78d54f1383464ff5be. --- src/repository.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repository.c b/src/repository.c index 99ac56ef9..e5f23e4e4 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1500,7 +1500,7 @@ int git_repository_is_empty(git_repository *repo) if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) return -1; - if (!((error = git_reference_type(head)) == GIT_REF_SYMBOLIC)) + if (!(error = (git_reference_type(head) == GIT_REF_SYMBOLIC))) goto cleanup; if (!(error = (strcmp( From 0001c0231607bd8bc9b09bde6c1d7d0fb396565f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 29 Aug 2013 13:22:44 +0200 Subject: [PATCH 303/367] Fix typo _delete -> _free --- tests-clar/threads/refdb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-clar/threads/refdb.c b/tests-clar/threads/refdb.c index ffe437575..f8d76cb9b 100644 --- a/tests-clar/threads/refdb.c +++ b/tests-clar/threads/refdb.c @@ -130,7 +130,7 @@ static void *delete_refs(void *arg) if (!git_reference_lookup(&ref, g_repo, name)) { cl_git_pass(git_reference_delete(ref)); - git_reference_delete(ref); + git_reference_free(ref); } if (i == 5) { From 8b2f230cd519010d202c57185dc4dc39ca3d53f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vicent=20Mart=C3=AD?= Date: Thu, 29 Aug 2013 13:27:37 +0200 Subject: [PATCH 304/367] repository: Make the is_empty check more explicit --- src/repository.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/repository.c b/src/repository.c index e5f23e4e4..80904d5fa 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1500,8 +1500,10 @@ int git_repository_is_empty(git_repository *repo) if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) return -1; - if (!(error = (git_reference_type(head) == GIT_REF_SYMBOLIC))) + if (git_reference_type(head) != GIT_REF_SYMBOLIC) { + error = -1; goto cleanup; + } if (!(error = (strcmp( git_reference_symbolic_target(head), From 4218183631faa48f97e76a23e928d1a98983be46 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 29 Aug 2013 10:27:01 -0700 Subject: [PATCH 305/367] Treat detached HEAD as non-empty repo This simplifies the git_repository_is_empty a bit so that a detached HEAD is just taken to mean the repo is not empty, since a newly initialized repo will not have a detached HEAD. --- src/repository.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/repository.c b/src/repository.c index 80904d5fa..eae22ce51 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1495,26 +1495,20 @@ static int repo_contains_no_reference(git_repository *repo) int git_repository_is_empty(git_repository *repo) { git_reference *head = NULL; - int error; + int is_empty = 0; if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) return -1; - if (git_reference_type(head) != GIT_REF_SYMBOLIC) { - error = -1; - goto cleanup; - } + if (git_reference_type(head) == GIT_REF_SYMBOLIC) + is_empty = + (strcmp(git_reference_symbolic_target(head), + GIT_REFS_HEADS_DIR "master") == 0) && + repo_contains_no_reference(repo); - if (!(error = (strcmp( - git_reference_symbolic_target(head), - GIT_REFS_HEADS_DIR "master") == 0))) - goto cleanup; - - error = repo_contains_no_reference(repo); - -cleanup: git_reference_free(head); - return error < 0 ? -1 : error; + + return is_empty; } const char *git_repository_path(git_repository *repo) From a12e069a3e904728d55dc5c92094b0a5cee63da2 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 30 Aug 2013 16:31:52 +0200 Subject: [PATCH 306/367] odb: Honor the non refreshing capability of a backend --- src/odb.c | 6 +- tests-clar/odb/backend/nonrefreshing.c | 261 +++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 tests-clar/odb/backend/nonrefreshing.c diff --git a/src/odb.c b/src/odb.c index 21b46bf56..9785c74da 100644 --- a/src/odb.c +++ b/src/odb.c @@ -622,7 +622,7 @@ attempt_lookup: backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; - if (b->exists != NULL) + if (b->exists != NULL && (!refreshed || b->refresh)) found = b->exists(b, id); } @@ -717,7 +717,7 @@ attempt_lookup: backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; - if (b->read != NULL) { + if (b->read != NULL && (!refreshed || b->refresh)) { ++reads; error = b->read(&raw.data, &raw.len, &raw.type, b, id); } @@ -774,7 +774,7 @@ attempt_lookup: backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; - if (b->read_prefix != NULL) { + if (b->read_prefix != NULL && (!refreshed || b->refresh)) { git_oid full_oid; error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, short_id, len); if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) diff --git a/tests-clar/odb/backend/nonrefreshing.c b/tests-clar/odb/backend/nonrefreshing.c new file mode 100644 index 000000000..abb824d4b --- /dev/null +++ b/tests-clar/odb/backend/nonrefreshing.c @@ -0,0 +1,261 @@ +#include "clar_libgit2.h" +#include "git2/sys/odb_backend.h" +#include "repository.h" + +typedef struct fake_backend { + git_odb_backend parent; + + git_error_code error_code; + + int exists_calls; + int read_calls; + int read_header_calls; + int read_prefix_calls; +} fake_backend; + +static git_repository *_repo; +static fake_backend *_fake; +static git_oid _oid; + +static int fake_backend__exists(git_odb_backend *backend, const git_oid *oid) +{ + fake_backend *fake; + + GIT_UNUSED(oid); + + fake = (fake_backend *)backend; + + fake->exists_calls++; + + return (fake->error_code == GIT_OK); +} + +static int fake_backend__read( + void **buffer_p, size_t *len_p, git_otype *type_p, + git_odb_backend *backend, const git_oid *oid) +{ + fake_backend *fake; + + GIT_UNUSED(buffer_p); + GIT_UNUSED(len_p); + GIT_UNUSED(type_p); + GIT_UNUSED(oid); + + fake = (fake_backend *)backend; + + fake->read_calls++; + + *len_p = 0; + *buffer_p = NULL; + *type_p = GIT_OBJ_BLOB; + + return fake->error_code; +} + +static int fake_backend__read_header( + size_t *len_p, git_otype *type_p, + git_odb_backend *backend, const git_oid *oid) +{ + fake_backend *fake; + + GIT_UNUSED(len_p); + GIT_UNUSED(type_p); + GIT_UNUSED(oid); + + fake = (fake_backend *)backend; + + fake->read_header_calls++; + + *len_p = 0; + *type_p = GIT_OBJ_BLOB; + + return fake->error_code; +} + +static int fake_backend__read_prefix( + git_oid *out_oid, void **buffer_p, size_t *len_p, git_otype *type_p, + git_odb_backend *backend, const git_oid *short_oid, size_t len) +{ + fake_backend *fake; + + GIT_UNUSED(out_oid); + GIT_UNUSED(buffer_p); + GIT_UNUSED(len_p); + GIT_UNUSED(type_p); + GIT_UNUSED(short_oid); + GIT_UNUSED(len); + + fake = (fake_backend *)backend; + + fake->read_prefix_calls++; + + *len_p = 0; + *buffer_p = NULL; + *type_p = GIT_OBJ_BLOB; + + return fake->error_code; +} + +static void fake_backend__free(git_odb_backend *_backend) +{ + fake_backend *backend; + + backend = (fake_backend *)_backend; + + git__free(backend); +} + +static int build_fake_backend( + git_odb_backend **out, + git_error_code error_code) +{ + fake_backend *backend; + + backend = git__calloc(1, sizeof(fake_backend)); + GITERR_CHECK_ALLOC(backend); + + backend->parent.version = GIT_ODB_BACKEND_VERSION; + + backend->parent.refresh = NULL; + backend->error_code = error_code; + + backend->parent.read = fake_backend__read; + backend->parent.read_prefix = fake_backend__read_prefix; + backend->parent.read_header = fake_backend__read_header; + backend->parent.exists = fake_backend__exists; + backend->parent.free = &fake_backend__free; + + *out = (git_odb_backend *)backend; + + return 0; +} + +static void setup_repository_and_backend(git_error_code error_code) +{ + git_odb *odb; + git_odb_backend *backend; + + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(build_fake_backend(&backend, error_code)); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_git_pass(git_odb_add_backend(odb, backend, 10)); + + _fake = (fake_backend *)backend; + + cl_git_pass(git_oid_fromstr(&_oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); +} + +void test_odb_backend_nonrefreshing__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_failure(void) +{ + git_odb *odb; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_b(false, git_odb_exists(odb, &_oid)); + + cl_assert_equal_i(1, _fake->exists_calls); +} + +void test_odb_backend_nonrefreshing__read_is_invoked_once_on_failure(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_fail_with( + git_object_lookup(&obj, _repo, &_oid, GIT_OBJ_ANY), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_calls); +} + +void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_failure(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_fail_with( + git_object_lookup_prefix(&obj, _repo, &_oid, 7, GIT_OBJ_ANY), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_prefix_calls); +} + +void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_failure(void) +{ + git_odb *odb; + size_t len; + git_otype type; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + + cl_git_fail_with( + git_odb_read_header(&len, &type, odb, &_oid), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_header_calls); +} + +void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_success(void) +{ + git_odb *odb; + + setup_repository_and_backend(GIT_OK); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_b(true, git_odb_exists(odb, &_oid)); + + cl_assert_equal_i(1, _fake->exists_calls); +} + +void test_odb_backend_nonrefreshing__read_is_invoked_once_on_success(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_OK); + + cl_git_pass(git_object_lookup(&obj, _repo, &_oid, GIT_OBJ_ANY)); + + cl_assert_equal_i(1, _fake->read_calls); + + git_object_free(obj); +} + +void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_success(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_OK); + + cl_git_pass(git_object_lookup_prefix(&obj, _repo, &_oid, 7, GIT_OBJ_ANY)); + + cl_assert_equal_i(1, _fake->read_prefix_calls); + + git_object_free(obj); +} + +void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_success(void) +{ + git_odb *odb; + size_t len; + git_otype type; + + setup_repository_and_backend(GIT_OK); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + + cl_git_pass(git_odb_read_header(&len, &type, odb, &_oid)); + + cl_assert_equal_i(1, _fake->read_header_calls); +} From 9b4ed214f484f22f127d7398467f45affcc014af Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 30 Aug 2013 17:07:41 +0200 Subject: [PATCH 307/367] odb: Code beautification --- src/odb_pack.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/odb_pack.c b/src/odb_pack.c index 43880612a..d7abd1020 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -342,8 +342,9 @@ static int pack_backend__refresh(git_odb_backend *_backend) return 0; } - -static int pack_backend__read_header(size_t *len_p, git_otype *type_p, struct git_odb_backend *backend, const git_oid *oid) +static int pack_backend__read_header( + size_t *len_p, git_otype *type_p, + struct git_odb_backend *backend, const git_oid *oid) { struct git_pack_entry e; int error; @@ -356,7 +357,9 @@ static int pack_backend__read_header(size_t *len_p, git_otype *type_p, struct gi return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); } -static int pack_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) +static int pack_backend__read( + void **buffer_p, size_t *len_p, git_otype *type_p, + git_odb_backend *backend, const git_oid *oid) { struct git_pack_entry e; git_rawobj raw; From e68938e0b98fac52da20171f33ca3abfdbf3f434 Mon Sep 17 00:00:00 2001 From: Linquize Date: Sat, 31 Aug 2013 18:19:44 +0800 Subject: [PATCH 308/367] Update documentation of git_oid_streq to remove outdated error code --- include/git2/oid.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/git2/oid.h b/include/git2/oid.h index 662338d93..c69968ad1 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -188,8 +188,7 @@ GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, size_t len); * * @param id oid structure. * @param str input hex string of an object id. - * @return GIT_ENOTOID if str is not a valid hex string, - * 0 in case of a match, GIT_ERROR otherwise. + * @return 0 in case of a match, -1 otherwise. */ GIT_EXTERN(int) git_oid_streq(const git_oid *id, const char *str); From d45e9480e74c3c6249aea631394e1364b138e66e Mon Sep 17 00:00:00 2001 From: Linquize Date: Sat, 31 Aug 2013 18:22:50 +0800 Subject: [PATCH 309/367] oid: git_oid_shorten_add() sets GITERR_INVALID when OID set is full --- include/git2/oid.h | 4 ++-- src/oid.c | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/git2/oid.h b/include/git2/oid.h index 662338d93..8bf43a279 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -241,13 +241,13 @@ GIT_EXTERN(git_oid_shorten *) git_oid_shorten_new(size_t min_length); * or freed. * * For performance reasons, there is a hard-limit of how many - * OIDs can be added to a single set (around ~22000, assuming + * OIDs can be added to a single set (around ~32000, assuming * a mostly randomized distribution), which should be enough * for any kind of program, and keeps the algorithm fast and * memory-efficient. * * Attempting to add more than those OIDs will result in a - * GIT_ENOMEM error + * GITERR_INVALID error * * @param os a `git_oid_shorten` instance * @param text_id an OID in text form diff --git a/src/oid.c b/src/oid.c index 8300e46c1..a70b7e099 100644 --- a/src/oid.c +++ b/src/oid.c @@ -369,8 +369,10 @@ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) bool is_leaf; node_index idx; - if (os->full) + if (os->full) { + giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full"); return -1; + } if (text_oid == NULL) return os->min_length; @@ -396,12 +398,19 @@ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) node->tail = NULL; node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]); - GITERR_CHECK_ALLOC(node); + if (node == NULL) { + if (os->full) + giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full"); + return -1; + } } if (node->children[c] == 0) { - if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) + if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) { + if (os->full) + giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full"); return -1; + } break; } From a402179ae58c9954794bb6b172a30249d6be6127 Mon Sep 17 00:00:00 2001 From: Nirvana Date: Sat, 31 Aug 2013 09:25:25 -0400 Subject: [PATCH 310/367] Update readme to point to the currently maintained Erlang bindings. Namely: https://github.com/carlosmn/geef --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a89463b7c..bcab8ac51 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Here are the bindings to libgit2 that are currently available: * Delphi * GitForDelphi * Erlang - * Geef + * Geef * Go * go-git * GObject From f2cda906e515bd4cb0cfe244652d764fdabbe23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 31 Aug 2013 17:42:38 +0200 Subject: [PATCH 311/367] Point to the right Go bindings --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bcab8ac51..44fd059c1 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ Here are the bindings to libgit2 that are currently available: * Erlang * Geef * Go - * go-git + * git2go * GObject * libgit2-glib * Haskell From 82b2fc2c8325830667ed780ae5402674c7b9bbf5 Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Sun, 1 Sep 2013 18:45:36 +0200 Subject: [PATCH 312/367] Create ANDROID build option CMake seems not to support Android as a target and this option lets us test this in CMakeLists.txt. --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c70ec2d6..a9b87b8a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,8 @@ OPTION( PROFILE "Generate profiling information" OFF ) OPTION( ENABLE_TRACE "Enables tracing support" OFF ) OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF ) +OPTION( ANDROID "Build for android NDK" OFF ) + IF(MSVC) # This option is only available when building with MSVC. By default, libgit2 # is build using the cdecl calling convention, which is useful if you're @@ -127,7 +129,7 @@ IF (ENABLE_TRACE STREQUAL "ON") ENDIF() # Include POSIX regex when it is required -IF(WIN32 OR AMIGA) +IF(WIN32 OR AMIGA OR ANDROID) INCLUDE_DIRECTORIES(deps/regex) SET(SRC_REGEX deps/regex/regex.c) ENDIF() @@ -409,7 +411,7 @@ ENDIF () IF (BUILD_EXAMPLES) FILE(GLOB_RECURSE EXAMPLE_SRC examples/network/*.c examples/network/*.h) ADD_EXECUTABLE(cgit2 ${EXAMPLE_SRC}) - IF(WIN32) + IF(WIN32 OR ANDROID) TARGET_LINK_LIBRARIES(cgit2 git2) ELSE() TARGET_LINK_LIBRARIES(cgit2 git2 pthread) From b1447edebc15de58f46e5863e59acb7b4988f304 Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Sun, 1 Sep 2013 18:47:56 +0200 Subject: [PATCH 313/367] Use git__insertsort_r on Android too. --- src/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.c b/src/util.c index ad7603829..d0c326ae5 100644 --- a/src/util.c +++ b/src/util.c @@ -711,7 +711,7 @@ void git__qsort_r( void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) { #if defined(__MINGW32__) || defined(__OpenBSD__) || defined(AMIGA) || \ - defined(__gnu_hurd__) || \ + defined(__gnu_hurd__) || defined(__ANDROID_API__) || \ (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8) git__insertsort_r(els, nel, elsize, NULL, cmp, payload); #elif defined(GIT_WIN32) From 3b75b684a1ac32cbf726cc3d647453a769b8dfe4 Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Sun, 1 Sep 2013 18:53:07 +0200 Subject: [PATCH 314/367] Define S_IREAD i S_IWRITE for Android. --- tests-clar/odb/loose.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests-clar/odb/loose.c b/tests-clar/odb/loose.c index 9539bb24c..eb6b788b7 100644 --- a/tests-clar/odb/loose.c +++ b/tests-clar/odb/loose.c @@ -3,6 +3,11 @@ #include "posix.h" #include "loose_data.h" +#ifdef __ANDROID_API__ +# define S_IREAD S_IRUSR +# define S_IWRITE S_IWUSR +#endif + static void write_object_files(object_data *d) { int fd; From d6d523486c26f8f10cd687fa2aa328c770650288 Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Sun, 1 Sep 2013 18:30:11 +0200 Subject: [PATCH 315/367] Removing unneeded code duplication in ls-remote.c --- examples/network/ls-remote.c | 40 +++++++----------------------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c index 252011828..e837654a1 100644 --- a/examples/network/ls-remote.c +++ b/examples/network/ls-remote.c @@ -14,31 +14,6 @@ static int show_ref__cb(git_remote_head *head, void *payload) return 0; } -static int use_unnamed(git_repository *repo, const char *url) -{ - git_remote *remote = NULL; - int error; - - // Create an instance of a remote from the URL. The transport to use - // is detected from the URL - error = git_remote_create_inmemory(&remote, repo, NULL, url); - if (error < 0) - goto cleanup; - - // When connecting, the underlying code needs to know wether we - // want to push or fetch - error = git_remote_connect(remote, GIT_DIRECTION_FETCH); - if (error < 0) - goto cleanup; - - // With git_remote_ls we can retrieve the advertised heads - error = git_remote_ls(remote, &show_ref__cb, NULL); - -cleanup: - git_remote_free(remote); - return error; -} - static int use_remote(git_repository *repo, char *name) { git_remote *remote = NULL; @@ -46,8 +21,12 @@ static int use_remote(git_repository *repo, char *name) // Find the remote by name error = git_remote_load(&remote, repo, name); - if (error < 0) - goto cleanup; + if (error < 0) { + error = git_remote_create_inmemory(&remote, repo, NULL, name); + if (error < 0) + goto cleanup; + } + error = git_remote_connect(remote, GIT_DIRECTION_FETCH); if (error < 0) @@ -72,12 +51,7 @@ int ls_remote(git_repository *repo, int argc, char **argv) return EXIT_FAILURE; } - /* If there's a ':' in the name, assume it's an URL */ - if (strchr(argv[1], ':') != NULL) { - error = use_unnamed(repo, argv[1]); - } else { - error = use_remote(repo, argv[1]); - } + error = use_remote(repo, argv[1]); return error; } From 255836ddac05418f6bb2d68d27f5ff290669e2a9 Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Sun, 1 Sep 2013 18:35:39 +0200 Subject: [PATCH 316/367] Adding credentials callback to ls-remote and fetch too. --- examples/network/Makefile | 3 ++- examples/network/clone.c | 33 +-------------------------------- examples/network/common.c | 34 ++++++++++++++++++++++++++++++++++ examples/network/common.h | 6 ++++++ examples/network/fetch.c | 1 + examples/network/ls-remote.c | 1 + 6 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 examples/network/common.c diff --git a/examples/network/Makefile b/examples/network/Makefile index 810eb705b..f65c6cb26 100644 --- a/examples/network/Makefile +++ b/examples/network/Makefile @@ -11,7 +11,8 @@ OBJECTS = \ ls-remote.o \ fetch.o \ clone.o \ - index-pack.o + index-pack.o \ + common.o all: $(OBJECTS) $(CC) $(CFLAGS) $(LDFLAGS) -o git2 $(OBJECTS) $(LIBRARIES) diff --git a/examples/network/clone.c b/examples/network/clone.c index 00c25c1ae..a09a94728 100644 --- a/examples/network/clone.c +++ b/examples/network/clone.c @@ -9,19 +9,6 @@ # include #endif -/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/ - * with permission of the original author, Martin Pool. - * http://sourcefrog.net/weblog/software/languages/C/unused.html - */ -#ifdef UNUSED -#elif defined(__GNUC__) -# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) -#elif defined(__LCLINT__) -# define UNUSED(x) /*@unused@*/ x -#else -# define UNUSED(x) x -#endif - typedef struct progress_data { git_transfer_progress fetch_progress; size_t completed_steps; @@ -63,24 +50,6 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa print_progress(pd); } -static int cred_acquire(git_cred **out, - const char * UNUSED(url), - const char * UNUSED(username_from_url), - unsigned int UNUSED(allowed_types), - void * UNUSED(payload)) -{ - char username[128] = {0}; - char password[128] = {0}; - - printf("Username: "); - scanf("%s", username); - - /* Yup. Right there on your terminal. Careful where you copy/paste output. */ - printf("Password: "); - scanf("%s", password); - - return git_cred_userpass_plaintext_new(out, username, password); -} int do_clone(git_repository *repo, int argc, char **argv) { @@ -107,7 +76,7 @@ int do_clone(git_repository *repo, int argc, char **argv) clone_opts.checkout_opts = checkout_opts; clone_opts.fetch_progress_cb = &fetch_progress; clone_opts.fetch_progress_payload = &pd; - clone_opts.cred_acquire_cb = cred_acquire; + clone_opts.cred_acquire_cb = cred_acquire_cb; // Do the clone error = git_clone(&cloned_repo, url, path, &clone_opts); diff --git a/examples/network/common.c b/examples/network/common.c new file mode 100644 index 000000000..d123eedbd --- /dev/null +++ b/examples/network/common.c @@ -0,0 +1,34 @@ +#include "common.h" +#include + +/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/ + * with permission of the original author, Martin Pool. + * http://sourcefrog.net/weblog/software/languages/C/unused.html + */ +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif + +int cred_acquire_cb(git_cred **out, + const char * UNUSED(url), + const char * UNUSED(username_from_url), + unsigned int UNUSED(allowed_types), + void * UNUSED(payload)) +{ + char username[128] = {0}; + char password[128] = {0}; + + printf("Username: "); + scanf("%s", username); + + /* Yup. Right there on your terminal. Careful where you copy/paste output. */ + printf("Password: "); + scanf("%s", password); + + return git_cred_userpass_plaintext_new(out, username, password); +} diff --git a/examples/network/common.h b/examples/network/common.h index a4cfa1a7e..1b09caad4 100644 --- a/examples/network/common.h +++ b/examples/network/common.h @@ -12,6 +12,12 @@ int fetch(git_repository *repo, int argc, char **argv); int index_pack(git_repository *repo, int argc, char **argv); int do_clone(git_repository *repo, int argc, char **argv); +int cred_acquire_cb(git_cred **out, + const char * url, + const char * username_from_url, + unsigned int allowed_types, + void *payload); + #ifndef PRIuZ /* Define the printf format specifer to use for size_t output */ #if defined(_MSC_VER) || defined(__MINGW32__) diff --git a/examples/network/fetch.c b/examples/network/fetch.c index 6020ec6ec..ce016ce0b 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -92,6 +92,7 @@ int fetch(git_repository *repo, int argc, char **argv) callbacks.update_tips = &update_cb; callbacks.progress = &progress_cb; git_remote_set_callbacks(remote, &callbacks); + git_remote_set_cred_acquire_cb(remote, &cred_acquire_cb, NULL); // Set up the information for the background worker thread data.remote = remote; diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c index e837654a1..b22ac47a0 100644 --- a/examples/network/ls-remote.c +++ b/examples/network/ls-remote.c @@ -27,6 +27,7 @@ static int use_remote(git_repository *repo, char *name) goto cleanup; } + git_remote_set_cred_acquire_cb(remote, &cred_acquire_cb, NULL); error = git_remote_connect(remote, GIT_DIRECTION_FETCH); if (error < 0) From 5c37f00505bf00c14468813d94df69f3387f15b3 Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Sun, 1 Sep 2013 18:59:42 +0200 Subject: [PATCH 317/367] Build all example files if BUILD_EXAMPLES used. --- CMakeLists.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c70ec2d6..317ed1bee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -426,4 +426,19 @@ IF (BUILD_EXAMPLES) ADD_EXECUTABLE(git-rev-list examples/rev-list.c) TARGET_LINK_LIBRARIES(git-rev-list git2) + + ADD_EXECUTABLE(git-rev-parse examples/rev-parse.c) + TARGET_LINK_LIBRARIES(git-rev-parse git2) + + ADD_EXECUTABLE(git-log examples/log.c) + TARGET_LINK_LIBRARIES(git-log git2) + + ADD_EXECUTABLE(git-status examples/status.c) + TARGET_LINK_LIBRARIES(git-status git2) + + ADD_EXECUTABLE(git-init examples/init.c) + TARGET_LINK_LIBRARIES(git-init git2) + + ADD_EXECUTABLE(git-cat-file examples/cat-file.c) + TARGET_LINK_LIBRARIES(git-cat-file git2) ENDIF () From 01cd5ae3772a32b29c6bd1cdc1cdc50d98961c1c Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Sun, 1 Sep 2013 19:43:35 +0200 Subject: [PATCH 318/367] Add instructions about buiding for Android to README.md --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 44fd059c1..9222f3dcf 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,28 @@ See [the wiki] (https://github.com/libgit2/libgit2/wiki/Building-libgit2-on-Windows) for more detailed instructions. +Android +------- + +Extract toolchain from NDK using, `make-standalone-toolchain.sh` script. +Optionaly, crosscompile and install OpenSSL inside of it. Then create CMake +toolchain file that configures paths to your crosscompiler (substitude `{PATH}` +with full path to the toolchain): + + SET(CMAKE_SYSTEM_NAME Linux) + SET(CMAKE_SYSTEM_VERSION Android) + + SET(CMAKE_C_COMPILER {PATH}/bin/arm-linux-androideabi-gcc) + SET(CMAKE_CXX_COMPILER {PATH}/bin/arm-linux-androideabi-g++) + SET(CMAKE_FIND_ROOT_PATH {PATH}/sysroot/) + + SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +Add `-DCMAKE_TOOLCHAIN_FILE={pathToToolchainFile} -DANDROID=1` to cmake command +when configuring. + Language Bindings ================================== From 6d9a6c5cecebfaaecb4c39d002c8e0cdd26c4b85 Mon Sep 17 00:00:00 2001 From: Nikolai Vladimirov Date: Tue, 3 Sep 2013 07:58:21 +0300 Subject: [PATCH 319/367] path: properly resolve relative paths --- src/path.c | 31 ++++++++++--- tests-clar/core/path.c | 98 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 119 insertions(+), 10 deletions(-) diff --git a/src/path.c b/src/path.c index a753a734d..50a990b27 100644 --- a/src/path.c +++ b/src/path.c @@ -646,12 +646,33 @@ int git_path_resolve_relative(git_buf *path, size_t ceiling) /* do nothing with singleton dot */; else if (len == 2 && from[0] == '.' && from[1] == '.') { - while (to > base && to[-1] == '/') to--; - while (to > base && to[-1] != '/') to--; - } + /* error out if trying to up one from a hard base */ + if (to == base && ceiling != 0) { + giterr_set(GITERR_INVALID, + "Cannot strip root component off url"); + return -1; + } - else { - if (*next == '/') + /* no more path segments to strip, + * use '../' as a new base path */ + if (to == base) { + if (*next == '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + /* this is now the base, can't back up from a + * relative prefix */ + base = to; + } else { + /* back up a path segment */ + while (to > base && to[-1] == '/') to--; + while (to > base && to[-1] != '/') to--; + } + } else { + if (*next == '/' && *from != '/') len++; if (to != from) diff --git a/tests-clar/core/path.c b/tests-clar/core/path.c index 407770baa..d35a5bda8 100644 --- a/tests-clar/core/path.c +++ b/tests-clar/core/path.c @@ -446,16 +446,15 @@ void test_core_path__14_apply_relative(void) cl_git_pass(git_path_apply_relative(&p, "../../../../../..")); cl_assert_equal_s("/this/", p.ptr); - cl_git_pass(git_path_apply_relative(&p, "../../../../../")); + cl_git_pass(git_path_apply_relative(&p, "../")); cl_assert_equal_s("/", p.ptr); - cl_git_pass(git_path_apply_relative(&p, "../../../../..")); - cl_assert_equal_s("/", p.ptr); + cl_git_fail(git_path_apply_relative(&p, "../../..")); cl_git_pass(git_buf_sets(&p, "d:/another/test")); - cl_git_pass(git_path_apply_relative(&p, "../../../../..")); + cl_git_pass(git_path_apply_relative(&p, "../..")); cl_assert_equal_s("d:/", p.ptr); cl_git_pass(git_path_apply_relative(&p, "from/here/to/../and/./back/.")); @@ -473,8 +472,97 @@ void test_core_path__14_apply_relative(void) cl_git_pass(git_path_apply_relative(&p, "..")); cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); - cl_git_pass(git_path_apply_relative(&p, "../../../../../")); + cl_git_pass(git_path_apply_relative(&p, "../../../")); cl_assert_equal_s("https://", p.ptr); + + cl_git_pass(git_buf_sets(&p, "../../this/is/relative")); + + cl_git_pass(git_path_apply_relative(&p, "../../preserves/the/prefix")); + cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../that")); + cl_assert_equal_s("../../that", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../there")); + cl_assert_equal_s("../../there", p.ptr); git_buf_free(&p); } + +static inline void assert_resolve_relative(git_buf *buf, const char *expected, const char *path) +{ + cl_git_pass(git_buf_sets(buf, path)); + cl_git_pass(git_path_resolve_relative(buf, 0)); + cl_assert_equal_s(expected, buf->ptr); +} + +void test_core_path__15_resolve_relative(void) +{ + git_buf buf = GIT_BUF_INIT; + + assert_resolve_relative(&buf, "", ""); + assert_resolve_relative(&buf, "", "."); + assert_resolve_relative(&buf, "", "./"); + assert_resolve_relative(&buf, "..", ".."); + assert_resolve_relative(&buf, "../", "../"); + assert_resolve_relative(&buf, "..", "./.."); + assert_resolve_relative(&buf, "../", "./../"); + assert_resolve_relative(&buf, "../", "../."); + assert_resolve_relative(&buf, "../", ".././"); + assert_resolve_relative(&buf, "../..", "../.."); + assert_resolve_relative(&buf, "../../", "../../"); + + assert_resolve_relative(&buf, "/", "/"); + assert_resolve_relative(&buf, "/", "/."); + + assert_resolve_relative(&buf, "", "a/.."); + assert_resolve_relative(&buf, "", "a/../"); + assert_resolve_relative(&buf, "", "a/../."); + + assert_resolve_relative(&buf, "/a", "/a"); + assert_resolve_relative(&buf, "/a/", "/a/."); + assert_resolve_relative(&buf, "/", "/a/../"); + assert_resolve_relative(&buf, "/", "/a/../."); + assert_resolve_relative(&buf, "/", "/a/.././"); + + assert_resolve_relative(&buf, "a", "a"); + assert_resolve_relative(&buf, "a/", "a/"); + assert_resolve_relative(&buf, "a/", "a/."); + assert_resolve_relative(&buf, "a/", "a/./"); + + assert_resolve_relative(&buf, "a/b", "a//b"); + assert_resolve_relative(&buf, "a/b/c", "a/b/c"); + assert_resolve_relative(&buf, "b/c", "./b/c"); + assert_resolve_relative(&buf, "a/c", "a/./c"); + assert_resolve_relative(&buf, "a/b/", "a/b/."); + + assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); + assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "/", "////"); + assert_resolve_relative(&buf, "/a", "///a"); + assert_resolve_relative(&buf, "/", "///."); + assert_resolve_relative(&buf, "/", "///a/.."); + + assert_resolve_relative(&buf, "../../path", "../../test//../././path"); + assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); + + cl_git_pass(git_buf_sets(&buf, "/..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "/./..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "/.//..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "/../.")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "/../.././../a")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "////..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + git_buf_free(&buf); +} From 0d1af399e958b50540c83d5eef51cf119da76667 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 3 Sep 2013 12:33:34 -0700 Subject: [PATCH 320/367] don't use inline in tests for win32 --- tests-clar/core/path.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests-clar/core/path.c b/tests-clar/core/path.c index d35a5bda8..300600115 100644 --- a/tests-clar/core/path.c +++ b/tests-clar/core/path.c @@ -489,7 +489,8 @@ void test_core_path__14_apply_relative(void) git_buf_free(&p); } -static inline void assert_resolve_relative(git_buf *buf, const char *expected, const char *path) +static void assert_resolve_relative( + git_buf *buf, const char *expected, const char *path) { cl_git_pass(git_buf_sets(buf, path)); cl_git_pass(git_path_resolve_relative(buf, 0)); From cae529385437a24094ef2997dbf546723f7fd1ee Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 3 Sep 2013 14:00:27 -0700 Subject: [PATCH 321/367] Fix resolving relative windows network paths --- src/path.c | 4 ++-- tests-clar/core/path.c | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/path.c b/src/path.c index 50a990b27..7c1ec2cd0 100644 --- a/src/path.c +++ b/src/path.c @@ -242,8 +242,8 @@ int git_path_root(const char *path) #ifdef GIT_WIN32 /* Are we dealing with a windows network path? */ - else if ((path[0] == '/' && path[1] == '/') || - (path[0] == '\\' && path[1] == '\\')) + else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') || + (path[0] == '\\' && path[1] == '\\' && path[2] != '\\')) { offset += 2; diff --git a/tests-clar/core/path.c b/tests-clar/core/path.c index 300600115..e584d6115 100644 --- a/tests-clar/core/path.c +++ b/tests-clar/core/path.c @@ -538,7 +538,6 @@ void test_core_path__15_resolve_relative(void) assert_resolve_relative(&buf, "a/b/", "a/b/."); assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); - assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); assert_resolve_relative(&buf, "/", "////"); assert_resolve_relative(&buf, "/a", "///a"); assert_resolve_relative(&buf, "/", "///."); @@ -565,5 +564,20 @@ void test_core_path__15_resolve_relative(void) cl_git_pass(git_buf_sets(&buf, "////..")); cl_git_fail(git_path_resolve_relative(&buf, 0)); + /* things that start with Windows network paths */ +#ifdef GIT_WIN32 + assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "//a/", "//a/b/.."); + assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); + + cl_git_pass(git_buf_sets(&buf, "//a/b/../..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); +#else + assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "/a/", "//a/b/.."); + assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); + assert_resolve_relative(&buf, "/", "//a/b/../.."); +#endif + git_buf_free(&buf); } From 60ee53dfce5ff0201e6f519c653cf0de195ff35b Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 3 Sep 2013 15:14:04 -0700 Subject: [PATCH 322/367] Split examples CMakeLists.txt Also, this converts the examples/CMakeLists.txt from explicitly listing to just globbing for all the individual C files. --- CMakeLists.txt | 35 +---------------------------------- examples/CMakeLists.txt | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 34 deletions(-) create mode 100644 examples/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 78c25b101..5c09b4178 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -409,38 +409,5 @@ IF (TAGS) ENDIF () IF (BUILD_EXAMPLES) - FILE(GLOB_RECURSE EXAMPLE_SRC examples/network/*.c examples/network/*.h) - ADD_EXECUTABLE(cgit2 ${EXAMPLE_SRC}) - IF(WIN32 OR ANDROID) - TARGET_LINK_LIBRARIES(cgit2 git2) - ELSE() - TARGET_LINK_LIBRARIES(cgit2 git2 pthread) - ENDIF() - - ADD_EXECUTABLE(git-diff examples/diff.c) - TARGET_LINK_LIBRARIES(git-diff git2) - - ADD_EXECUTABLE(git-general examples/general.c) - TARGET_LINK_LIBRARIES(git-general git2) - - ADD_EXECUTABLE(git-showindex examples/showindex.c) - TARGET_LINK_LIBRARIES(git-showindex git2) - - ADD_EXECUTABLE(git-rev-list examples/rev-list.c) - TARGET_LINK_LIBRARIES(git-rev-list git2) - - ADD_EXECUTABLE(git-rev-parse examples/rev-parse.c) - TARGET_LINK_LIBRARIES(git-rev-parse git2) - - ADD_EXECUTABLE(git-log examples/log.c) - TARGET_LINK_LIBRARIES(git-log git2) - - ADD_EXECUTABLE(git-status examples/status.c) - TARGET_LINK_LIBRARIES(git-status git2) - - ADD_EXECUTABLE(git-init examples/init.c) - TARGET_LINK_LIBRARIES(git-init git2) - - ADD_EXECUTABLE(git-cat-file examples/cat-file.c) - TARGET_LINK_LIBRARIES(git-cat-file git2) + ADD_SUBDIRECTORY(examples) ENDIF () diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..c20a6df3b --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,14 @@ +FILE(GLOB_RECURSE SRC_EXAMPLE_GIT2 network/*.c network/*.h) +ADD_EXECUTABLE(cgit2 ${SRC_EXAMPLE_GIT2}) +IF(WIN32 OR ANDROID) + TARGET_LINK_LIBRARIES(cgit2 git2) +ELSE() + TARGET_LINK_LIBRARIES(cgit2 git2 pthread) +ENDIF() + +FILE(GLOB SRC_EXAMPLE_APPS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c) +FOREACH(src_app ${SRC_EXAMPLE_APPS}) + STRING(REPLACE ".c" "" app_name ${src_app}) + ADD_EXECUTABLE(${app_name} ${src_app}) + TARGET_LINK_LIBRARIES(${app_name} git2) +ENDFOREACH() From b1a6c316a6070fac4ab1ec5792979838f7145c39 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 30 Aug 2013 17:36:00 +0200 Subject: [PATCH 323/367] odb: Move the auto refresh logic to the pack backend Previously, `git_object_read()`, `git_object_read_prefix()` and `git_object_exists()` were implementing an auto refresh logic. When the expected object couldn't be found in any backend, a call to `git_odb_refresh()` was triggered and the lookup was once again performed against all backends. This commit removes this auto-refresh logic from the odb layer and pushes it down into the pack-backend (as it's the only one currently exposing a `refresh()` endpoint). --- include/git2/sys/odb_backend.h | 10 +++++ src/odb.c | 39 ++--------------- src/odb_pack.c | 76 ++++++++++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 38 deletions(-) diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index 31ffe1c33..4365906d4 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -64,6 +64,16 @@ struct git_odb_backend { int (* exists)( git_odb_backend *, const git_oid *); + /** + * If the backend implements a refreshing mechanism, it should be exposed + * through this endpoint. Each call to `git_odb_refresh()` will invoke it. + * + * However, the backend implementation should try to stay up-to-date as much + * as possible by itself as libgit2 will not automatically invoke + * `git_odb_refresh()`. For instance, a potential strategy for the backend + * implementation to achieve this could be to internally invoke this + * endpoint on failed lookups (ie. `exists()`, `read()`, `read_header()`). + */ int (* refresh)(git_odb_backend *); int (* foreach)( diff --git a/src/odb.c b/src/odb.c index 9785c74da..e47715f79 100644 --- a/src/odb.c +++ b/src/odb.c @@ -608,7 +608,6 @@ int git_odb_exists(git_odb *db, const git_oid *id) git_odb_object *object; size_t i; bool found = false; - bool refreshed = false; assert(db && id); @@ -617,25 +616,14 @@ int git_odb_exists(git_odb *db, const git_oid *id) return (int)true; } -attempt_lookup: for (i = 0; i < db->backends.length && !found; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; - if (b->exists != NULL && (!refreshed || b->refresh)) + if (b->exists != NULL) found = b->exists(b, id); } - if (!found && !refreshed) { - if (git_odb_refresh(db) < 0) { - giterr_clear(); - return (int)false; - } - - refreshed = true; - goto attempt_lookup; - } - return (int)found; } @@ -700,7 +688,6 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) { size_t i, reads = 0; int error; - bool refreshed = false; git_rawobj raw; git_odb_object *object; @@ -710,27 +697,18 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) if (*out != NULL) return 0; -attempt_lookup: error = GIT_ENOTFOUND; for (i = 0; i < db->backends.length && error < 0; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; - if (b->read != NULL && (!refreshed || b->refresh)) { + if (b->read != NULL) { ++reads; error = b->read(&raw.data, &raw.len, &raw.type, b, id); } } - if (error == GIT_ENOTFOUND && !refreshed) { - if ((error = git_odb_refresh(db)) < 0) - return error; - - refreshed = true; - goto attempt_lookup; - } - if (error && error != GIT_PASSTHROUGH) { if (!reads) return git_odb__error_notfound("no match for id", id); @@ -752,7 +730,7 @@ int git_odb_read_prefix( git_oid found_full_oid = {{0}}; git_rawobj raw; void *data = NULL; - bool found = false, refreshed = false; + bool found = false; git_odb_object *object; assert(out && db); @@ -769,12 +747,11 @@ int git_odb_read_prefix( return 0; } -attempt_lookup: for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; - if (b->read_prefix != NULL && (!refreshed || b->refresh)) { + if (b->read_prefix != NULL) { git_oid full_oid; error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, short_id, len); if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) @@ -796,14 +773,6 @@ attempt_lookup: } } - if (!found && !refreshed) { - if ((error = git_odb_refresh(db)) < 0) - return error; - - refreshed = true; - goto attempt_lookup; - } - if (!found) return git_odb__error_notfound("no match for prefix", short_id); diff --git a/src/odb_pack.c b/src/odb_pack.c index d7abd1020..d24b4aa99 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -342,7 +342,7 @@ static int pack_backend__refresh(git_odb_backend *_backend) return 0; } -static int pack_backend__read_header( +static int pack_backend__read_header_internal( size_t *len_p, git_otype *type_p, struct git_odb_backend *backend, const git_oid *oid) { @@ -357,7 +357,24 @@ static int pack_backend__read_header( return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); } -static int pack_backend__read( +static int pack_backend__read_header( + size_t *len_p, git_otype *type_p, + struct git_odb_backend *backend, const git_oid *oid) +{ + int error; + + error = pack_backend__read_header_internal(len_p, type_p, backend, oid); + + if (error != GIT_ENOTFOUND) + return error; + + if ((error = pack_backend__refresh(backend)) < 0) + return error; + + return pack_backend__read_header_internal(len_p, type_p, backend, oid); +} + +static int pack_backend__read_internal( void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) { @@ -376,7 +393,24 @@ static int pack_backend__read( return 0; } -static int pack_backend__read_prefix( +static int pack_backend__read( + void **buffer_p, size_t *len_p, git_otype *type_p, + git_odb_backend *backend, const git_oid *oid) +{ + int error; + + error = pack_backend__read_internal(buffer_p, len_p, type_p, backend, oid); + + if (error != GIT_ENOTFOUND) + return error; + + if ((error = pack_backend__refresh(backend)) < 0) + return error; + + return pack_backend__read_internal(buffer_p, len_p, type_p, backend, oid); +} + +static int pack_backend__read_prefix_internal( git_oid *out_oid, void **buffer_p, size_t *len_p, @@ -413,9 +447,45 @@ static int pack_backend__read_prefix( return error; } +static int pack_backend__read_prefix( + git_oid *out_oid, + void **buffer_p, + size_t *len_p, + git_otype *type_p, + git_odb_backend *backend, + const git_oid *short_oid, + size_t len) +{ + int error; + + error = pack_backend__read_prefix_internal( + out_oid, buffer_p, len_p, type_p, backend, short_oid, len); + + if (error != GIT_ENOTFOUND) + return error; + + if ((error = pack_backend__refresh(backend)) < 0) + return error; + + return pack_backend__read_prefix_internal( + out_oid, buffer_p, len_p, type_p, backend, short_oid, len); +} + static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) { struct git_pack_entry e; + int error; + + error = pack_entry_find(&e, (struct pack_backend *)backend, oid); + + if (error != GIT_ENOTFOUND) + return error == 0; + + if ((error = pack_backend__refresh(backend)) < 0) { + giterr_clear(); + return (int)false; + } + return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; } From 74b38d199ebe74c25180e0baf4bf945c97cd3daf Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Wed, 4 Sep 2013 13:16:57 +0200 Subject: [PATCH 324/367] Backport @peff's fix for duplicates in sha1_lookup --- src/sha1_lookup.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index cdcadfaa9..c6b561340 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -109,7 +109,54 @@ int sha1_entry_pos(const void *table, * byte 0 thru (ofs-1) are the same between * lo and hi; ofs is the first byte that is * different. + * + * If ofs==20, then no bytes are different, + * meaning we have entries with duplicate + * keys. We know that we are in a solid run + * of this entry (because the entries are + * sorted, and our lo and hi are the same, + * there can be nothing but this single key + * in between). So we can stop the search. + * Either one of these entries is it (and + * we do not care which), or we do not have + * it. + * + * Furthermore, we know that one of our + * endpoints must be the edge of the run of + * duplicates. For example, given this + * sequence: + * + * idx 0 1 2 3 4 5 + * key A C C C C D + * + * If we are searching for "B", we might + * hit the duplicate run at lo=1, hi=3 + * (e.g., by first mi=3, then mi=0). But we + * can never have lo > 1, because B < C. + * That is, if our key is less than the + * run, we know that "lo" is the edge, but + * we can say nothing of "hi". Similarly, + * if our key is greater than the run, we + * know that "hi" is the edge, but we can + * say nothing of "lo". + * + * Therefore if we do not find it, we also + * know where it would go if it did exist: + * just on the far side of the edge that we + * know about. */ + if (ofs == 20) { + mi = lo; + mi_key = base + elem_size * mi + key_offset; + cmp = memcmp(mi_key, key, 20); + if (!cmp) + return mi; + if (cmp < 0) + return -1 - hi; + else + return -1 - lo; + } + hiv = hi_key[ofs_0]; if (ofs_0 < 19) hiv = (hiv << 8) | hi_key[ofs_0+1]; From f42d546c632d0458503efd01390bac76c185ebef Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 4 Sep 2013 13:07:42 -0700 Subject: [PATCH 325/367] Provide better errors for push on non-bare local remotes --- src/transports/local.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transports/local.c b/src/transports/local.c index 8a75de727..9ebea979c 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -361,7 +361,8 @@ static int local_push( 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; + error = GIT_EBAREREPO; + giterr_set(GITERR_INVALID, "Local push doesn't (yet) support pushing to non-bare repos."); goto on_error; } From 61d57b7a21b2ac5ca7a763a1b768481926f56014 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 4 Sep 2013 14:27:59 -0700 Subject: [PATCH 326/367] Test pushing to remotes with "file:///" urls --- tests-clar/network/remote/local.c | 71 ++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/tests-clar/network/remote/local.c b/tests-clar/network/remote/local.c index d5d75fdc6..79eb73c5a 100644 --- a/tests-clar/network/remote/local.c +++ b/tests-clar/network/remote/local.c @@ -52,7 +52,6 @@ static void connect_to_local_repository(const char *local_repository) cl_git_pass(git_remote_create_inmemory(&remote, repo, NULL, git_buf_cstr(&file_path_buf))); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); - } void test_network_remote_local__connected(void) @@ -170,3 +169,73 @@ void test_network_remote_local__tagopt(void) cl_git_pass(git_reference_lookup(&ref, repo, "refs/tags/hard_tag")); git_reference_free(ref); } + +void test_network_remote_local__push_to_bare_remote(void) +{ + /* Should be able to push to a bare remote */ + git_remote *localremote; + git_push *push; + + /* Get some commits */ + connect_to_local_repository(cl_fixture("testrepo.git")); + cl_git_pass(git_remote_add_fetch(remote, "master:master")); + cl_git_pass(git_remote_download(remote, NULL, NULL)); + cl_git_pass(git_remote_update_tips(remote)); + git_remote_disconnect(remote); + + /* Set up an empty bare repo to push into */ + { + git_repository *localbarerepo; + cl_git_pass(git_repository_init(&localbarerepo, "./localbare.git", 1)); + git_repository_free(localbarerepo); + } + + /* Connect to the bare repo */ + cl_git_pass(git_remote_create_inmemory(&localremote, repo, NULL, "./localbare.git")); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH)); + + /* Try to push */ + cl_git_pass(git_push_new(&push, localremote)); + cl_git_pass(git_push_add_refspec(push, "refs/heads/master:")); + cl_git_pass(git_push_finish(push)); + cl_assert(git_push_unpack_ok(push)); + + /* Clean up */ + git_remote_free(localremote); + cl_fixture_cleanup("localbare.git"); +} + +void test_network_remote_local__push_to_non_bare_remote(void) +{ + /* Shouldn't be able to push to a non-bare remote */ + git_remote *localremote; + git_push *push; + + /* Get some commits */ + connect_to_local_repository(cl_fixture("testrepo.git")); + cl_git_pass(git_remote_add_fetch(remote, "master:master")); + cl_git_pass(git_remote_download(remote, NULL, NULL)); + cl_git_pass(git_remote_update_tips(remote)); + git_remote_disconnect(remote); + + /* Set up an empty non-bare repo to push into */ + { + git_repository *remoterepo = NULL; + cl_git_pass(git_repository_init(&remoterepo, "localnonbare", 0)); + git_repository_free(remoterepo); + } + + /* Connect to the bare repo */ + cl_git_pass(git_remote_create_inmemory(&localremote, repo, NULL, "./localnonbare")); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH)); + + /* Try to push */ + cl_git_pass(git_push_new(&push, localremote)); + cl_git_pass(git_push_add_refspec(push, "refs/heads/master:")); + cl_git_fail_with(git_push_finish(push), GIT_EBAREREPO); + cl_assert_equal_i(0, git_push_unpack_ok(push)); + + /* Clean up */ + git_remote_free(localremote); + cl_fixture_cleanup("localbare.git"); +} From cf94024c589591e9a5c46a71f8fdce9f8b467161 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 4 Sep 2013 11:42:48 -0700 Subject: [PATCH 327/367] Update clar --- tests-clar/checkout/checkout_helpers.c | 4 +- tests-clar/clar.c | 136 +++++++++++++++++-------- tests-clar/clar.h | 23 +++-- tests-clar/clar/sandbox.h | 6 +- tests-clar/diff/submodules.c | 5 +- 5 files changed, 116 insertions(+), 58 deletions(-) diff --git a/tests-clar/checkout/checkout_helpers.c b/tests-clar/checkout/checkout_helpers.c index 8da024dda..f55f7b611 100644 --- a/tests-clar/checkout/checkout_helpers.c +++ b/tests-clar/checkout/checkout_helpers.c @@ -74,8 +74,8 @@ static void check_file_contents_internal( if (strip_cr) strip_cr_from_buf(&buf); - clar__assert_equal_i((int)expected_len, (int)buf.size, file, line, "strlen(expected_content) != strlen(actual_content)", 1); - clar__assert_equal_s(expected_content, buf.ptr, file, line, msg, 1); + clar__assert_equal(file, line, "strlen(expected_content) != strlen(actual_content)", 1, PRIuZ, expected_len, (size_t)buf.size); + clar__assert_equal(file, line, msg, 1, "%s", expected_content, buf.ptr); } void check_file_contents_at_line( diff --git a/tests-clar/clar.c b/tests-clar/clar.c index 585af8a74..5189e7919 100644 --- a/tests-clar/clar.c +++ b/tests-clar/clar.c @@ -24,28 +24,59 @@ # define _MAIN_CC __cdecl -# define stat(path, st) _stat(path, st) -# define mkdir(path, mode) _mkdir(path) -# define chdir(path) _chdir(path) -# define access(path, mode) _access(path, mode) -# define strdup(str) _strdup(str) -# define strcasecmp(a,b) _stricmp(a,b) +# ifndef stat +# define stat(path, st) _stat(path, st) +# endif +# ifndef mkdir +# define mkdir(path, mode) _mkdir(path) +# endif +# ifndef chdir +# define chdir(path) _chdir(path) +# endif +# ifndef access +# define access(path, mode) _access(path, mode) +# endif +# ifndef strdup +# define strdup(str) _strdup(str) +# endif +# ifndef strcasecmp +# define strcasecmp(a,b) _stricmp(a,b) +# endif # ifndef __MINGW32__ # pragma comment(lib, "shell32") -# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) -# define W_OK 02 -# define S_ISDIR(x) ((x & _S_IFDIR) != 0) -# define snprint_eq(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) +# ifndef strncpy +# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) +# endif +# ifndef W_OK +# define W_OK 02 +# endif +# ifndef S_ISDIR +# define S_ISDIR(x) ((x & _S_IFDIR) != 0) +# endif +# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) # else -# define snprint_eq snprintf +# define p_snprintf snprintf +# endif + +# ifndef PRIuZ +# define PRIuZ "Iu" +# endif +# ifndef PRIxZ +# define PRIxZ "Ix" # endif typedef struct _stat STAT_T; #else # include /* waitpid(2) */ # include # define _MAIN_CC -# define snprint_eq snprintf +# define p_snprintf snprintf +# ifndef PRIuZ +# define PRIuZ "zu" +# endif +# ifndef PRIxZ +# define PRIxZ "zx" +# endif typedef struct stat STAT_T; #endif @@ -406,45 +437,66 @@ void clar__assert( clar__fail(file, line, error_msg, description, should_abort); } -void clar__assert_equal_s( - const char *s1, - const char *s2, +void clar__assert_equal( const char *file, int line, const char *err, - int should_abort) + int should_abort, + const char *fmt, + ...) { - int match = (s1 == NULL || s2 == NULL) ? (s1 == s2) : (strcmp(s1, s2) == 0); + va_list args; + char buf[4096]; + int is_equal = 1; - if (!match) { - char buf[4096]; + va_start(args, fmt); - if (s1 && s2) { - int pos; - for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) - /* find differing byte offset */; - snprint_eq(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", s1, s2, pos); - } else { - snprint_eq(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + if (!strcmp("%s", fmt)) { + const char *s1 = va_arg(args, const char *); + const char *s2 = va_arg(args, const char *); + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2); + + if (!is_equal) { + if (s1 && s2) { + int pos; + for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", + s1, s2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + } } - - clar__fail(file, line, err, buf, should_abort); } -} - -void clar__assert_equal_i( - int i1, - int i2, - const char *file, - int line, - const char *err, - int should_abort) -{ - if (i1 != i2) { - char buf[128]; - snprint_eq(buf, sizeof(buf), "%d != %d", i1, i2); - clar__fail(file, line, err, buf, should_abort); + else if (!strcmp(PRIuZ, fmt) || !strcmp(PRIxZ, fmt)) { + size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); + is_equal = (sz1 == sz2); + if (!is_equal) { + int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); + strncat(buf, " != ", sizeof(buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2); + } } + else if (!strcmp("%p", fmt)) { + void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); + is_equal = (p1 == p2); + if (!is_equal) + p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); + } + else { + int i1 = va_arg(args, int), i2 = va_arg(args, int); + is_equal = (i1 == i2); + if (!is_equal) { + int offset = p_snprintf(buf, sizeof(buf), fmt, i1); + strncat(buf, " != ", sizeof(buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2); + } + } + + va_end(args); + + if (!is_equal) + clar__fail(file, line, err, buf, should_abort); } void cl_set_cleanup(void (*cleanup)(void *), void *opaque) diff --git a/tests-clar/clar.h b/tests-clar/clar.h index d92318bd4..e1f244eba 100644 --- a/tests-clar/clar.h +++ b/tests-clar/clar.h @@ -57,15 +57,17 @@ void cl_fixture_cleanup(const char *fixture_name); /** * Typed assertion macros */ -#define cl_assert_equal_s(s1,s2) clar__assert_equal_s((s1),(s2),__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1) -#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal_s((s1),(s2),__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1) +#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) +#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) -#define cl_assert_equal_i(i1,i2) clar__assert_equal_i((i1),(i2),__FILE__,__LINE__,#i1 " != " #i2, 1) -#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal_i((i1),(i2),__FILE__,__LINE__,#i1 " != " #i2 " (" #note ")", 1) +#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) +#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) +#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) -#define cl_assert_equal_b(b1,b2) clar__assert_equal_i(!!(b1),!!(b2),__FILE__,__LINE__,#b1 " != " #b2, 1) +#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) + +#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) -#define cl_assert_equal_p(p1,p2) cl_assert((p1) == (p2)) void clar__fail( const char *file, @@ -82,7 +84,12 @@ void clar__assert( const char *description, int should_abort); -void clar__assert_equal_s(const char *,const char *,const char *,int,const char *,int); -void clar__assert_equal_i(int,int,const char *,int,const char *,int); +void clar__assert_equal( + const char *file, + int line, + const char *err, + int should_abort, + const char *fmt, + ...); #endif diff --git a/tests-clar/clar/sandbox.h b/tests-clar/clar/sandbox.h index 1ca6fcae8..ee7564148 100644 --- a/tests-clar/clar/sandbox.h +++ b/tests-clar/clar/sandbox.h @@ -43,10 +43,8 @@ find_tmp_path(char *buffer, size_t length) } #else - DWORD env_len; - - if ((env_len = GetEnvironmentVariable("CLAR_TMP", buffer, length)) > 0 && - env_len < length) + DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); + if (env_len > 0 && env_len < (DWORD)length) return 0; if (GetTempPath((DWORD)length, buffer)) diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c index 94804db22..9dcf8194e 100644 --- a/tests-clar/diff/submodules.c +++ b/tests-clar/diff/submodules.c @@ -39,8 +39,9 @@ static void check_diff_patches_at_line( cl_git_pass(git_diff_patch_to_str(&patch_text, patch)); - clar__assert_equal_s(expected[d], patch_text, file, line, - "expected diff did not match actual diff", 1); + clar__assert_equal( + file, line, "expected diff did not match actual diff", 1, + "%s", expected[d], patch_text); git__free(patch_text); } From 780f3e540fa6492b244ef2750a71e39407526c1e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 4 Sep 2013 16:13:18 -0700 Subject: [PATCH 328/367] Make tests take umask into account It seems that libgit2 is correctly applying the umask when initializing a repository from a template and when creating new directories during checkout, but the test suite is not accounting for possible variations due to the umask. This updates that so that the test suite will work regardless of the umask. --- tests-clar/checkout/index.c | 10 +++++++--- tests-clar/clar_libgit2.h | 2 +- tests-clar/repo/init.c | 15 +++++++++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index 982bf9ee5..a185aec44 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -229,6 +229,7 @@ void test_checkout_index__options_dir_modes(void) struct stat st; git_oid oid; git_commit *commit; + mode_t um; cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); @@ -240,12 +241,15 @@ void test_checkout_index__options_dir_modes(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + /* umask will influence actual directory creation mode */ + (void)p_umask(um = p_umask(022)); + cl_git_pass(p_stat("./testrepo/a", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0701); + cl_assert_equal_i_fmt(st.st_mode, GIT_FILEMODE_TREE | 0701 & ~um, "%07o"); /* File-mode test, since we're on the 'dir' branch */ cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0755); + cl_assert_equal_i_fmt(st.st_mode, GIT_FILEMODE_BLOB_EXECUTABLE, "%07o"); git_commit_free(commit); #endif @@ -263,7 +267,7 @@ void test_checkout_index__options_override_file_modes(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); cl_git_pass(p_stat("./testrepo/new.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0700); + cl_assert_equal_i_fmt(st.st_mode & 0777, 0700, "%07o"); #endif } diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h index 8c8357e40..080d32bea 100644 --- a/tests-clar/clar_libgit2.h +++ b/tests-clar/clar_libgit2.h @@ -32,7 +32,7 @@ void cl_git_report_failure(int, const char *, int, const char *); #define cl_assert_at_line(expr,file,line) \ clar__assert((expr) != 0, file, line, "Expression is not true: " #expr, NULL, 1) -#define cl_assert_equal_sz(sz1,sz2) cl_assert_equal_i((int)sz1, (int)(sz2)) +#define cl_assert_equal_sz(sz1,sz2) clar__assert_equal(__FILE__,__LINE__,#sz1 " != " #sz2, 1, PRIuZ, (size_t)(sz1), (size_t)(sz2)) GIT_INLINE(void) clar__assert_in_range( int lo, int val, int hi, diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 5076184b8..4d93f3d0e 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -10,10 +10,17 @@ enum repo_mode { }; static git_repository *_repo = NULL; +static mode_t _umask = 0; void test_repo_init__initialize(void) { _repo = NULL; + + /* load umask if not already loaded */ + if (!_umask) { + _umask = p_umask(022); + (void)p_umask(_umask); + } } static void cleanup_repository(void *path) @@ -377,14 +384,18 @@ static void assert_hooks_match( cl_git_pass(git_buf_joinpath(&actual, repo_dir, hook_path)); cl_git_pass(git_path_lstat(actual.ptr, &st)); - cl_assert(expected_st.st_size == st.st_size); + cl_assert_equal_sz(expected_st.st_size, st.st_size); + + expected_st.st_mode = + (expected_st.st_mode & ~0777) | + (((expected_st.st_mode & 0111) ? 0100777 : 0100666) & ~_umask); if (!core_filemode) { expected_st.st_mode = expected_st.st_mode & ~0111; st.st_mode = st.st_mode & ~0111; } - cl_assert_equal_i((int)expected_st.st_mode, (int)st.st_mode); + cl_assert_equal_i_fmt(expected_st.st_mode, st.st_mode, "%07o"); git_buf_free(&expected); git_buf_free(&actual); From abfed59c2797e9436f2b20fadecd982d6637dc22 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 4 Sep 2013 16:21:18 -0700 Subject: [PATCH 329/367] Clean up one other mode_t assertion --- tests-clar/core/mkdir.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-clar/core/mkdir.c b/tests-clar/core/mkdir.c index 1e50b4336..a969e4de2 100644 --- a/tests-clar/core/mkdir.c +++ b/tests-clar/core/mkdir.c @@ -115,9 +115,9 @@ static void check_mode(mode_t expected, mode_t actual) { #ifdef GIT_WIN32 /* chmod on Win32 doesn't support exec bit, not group/world bits */ - cl_assert((expected & 0600) == (actual & 0777)); + cl_assert_equal_i_fmt((expected & 0600), (actual & 0777), "%07o"); #else - cl_assert(expected == (actual & 0777)); + cl_assert_equal_i_fmt(expected, (actual & 0777), "%07o"); #endif } From 2a54c7f447acfc74368a766369a9923ecdf19b21 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 4 Sep 2013 16:24:36 -0700 Subject: [PATCH 330/367] _umask is function name on Windows --- tests-clar/repo/init.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 4d93f3d0e..aeb35d3b4 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -10,16 +10,16 @@ enum repo_mode { }; static git_repository *_repo = NULL; -static mode_t _umask = 0; +static mode_t g_umask = 0; void test_repo_init__initialize(void) { _repo = NULL; /* load umask if not already loaded */ - if (!_umask) { - _umask = p_umask(022); - (void)p_umask(_umask); + if (!g_umask) { + g_umask = p_umask(022); + (void)p_umask(g_umask); } } @@ -388,7 +388,7 @@ static void assert_hooks_match( expected_st.st_mode = (expected_st.st_mode & ~0777) | - (((expected_st.st_mode & 0111) ? 0100777 : 0100666) & ~_umask); + (((expected_st.st_mode & 0111) ? 0100777 : 0100666) & ~g_umask); if (!core_filemode) { expected_st.st_mode = expected_st.st_mode & ~0111; From 9ce4f7da4a69b3853da587b67e8f137ddf036e4c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 4 Sep 2013 16:41:34 -0700 Subject: [PATCH 331/367] Fix tests to use core.filemode correctly Some windows tests were failing --- tests-clar/clar_libgit2.c | 10 ++++++++++ tests-clar/clar_libgit2.h | 1 + tests-clar/repo/init.c | 26 +++++++++++--------------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/tests-clar/clar_libgit2.c b/tests-clar/clar_libgit2.c index bf35a68eb..340943ca8 100644 --- a/tests-clar/clar_libgit2.c +++ b/tests-clar/clar_libgit2.c @@ -344,3 +344,13 @@ void cl_repo_set_bool(git_repository *repo, const char *cfg, int value) cl_git_pass(git_config_set_bool(config, cfg, value != 0)); git_config_free(config); } + +int cl_repo_get_bool(git_repository *repo, const char *cfg) +{ + int val = 0; + git_config *config; + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_get_bool(&val, config, cfg));; + git_config_free(config); + return val; +} diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h index 080d32bea..3cb0607f1 100644 --- a/tests-clar/clar_libgit2.h +++ b/tests-clar/clar_libgit2.h @@ -88,5 +88,6 @@ int cl_git_remove_placeholders(const char *directory_path, const char *filename) /* config setting helpers */ void cl_repo_set_bool(git_repository *repo, const char *cfg, int value); +int cl_repo_get_bool(git_repository *repo, const char *cfg); #endif diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index aeb35d3b4..62e4ecd59 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -270,7 +270,6 @@ void test_repo_init__reinit_doesnot_overwrite_ignorecase(void) void test_repo_init__reinit_overwrites_filemode(void) { - git_config *config; int expected, current_value; #ifdef GIT_WIN32 @@ -291,13 +290,10 @@ void test_repo_init__reinit_overwrites_filemode(void) /* Reinit the repository */ cl_git_pass(git_repository_init(&_repo, "overwrite.git", 1)); - git_repository_config(&config, _repo); /* Ensure the "core.filemode" config value has been reset */ - cl_git_pass(git_config_get_bool(¤t_value, config, "core.filemode")); + current_value = cl_repo_get_bool(_repo, "core.filemode"); cl_assert_equal_i(expected, current_value); - - git_config_free(config); } void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void) @@ -391,8 +387,8 @@ static void assert_hooks_match( (((expected_st.st_mode & 0111) ? 0100777 : 0100666) & ~g_umask); if (!core_filemode) { - expected_st.st_mode = expected_st.st_mode & ~0111; - st.st_mode = st.st_mode & ~0111; + expected_st.st_mode = expected_st.st_mode & ~0177; + st.st_mode = st.st_mode & ~0177; } cl_assert_equal_i_fmt(expected_st.st_mode, st.st_mode, "%07o"); @@ -438,6 +434,7 @@ void test_repo_init__extended_with_template(void) git_buf expected = GIT_BUF_INIT; git_buf actual = GIT_BUF_INIT; git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + int filemode; cl_set_cleanup(&cleanup_repository, "templated.git"); @@ -461,13 +458,15 @@ void test_repo_init__extended_with_template(void) git_buf_free(&expected); git_buf_free(&actual); - assert_hooks_match( - cl_fixture("template"), git_repository_path(_repo), - "hooks/update.sample", true); + filemode = cl_repo_get_bool(_repo, "core.filemode"); assert_hooks_match( cl_fixture("template"), git_repository_path(_repo), - "hooks/link.sample", true); + "hooks/update.sample", filemode); + + assert_hooks_match( + cl_fixture("template"), git_repository_path(_repo), + "hooks/link.sample", filemode); } void test_repo_init__extended_with_template_and_shared_mode(void) @@ -475,7 +474,6 @@ void test_repo_init__extended_with_template_and_shared_mode(void) git_buf expected = GIT_BUF_INIT; git_buf actual = GIT_BUF_INIT; git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_config *config; int filemode = true; const char *repo_path = NULL; @@ -491,9 +489,7 @@ void test_repo_init__extended_with_template_and_shared_mode(void) cl_assert(!git_repository_is_bare(_repo)); cl_assert(!git__suffixcmp(git_repository_path(_repo), "/init_shared_from_tpl/.git/")); - cl_git_pass(git_repository_config(&config, _repo)); - cl_git_pass(git_config_get_bool(&filemode, config, "core.filemode")); - git_config_free(config); + filemode = cl_repo_get_bool(_repo, "core.filemode"); cl_git_pass(git_futils_readbuffer( &expected, cl_fixture("template/description"))); From 21753d48691c41fbedc0c074d8b0f278f3ba2f1d Mon Sep 17 00:00:00 2001 From: Linquize Date: Thu, 5 Sep 2013 20:42:47 +0800 Subject: [PATCH 332/367] Fix warning in src/win32/version.h --- src/win32/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win32/version.h b/src/win32/version.h index 518b0a379..79667697f 100644 --- a/src/win32/version.h +++ b/src/win32/version.h @@ -18,7 +18,7 @@ GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack) version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); version_test.dwMajorVersion = major; version_test.dwMinorVersion = minor; - version_test.wServicePackMajor = service_pack; + version_test.wServicePackMajor = (WORD)service_pack; version_test.wServicePackMinor = 0; version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); From 27061b151a7e0225186365ee0b5ca802d68782a9 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 5 Sep 2013 10:25:16 -0700 Subject: [PATCH 333/367] Fix some newer GCC compiler warnings --- tests-clar/checkout/index.c | 2 +- tests-clar/diff/rename.c | 3 --- tests-clar/odb/backend/nonrefreshing.c | 4 ++-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index a185aec44..c9352d8ce 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -245,7 +245,7 @@ void test_checkout_index__options_dir_modes(void) (void)p_umask(um = p_umask(022)); cl_git_pass(p_stat("./testrepo/a", &st)); - cl_assert_equal_i_fmt(st.st_mode, GIT_FILEMODE_TREE | 0701 & ~um, "%07o"); + cl_assert_equal_i_fmt(st.st_mode, (GIT_FILEMODE_TREE | 0701) & ~um, "%07o"); /* File-mode test, since we're on the 'dir' branch */ cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index ac3814d59..b5a9935fd 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -1100,7 +1100,6 @@ void test_diff_rename__can_rename_from_rewrite(void) { git_index *index; git_tree *tree; - git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; git_diff_list *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; @@ -1110,8 +1109,6 @@ void test_diff_rename__can_rename_from_rewrite(void) const char *targets[] = { "songof7cities.txt", "this-is-a-rename.txt" }; struct rename_expected expect = { 2, status, sources, targets }; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_repository_index(&index, g_repo)); cl_git_pass(p_rename("renames/songof7cities.txt", "renames/this-is-a-rename.txt")); diff --git a/tests-clar/odb/backend/nonrefreshing.c b/tests-clar/odb/backend/nonrefreshing.c index abb824d4b..9abca2bd3 100644 --- a/tests-clar/odb/backend/nonrefreshing.c +++ b/tests-clar/odb/backend/nonrefreshing.c @@ -132,8 +132,8 @@ static int build_fake_backend( static void setup_repository_and_backend(git_error_code error_code) { - git_odb *odb; - git_odb_backend *backend; + git_odb *odb = NULL; + git_odb_backend *backend = NULL; _repo = cl_git_sandbox_init("testrepo.git"); From f240acce865ec14df0d517d5000316a933e7ffed Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 5 Sep 2013 11:20:12 -0700 Subject: [PATCH 334/367] Add more file mode permissions macros This adds some more macros for some standard operations on file modes, particularly related to permissions, and then updates a number of places around the code base to use the new macros. --- src/checkout.c | 13 +++++-------- src/diff_print.c | 4 ++-- src/fileops.c | 4 ++-- src/fileops.h | 5 ++++- src/index.c | 2 +- src/tree.c | 12 ++++++------ 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index ec9da7e2e..f3a9b343d 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -693,17 +693,14 @@ static int buffer_to_file( buffer, path, file_open_flags, file_mode)) < 0) return error; - if (st != NULL && (error = p_stat(path, st)) < 0) { - giterr_set(GITERR_OS, "Error while statting '%s'", path); - return error; - } + if (st != NULL && (error = p_stat(path, st)) < 0) + giterr_set(GITERR_OS, "Error statting '%s'", path); - if ((file_mode & 0100) != 0 && (error = p_chmod(path, file_mode)) < 0) { + else if (GIT_PERMS_EXECUTABLE(file_mode) && + (error = p_chmod(path, file_mode)) < 0) giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); - return error; - } - return 0; + return error; } static int blob_content_to_file( diff --git a/src/diff_print.c b/src/diff_print.c index 4ddd72443..96937d84d 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -7,7 +7,7 @@ #include "common.h" #include "diff.h" #include "diff_patch.h" -#include "buffer.h" +#include "fileops.h" typedef struct { git_diff_list *diff; @@ -46,7 +46,7 @@ static char diff_pick_suffix(int mode) { if (S_ISDIR(mode)) return '/'; - else if (mode & 0100) /* -V536 */ + else if (GIT_PERMS_EXECUTABLE(mode)) /* -V536 */ /* in git, modes are very regular, so we must have 0100755 mode */ return '*'; else diff --git a/src/fileops.c b/src/fileops.c index 76119e02e..92cda82e7 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -110,7 +110,7 @@ git_off_t git_futils_filesize(git_file fd) mode_t git_futils_canonical_mode(mode_t raw_mode) { if (S_ISREG(raw_mode)) - return S_IFREG | GIT_CANONICAL_PERMS(raw_mode); + return S_IFREG | GIT_PERMS_CANONICAL(raw_mode); else if (S_ISLNK(raw_mode)) return S_IFLNK; else if (S_ISGITLINK(raw_mode)) @@ -972,7 +972,7 @@ static int _cp_r_callback(void *ref, git_buf *from) mode_t usemode = from_st.st_mode; if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) - usemode = (usemode & 0111) ? 0777 : 0666; + usemode = GIT_PERMS_FOR_WRITE(usemode); error = git_futils_cp(from->ptr, info->to.ptr, usemode); } diff --git a/src/fileops.h b/src/fileops.h index 5adedfc57..142eb99d2 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -223,8 +223,11 @@ extern int git_futils_open_ro(const char *path); */ extern git_off_t git_futils_filesize(git_file fd); +#define GIT_PERMS_EXECUTABLE(MODE) (((MODE) & 0111) != 0) +#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_EXECUTABLE(MODE) ? 0755 : 0644) +#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_EXECUTABLE(MODE) ? 0777 : 0666) + #define GIT_MODE_PERMS_MASK 0777 -#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644) #define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK) #define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) diff --git a/src/index.c b/src/index.c index 17e43903f..9b32222a7 100644 --- a/src/index.c +++ b/src/index.c @@ -284,7 +284,7 @@ static unsigned int index_create_mode(unsigned int mode) if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) return (S_IFLNK | S_IFDIR); - return S_IFREG | ((mode & 0100) ? 0755 : 0644); + return S_IFREG | GIT_PERMS_CANONICAL(mode); } static unsigned int index_merge_mode( diff --git a/src/tree.c b/src/tree.c index 65d01b4d5..91309e107 100644 --- a/src/tree.c +++ b/src/tree.c @@ -10,7 +10,7 @@ #include "tree.h" #include "git2/repository.h" #include "git2/object.h" -#include "path.h" +#include "fileops.h" #include "tree-cache.h" #include "index.h" @@ -29,19 +29,19 @@ static bool valid_filemode(const int filemode) GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) { /* Tree bits set, but it's not a commit */ - if (filemode & GIT_FILEMODE_TREE && !(filemode & 0100000)) + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_TREE) return GIT_FILEMODE_TREE; - /* If any of the x bits is set */ - if (filemode & 0111) + /* If any of the x bits are set */ + if (GIT_PERMS_EXECUTABLE(filemode)) return GIT_FILEMODE_BLOB_EXECUTABLE; /* 16XXXX means commit */ - if ((filemode & GIT_FILEMODE_COMMIT) == GIT_FILEMODE_COMMIT) + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_COMMIT) return GIT_FILEMODE_COMMIT; /* 12XXXX means commit */ - if ((filemode & GIT_FILEMODE_LINK) == GIT_FILEMODE_LINK) + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_LINK) return GIT_FILEMODE_LINK; /* Otherwise, return a blob */ From c97d407d9cc54fc99af0a57e09e04e9e0bc68cb6 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 5 Sep 2013 11:45:29 -0700 Subject: [PATCH 335/367] Fix tests of file modes This fixes an issue checking file modes in the tests that initialize a repo from a template directory when a symlink is used in the template. Also, this updates some other places where we are examining file modes to use the new macros. --- tests-clar/checkout/index.c | 2 +- tests-clar/index/addall.c | 7 +++++-- tests-clar/repo/init.c | 33 ++++++++++++++++++--------------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index c9352d8ce..73050d08e 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -267,7 +267,7 @@ void test_checkout_index__options_override_file_modes(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); cl_git_pass(p_stat("./testrepo/new.txt", &st)); - cl_assert_equal_i_fmt(st.st_mode & 0777, 0700, "%07o"); + cl_assert_equal_i_fmt(st.st_mode & GIT_MODE_PERMS_MASK, 0700, "%07o"); #endif } diff --git a/tests-clar/index/addall.c b/tests-clar/index/addall.c index fca6e77fa..e6ce463a3 100644 --- a/tests-clar/index/addall.c +++ b/tests-clar/index/addall.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "../status/status_helpers.h" #include "posix.h" +#include "fileops.h" git_repository *g_repo = NULL; @@ -108,8 +109,10 @@ static void check_stat_data(git_index *index, const char *path, bool match) cl_assert(st.st_size == entry->file_size); cl_assert(st.st_uid == entry->uid); cl_assert(st.st_gid == entry->gid); - cl_assert_equal_b(st.st_mode & ~0777, entry->mode & ~0777); - cl_assert_equal_b(st.st_mode & 0111, entry->mode & 0111); + cl_assert_equal_i_fmt( + GIT_MODE_TYPE(st.st_mode), GIT_MODE_TYPE(entry->mode), "%07o"); + cl_assert_equal_b( + GIT_PERMS_EXECUTABLE(st.st_mode), GIT_PERMS_EXECUTABLE(entry->mode)); } else { /* most things will still match */ cl_assert(st.st_size != entry->file_size); diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 62e4ecd59..d7f2524c8 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -364,6 +364,8 @@ void test_repo_init__extended_1(void) cl_fixture_cleanup("root"); } +#define CLEAR_FOR_CORE_FILEMODE(M) ((M) &= ~0177) + static void assert_hooks_match( const char *template_dir, const char *repo_dir, @@ -382,17 +384,19 @@ static void assert_hooks_match( cl_assert_equal_sz(expected_st.st_size, st.st_size); - expected_st.st_mode = - (expected_st.st_mode & ~0777) | - (((expected_st.st_mode & 0111) ? 0100777 : 0100666) & ~g_umask); + if (GIT_MODE_TYPE(expected_st.st_mode) != GIT_FILEMODE_LINK) { + mode_t expected_mode = + GIT_MODE_TYPE(expected_st.st_mode) | + (GIT_PERMS_FOR_WRITE(expected_st.st_mode) & ~g_umask); - if (!core_filemode) { - expected_st.st_mode = expected_st.st_mode & ~0177; - st.st_mode = st.st_mode & ~0177; + if (!core_filemode) { + CLEAR_FOR_CORE_FILEMODE(expected_mode); + CLEAR_FOR_CORE_FILEMODE(st.st_mode); + } + + cl_assert_equal_i_fmt(expected_mode, st.st_mode, "%07o"); } - cl_assert_equal_i_fmt(expected_st.st_mode, st.st_mode, "%07o"); - git_buf_free(&expected); git_buf_free(&actual); } @@ -409,8 +413,8 @@ static void assert_mode_seems_okay( git_buf_free(&full); if (!core_filemode) { - expect_mode = expect_mode & ~0111; - st.st_mode = st.st_mode & ~0111; + CLEAR_FOR_CORE_FILEMODE(expect_mode); + CLEAR_FOR_CORE_FILEMODE(st.st_mode); expect_setgid = false; } @@ -421,12 +425,11 @@ static void assert_mode_seems_okay( cl_assert((st.st_mode & S_ISGID) == 0); } - if ((expect_mode & 0111) != 0) - cl_assert((st.st_mode & 0111) != 0); - else - cl_assert((st.st_mode & 0111) == 0); + cl_assert_equal_b( + GIT_PERMS_EXECUTABLE(expect_mode), GIT_PERMS_EXECUTABLE(st.st_mode)); - cl_assert((expect_mode & 0170000) == (st.st_mode & 0170000)); + cl_assert_equal_i_fmt( + GIT_MODE_TYPE(expect_mode), GIT_MODE_TYPE(st.st_mode), "%07o"); } void test_repo_init__extended_with_template(void) From af22dabb4366f8b2dd4acd5725a25e88842d6938 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 5 Sep 2013 12:01:17 -0700 Subject: [PATCH 336/367] GIT_MODE_TYPE should exclude setgid bits The GIT_MODE_TYPE macro was looking at all bits above the permissions, but it should really just look at the top bits so that it will give the right results for a setgid or setuid entry. Since we're now using these macros in the tests, this was causing a test failure on platforms that don't support setgid. --- src/fileops.h | 3 ++- tests-clar/repo/init.c | 8 ++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/fileops.h b/src/fileops.h index 142eb99d2..f2144566d 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -228,7 +228,8 @@ extern git_off_t git_futils_filesize(git_file fd); #define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_EXECUTABLE(MODE) ? 0777 : 0666) #define GIT_MODE_PERMS_MASK 0777 -#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK) +#define GIT_MODE_TYPE_MASK 0170000 +#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK) #define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) /** diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index d7f2524c8..43bd7afe0 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -418,12 +418,8 @@ static void assert_mode_seems_okay( expect_setgid = false; } - if (S_ISGID != 0) { - if (expect_setgid) - cl_assert((st.st_mode & S_ISGID) != 0); - else - cl_assert((st.st_mode & S_ISGID) == 0); - } + if (S_ISGID != 0) + cl_assert_equal_b(expect_setgid, (st.st_mode & S_ISGID) != 0); cl_assert_equal_b( GIT_PERMS_EXECUTABLE(expect_mode), GIT_PERMS_EXECUTABLE(st.st_mode)); From a7fcc44dcf3b2925ba366543486afd102b41838c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 5 Sep 2013 16:14:32 -0700 Subject: [PATCH 337/367] Better macro name for is-exec-bit-set test --- src/checkout.c | 2 +- src/diff_print.c | 2 +- src/fileops.h | 6 +++--- src/tree.c | 2 +- tests-clar/index/addall.c | 2 +- tests-clar/repo/init.c | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index f3a9b343d..aae354ca6 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -696,7 +696,7 @@ static int buffer_to_file( if (st != NULL && (error = p_stat(path, st)) < 0) giterr_set(GITERR_OS, "Error statting '%s'", path); - else if (GIT_PERMS_EXECUTABLE(file_mode) && + else if (GIT_PERMS_IS_EXEC(file_mode) && (error = p_chmod(path, file_mode)) < 0) giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); diff --git a/src/diff_print.c b/src/diff_print.c index 96937d84d..ee4b5fc17 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -46,7 +46,7 @@ static char diff_pick_suffix(int mode) { if (S_ISDIR(mode)) return '/'; - else if (GIT_PERMS_EXECUTABLE(mode)) /* -V536 */ + else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */ /* in git, modes are very regular, so we must have 0100755 mode */ return '*'; else diff --git a/src/fileops.h b/src/fileops.h index f2144566d..02f79b9e7 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -223,9 +223,9 @@ extern int git_futils_open_ro(const char *path); */ extern git_off_t git_futils_filesize(git_file fd); -#define GIT_PERMS_EXECUTABLE(MODE) (((MODE) & 0111) != 0) -#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_EXECUTABLE(MODE) ? 0755 : 0644) -#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_EXECUTABLE(MODE) ? 0777 : 0666) +#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0111) != 0) +#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644) +#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666) #define GIT_MODE_PERMS_MASK 0777 #define GIT_MODE_TYPE_MASK 0170000 diff --git a/src/tree.c b/src/tree.c index 91309e107..f9469195a 100644 --- a/src/tree.c +++ b/src/tree.c @@ -33,7 +33,7 @@ GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) return GIT_FILEMODE_TREE; /* If any of the x bits are set */ - if (GIT_PERMS_EXECUTABLE(filemode)) + if (GIT_PERMS_IS_EXEC(filemode)) return GIT_FILEMODE_BLOB_EXECUTABLE; /* 16XXXX means commit */ diff --git a/tests-clar/index/addall.c b/tests-clar/index/addall.c index e6ce463a3..00388ee00 100644 --- a/tests-clar/index/addall.c +++ b/tests-clar/index/addall.c @@ -112,7 +112,7 @@ static void check_stat_data(git_index *index, const char *path, bool match) cl_assert_equal_i_fmt( GIT_MODE_TYPE(st.st_mode), GIT_MODE_TYPE(entry->mode), "%07o"); cl_assert_equal_b( - GIT_PERMS_EXECUTABLE(st.st_mode), GIT_PERMS_EXECUTABLE(entry->mode)); + GIT_PERMS_IS_EXEC(st.st_mode), GIT_PERMS_IS_EXEC(entry->mode)); } else { /* most things will still match */ cl_assert(st.st_size != entry->file_size); diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 43bd7afe0..e3fc112b3 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -422,7 +422,7 @@ static void assert_mode_seems_okay( cl_assert_equal_b(expect_setgid, (st.st_mode & S_ISGID) != 0); cl_assert_equal_b( - GIT_PERMS_EXECUTABLE(expect_mode), GIT_PERMS_EXECUTABLE(st.st_mode)); + GIT_PERMS_IS_EXEC(expect_mode), GIT_PERMS_IS_EXEC(st.st_mode)); cl_assert_equal_i_fmt( GIT_MODE_TYPE(expect_mode), GIT_MODE_TYPE(st.st_mode), "%07o"); From fb23d05f0bc3084cdb5a9737b1c678817c5bc9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 17 Aug 2013 07:58:55 +0200 Subject: [PATCH 338/367] revwalk: make mark_unintersting use a loop Using a recursive function can blow the stack when dealing with long histories. Use a loop instead to limit the call chain depth. This fixes #1223. --- src/array.h | 2 ++ src/revwalk.c | 42 ++++++++++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/array.h b/src/array.h index c25a1b29e..b82079bd8 100644 --- a/src/array.h +++ b/src/array.h @@ -63,6 +63,8 @@ GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size) #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL) +#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : NULL) + #define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : NULL) #define git_array_size(a) (a).size diff --git a/src/revwalk.c b/src/revwalk.c index 528d02b20..1ff41bfb1 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -41,28 +41,50 @@ git_commit_list_node *git_revwalk__commit_lookup( return commit; } -static void mark_uninteresting(git_commit_list_node *commit) +static int mark_uninteresting(git_commit_list_node *commit) { unsigned short i; + git_array_t(git_commit_list_node *) pending = GIT_ARRAY_INIT; + git_commit_list_node **tmp; + assert(commit); - commit->uninteresting = 1; + git_array_alloc(pending); + GITERR_CHECK_ARRAY(pending); - /* This means we've reached a merge base, so there's no need to walk any more */ - if ((commit->flags & (RESULT | STALE)) == RESULT) - return; + do { + commit->uninteresting = 1; - for (i = 0; i < commit->out_degree; ++i) - if (!commit->parents[i]->uninteresting) - mark_uninteresting(commit->parents[i]); + /* This means we've reached a merge base, so there's no need to walk any more */ + if ((commit->flags & (RESULT | STALE)) == RESULT) { + tmp = git_array_pop(pending); + commit = tmp ? *tmp : NULL; + continue; + } + + for (i = 0; i < commit->out_degree; ++i) + if (!commit->parents[i]->uninteresting) { + git_commit_list_node **node = git_array_alloc(pending); + GITERR_CHECK_ALLOC(node); + *node = commit->parents[i]; + } + + tmp = git_array_pop(pending); + commit = tmp ? *tmp : NULL; + + } while (git_array_size(pending) > 0); + + git_array_clear(pending); + + return 0; } static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int hide) { int error; - if (hide) - mark_uninteresting(commit); + if (hide && mark_uninteresting(commit) < 0) + return -1; if (commit->seen) return 0; From ae4a486605c258aa38a53534c99f94e66379c9ae Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 29 Aug 2013 14:12:13 +0200 Subject: [PATCH 339/367] blob: Slightly enforce a create_fromchunks() test --- tests-clar/object/blob/fromchunks.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests-clar/object/blob/fromchunks.c b/tests-clar/object/blob/fromchunks.c index dc57d4fbe..9fe62daef 100644 --- a/tests-clar/object/blob/fromchunks.c +++ b/tests-clar/object/blob/fromchunks.c @@ -41,11 +41,15 @@ void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provi cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); - cl_git_fail(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY)); + cl_git_fail_with( + git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY), + GIT_ENOTFOUND); cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); cl_git_pass(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY)); + cl_assert(git_oid_cmp(&expected_oid, git_object_id(blob)) == 0); + git_object_free(blob); } From 4047950f30618c160cd2fdf5da39fb8e26b031d9 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 29 Aug 2013 14:19:34 +0200 Subject: [PATCH 340/367] odb: Prevent stream_finalize_write() from overwriting Now that #1785 is merged, git_odb_stream_finalize_write() calculates the object id before invoking the odb backend. This commit gives a chance to the backend to check if it already knows this object. --- include/git2/odb_backend.h | 4 ++++ src/odb.c | 4 ++++ src/odb_loose.c | 7 ------- tests-clar/object/blob/fromchunks.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index 7b3c6a356..bafeec047 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -92,6 +92,10 @@ struct git_odb_stream { /** * Store the contents of the stream as an object with the id * specified in `oid`. + * + * This method will *not* be invoked by libgit2 if the object pointed at + * by `oid` already exists in any backend. Libgit2 will however take care + * of properly disposing the stream through a call to `free()`. */ int (*finalize_write)(git_odb_stream *stream, const git_oid *oid); diff --git a/src/odb.c b/src/odb.c index e47715f79..dfb252178 100644 --- a/src/odb.c +++ b/src/odb.c @@ -900,6 +900,10 @@ int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) { git_hash_final(out, stream->hash_ctx); + + if (git_odb_exists(stream->backend->odb, out)) + return 0; + return stream->finalize_write(stream, out); } diff --git a/src/odb_loose.c b/src/odb_loose.c index abf46a118..ce63f4673 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -781,13 +781,6 @@ static int loose_backend__stream_fwrite(git_odb_stream *_stream, const git_oid * if (object_file_name(&final_path, backend, oid) < 0 || object_mkdir(&final_path, backend) < 0) error = -1; - /* - * Don't try to add an existing object to the repository. This - * is what git does and allows us to sidestep the fact that - * we're not allowed to overwrite a read-only file on Windows. - */ - else if (git_path_exists(final_path.ptr) == true) - git_filebuf_cleanup(&stream->fbuf); else error = git_filebuf_commit_at( &stream->fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE); diff --git a/tests-clar/object/blob/fromchunks.c b/tests-clar/object/blob/fromchunks.c index 9fe62daef..03ed4efb4 100644 --- a/tests-clar/object/blob/fromchunks.c +++ b/tests-clar/object/blob/fromchunks.c @@ -53,6 +53,34 @@ void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provi git_object_free(blob); } +void test_object_blob_fromchunks__doesnot_overwrite_an_already_existing_object(void) +{ + git_buf path = GIT_BUF_INIT; + git_buf content = GIT_BUF_INIT; + git_oid expected_oid, oid; + int howmany = 7; + + cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); + + cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); + + /* Let's replace the content of the blob file storage with something else... */ + cl_git_pass(git_buf_joinpath(&path, git_repository_path(repo), "objects/32/1cbdf08803c744082332332838df6bd160f8f9")); + cl_git_pass(p_unlink(git_buf_cstr(&path))); + cl_git_mkfile(git_buf_cstr(&path), "boom"); + + /* ...request a creation of the same blob... */ + howmany = 7; + cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); + + /* ...and ensure the content of the faked blob file hasn't been altered */ + cl_git_pass(git_futils_readbuffer(&content, git_buf_cstr(&path))); + cl_assert(!git__strcmp("boom", git_buf_cstr(&content))); + + git_buf_free(&path); + git_buf_free(&content); +} + #define GITATTR "* text=auto\n" \ "*.txt text\n" \ "*.data binary\n" From a8d67afe42e6c79cb15383ceb1264f665dc4ad8d Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 7 Sep 2013 17:21:41 +0200 Subject: [PATCH 341/367] revparse: Prevent unnecessary odb backend calls --- src/revparse.c | 9 +++------ tests-clar/odb/backend/nonrefreshing.c | 13 +++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/revparse.c b/src/revparse.c index 329b96dbc..05ddc6c35 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -115,12 +115,9 @@ static int revparse_lookup_object( if (error < 0 && error != GIT_ENOTFOUND) return error; - error = maybe_abbrev(object_out, repo, spec); - if (!error) - return 0; - - if (error < 0 && error != GIT_ENOTFOUND) - return error; + if ((strlen(spec) < GIT_OID_HEXSZ) && + ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND)) + return error; error = maybe_describe(object_out, repo, spec); if (!error) diff --git a/tests-clar/odb/backend/nonrefreshing.c b/tests-clar/odb/backend/nonrefreshing.c index 9abca2bd3..b43529479 100644 --- a/tests-clar/odb/backend/nonrefreshing.c +++ b/tests-clar/odb/backend/nonrefreshing.c @@ -259,3 +259,16 @@ void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_success(void) cl_assert_equal_i(1, _fake->read_header_calls); } + +void test_odb_backend_nonrefreshing__read_is_invoked_once_when_revparsing_a_full_oid(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_fail_with( + git_revparse_single(&obj, _repo, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_calls); +} From 1634df8c287da5acbe017ac786e2bb9fcd723eaa Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 7 Sep 2013 17:31:30 +0200 Subject: [PATCH 342/367] revparse: Simplify error handling --- src/revparse.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/revparse.c b/src/revparse.c index 05ddc6c35..3dde22ce1 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -93,11 +93,7 @@ static int revparse_lookup_object( int error; git_reference *ref; - error = maybe_sha(object_out, repo, spec); - if (!error) - return 0; - - if (error < 0 && error != GIT_ENOTFOUND) + if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND) return error; error = git_reference_dwim(&ref, repo, spec); @@ -112,18 +108,14 @@ static int revparse_lookup_object( return error; } - if (error < 0 && error != GIT_ENOTFOUND) + if (error != GIT_ENOTFOUND) return error; if ((strlen(spec) < GIT_OID_HEXSZ) && ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND)) return error; - error = maybe_describe(object_out, repo, spec); - if (!error) - return 0; - - if (error < 0 && error != GIT_ENOTFOUND) + if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND) return error; giterr_set(GITERR_REFERENCE, "Revspec '%s' not found.", spec); From e839efbe2468905d82ffb717c225a738de6f192c Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 7 Sep 2013 17:51:24 +0200 Subject: [PATCH 343/367] tests: Fix memory leaks --- tests-clar/network/remote/local.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests-clar/network/remote/local.c b/tests-clar/network/remote/local.c index 79eb73c5a..c8edd37f5 100644 --- a/tests-clar/network/remote/local.c +++ b/tests-clar/network/remote/local.c @@ -201,6 +201,7 @@ void test_network_remote_local__push_to_bare_remote(void) cl_assert(git_push_unpack_ok(push)); /* Clean up */ + git_push_free(push); git_remote_free(localremote); cl_fixture_cleanup("localbare.git"); } @@ -236,6 +237,7 @@ void test_network_remote_local__push_to_non_bare_remote(void) cl_assert_equal_i(0, git_push_unpack_ok(push)); /* Clean up */ + git_push_free(push); git_remote_free(localremote); cl_fixture_cleanup("localbare.git"); } From 031f3f8028835c935d1e75ebd136aaaefffea821 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 7 Sep 2013 22:39:05 +0200 Subject: [PATCH 344/367] odb: Error when streaming in too [few|many] bytes --- include/git2/odb.h | 6 ++++ include/git2/odb_backend.h | 13 +++++++-- src/odb.c | 27 +++++++++++++++++ tests-clar/odb/streamwrite.c | 56 ++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 tests-clar/odb/streamwrite.c diff --git a/include/git2/odb.h b/include/git2/odb.h index 3e93a932c..e50a2a1c1 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -238,6 +238,9 @@ GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, size_t s /** * Write to an odb stream * + * This method will fail as soon as the total number of + * received bytes exceeds the size declared with `git_odb_open_wstream()` + * * @param stream the stream * @param buffer the data to write * @param len the buffer's length @@ -251,6 +254,9 @@ GIT_EXTERN(int) git_odb_stream_write(git_odb_stream *stream, const char *buffer, * The object will take its final name and will be available to the * odb. * + * This method will fail if the total number of received bytes + * differs from the size declared with `git_odb_open_wstream()` + * * @param out pointer to store the resulting object's id * @param stream the stream * @return 0 on success; an error code otherwise diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index bafeec047..e558bbb1c 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -78,6 +78,9 @@ struct git_odb_stream { unsigned int mode; void *hash_ctx; + size_t declared_size; + size_t received_bytes; + /** * Write at most `len` bytes into `buffer` and advance the * stream. @@ -93,9 +96,13 @@ struct git_odb_stream { * Store the contents of the stream as an object with the id * specified in `oid`. * - * This method will *not* be invoked by libgit2 if the object pointed at - * by `oid` already exists in any backend. Libgit2 will however take care - * of properly disposing the stream through a call to `free()`. + * This method will *not* be invoked by libgit2 when: + * - the object pointed at by `oid` already exists in any backend. + * - the total number of received bytes differs from the size declared + * with `git_odb_open_wstream()` + * + * Libgit2 will however take care of properly disposing the stream through + * a call to `free()`. */ int (*finalize_write)(git_odb_stream *stream, const git_oid *oid); diff --git a/src/odb.c b/src/odb.c index dfb252178..2e6869547 100644 --- a/src/odb.c +++ b/src/odb.c @@ -888,17 +888,44 @@ int git_odb_open_wstream( hash_header(ctx, size, type); (*stream)->hash_ctx = ctx; + (*stream)->declared_size = size; + (*stream)->received_bytes = 0; + return error; } +static int git_odb_stream__invalid_length( + const git_odb_stream *stream, + const char *action) +{ + giterr_set(GITERR_ODB, + "Cannot %s - " + "Invalid length. %"PRIuZ" was expected. The " + "total size of the received chunks amounts to %"PRIuZ".", + action, stream->declared_size, stream->received_bytes); + + return -1; +} + int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) { git_hash_update(stream->hash_ctx, buffer, len); + + stream->received_bytes += len; + + if (stream->received_bytes > stream->declared_size) + return git_odb_stream__invalid_length(stream, + "stream_write()"); + return stream->write(stream, buffer, len); } int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) { + if (stream->received_bytes != stream->declared_size) + return git_odb_stream__invalid_length(stream, + "stream_finalize_write()"); + git_hash_final(out, stream->hash_ctx); if (git_odb_exists(stream->backend->odb, out)) diff --git a/tests-clar/odb/streamwrite.c b/tests-clar/odb/streamwrite.c new file mode 100644 index 000000000..591a20040 --- /dev/null +++ b/tests-clar/odb/streamwrite.c @@ -0,0 +1,56 @@ +#include "clar_libgit2.h" +#include "git2/odb_backend.h" + +static git_repository *repo; +static git_odb *odb; +static git_odb_stream *stream; + +void test_odb_streamwrite__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_odb(&odb, repo)); + + cl_git_pass(git_odb_open_wstream(&stream, odb, 14, GIT_OBJ_BLOB)); + cl_assert_equal_sz(14, stream->declared_size); +} + +void test_odb_streamwrite__cleanup(void) +{ + git_odb_stream_free(stream); + git_odb_free(odb); + cl_git_sandbox_cleanup(); +} + +void test_odb_streamwrite__can_accept_chunks(void) +{ + git_oid oid; + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); + cl_assert_equal_sz(8, stream->received_bytes); + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 6)); + cl_assert_equal_sz(8 + 6, stream->received_bytes); + + cl_git_pass(git_odb_stream_finalize_write(&oid, stream)); +} + +void test_odb_streamwrite__can_detect_missing_bytes(void) +{ + git_oid oid; + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); + cl_assert_equal_sz(8, stream->received_bytes); + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 4)); + cl_assert_equal_sz(8 + 4, stream->received_bytes); + + cl_git_fail(git_odb_stream_finalize_write(&oid, stream)); +} + +void test_odb_streamwrite__can_detect_additional_bytes(void) +{ + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); + cl_assert_equal_sz(8, stream->received_bytes); + + cl_git_fail(git_odb_stream_write(stream, "deadbeef", 7)); +} From fbabe855ad6f9a7b2a50a54d0d826d2e65538154 Mon Sep 17 00:00:00 2001 From: Isaac Kearse Date: Sun, 8 Sep 2013 14:11:08 +1200 Subject: [PATCH 345/367] Trim leading colon from ssh repository path --- src/transports/ssh.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index e0126a8fb..47ea5ccf7 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -51,6 +51,7 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) repo = strchr(url, '/'); } else { repo = strchr(url, ':'); + repo++; } if (!repo) { From 917e5fa9a18b42bb00140db9112e362bc810aa45 Mon Sep 17 00:00:00 2001 From: John Josef Date: Sun, 8 Sep 2013 18:31:56 -0400 Subject: [PATCH 346/367] fixes issues with objective-git --- include/git2/merge.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/git2/merge.h b/include/git2/merge.h index cef6f775b..3f21fb4c8 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -7,11 +7,11 @@ #ifndef INCLUDE_git_merge_h__ #define INCLUDE_git_merge_h__ -#include "git2/common.h" -#include "git2/types.h" -#include "git2/oid.h" -#include "git2/checkout.h" -#include "git2/index.h" +#include "common.h" +#include "types.h" +#include "oid.h" +#include "checkout.h" +#include "index.h" /** * @file git2/merge.h From b345026baaed21ffe8d325f784e597cf6bb779a6 Mon Sep 17 00:00:00 2001 From: Isaac Kearse Date: Tue, 10 Sep 2013 05:16:52 +1200 Subject: [PATCH 347/367] Test for repo before removing leading colon --- src/transports/ssh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 47ea5ccf7..bf62bd185 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -51,7 +51,7 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) repo = strchr(url, '/'); } else { repo = strchr(url, ':'); - repo++; + if (repo) repo++; } if (!repo) { From 4dfe38205b7ddd255c87ff4656720072991fc432 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 9 Sep 2013 10:24:48 -0700 Subject: [PATCH 348/367] Comment updates --- include/git2/odb.h | 7 ++++--- include/git2/odb_backend.h | 18 +++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/include/git2/odb.h b/include/git2/odb.h index e50a2a1c1..3bd18e782 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -223,7 +223,8 @@ GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size * won't be effective until `git_odb_stream_finalize_write` is called * and returns without an error * - * The stream must always be free'd or will leak memory. + * The stream must always be freed when done with `git_odb_stream_free` or + * will leak memory. * * @see git_odb_stream * @@ -238,8 +239,8 @@ GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, size_t s /** * Write to an odb stream * - * This method will fail as soon as the total number of - * received bytes exceeds the size declared with `git_odb_open_wstream()` + * This method will fail if the total number of received bytes exceeds the + * size declared with `git_odb_open_wstream()` * * @param stream the stream * @param buffer the data to write diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index e558bbb1c..87010f5eb 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -82,8 +82,7 @@ struct git_odb_stream { size_t received_bytes; /** - * Write at most `len` bytes into `buffer` and advance the - * stream. + * Write at most `len` bytes into `buffer` and advance the stream. */ int (*read)(git_odb_stream *stream, char *buffer, size_t len); @@ -96,18 +95,19 @@ struct git_odb_stream { * Store the contents of the stream as an object with the id * specified in `oid`. * - * This method will *not* be invoked by libgit2 when: - * - the object pointed at by `oid` already exists in any backend. - * - the total number of received bytes differs from the size declared - * with `git_odb_open_wstream()` - * - * Libgit2 will however take care of properly disposing the stream through - * a call to `free()`. + * This method might not be invoked if: + * - an error occurs earlier with the `write` callback, + * - the object referred to by `oid` already exists in any backend, or + * - the final number of received bytes differs from the size declared + * with `git_odb_open_wstream()` */ int (*finalize_write)(git_odb_stream *stream, const git_oid *oid); /** * Free the stream's memory. + * + * This method might be called without a call to `finalize_write` if + * an error occurs or if the object is already present in the ODB. */ void (*free)(git_odb_stream *stream); }; From f313843c8a3e9716d030ad0c23ce2194034c7f59 Mon Sep 17 00:00:00 2001 From: John Josef Date: Mon, 9 Sep 2013 13:53:22 -0400 Subject: [PATCH 349/367] fixing headers with bad values for objective-c --- include/git2/cred_helpers.h | 2 +- include/git2/odb_backend.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/git2/cred_helpers.h b/include/git2/cred_helpers.h index 5d93cf4dd..1d8809211 100644 --- a/include/git2/cred_helpers.h +++ b/include/git2/cred_helpers.h @@ -7,7 +7,7 @@ #ifndef INCLUDE_git_cred_helpers_h__ #define INCLUDE_git_cred_helpers_h__ -#include "git2/transport.h" +#include "transport.h" /** * @file git2/cred_helpers.h diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index 87010f5eb..a6cb285dc 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -7,8 +7,8 @@ #ifndef INCLUDE_git_odb_backend_h__ #define INCLUDE_git_odb_backend_h__ -#include "git2/common.h" -#include "git2/types.h" +#include "common.h" +#include "types.h" /** * @file git2/backend.h From 62020aa8f999a89df8458dbf50540eb1aee5c97b Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Mon, 2 Sep 2013 02:01:40 +0200 Subject: [PATCH 350/367] Adding add example. --- examples/add.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 examples/add.c diff --git a/examples/add.c b/examples/add.c new file mode 100644 index 000000000..b4bc6a166 --- /dev/null +++ b/examples/add.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +enum print_options { + SKIP = 1, + VERBOSE = 2, +}; + +struct print_payload { + enum print_options options; + git_repository *repo; +}; + +void init_array(git_strarray *array, int argc, char **argv) +{ + unsigned int i; + + array->count = argc; + array->strings = malloc(sizeof(char*) * array->count); + assert(array->strings!=NULL); + + for(i=0; icount; i++) { + array->strings[i]=argv[i]; + } + + return; +} + +int print_matched_cb(const char *path, const char *matched_pathspec, void *payload) +{ + (void)matched_pathspec; + + struct print_payload p = *(struct print_payload*)(payload); + int ret; + git_status_t status; + + if (git_status_file(&status, p.repo, path)) { + return -1; //abort + } + + if (status & GIT_STATUS_WT_MODIFIED || + status & GIT_STATUS_WT_NEW) { + if (p.options & VERBOSE || p.options & SKIP) { + printf("add '%s'\n", path); + } + ret = 0; + } else { + ret = 1; + } + + if(p.options & SKIP) { + ret = 1; + } + + return ret; +} + +int main (int argc, char** argv) +{ + git_index_matched_path_cb matched_cb = NULL; + git_repository *repo = NULL; + git_index *index; + git_strarray array = {0}; + int i, options = 0; + struct print_payload payload = {0}; + + if (argc < 2) { + fprintf(stderr, "usage: add file-spec [file-spec] [...]\n"); + return 1; + } + + for (i = 1; i < argc; ++i) { + if (argv[i][0] != '-') { + break; + } + else if(!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { + options |= VERBOSE; + } + else if(!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) { + options |= SKIP; + } + else if(!strcmp(argv[i], "--")) { + break; + } + else { + fprintf(stderr, "Unsupported option %s.\n", argv[i]); + return 1; + } + } + + init_array(&array, argc-i, argv+i); + + printf("args:\n"); + for(i=0; i Date: Tue, 3 Sep 2013 19:11:50 +0200 Subject: [PATCH 351/367] Supported options information in add example. --- examples/add.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/add.c b/examples/add.c index b4bc6a166..f1e7b7b7e 100644 --- a/examples/add.c +++ b/examples/add.c @@ -67,7 +67,7 @@ int main (int argc, char** argv) struct print_payload payload = {0}; if (argc < 2) { - fprintf(stderr, "usage: add file-spec [file-spec] [...]\n"); + fprintf(stderr, "usage: add [-n|--dry-run] [-v|--verbose] file-spec [file-spec] [...]\n"); return 1; } From 24d23220969a976c9a6ede2993837715824cac64 Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Wed, 4 Sep 2013 18:34:03 +0200 Subject: [PATCH 352/367] Add -u option to add example. --- examples/add.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/add.c b/examples/add.c index f1e7b7b7e..12e660d43 100644 --- a/examples/add.c +++ b/examples/add.c @@ -6,6 +6,7 @@ enum print_options { SKIP = 1, VERBOSE = 2, + UPDATE = 4, }; struct print_payload { @@ -67,7 +68,7 @@ int main (int argc, char** argv) struct print_payload payload = {0}; if (argc < 2) { - fprintf(stderr, "usage: add [-n|--dry-run] [-v|--verbose] file-spec [file-spec] [...]\n"); + fprintf(stderr, "usage: add [-n|--dry-run] [-v|--verbose] [-u|--update] file-spec [file-spec] [...]\n"); return 1; } @@ -81,6 +82,9 @@ int main (int argc, char** argv) else if(!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) { options |= SKIP; } + else if(!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) { + options |= UPDATE; + } else if(!strcmp(argv[i], "--")) { break; } @@ -111,7 +115,12 @@ int main (int argc, char** argv) payload.options = options; payload.repo = repo; - git_index_add_all(index, &array, 0, matched_cb, &payload); + + if (options&UPDATE) { + git_index_update_all(index, &array, matched_cb, &payload); + } else { + git_index_add_all(index, &array, 0, matched_cb, &payload); + } git_index_write(index); git_index_free(index); From 813937ce4e99f8ba208548ef557a776bbc7029f9 Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Wed, 4 Sep 2013 18:42:47 +0200 Subject: [PATCH 353/367] Better usage info in add example. --- examples/add.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/examples/add.c b/examples/add.c index 12e660d43..f2a33f959 100644 --- a/examples/add.c +++ b/examples/add.c @@ -58,6 +58,15 @@ int print_matched_cb(const char *path, const char *matched_pathspec, void *paylo return ret; } +void print_usage(void) +{ + fprintf(stderr, "usage: add [options] [--] file-spec [file-spec] [...]\n\n"); + fprintf(stderr, "\t-n, --dry-run dry run\n"); + fprintf(stderr, "\t-v, --verbose be verbose\n"); + fprintf(stderr, "\t-u, --update update tracked files\n"); +} + + int main (int argc, char** argv) { git_index_matched_path_cb matched_cb = NULL; @@ -67,11 +76,6 @@ int main (int argc, char** argv) int i, options = 0; struct print_payload payload = {0}; - if (argc < 2) { - fprintf(stderr, "usage: add [-n|--dry-run] [-v|--verbose] [-u|--update] file-spec [file-spec] [...]\n"); - return 1; - } - for (i = 1; i < argc; ++i) { if (argv[i][0] != '-') { break; @@ -85,22 +89,31 @@ int main (int argc, char** argv) else if(!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) { options |= UPDATE; } + else if(!strcmp(argv[i], "-h")) { + print_usage(); + break; + } else if(!strcmp(argv[i], "--")) { + i++; break; } else { fprintf(stderr, "Unsupported option %s.\n", argv[i]); + print_usage(); return 1; } } - - init_array(&array, argc-i, argv+i); printf("args:\n"); for(i=0; i Date: Wed, 4 Sep 2013 18:43:14 +0200 Subject: [PATCH 354/367] Remove unnececery arguments priting in add example. --- examples/add.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/add.c b/examples/add.c index f2a33f959..5e59b2b92 100644 --- a/examples/add.c +++ b/examples/add.c @@ -104,9 +104,6 @@ int main (int argc, char** argv) } } - printf("args:\n"); - for(i=0; i Date: Wed, 4 Sep 2013 18:44:12 +0200 Subject: [PATCH 355/367] Move statement after declarations in add example. --- examples/add.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/add.c b/examples/add.c index 5e59b2b92..96595f895 100644 --- a/examples/add.c +++ b/examples/add.c @@ -31,11 +31,10 @@ void init_array(git_strarray *array, int argc, char **argv) int print_matched_cb(const char *path, const char *matched_pathspec, void *payload) { - (void)matched_pathspec; - struct print_payload p = *(struct print_payload*)(payload); int ret; git_status_t status; + (void)matched_pathspec; if (git_status_file(&status, p.repo, path)) { return -1; //abort From b2395a826780c84640c3bd5d3ccec51d34086524 Mon Sep 17 00:00:00 2001 From: Krzysztof Adamski Date: Wed, 4 Sep 2013 18:49:10 +0200 Subject: [PATCH 356/367] Only use callbacks when -n or -v in add example. --- examples/add.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/add.c b/examples/add.c index 96595f895..cab57dca7 100644 --- a/examples/add.c +++ b/examples/add.c @@ -42,9 +42,7 @@ int print_matched_cb(const char *path, const char *matched_pathspec, void *paylo if (status & GIT_STATUS_WT_MODIFIED || status & GIT_STATUS_WT_NEW) { - if (p.options & VERBOSE || p.options & SKIP) { - printf("add '%s'\n", path); - } + printf("add '%s'\n", path); ret = 0; } else { ret = 1; @@ -120,7 +118,9 @@ int main (int argc, char** argv) return 1; } - matched_cb = &print_matched_cb; + if (options&VERBOSE || options&SKIP) { + matched_cb = &print_matched_cb; + } payload.options = options; payload.repo = repo; From 0c52b204c5a792cb9515e93fdea35811ae09807b Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 9 Sep 2013 11:07:17 -0700 Subject: [PATCH 357/367] Make work if built with threading enabled --- examples/add.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/add.c b/examples/add.c index cab57dca7..a0edf4376 100644 --- a/examples/add.c +++ b/examples/add.c @@ -106,6 +106,8 @@ int main (int argc, char** argv) return 1; } + git_threads_init(); + init_array(&array, argc-i, argv+i); if (git_repository_open(&repo, ".") < 0) { @@ -135,5 +137,7 @@ int main (int argc, char** argv) git_index_free(index); git_repository_free(repo); + git_threads_shutdown(); + return 0; } From 15f7b9b8d9bdfb68ca52d582be40cf6112464e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 8 Sep 2013 00:52:26 +0200 Subject: [PATCH 358/367] revwalk: allow simplifying by first-parent When enabled, only the first parent of each commit will be queued, enabling a simple way of using first-parent simplification. --- include/git2/revwalk.h | 8 ++++++ src/revwalk.c | 22 ++++++++++++--- src/revwalk.h | 3 ++- tests-clar/revwalk/simplify.c | 51 +++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests-clar/revwalk/simplify.c diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index 8bfe0b502..c59b79938 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -231,6 +231,14 @@ GIT_EXTERN(void) git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode); */ GIT_EXTERN(int) git_revwalk_push_range(git_revwalk *walk, const char *range); +/** + * Simplify the history by first-parent + * + * No parents other than the first for each commit will be enqueued. + */ +GIT_EXTERN(void) git_revwalk_simplify_first_parent(git_revwalk *walk); + + /** * Free a revision walker previously allocated. * diff --git a/src/revwalk.c b/src/revwalk.c index 1ff41bfb1..9e1e39ca4 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -99,10 +99,14 @@ static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int h static int process_commit_parents(git_revwalk *walk, git_commit_list_node *commit) { - unsigned short i; + unsigned short i, max; int error = 0; - for (i = 0; i < commit->out_degree && !error; ++i) + max = commit->out_degree; + if (walk->first_parent && commit->out_degree) + max = 1; + + for (i = 0; i < max && !error; ++i) error = process_commit(walk, commit->parents[i], commit->uninteresting); return error; @@ -333,7 +337,7 @@ static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk) { git_commit_list_node *next; - unsigned short i; + unsigned short i, max; for (;;) { next = git_commit_list_pop(&walk->iterator_topo); @@ -347,7 +351,12 @@ static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk continue; } - for (i = 0; i < next->out_degree; ++i) { + + max = next->out_degree; + if (walk->first_parent && next->out_degree) + max = 1; + + for (i = 0; i < max; ++i) { git_commit_list_node *parent = next->parents[i]; if (--parent->in_degree == 0 && parent->topo_delay) { @@ -505,6 +514,11 @@ void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode) } } +void git_revwalk_simplify_first_parent(git_revwalk *walk) +{ + walk->first_parent = 1; +} + int git_revwalk_next(git_oid *oid, git_revwalk *walk) { int error; diff --git a/src/revwalk.h b/src/revwalk.h index 22696dfcd..8c821d098 100644 --- a/src/revwalk.h +++ b/src/revwalk.h @@ -31,7 +31,8 @@ struct git_revwalk { int (*get_next)(git_commit_list_node **, git_revwalk *); int (*enqueue)(git_revwalk *, git_commit_list_node *); - unsigned walking:1; + unsigned walking:1, + first_parent: 1; unsigned int sorting; /* merge base calculation */ diff --git a/tests-clar/revwalk/simplify.c b/tests-clar/revwalk/simplify.c new file mode 100644 index 000000000..c94952105 --- /dev/null +++ b/tests-clar/revwalk/simplify.c @@ -0,0 +1,51 @@ +#include "clar_libgit2.h" + +/* + * a4a7dce [0] Merge branch 'master' into br2 + |\ + | * 9fd738e [1] a fourth commit + | * 4a202b3 [2] a third commit + * | c47800c [3] branch commit one + |/ + * 5b5b025 [5] another commit + * 8496071 [4] testing +*/ +static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f"; + +static const char *expected_str[] = { + "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ + "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ + "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ + "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ +}; + +void test_revwalk_simplify__first_parent(void) +{ + git_repository *repo; + git_revwalk *walk; + git_oid id, expected[4]; + int i, error; + + for (i = 0; i < 4; i++) { + git_oid_fromstr(&expected[i], expected_str[i]); + } + + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_revwalk_new(&walk, repo)); + + git_oid_fromstr(&id, commit_head); + cl_git_pass(git_revwalk_push(walk, &id)); + git_revwalk_simplify_first_parent(walk); + + i = 0; + while ((error = git_revwalk_next(&id, walk)) == 0) { + git_oid_cmp(&id, &expected[i]); + i++; + } + + cl_assert_equal_i(i, 4); + cl_assert_equal_i(error, GIT_ITEROVER); + + git_revwalk_free(walk); + git_repository_free(repo); +} From d0cd6c427a35b257373c7178d1e17d82001e125f Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 8 Sep 2013 18:22:28 +0200 Subject: [PATCH 359/367] path: Make direach() return EUSER on callback error --- src/fileops.c | 30 +++++++++++++++++++++--------- src/odb_pack.c | 2 +- src/path.c | 4 ++-- src/refdb_fs.c | 2 +- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 92cda82e7..126d45f26 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -869,6 +869,7 @@ typedef struct { uint32_t flags; uint32_t mkdir_flags; mode_t dirmode; + int error; } cp_r_info; #define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) @@ -907,20 +908,23 @@ static int _cp_r_callback(void *ref, git_buf *from) return 0; if (git_buf_joinpath( - &info->to, info->to_root, from->ptr + info->from_prefix) < 0) - return -1; + &info->to, info->to_root, from->ptr + info->from_prefix) < 0) { + error = -1; + goto exit; + } if (p_lstat(info->to.ptr, &to_st) < 0) { if (errno != ENOENT && errno != ENOTDIR) { giterr_set(GITERR_OS, "Could not access %s while copying files", info->to.ptr); - return -1; + error = -1; + goto exit; } } else exists = true; if ((error = git_path_lstat(from->ptr, &from_st)) < 0) - return error; + goto exit; if (S_ISDIR(from_st.st_mode)) { mode_t oldmode = info->dirmode; @@ -934,13 +938,14 @@ static int _cp_r_callback(void *ref, git_buf *from) error = _cp_r_mkdir(info, from); /* recurse onto target directory */ - if (!error && (!exists || S_ISDIR(to_st.st_mode))) - error = git_path_direach(from, _cp_r_callback, info); + if (!error && (!exists || S_ISDIR(to_st.st_mode)) && + ((error = git_path_direach(from, _cp_r_callback, info)) == GIT_EUSER)) + error = info->error; if (oldmode != 0) info->dirmode = oldmode; - return error; + goto exit; } if (exists) { @@ -950,7 +955,8 @@ static int _cp_r_callback(void *ref, git_buf *from) if (p_unlink(info->to.ptr) < 0) { giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'", info->to.ptr); - return -1; + error = -1; + goto exit; } } @@ -963,7 +969,7 @@ static int _cp_r_callback(void *ref, git_buf *from) /* Make container directory on demand if needed */ if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && (error = _cp_r_mkdir(info, from)) < 0) - return error; + goto exit; /* make symlink or regular file */ if (S_ISLNK(from_st.st_mode)) @@ -977,6 +983,8 @@ static int _cp_r_callback(void *ref, git_buf *from) error = git_futils_cp(from->ptr, info->to.ptr, usemode); } +exit: + info->error = error; return error; } @@ -997,6 +1005,7 @@ int git_futils_cp_r( info.flags = flags; info.dirmode = dirmode; info.from_prefix = path.size; + info.error = 0; git_buf_init(&info.to, 0); /* precalculate mkdir flags */ @@ -1018,6 +1027,9 @@ int git_futils_cp_r( git_buf_free(&path); git_buf_free(&info.to); + if (error == GIT_EUSER) + error = info.error; + return error; } diff --git a/src/odb_pack.c b/src/odb_pack.c index d24b4aa99..cadc93a65 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -336,7 +336,7 @@ static int pack_backend__refresh(git_odb_backend *_backend) git_buf_free(&path); if (error < 0) - return error; + return -1; git_vector_sort(&backend->packs); return 0; diff --git a/src/path.c b/src/path.c index 7c1ec2cd0..56b0b49ca 100644 --- a/src/path.c +++ b/src/path.c @@ -765,10 +765,10 @@ int git_path_direach( git_buf_truncate(path, wd_len); /* restore path */ - if (result < 0) { + if (result) { closedir(dir); git__free(de_buf); - return -1; + return GIT_EUSER; } } diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 04516a5b0..894ff7c84 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -299,7 +299,7 @@ static int packed_loadloose(refdb_fs_backend *backend) git_buf_free(&refs_path); - return error; + return (error == GIT_EUSER) ? -1 : error; } static int refdb_fs_backend__exists( From 209f9b67c4f9b2c5bba26f2bdcbee10cf4e25a6b Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 8 Sep 2013 18:25:17 +0200 Subject: [PATCH 360/367] odb: Teach loose backend to return EAMBIGUOUS --- src/odb_loose.c | 8 ++++++-- tests-clar/refs/revparse.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/odb_loose.c b/src/odb_loose.c index ce63f4673..4ff57158d 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -499,7 +499,7 @@ static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) { } if (sstate->found > 1) - return git_odb__error_ambiguous("multiple matches in loose objects"); + return GIT_EAMBIGUOUS; return 0; } @@ -545,12 +545,16 @@ static int locate_object_short_oid( /* Explore directory to find a unique object matching short_oid */ error = git_path_direach( object_location, fn_locate_object_short_oid, &state); - if (error) + + if (error && error != GIT_EUSER) return error; if (!state.found) return git_odb__error_notfound("no matching loose object for prefix", short_oid); + if (state.found > 1) + return git_odb__error_ambiguous("multiple matches in loose objects"); + /* Convert obtained hex formatted oid to raw */ error = git_oid_fromstr(res_oid, (char *)state.res_oid); if (error) diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c index 9657054de..37d3981bb 100644 --- a/tests-clar/refs/revparse.c +++ b/tests-clar/refs/revparse.c @@ -544,6 +544,37 @@ void test_refs_revparse__a_too_short_objectid_returns_EAMBIGUOUS(void) GIT_EAMBIGUOUS, git_revparse_single(&g_obj, g_repo, "e90")); } +/* + * $ echo "aabqhq" | git hash-object -t blob --stdin + * dea509d0b3cb8ee0650f6ca210bc83f4678851ba + * + * $ echo "aaazvc" | git hash-object -t blob --stdin + * dea509d097ce692e167dfc6a48a7a280cc5e877e + */ +void test_refs_revparse__a_not_precise_enough_objectid_returns_EAMBIGUOUS(void) +{ + git_repository *repo; + git_index *index; + git_object *obj; + + repo = cl_git_sandbox_init("testrepo"); + + cl_git_mkfile("testrepo/one.txt", "aabqhq\n"); + cl_git_mkfile("testrepo/two.txt", "aaazvc\n"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "one.txt")); + cl_git_pass(git_index_add_bypath(index, "two.txt")); + + cl_git_fail_with(git_revparse_single(&obj, repo, "dea509d0"), GIT_EAMBIGUOUS); + + cl_git_pass(git_revparse_single(&obj, repo, "dea509d09")); + + git_object_free(obj); + git_index_free(index); + cl_git_sandbox_cleanup(); +} + void test_refs_revparse__issue_994(void) { git_repository *repo; From 8cf805253407e35627aa86f79b17070279106322 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 11 Sep 2013 20:13:59 +0200 Subject: [PATCH 361/367] errors: Fix format of some error messages --- src/odb.c | 2 +- src/reflog.c | 2 +- src/revparse.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/odb.c b/src/odb.c index 2e6869547..a0bfec403 100644 --- a/src/odb.c +++ b/src/odb.c @@ -445,7 +445,7 @@ int git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos) return 0; } - giterr_set(GITERR_ODB, "No ODB backend loaded at index " PRIuZ, pos); + giterr_set(GITERR_ODB, "No ODB backend loaded at index %" PRIuZ, pos); return GIT_ENOTFOUND; } diff --git a/src/reflog.c b/src/reflog.c index 4cc20d2c7..a6752f618 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -484,7 +484,7 @@ int git_reflog_drop( entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); if (entry == NULL) { - giterr_set(GITERR_REFERENCE, "No reflog entry at index "PRIuZ, idx); + giterr_set(GITERR_REFERENCE, "No reflog entry at index %"PRIuZ, idx); return GIT_ENOTFOUND; } diff --git a/src/revparse.c b/src/revparse.c index 3dde22ce1..e470a954d 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -217,7 +217,7 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t ide if (numentries < identifier + 1) { giterr_set( GITERR_REFERENCE, - "Reflog for '%s' has only "PRIuZ" entries, asked for "PRIuZ, + "Reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ, git_reference_name(ref), numentries, identifier); error = GIT_ENOTFOUND; From 273ddc54f5818b73725298f17915a307bd7c884b Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 12 Sep 2013 13:50:00 +0200 Subject: [PATCH 362/367] clar: Fix clar__assert_equal error message formating --- tests-clar/clar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-clar/clar.c b/tests-clar/clar.c index 5189e7919..6c7399a54 100644 --- a/tests-clar/clar.c +++ b/tests-clar/clar.c @@ -468,7 +468,7 @@ void clar__assert_equal( } } } - else if (!strcmp(PRIuZ, fmt) || !strcmp(PRIxZ, fmt)) { + else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); is_equal = (sz1 == sz2); if (!is_equal) { From 6f2003612c27ffbcc865059e57e257ed92acb722 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 12 Sep 2013 13:50:35 +0200 Subject: [PATCH 363/367] clar: Move cl_assert_equal_sz() definition to clar.h --- tests-clar/clar.h | 1 + tests-clar/clar_libgit2.h | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests-clar/clar.h b/tests-clar/clar.h index e1f244eba..c40bc7ac9 100644 --- a/tests-clar/clar.h +++ b/tests-clar/clar.h @@ -68,6 +68,7 @@ void cl_fixture_cleanup(const char *fixture_name); #define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) +#define cl_assert_equal_sz(sz1,sz2) clar__assert_equal(__FILE__,__LINE__,#sz1 " != " #sz2, 1, "%"PRIuZ, (size_t)(sz1), (size_t)(sz2)) void clar__fail( const char *file, diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h index 3cb0607f1..8dcfdee48 100644 --- a/tests-clar/clar_libgit2.h +++ b/tests-clar/clar_libgit2.h @@ -32,8 +32,6 @@ void cl_git_report_failure(int, const char *, int, const char *); #define cl_assert_at_line(expr,file,line) \ clar__assert((expr) != 0, file, line, "Expression is not true: " #expr, NULL, 1) -#define cl_assert_equal_sz(sz1,sz2) clar__assert_equal(__FILE__,__LINE__,#sz1 " != " #sz2, 1, PRIuZ, (size_t)(sz1), (size_t)(sz2)) - GIT_INLINE(void) clar__assert_in_range( int lo, int val, int hi, const char *file, int line, const char *err, int should_abort) From 4e01e3029b389dc45a040b794ceeeb0cbe32004a Mon Sep 17 00:00:00 2001 From: wilke Date: Fri, 13 Sep 2013 21:21:33 +0200 Subject: [PATCH 364/367] Prevent git_tree_walk 'skip entry' callback return code from leaking through as the return value of git_tree_walk --- src/tree.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index f9469195a..cd8adfb96 100644 --- a/src/tree.c +++ b/src/tree.c @@ -881,8 +881,10 @@ static int tree_walk( git_vector_foreach(&tree->entries, i, entry) { if (preorder) { error = callback(path->ptr, entry, payload); - if (error > 0) + if (error > 0) { + error = 0; continue; + } if (error < 0) { giterr_clear(); return GIT_EUSER; From d7fc2eb29b4574b96b190572470bfcb7dab9d078 Mon Sep 17 00:00:00 2001 From: wilke Date: Fri, 13 Sep 2013 21:36:39 +0200 Subject: [PATCH 365/367] Fix memory leak in git_tree_walk on error or when stopping the walk from the supplied callback --- src/tree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index cd8adfb96..0bdf9a93e 100644 --- a/src/tree.c +++ b/src/tree.c @@ -907,11 +907,12 @@ static int tree_walk( return -1; error = tree_walk(subtree, callback, path, payload, preorder); + git_tree_free(subtree); + if (error != 0) break; git_buf_truncate(path, path_len); - git_tree_free(subtree); } if (!preorder && callback(path->ptr, entry, payload) < 0) { From e580afd8634c99449b0ed6e8113873518359c2c5 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 13 Sep 2013 14:33:26 -0700 Subject: [PATCH 366/367] Add tests for git_tree_walk This tests the fixes for issues from #1849 --- tests-clar/object/tree/walk.c | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests-clar/object/tree/walk.c b/tests-clar/object/tree/walk.c index b7af4924d..1207e864c 100644 --- a/tests-clar/object/tree/walk.c +++ b/tests-clar/object/tree/walk.c @@ -101,3 +101,77 @@ void test_object_tree_walk__1(void) git_tree_free(tree); } + + +struct treewalk_skip_data { + int files; + int dirs; + const char *skip; + const char *stop; +}; + +static int treewalk_skip_de_cb( + const char *root, const git_tree_entry *entry, void *payload) +{ + struct treewalk_skip_data *data = payload; + const char *name = git_tree_entry_name(entry); + + GIT_UNUSED(root); + + if (git_tree_entry_type(entry) == GIT_OBJ_TREE) + data->dirs++; + else + data->files++; + + if (data->skip && !strcmp(name, data->skip)) + return 1; + else if (data->stop && !strcmp(name, data->stop)) + return -1; + else + return 0; +} + +void test_object_tree_walk__2(void) +{ + git_oid id; + git_tree *tree; + struct treewalk_skip_data data; + + /* look up a deep tree */ + git_oid_fromstr(&id, "ae90f12eea699729ed24555e40b9fd669da12a12"); + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + memset(&data, 0, sizeof(data)); + data.skip = "de"; + + cl_assert_equal_i(0, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(5, data.files); + cl_assert_equal_i(3, data.dirs); + + memset(&data, 0, sizeof(data)); + data.stop = "3.txt"; + + cl_assert_equal_i(GIT_EUSER, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(3, data.files); + cl_assert_equal_i(2, data.dirs); + + memset(&data, 0, sizeof(data)); + data.skip = "new.txt"; + + cl_assert_equal_i(0, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(7, data.files); + cl_assert_equal_i(4, data.dirs); + + memset(&data, 0, sizeof(data)); + data.stop = "new.txt"; + + cl_assert_equal_i(GIT_EUSER, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(7, data.files); + cl_assert_equal_i(4, data.dirs); + + git_tree_free(tree); +} From f2df503bab6ae6e77ff0bbe17f358822be51b944 Mon Sep 17 00:00:00 2001 From: Linquize Date: Sat, 14 Sep 2013 18:22:16 +0800 Subject: [PATCH 367/367] git_clone supports optional init_options --- include/git2/clone.h | 1 + src/clone.c | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/git2/clone.h b/include/git2/clone.h index 5858b4e32..580352ac1 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -67,6 +67,7 @@ typedef struct git_clone_options { unsigned int version; git_checkout_opts checkout_opts; + git_repository_init_options *init_options; int bare; git_transfer_progress_callback fetch_progress_cb; void *fetch_progress_payload; diff --git a/src/clone.c b/src/clone.c index 5c11872cc..5b8fc5e45 100644 --- a/src/clone.c +++ b/src/clone.c @@ -418,7 +418,7 @@ static bool should_checkout( return !git_repository_head_orphan(repo); } -static void normalize_options(git_clone_options *dst, const git_clone_options *src) +static void normalize_options(git_clone_options *dst, const git_clone_options *src, git_repository_init_options *initOptions) { git_clone_options default_options = GIT_CLONE_OPTIONS_INIT; if (!src) src = &default_options; @@ -427,6 +427,13 @@ static void normalize_options(git_clone_options *dst, const git_clone_options *s /* Provide defaults for null pointers */ if (!dst->remote_name) dst->remote_name = "origin"; + if (!dst->init_options) + { + dst->init_options = initOptions; + initOptions->flags = GIT_REPOSITORY_INIT_MKPATH; + if (dst->bare) + initOptions->flags |= GIT_REPOSITORY_INIT_BARE; + } } int git_clone( @@ -439,10 +446,11 @@ int git_clone( git_repository *repo = NULL; git_clone_options normOptions; int remove_directory_on_failure = 0; + git_repository_init_options initOptions = GIT_REPOSITORY_INIT_OPTIONS_INIT; assert(out && url && local_path); - normalize_options(&normOptions, options); + normalize_options(&normOptions, options, &initOptions); GITERR_CHECK_VERSION(&normOptions, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); /* Only clone to a new directory or an empty directory */ @@ -455,7 +463,7 @@ int git_clone( /* Only remove the directory on failure if we create it */ remove_directory_on_failure = !git_path_exists(local_path); - if (!(retcode = git_repository_init(&repo, local_path, normOptions.bare))) { + if (!(retcode = git_repository_init_ext(&repo, local_path, normOptions.init_options))) { if ((retcode = setup_remotes_and_fetch(repo, url, &normOptions)) < 0) { /* Failed to fetch; clean up */ git_repository_free(repo);