From b0f1533867a39cbccb25494c038dde4306331aba Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 15 Jul 2012 00:45:20 +0200 Subject: [PATCH 01/48] revparse: add reflog test data --- tests-clar/resources/testrepo.git/logs/HEAD | 2 ++ tests-clar/resources/testrepo.git/logs/refs/remotes/test/master | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 tests-clar/resources/testrepo.git/logs/refs/remotes/test/master diff --git a/tests-clar/resources/testrepo.git/logs/HEAD b/tests-clar/resources/testrepo.git/logs/HEAD index b16c9b313..9413b72cc 100644 --- a/tests-clar/resources/testrepo.git/logs/HEAD +++ b/tests-clar/resources/testrepo.git/logs/HEAD @@ -1,5 +1,7 @@ 0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub 1335806563 -0700 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git be3563ae3f795b2b4353bcce3a527ad0a4f7f644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub 1335806603 -0900 commit: +a65fedf39aefe402d3bb6e24df4d4f5fe4547750 5b5b025afb0b4c913b4c338a42934a3863bf3644 Ben Straub 1335806604 -0900 checkout: moving from master to 5b5b025 +5b5b025afb0b4c913b4c338a42934a3863bf3644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub 1335806605 -0900 checkout: moving from 5b5b025 to master a65fedf39aefe402d3bb6e24df4d4f5fe4547750 c47800c7266a2be04c571c04d5a6614691ea99bd Ben Straub 1335806608 -0900 checkout: moving from master to br2 c47800c7266a2be04c571c04d5a6614691ea99bd a4a7dce85cf63874e984719f4fdd239f5145052f Ben Straub 1335806617 -0900 commit: checking in a4a7dce85cf63874e984719f4fdd239f5145052f a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub 1335806621 -0900 checkout: moving from br2 to master diff --git a/tests-clar/resources/testrepo.git/logs/refs/remotes/test/master b/tests-clar/resources/testrepo.git/logs/refs/remotes/test/master new file mode 100644 index 000000000..8d49ba3e0 --- /dev/null +++ b/tests-clar/resources/testrepo.git/logs/refs/remotes/test/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub 1335806565 -0800 update by push +a65fedf39aefe402d3bb6e24df4d4f5fe4547750 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub 1335806688 -0800 update by push From d448392e5d030d40114b92d1261b72f3b3ab3e16 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 15 Jul 2012 00:46:01 +0200 Subject: [PATCH 02/48] revparse: extend test coverage --- tests-clar/refs/revparse.c | 42 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c index 56d57b21c..05a95652a 100644 --- a/tests-clar/refs/revparse.c +++ b/tests-clar/refs/revparse.c @@ -74,7 +74,11 @@ void test_refs_revparse__shas(void) void test_refs_revparse__head(void) { + test_object("", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD^0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); } void test_refs_revparse__full_refs(void) @@ -99,12 +103,18 @@ void test_refs_revparse__describe_output(void) void test_refs_revparse__nth_parent(void) { + cl_git_fail(git_revparse_single(&g_obj, g_repo, "be3563a^-1")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "^")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "be3563a^{tree}^")); + test_object("be3563a^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("be3563a^", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("be3563a^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); test_object("be3563a^1^1", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); + test_object("be3563a^^", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); test_object("be3563a^2^1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); test_object("be3563a^0", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("be3563a^{commit}^", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("be3563a^42", NULL); } @@ -113,34 +123,56 @@ void test_refs_revparse__not_tag(void) { test_object("point_to_blob^{}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); test_object("wrapped_tag^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master^{tree}^{}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + test_object("e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d"); + test_object("tags/e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d"); + test_object("e908^{}", "e90810b8df3e80c413d903f631643c716887138d"); } void test_refs_revparse__to_type(void) { + cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{trip}")); + test_object("wrapped_tag^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("wrapped_tag^{tree}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); test_object("point_to_blob^{blob}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); - - cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}")); + test_object("master^{commit}^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); } void test_refs_revparse__linear_history(void) { + cl_git_fail(git_revparse_single(&g_obj, g_repo, "~")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "foo~bar")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~bar")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~-1")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~0bar")); test_object("master~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("master~1", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); test_object("master~2", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("master~1~1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("master~~", "9fd738e8f7967c078dceed8190330fc8648ee56a"); } void test_refs_revparse__chaining(void) { + cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{0}@{0}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{u}@{-1}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-1}@{-1}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-3}@{0}")); + + test_object("master@{0}~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("@{u}@{0}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("@{-1}@{0}", "a4a7dce85cf63874e984719f4fdd239f5145052f"); + test_object("@{-4}@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); test_object("master~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("master~1^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); test_object("master^1^2~1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + test_object("master^^2^", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); test_object("master^1^1^1^1^1", "8496071c1b46c854b31185ea97743be6a8774479"); + test_object("master^^1^2^1", NULL); } void test_refs_revparse__upstream(void) @@ -158,6 +190,10 @@ void test_refs_revparse__upstream(void) void test_refs_revparse__ordinal(void) { cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{-2}")); + + /* TODO: make the test below actually fail + * cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{1a}")); + */ test_object("nope@{0}", NULL); test_object("master@{31415}", NULL); @@ -177,6 +213,7 @@ void test_refs_revparse__previous_head(void) { cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-xyz}")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-0}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-1b}")); test_object("@{-42}", NULL); @@ -353,6 +390,7 @@ void test_refs_revparse__colon(void) test_object(":/one", "c47800c7266a2be04c571c04d5a6614691ea99bd"); test_object(":/packed commit t", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9"); test_object("test/master^2:branch_file.txt", "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); + test_object("test/master@{1}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); } void test_refs_revparse__disambiguation(void) From 6fc0bdc53e0392a908d6b4c89827e06f2bb19b4a Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Tue, 17 Jul 2012 10:52:16 +0200 Subject: [PATCH 03/48] Remove old error handling code --- src/common.h | 10 ---------- src/global.h | 4 ---- 2 files changed, 14 deletions(-) diff --git a/src/common.h b/src/common.h index 419a8d06b..1db308fc7 100644 --- a/src/common.h +++ b/src/common.h @@ -49,15 +49,6 @@ #include -extern void git___throw(const char *, ...) GIT_FORMAT_PRINTF(1, 2); -#define git__throw(error, ...) \ - (git___throw(__VA_ARGS__), error) - -extern void git___rethrow(const char *, ...) GIT_FORMAT_PRINTF(1, 2); -#define git__rethrow(error, ...) \ - (git___rethrow(__VA_ARGS__), error) - - #define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; } void giterr_set_oom(void); @@ -68,5 +59,4 @@ void giterr_set_regex(const regex_t *regex, int error_code); #include "util.h" - #endif /* INCLUDE_common_h__ */ diff --git a/src/global.h b/src/global.h index 2b525ce07..6e7373fa3 100644 --- a/src/global.h +++ b/src/global.h @@ -10,10 +10,6 @@ #include "mwindow.h" typedef struct { - struct { - char last[1024]; - } error; - git_error *last_error; git_error error_t; From b8748c1217445a95d3b29b361b467eb66992f8a7 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 15 Jul 2012 00:46:26 +0200 Subject: [PATCH 04/48] revparse: enhance parsing engine --- src/repository.h | 7 + src/revparse.c | 1099 ++++++++++++++++++++++++++-------------------- 2 files changed, 632 insertions(+), 474 deletions(-) diff --git a/src/repository.h b/src/repository.h index 91c69a655..4e03e632b 100644 --- a/src/repository.h +++ b/src/repository.h @@ -98,6 +98,13 @@ struct git_repository { * export */ void git_object__free(void *object); +GIT_INLINE(int) git_object__dup(git_object **dest, git_object *source) +{ + git_cached_obj_incref(source); + *dest = source; + return 0; +} + int git_object__resolve_to_type(git_object **obj, git_otype type); int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); diff --git a/src/revparse.c b/src/revparse.c index 777dee685..270129ceb 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -13,48 +13,12 @@ #include "git2.h" -typedef enum { - REVPARSE_STATE_INIT, - REVPARSE_STATE_CARET, - REVPARSE_STATE_LINEAR, - REVPARSE_STATE_COLON, - REVPARSE_STATE_DONE, -} revparse_state; - static int revspec_error(const char *revspec) { giterr_set(GITERR_INVALID, "Failed to parse revision specifier - Invalid pattern '%s'", revspec); return -1; } -static int revparse_lookup_fully_qualifed_ref(git_object **out, git_repository *repo, const char*spec) -{ - git_oid resolved; - int error; - - if ((error = git_reference_name_to_oid(&resolved, repo, spec)) < 0) - return error; - - return git_object_lookup(out, repo, &resolved, GIT_OBJ_ANY); -} - -/* Returns non-zero if yes */ -static int spec_looks_like_describe_output(const char *spec) -{ - regex_t regex; - int regex_error, retcode; - - regex_error = regcomp(®ex, ".+-[0-9]+-g[0-9a-fA-F]+", REG_EXTENDED); - if (regex_error != 0) { - giterr_set_regex(®ex, regex_error); - return regex_error; - } - - retcode = regexec(®ex, spec, 0, NULL, 0); - regfree(®ex); - return retcode == 0; -} - static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname) { int error, i; @@ -75,7 +39,7 @@ static int disambiguate_refname(git_reference **out, git_repository *repo, const if (*refname) git_buf_puts(&name, refname); else { - git_buf_puts(&name, "HEAD"); + git_buf_puts(&name, GIT_HEAD_FILE); fallbackmode = false; } @@ -115,21 +79,43 @@ static int maybe_sha_or_abbrev(git_object**out, git_repository *repo, const char return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY); } +static int build_regex(regex_t *regex, const char *pattern) +{ + int error; + + if (*pattern == '\0') { + giterr_set(GITERR_REGEX, "Empty pattern"); + return -1; + } + + error = regcomp(regex, pattern, REG_EXTENDED); + if (!error) + return 0; + + giterr_set_regex(regex, error); + regfree(regex); + + return -1; +} + static int maybe_describe(git_object**out, git_repository *repo, const char *spec) { const char *substr; - int match; + int error; + regex_t regex; - /* "git describe" output; snip everything before/including "-g" */ substr = strstr(spec, "-g"); if (substr == NULL) return GIT_ENOTFOUND; - if ((match = spec_looks_like_describe_output(spec)) < 0) - return match; + if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0) + return -1; - if (!match) + error = regexec(®ex, spec, 0, NULL, 0); + regfree(®ex); + + if (error) return GIT_ENOTFOUND; return maybe_sha_or_abbrev(out, repo, substr+2); @@ -168,373 +154,359 @@ static int revparse_lookup_object(git_object **out, git_repository *repo, const return GIT_ENOTFOUND; } -static int all_chars_are_digits(const char *str, size_t len) +static int try_parse_numeric(int *n, const char *curly_braces_content) { - size_t i = 0; + int content; + const char *end_ptr; - for (i = 0; i < len; i++) - if (!git__isdigit(str[i])) return 0; + if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0) + return -1; - return 1; + if (*end_ptr != '\0') + return -1; + + *n = content; + return 0; } -static int walk_ref_history(git_object **out, git_repository *repo, const char *refspec, const char *reflogspec) +static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *spec, const char *identifier, unsigned int position) { - git_reference *disambiguated = NULL; + git_reference *ref = NULL; git_reflog *reflog = NULL; - int n, retcode = GIT_ERROR; - int i, refloglen; + regex_t preg; + int numentries, i, cur, error = -1; const git_reflog_entry *entry; + const char *msg; + regmatch_t regexmatches[2]; git_buf buf = GIT_BUF_INIT; - size_t refspeclen = strlen(refspec); - size_t reflogspeclen = strlen(reflogspec); - if (git__prefixcmp(reflogspec, "@{") != 0 || - git__suffixcmp(reflogspec, "}") != 0) - return revspec_error(reflogspec); + cur = position; - /* "@{-N}" form means walk back N checkouts. That means the HEAD log. */ - if (!git__prefixcmp(reflogspec, "@{-")) { - regex_t regex; - int regex_error; + if (*identifier != '\0' || *base_ref != NULL) + return revspec_error(spec); - if (refspeclen > 0) - return revspec_error(reflogspec); + if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0) + return -1; - if (git__strtol32(&n, reflogspec+3, NULL, 10) < 0 || n < 1) - return revspec_error(reflogspec); + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + goto cleanup; - if (!git_reference_lookup(&disambiguated, repo, "HEAD")) { - if (!git_reflog_read(&reflog, disambiguated)) { - regex_error = regcomp(®ex, "checkout: moving from (.*) to .*", REG_EXTENDED); - if (regex_error != 0) { - giterr_set_regex(®ex, regex_error); - } else { - regmatch_t regexmatches[2]; + if (git_reflog_read(&reflog, ref) < 0) + goto cleanup; - retcode = GIT_ENOTFOUND; + numentries = git_reflog_entrycount(reflog); - refloglen = git_reflog_entrycount(reflog); - for (i=refloglen-1; i >= 0; i--) { - const char *msg; - entry = git_reflog_entry_byindex(reflog, i); + for (i = numentries - 1; i >= 0; i--) { + entry = git_reflog_entry_byindex(reflog, i); + msg = git_reflog_entry_msg(entry); + + if (regexec(&preg, msg, 2, regexmatches, 0)) + continue; - msg = git_reflog_entry_msg(entry); - if (!regexec(®ex, msg, 2, regexmatches, 0)) { - n--; - if (!n) { - git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so); - retcode = revparse_lookup_object(out, repo, git_buf_cstr(&buf)); - break; - } - } - } - regfree(®ex); - } - } - } - } else { - int date_error = 0, result; - git_time_t timestamp; - git_buf datebuf = GIT_BUF_INIT; + cur--; - result = disambiguate_refname(&disambiguated, repo, refspec); + if (cur > 0) + continue; + + git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so); - if (result < 0) { - retcode = result; + if ((error = disambiguate_refname(base_ref, repo, git_buf_cstr(&buf))) == 0) + goto cleanup; + + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + error = maybe_sha_or_abbrev(out, repo, git_buf_cstr(&buf)); + + goto cleanup; + } + + error = GIT_ENOTFOUND; + +cleanup: + git_reference_free(ref); + git_buf_free(&buf); + regfree(&preg); + git_reflog_free(reflog); + return error; +} + +static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, unsigned int identifier) +{ + git_reflog *reflog; + int error = -1; + unsigned int numentries; + const git_reflog_entry *entry; + bool search_by_pos = (identifier <= 100000000); + + if (git_reflog_read(&reflog, ref) < 0) + return -1; + + numentries = git_reflog_entrycount(reflog); + + if (search_by_pos) { + if (numentries < identifier + 1) { + giterr_set( + GITERR_REFERENCE, + "Reflog for '%s' has only %d entries, asked for %d", + git_reference_name(ref), + numentries, + identifier); + + error = GIT_ENOTFOUND; goto cleanup; } - git_buf_put(&datebuf, reflogspec+2, reflogspeclen-3); - date_error = git__date_parse(×tamp, git_buf_cstr(&datebuf)); + entry = git_reflog_entry_byindex(reflog, identifier); + git_oid_cpy(oid, git_reflog_entry_oidold(entry)); + error = 0; + goto cleanup; - /* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */ - if (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}")) { - git_reference *tracking; - - if (!(retcode = git_reference_remote_tracking_from_branch(&tracking, disambiguated))) { - retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_reference_name(tracking)); - git_reference_free(tracking); - } + } else { + int i; + git_time commit_time; + + for (i = numentries - 1; i >= 0; i--) { + entry = git_reflog_entry_byindex(reflog, i); + commit_time = git_reflog_entry_committer(entry)->when; + + if (commit_time.time - identifier > 0) + continue; + + git_oid_cpy(oid, git_reflog_entry_oidnew(entry)); + error = 0; + goto cleanup; } - /* @{N} -> Nth prior value for the ref (from reflog) */ - else if (all_chars_are_digits(reflogspec+2, reflogspeclen-3) && - !git__strtol32(&n, reflogspec+2, NULL, 10) && - n <= 100000000) { /* Allow integer time */ - - git_buf_puts(&buf, git_reference_name(disambiguated)); - - if (n == 0) - retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf)); - else if (!git_reflog_read(&reflog, disambiguated)) { - int numentries = git_reflog_entrycount(reflog); - if (numentries < n + 1) { - giterr_set(GITERR_REFERENCE, "Reflog for '%s' has only %d entries, asked for %d", - git_buf_cstr(&buf), numentries, n); - retcode = GIT_ENOTFOUND; - } else { - const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, n); - const git_oid *oid = git_reflog_entry_oidold(entry); - retcode = git_object_lookup(out, repo, oid, GIT_OBJ_ANY); - } - } - } - - else if (!date_error) { - /* Ref as it was on a certain date */ - git_reflog *reflog; - if (!git_reflog_read(&reflog, disambiguated)) { - /* Keep walking until we find an entry older than the given date */ - int numentries = git_reflog_entrycount(reflog); - int i; - - for (i = numentries - 1; i >= 0; i--) { - const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, i); - git_time commit_time = git_reflog_entry_committer(entry)->when; - if (commit_time.time - timestamp <= 0) { - retcode = git_object_lookup(out, repo, git_reflog_entry_oidnew(entry), GIT_OBJ_ANY); - break; - } - } - - if (i == -1) { - /* Didn't find a match */ - retcode = GIT_ENOTFOUND; - } - - git_reflog_free(reflog); - } - } - - git_buf_free(&datebuf); + error = GIT_ENOTFOUND; } cleanup: - if (reflog) - git_reflog_free(reflog); - git_buf_free(&buf); - git_reference_free(disambiguated); - return retcode; + git_reflog_free(reflog); + return error; } -static git_object* dereference_object(git_object *obj) +static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, unsigned int position) +{ + git_reference *ref; + git_oid oid; + int error = -1; + + if (*base_ref == NULL) { + if ((error = disambiguate_refname(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; + } + + if (position == 0) { + error = git_object_lookup(out, repo, git_reference_oid(ref), GIT_OBJ_ANY); + goto cleanup; + } + + if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0) + goto cleanup; + + error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY); + +cleanup: + git_reference_free(ref); + return error; +} + +static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo) +{ + git_reference *tracking, *ref; + int error = -1; + + if (*base_ref == NULL) { + if ((error = disambiguate_refname(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; + } + + if ((error = git_reference_remote_tracking_from_branch(&tracking, ref)) < 0) + goto cleanup; + + *base_ref = tracking; + +cleanup: + git_reference_free(ref); + return error; +} + +static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, int identifier_len, git_repository* repo, const char *curly_braces_content) +{ + bool is_numeric; + int parsed, error = -1; + git_buf identifier = GIT_BUF_INIT; + git_time_t timestamp; + + assert(*out == NULL); + + if (git_buf_put(&identifier, spec, identifier_len) < 0) + return -1; + + is_numeric = !try_parse_numeric(&parsed, curly_braces_content); + + if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) { + error = revspec_error(spec); + goto cleanup; + } + + if (is_numeric) { + if (parsed < 0) + error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, spec, git_buf_cstr(&identifier), -parsed); + else + error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed); + + goto cleanup; + } + + if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) { + error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo); + + goto cleanup; + } + + if (git__date_parse(×tamp, curly_braces_content) < 0) + goto cleanup; + + error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (unsigned int)timestamp); + +cleanup: + git_buf_free(&identifier); + return error; +} + +static int dereference_object(git_object **dereferenced, git_object *obj) { git_otype type = git_object_type(obj); switch (type) { case GIT_OBJ_COMMIT: - { - git_tree *tree = NULL; - if (0 == git_commit_tree(&tree, (git_commit*)obj)) { - return (git_object*)tree; - } - } + return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); break; + case GIT_OBJ_TAG: - { - git_object *newobj = NULL; - if (0 == git_tag_target(&newobj, (git_tag*)obj)) { - return newobj; - } - } + return git_tag_target(dereferenced, (git_tag*)obj); break; default: - case GIT_OBJ_TREE: - case GIT_OBJ_BLOB: - case GIT_OBJ_OFS_DELTA: - case GIT_OBJ_REF_DELTA: + return GIT_ENOTFOUND; break; } - - /* Can't dereference some types */ - return NULL; } static int dereference_to_type(git_object **out, git_object *obj, git_otype target_type) { - int retcode = 1; - git_object *obj1 = obj, *obj2 = obj; + git_object *source, *deref = NULL; - while (retcode > 0) { - git_otype this_type = git_object_type(obj1); + if (git_object_type(obj) == target_type) + return git_object_lookup(out, git_object_owner(obj), git_object_id(obj), target_type); - if (this_type == target_type) { - *out = obj1; - retcode = 0; - } else { - /* Dereference once, if possible. */ - obj2 = dereference_object(obj1); - if (!obj2) { - giterr_set(GITERR_REFERENCE, "Can't dereference to type"); - retcode = GIT_ERROR; - } + source = obj; + + while (true) { + if (dereference_object(&deref, source) < 0) + goto cleanup; + + if (source != obj) + git_object_free(source); + + if (git_object_type(deref) == target_type) { + *out = deref; + return 0; } - if (obj1 != obj && obj1 != obj2) { - git_object_free(obj1); - } - obj1 = obj2; + + source = deref; + deref = NULL; } - return retcode; + +cleanup: + if (source != obj) + git_object_free(source); + git_object_free(deref); + return -1; } static git_otype parse_obj_type(const char *str) { - if (!strcmp(str, "{commit}")) return GIT_OBJ_COMMIT; - if (!strcmp(str, "{tree}")) return GIT_OBJ_TREE; - if (!strcmp(str, "{blob}")) return GIT_OBJ_BLOB; - if (!strcmp(str, "{tag}")) return GIT_OBJ_TAG; + if (!strcmp(str, "commit")) + return GIT_OBJ_COMMIT; + + if (!strcmp(str, "tree")) + return GIT_OBJ_TREE; + + if (!strcmp(str, "blob")) + return GIT_OBJ_BLOB; + + if (!strcmp(str, "tag")) + return GIT_OBJ_TAG; + return GIT_OBJ_BAD; } -static int handle_caret_syntax(git_object **out, git_repository *repo, git_object *obj, const char *movement) +static int dereference_to_non_tag(git_object **out, git_object *obj) { - git_commit *commit; - size_t movementlen = strlen(movement); - int n; + if (git_object_type(obj) == GIT_OBJ_TAG) + return git_tag_peel(out, (git_tag *)obj); - if (*movement == '{') { - if (movement[movementlen-1] != '}') - return revspec_error(movement); + return git_object__dup(out, obj); +} - /* {} -> Dereference until we reach an object that isn't a tag. */ - if (movementlen == 2) { - git_object *newobj = obj; - git_object *newobj2 = newobj; - while (git_object_type(newobj2) == GIT_OBJ_TAG) { - newobj2 = dereference_object(newobj); - if (newobj != obj) git_object_free(newobj); - if (!newobj2) { - giterr_set(GITERR_REFERENCE, "Couldn't find object of target type."); - return GIT_ERROR; - } - newobj = newobj2; - } - *out = newobj2; - return 0; - } +static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n) +{ + git_object *temp_commit = NULL; + int error; - /* {/...} -> Walk all commits until we see a commit msg that matches the phrase. */ - if (movement[1] == '/') { - int retcode = GIT_ERROR; - git_revwalk *walk; - if (!git_revwalk_new(&walk, repo)) { - git_oid oid; - regex_t preg; - int reg_error; - git_buf buf = GIT_BUF_INIT; + if (dereference_to_type(&temp_commit, obj, GIT_OBJ_COMMIT) < 0) + return -1; - git_revwalk_sorting(walk, GIT_SORT_TIME); - git_revwalk_push(walk, git_object_id(obj)); - - /* Extract the regex from the movement string */ - git_buf_put(&buf, movement+2, strlen(movement)-3); - - reg_error = regcomp(&preg, git_buf_cstr(&buf), REG_EXTENDED); - if (reg_error != 0) { - giterr_set_regex(&preg, reg_error); - } else { - while(!git_revwalk_next(&oid, walk)) { - git_object *walkobj; - - /* Fetch the commit object, and check for matches in the message */ - if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) { - if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) { - /* Found it! */ - retcode = 0; - *out = walkobj; - if (obj == walkobj) { - /* Avoid leaking an object */ - git_object_free(walkobj); - } - break; - } - git_object_free(walkobj); - } - } - if (retcode < 0) { - giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", movement); - } - regfree(&preg); - } - - git_buf_free(&buf); - git_revwalk_free(walk); - } - return retcode; - } - - /* {...} -> Dereference until we reach an object of a certain type. */ - if (dereference_to_type(out, obj, parse_obj_type(movement)) < 0) { - return GIT_ERROR; - } - return 0; - } - - /* Dereference until we reach a commit. */ - if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { - /* Can't dereference to a commit; fail */ - return GIT_ERROR; - } - - /* "^" is the same as "^1" */ - if (movementlen == 0) { - n = 1; - } else { - git__strtol32(&n, movement, NULL, 10); - } - commit = (git_commit*)obj; - - /* "^0" just returns the input */ if (n == 0) { - *out = obj; + *out = temp_commit; return 0; } - if (git_commit_parent(&commit, commit, n-1) < 0) { - return GIT_ENOTFOUND; - } + error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1); - *out = (git_object*)commit; - return 0; + git_object_free(temp_commit); + return error; } -static int handle_linear_syntax(git_object **out, git_object *obj, const char *movement) +static int handle_linear_syntax(git_object **out, git_object *obj, int n) { - int n; + git_object *temp_commit = NULL; + int error; - /* Dereference until we reach a commit. */ - if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { - /* Can't dereference to a commit; fail */ - return GIT_ERROR; - } + if (dereference_to_type(&temp_commit, obj, GIT_OBJ_COMMIT) < 0) + return -1; - /* "~" is the same as "~1" */ - if (*movement == '\0') { - n = 1; - } else if (git__strtol32(&n, movement, NULL, 10) < 0) { - return GIT_ERROR; - } + error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n); - return git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)obj, n); + git_object_free(temp_commit); + return error; } -static int handle_colon_syntax(git_object **out, - git_repository *repo, +static int handle_colon_syntax( + git_object **out, git_object *obj, const char *path) { - git_object *tree = obj; + git_object *tree; int error = -1; git_tree_entry *entry = NULL; - /* Dereference until we reach a tree. */ if (dereference_to_type(&tree, obj, GIT_OBJ_TREE) < 0) - return GIT_ERROR; + return -1; - if (*path == '\0') - return git_object_lookup(out, repo, git_object_id(tree), GIT_OBJ_TREE); + if (*path == '\0') { + *out = tree; + return 0; + } /* * TODO: Handle the relative path syntax @@ -543,188 +515,367 @@ static int handle_colon_syntax(git_object **out, if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0) goto cleanup; - error = git_tree_entry_to_object(out, repo, entry); + error = git_tree_entry_to_object(out, git_object_owner(tree), entry); cleanup: git_tree_entry_free(entry); - if (tree != obj) - git_object_free(tree); + git_object_free(tree); return error; } -static int revparse_global_grep(git_object **out, git_repository *repo, const char *pattern) +static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex) { - git_revwalk *walk; - int retcode = GIT_ERROR; + int error; + git_oid oid; + git_object *obj; + + while (!(error = git_revwalk_next(&oid, walk))) { - if (!pattern[0]) { - giterr_set(GITERR_REGEX, "Empty pattern"); - return GIT_ERROR; - } + if ((error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT) < 0) && + (error != GIT_ENOTFOUND)) + return -1; - if (!git_revwalk_new(&walk, repo)) { - regex_t preg; - int reg_error; - git_oid oid; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - git_revwalk_push_glob(walk, "refs/heads/*"); - - reg_error = regcomp(&preg, pattern, REG_EXTENDED); - if (reg_error != 0) { - giterr_set_regex(&preg, reg_error); - } else { - git_object *walkobj = NULL, *resultobj = NULL; - while(!git_revwalk_next(&oid, walk)) { - /* Fetch the commit object, and check for matches in the message */ - if (walkobj != resultobj) git_object_free(walkobj); - if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) { - if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) { - /* Match! */ - resultobj = walkobj; - retcode = 0; - break; - } - } - } - if (!resultobj) { - giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", pattern); - retcode = GIT_ENOTFOUND; - git_object_free(walkobj); - } else { - *out = resultobj; - } - regfree(&preg); - git_revwalk_free(walk); + if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) { + *out = obj; + return 0; } + + git_object_free(obj); } - return retcode; + if (error < 0 && error == GIT_REVWALKOVER) + error = GIT_ENOTFOUND; + + return error; +} + +static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern) +{ + regex_t preg; + git_revwalk *walk = NULL; + int error = -1; + + if (build_regex(&preg, pattern) < 0) + return -1; + + if (git_revwalk_new(&walk, repo) < 0) + goto cleanup; + + git_revwalk_sorting(walk, GIT_SORT_TIME); + + if (spec_oid == NULL) { + // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails + if (git_revwalk_push_glob(walk, "refs/heads/*") < 0) + goto cleanup; + } else if (git_revwalk_push(walk, spec_oid) < 0) + goto cleanup; + + error = walk_and_search(out, walk, &preg); + +cleanup: + regfree(&preg); + git_revwalk_free(walk); + + return error; +} + +static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content) +{ + git_otype expected_type; + + if (*curly_braces_content == '\0') + return dereference_to_non_tag(out, obj); + + if (*curly_braces_content == '/') + return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1); + + expected_type = parse_obj_type(curly_braces_content); + + if (expected_type == GIT_OBJ_BAD) + return -1; + + return dereference_to_type(out, obj, expected_type); +} + +static int extract_curly_braces_content(git_buf *buf, const char *spec, int *pos) +{ + git_buf_clear(buf); + + assert(spec[*pos] == '^' || spec[*pos] == '@'); + + (*pos)++; + + if (spec[*pos] == '\0' || spec[*pos] != '{') + return revspec_error(spec); + + (*pos)++; + + while (spec[*pos] != '}') { + if (spec[*pos] == '\0') + return revspec_error(spec); + + git_buf_putc(buf, spec[(*pos)++]); + } + + (*pos)++; + + return 0; +} + +static int extract_path(git_buf *buf, const char *spec, int *pos) +{ + git_buf_clear(buf); + + assert(spec[*pos] == ':'); + + (*pos)++; + + if (git_buf_puts(buf, spec + *pos) < 0) + return -1; + + *pos += git_buf_len(buf); + + return 0; +} + +static int extract_how_many(int *n, const char *spec, int *pos) +{ + const char *end_ptr; + int parsed, accumulated; + char kind = spec[*pos]; + + assert(spec[*pos] == '^' || spec[*pos] == '~'); + + accumulated = 0; + + do { + do { + (*pos)++; + accumulated++; + } while (spec[(*pos)] == kind && kind == '~'); + + if (git__isdigit(spec[*pos])) { + if ((git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0) < 0) + return revspec_error(spec); + + accumulated += (parsed - 1); + *pos = end_ptr - spec; + } + + } while (spec[(*pos)] == kind && kind == '~'); + + *n = accumulated; + + return 0; +} + +static int object_from_reference(git_object **object, git_reference *reference) +{ + git_reference *resolved = NULL; + int error; + + if (git_reference_resolve(&resolved, reference) < 0) + return -1; + + error = git_object_lookup(object, reference->owner, git_reference_oid(resolved), GIT_OBJ_ANY); + git_reference_free(resolved); + + return error; +} + +static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, int identifier_len, git_repository *repo, bool allow_empty_identifier) +{ + int error; + git_buf identifier = GIT_BUF_INIT; + + if (*object != NULL) + return 0; + + if (*reference != NULL) { + if ((error = object_from_reference(object, *reference)) < 0) + return error; + + git_reference_free(*reference); + *reference = NULL; + return 0; + } + + if (!allow_empty_identifier && identifier_len == 0) + return revspec_error(spec); + + if (git_buf_put(&identifier, spec, identifier_len) < 0) + return -1; + + error = revparse_lookup_object(object, repo, git_buf_cstr(&identifier)); + git_buf_free(&identifier); + + return error; +} + +static int ensure_base_rev_is_not_known_yet(git_object *object, const char *spec) +{ + if (object == NULL) + return 0; + + return revspec_error(spec); +} + +static bool any_left_hand_identifier(git_object *object, git_reference *reference, int identifier_len) +{ + if (object != NULL) + return true; + + if (reference != NULL) + return true; + + if (identifier_len > 0) + return true; + + return false; +} + +static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference, const char *spec) +{ + if (!ensure_base_rev_is_not_known_yet(object, spec) && reference == NULL) + return 0; + + return revspec_error(spec); } int git_revparse_single(git_object **out, git_repository *repo, const char *spec) { - revparse_state current_state = REVPARSE_STATE_INIT, next_state = REVPARSE_STATE_INIT; - const char *spec_cur = spec; - git_object *cur_obj = NULL, *next_obj = NULL; - git_buf specbuffer = GIT_BUF_INIT, stepbuffer = GIT_BUF_INIT; - int retcode = 0; + int pos = 0, identifier_len = 0; + int error = -1, n; + git_buf buf = GIT_BUF_INIT; + + git_reference *reference = NULL; + git_object *base_rev = NULL; assert(out && repo && spec); - if (spec[0] == ':') { - if (spec[1] == '/') { - return revparse_global_grep(out, repo, spec+2); - } - /* TODO: support merge-stage path lookup (":2:Makefile"). */ - giterr_set(GITERR_INVALID, "Unimplemented"); - return GIT_ERROR; - } + *out = NULL; - while (current_state != REVPARSE_STATE_DONE) { - switch (current_state) { - case REVPARSE_STATE_INIT: - if (!*spec_cur) { - /* No operators, just a name. Find it and return. */ - retcode = revparse_lookup_object(out, repo, spec); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '@') { - /* '@' syntax doesn't allow chaining */ - git_buf_puts(&stepbuffer, spec_cur); - retcode = walk_ref_history(out, repo, git_buf_cstr(&specbuffer), git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '^') { - next_state = REVPARSE_STATE_CARET; - } else if (*spec_cur == '~') { - next_state = REVPARSE_STATE_LINEAR; - } else if (*spec_cur == ':') { - next_state = REVPARSE_STATE_COLON; + do { + switch (spec[pos]) { + case '^': + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if (spec[pos+1] == '{') { + git_object *temp_object = NULL; + + if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) + goto cleanup; + + if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; } else { - git_buf_putc(&specbuffer, *spec_cur); - } - spec_cur++; + git_object *temp_object = NULL; - if (current_state != next_state && next_state != REVPARSE_STATE_DONE) { - /* Leaving INIT state, find the object specified, in case that state needs it */ - if ((retcode = revparse_lookup_object(&next_obj, repo, git_buf_cstr(&specbuffer))) < 0) - next_state = REVPARSE_STATE_DONE; + if ((error = extract_how_many(&n, spec, &pos)) < 0) + goto cleanup; + + if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; } break; + case '~': + { + git_object *temp_object = NULL; - case REVPARSE_STATE_CARET: - /* Gather characters until NULL, '~', or '^' */ - if (!*spec_cur) { - retcode = handle_caret_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '~') { - retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - next_state = !retcode ? REVPARSE_STATE_LINEAR : REVPARSE_STATE_DONE; - } else if (*spec_cur == '^') { - retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - if (retcode < 0) { - next_state = REVPARSE_STATE_DONE; + if ((error = extract_how_many(&n, spec, &pos)) < 0) + goto cleanup; + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; + break; + } + + case ':': + { + git_object *temp_object = NULL; + + if ((error = extract_path(&buf, spec, &pos)) < 0) + goto cleanup; + + if (any_left_hand_identifier(base_rev, reference, identifier_len)) { + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0) + goto cleanup; + + if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0) + goto cleanup; + } else { + if (*git_buf_cstr(&buf) == '/') { + if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0) + goto cleanup; + } else { + + /* + * TODO: support merge-stage path lookup (":2:Makefile") + * and plain index blob lookup (:i-am/a/blob) + */ + giterr_set(GITERR_INVALID, "Unimplemented"); + error = GIT_ERROR; + goto cleanup; } - } else if (*spec_cur == ':') { - retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - next_state = !retcode ? REVPARSE_STATE_COLON : REVPARSE_STATE_DONE; - } else { - git_buf_putc(&stepbuffer, *spec_cur); } - spec_cur++; - break; - case REVPARSE_STATE_LINEAR: - if (!*spec_cur) { - retcode = handle_linear_syntax(out, cur_obj, git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '~') { - retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - if (retcode < 0) { - next_state = REVPARSE_STATE_DONE; - } - } else if (*spec_cur == '^') { - retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - next_state = !retcode ? REVPARSE_STATE_CARET : REVPARSE_STATE_DONE; - } else { - git_buf_putc(&stepbuffer, *spec_cur); - } - spec_cur++; - break; - - case REVPARSE_STATE_COLON: - if (*spec_cur) { - git_buf_putc(&stepbuffer, *spec_cur); - } else { - retcode = handle_colon_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } - spec_cur++; - break; - - case REVPARSE_STATE_DONE: - if (cur_obj && *out != cur_obj) git_object_free(cur_obj); - if (next_obj && *out != next_obj) git_object_free(next_obj); + git_object_free(base_rev); + base_rev = temp_object; break; } - current_state = next_state; - if (cur_obj != next_obj) { - if (cur_obj) git_object_free(cur_obj); - cur_obj = next_obj; + case '@': + { + git_object *temp_object = NULL; + + if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) + goto cleanup; + + if ((error = ensure_base_rev_is_not_known_yet(base_rev, spec)) < 0) + goto cleanup; + + if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0) + goto cleanup; + + if (temp_object != NULL) + base_rev = temp_object; + break; } - } - if (*out != cur_obj) git_object_free(cur_obj); - if (*out != next_obj && next_obj != cur_obj) git_object_free(next_obj); + default: + if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference, spec)) < 0) + goto cleanup; - git_buf_free(&specbuffer); - git_buf_free(&stepbuffer); - return retcode; + pos++; + identifier_len++; + } + } while (spec[pos]); + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0) + goto cleanup; + + *out = base_rev; + error = 0; + +cleanup: + if (error) + git_object_free(base_rev); + git_reference_free(reference); + git_buf_free(&buf); + return error; } From db9be9457d74a683916f107b39cad05a347b4c2c Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 15 Jul 2012 11:06:15 +0200 Subject: [PATCH 05/48] object: introduce git_object_peel() Partially fix #530 --- include/git2/object.h | 17 +++++++++ src/object.c | 69 +++++++++++++++++++++++++++++++++++ tests-clar/object/peel.c | 79 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 tests-clar/object/peel.c diff --git a/include/git2/object.h b/include/git2/object.h index 414325121..d9e653fd4 100644 --- a/include/git2/object.h +++ b/include/git2/object.h @@ -167,6 +167,23 @@ GIT_EXTERN(int) git_object_typeisloose(git_otype type); */ GIT_EXTERN(size_t) git_object__size(git_otype type); +/** + * Recursively peel an object until an object of the specified + * type is met + * + * The retrieved `peeled` object is owned by the repository + * and should be closed with the `git_object_free` method. + * + * @param peeled Pointer to the peeled git_object + * @param object The object to be processed + * @param target_type The type of the requested object + * @return 0 or an error code + */ +GIT_EXTERN(int) git_object_peel( + git_object **peeled, + git_object *object, + git_otype target_type); + /** @} */ GIT_END_DECL diff --git a/src/object.c b/src/object.c index 14d64befe..3ff894212 100644 --- a/src/object.c +++ b/src/object.c @@ -333,3 +333,72 @@ int git_object__resolve_to_type(git_object **obj, git_otype type) *obj = scan; return error; } + +static int dereference_object(git_object **dereferenced, git_object *obj) +{ + git_otype type = git_object_type(obj); + + switch (type) { + case GIT_OBJ_COMMIT: + return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); + break; + + case GIT_OBJ_TAG: + return git_tag_target(dereferenced, (git_tag*)obj); + break; + + default: + return GIT_ENOTFOUND; + break; + } +} + +static int peel_error(int error, const char* msg) +{ + giterr_set(GITERR_INVALID, "The given object cannot be peeled - %s", msg); + return error; +} + +int git_object_peel( + git_object **peeled, + git_object *object, + git_otype target_type) +{ + git_object *source, *deref = NULL; + + assert(object); + + if (git_object_type(object) == target_type) + return git_object__dup(peeled, object); + + if (target_type == GIT_OBJ_BLOB + || target_type == GIT_OBJ_ANY) + return peel_error(GIT_EAMBIGUOUS, "Ambiguous target type"); + + if (git_object_type(object) == GIT_OBJ_BLOB) + return peel_error(GIT_ERROR, "A blob cannot be dereferenced"); + + source = object; + + while (true) { + if (dereference_object(&deref, source) < 0) + goto cleanup; + + if (source != object) + git_object_free(source); + + if (git_object_type(deref) == target_type) { + *peeled = deref; + return 0; + } + + source = deref; + deref = NULL; + } + +cleanup: + if (source != object) + git_object_free(source); + git_object_free(deref); + return -1; +} diff --git a/tests-clar/object/peel.c b/tests-clar/object/peel.c new file mode 100644 index 000000000..f6d2a776f --- /dev/null +++ b/tests-clar/object/peel.c @@ -0,0 +1,79 @@ +#include "clar_libgit2.h" + +static git_repository *g_repo; + +void test_object_peel__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); +} + +void test_object_peel__cleanup(void) +{ + git_repository_free(g_repo); +} + +static void assert_peel(const char* expected_sha, const char *sha, git_otype requested_type) +{ + git_oid oid, expected_oid; + git_object *obj; + git_object *peeled; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_object_peel(&peeled, obj, requested_type)); + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); + cl_assert_equal_i(0, git_oid_cmp(&expected_oid, git_object_id(peeled))); + + git_object_free(peeled); + git_object_free(obj); +} + +static void assert_peel_error(int error, const char *sha, git_otype requested_type) +{ + git_oid oid; + git_object *obj; + git_object *peeled; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_assert_equal_i(error, git_object_peel(&peeled, obj, requested_type)); + + git_object_free(obj); +} + +void test_object_peel__peeling_an_object_into_its_own_type_returns_another_instance_of_it(void) +{ + assert_peel("e90810b8df3e80c413d903f631643c716887138d", "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG); + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); + assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB); +} + +void test_object_peel__can_peel_a_tag(void) +{ + assert_peel("e90810b8df3e80c413d903f631643c716887138d", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_COMMIT); + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TREE); +} + +void test_object_peel__can_peel_a_commit(void) +{ + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_TREE); +} + +void test_object_peel__cannot_peel_a_tree(void) +{ + assert_peel_error(GIT_EAMBIGUOUS, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_BLOB); +} + +void test_object_peel__cannot_peel_a_blob(void) +{ + assert_peel_error(GIT_ERROR, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_COMMIT); +} + +void test_object_peel__cannot_target_any_object(void) +{ + assert_peel_error(GIT_EAMBIGUOUS, "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_ANY); +} From e2c81fca8f41cd8a4f7c908375a738320474fd9d Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 15 Jul 2012 11:59:31 +0200 Subject: [PATCH 06/48] revparse: deploy git_object_peel() --- src/revparse.c | 59 ++++---------------------------------------------- 1 file changed, 4 insertions(+), 55 deletions(-) diff --git a/src/revparse.c b/src/revparse.c index 270129ceb..6cfea0ca8 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -382,57 +382,6 @@ cleanup: return error; } -static int dereference_object(git_object **dereferenced, git_object *obj) -{ - git_otype type = git_object_type(obj); - - switch (type) { - case GIT_OBJ_COMMIT: - return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); - break; - - case GIT_OBJ_TAG: - return git_tag_target(dereferenced, (git_tag*)obj); - break; - - default: - return GIT_ENOTFOUND; - break; - } -} - -static int dereference_to_type(git_object **out, git_object *obj, git_otype target_type) -{ - git_object *source, *deref = NULL; - - if (git_object_type(obj) == target_type) - return git_object_lookup(out, git_object_owner(obj), git_object_id(obj), target_type); - - source = obj; - - while (true) { - if (dereference_object(&deref, source) < 0) - goto cleanup; - - if (source != obj) - git_object_free(source); - - if (git_object_type(deref) == target_type) { - *out = deref; - return 0; - } - - source = deref; - deref = NULL; - } - -cleanup: - if (source != obj) - git_object_free(source); - git_object_free(deref); - return -1; -} - static git_otype parse_obj_type(const char *str) { if (!strcmp(str, "commit")) @@ -463,7 +412,7 @@ static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n) git_object *temp_commit = NULL; int error; - if (dereference_to_type(&temp_commit, obj, GIT_OBJ_COMMIT) < 0) + if (git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT) < 0) return -1; if (n == 0) { @@ -482,7 +431,7 @@ static int handle_linear_syntax(git_object **out, git_object *obj, int n) git_object *temp_commit = NULL; int error; - if (dereference_to_type(&temp_commit, obj, GIT_OBJ_COMMIT) < 0) + if (git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT) < 0) return -1; error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n); @@ -500,7 +449,7 @@ static int handle_colon_syntax( int error = -1; git_tree_entry *entry = NULL; - if (dereference_to_type(&tree, obj, GIT_OBJ_TREE) < 0) + if (git_object_peel(&tree, obj, GIT_OBJ_TREE) < 0) return -1; if (*path == '\0') { @@ -595,7 +544,7 @@ static int handle_caret_curly_syntax(git_object **out, git_object *obj, const ch if (expected_type == GIT_OBJ_BAD) return -1; - return dereference_to_type(out, obj, expected_type); + return git_object_peel(out, obj, expected_type); } static int extract_curly_braces_content(git_buf *buf, const char *spec, int *pos) From c6f429535c011160dc60547e5147695f2107a260 Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Thu, 19 Jul 2012 17:33:48 +0200 Subject: [PATCH 07/48] tree: fix ordering for git_tree_walk Josh Triplett noticed libgit2 actually does preorder entries in tree_walk_post instead of postorder. Also, we continued walking even when an error occured in the callback. Fix #773; also, allow both pre- and postorder walking. --- src/tree.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/tree.c b/src/tree.c index 31a581cdb..9d793cbb8 100644 --- a/src/tree.c +++ b/src/tree.c @@ -759,11 +759,12 @@ int git_tree_entry_bypath( return error; } -static int tree_walk_post( +static int tree_walk( git_tree *tree, git_treewalk_cb callback, git_buf *path, - void *payload) + void *payload, + bool preorder) { int error = 0; unsigned int i; @@ -771,8 +772,8 @@ static int tree_walk_post( for (i = 0; i < tree->entries.length; ++i) { git_tree_entry *entry = tree->entries.contents[i]; - if (callback(path->ptr, entry, payload) < 0) - continue; + if (preorder && callback(path->ptr, entry, payload) < 0) + return -1; if (git_tree_entry__is_tree(entry)) { git_tree *subtree; @@ -789,12 +790,15 @@ static int tree_walk_post( if (git_buf_oom(path)) return -1; - if (tree_walk_post(subtree, callback, path, payload) < 0) + if (tree_walk(subtree, callback, path, payload, preorder) < 0) return -1; git_buf_truncate(path, path_len); git_tree_free(subtree); } + + if (!preorder && callback(path->ptr, entry, payload) < 0) + return -1; } return 0; @@ -807,12 +811,12 @@ int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payl switch (mode) { case GIT_TREEWALK_POST: - error = tree_walk_post(tree, callback, &root_path, payload); + error = tree_walk(tree, callback, &root_path, payload, false); break; case GIT_TREEWALK_PRE: - tree_error("Preorder tree walking is still not implemented"); - return -1; + error = tree_walk(tree, callback, &root_path, payload, true); + break; default: giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk"); From 71d273583755c0a2b7f5d608f017f4586add51e4 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 19 Jul 2012 10:23:45 -0700 Subject: [PATCH 08/48] Fix bug with merging diffs with null options A diff that is created with a NULL options parameter could result in a NULL prefix string, but diff merge was unconditionally strdup'ing it. I added a test to replicate the issue and then a new method that does the right thing with NULL values. --- src/diff.c | 4 ++-- src/pool.c | 5 +++++ src/pool.h | 7 ++++++ tests-clar/diff/tree.c | 48 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/diff.c b/src/diff.c index 4fea894f8..09f319e6e 100644 --- a/src/diff.c +++ b/src/diff.c @@ -814,9 +814,9 @@ int git_diff_merge( /* prefix strings also come from old pool, so recreate those.*/ onto->opts.old_prefix = - git_pool_strdup(&onto->pool, onto->opts.old_prefix); + git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix); onto->opts.new_prefix = - git_pool_strdup(&onto->pool, onto->opts.new_prefix); + git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); } git_vector_foreach(&onto_new, i, delta) diff --git a/src/pool.c b/src/pool.c index 63bf09cee..64b5c6b00 100644 --- a/src/pool.c +++ b/src/pool.c @@ -206,6 +206,11 @@ char *git_pool_strdup(git_pool *pool, const char *str) return git_pool_strndup(pool, str, strlen(str)); } +char *git_pool_strdup_safe(git_pool *pool, const char *str) +{ + return str ? git_pool_strdup(pool, str) : NULL; +} + char *git_pool_strcat(git_pool *pool, const char *a, const char *b) { void *ptr; diff --git a/src/pool.h b/src/pool.h index 54a2861ed..05d339244 100644 --- a/src/pool.h +++ b/src/pool.h @@ -89,6 +89,13 @@ extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); */ extern char *git_pool_strdup(git_pool *pool, const char *str); +/** + * Allocate space and duplicate a string into it, NULL is no error. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup_safe(git_pool *pool, const char *str); + /** * Allocate space for the concatenation of two strings. * diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c index 4201ad2a7..be9eb6c13 100644 --- a/tests-clar/diff/tree.c +++ b/tests-clar/diff/tree.c @@ -208,3 +208,51 @@ void test_diff_tree__bare(void) git_tree_free(a); git_tree_free(b); } + +void test_diff_tree__merge(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "605812a"; + const char *b_commit = "370fe9ec22"; + const char *c_commit = "f5b0af1fb4f5c"; + git_tree *a, *b, *c; + git_diff_list *diff1 = NULL, *diff2 = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("attr"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); + + cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, a, b, &diff1)); + + cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, c, b, &diff2)); + + git_tree_free(a); + git_tree_free(b); + git_tree_free(c); + + cl_git_pass(git_diff_merge(diff1, diff2)); + + git_diff_list_free(diff2); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff1, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 6); + cl_assert(exp.file_adds == 2); + cl_assert(exp.file_dels == 1); + cl_assert(exp.file_mods == 3); + + cl_assert(exp.hunks == 6); + + cl_assert(exp.lines == 59); + cl_assert(exp.line_ctxt == 1); + cl_assert(exp.line_adds == 36); + cl_assert(exp.line_dels == 22); + + git_diff_list_free(diff1); +} From 5d9cfa07ac62ad15ebb669b01e723a990450383e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 20 Jul 2012 17:52:53 +0200 Subject: [PATCH 09/48] config: escape subsection names when creating them This allows us to set options like "some.foo\\ish.var". This closes #830 --- src/config_file.c | 7 +++++-- tests-clar/config/stress.c | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/config_file.c b/src/config_file.c index 1f3ebfca9..7ced1e5ba 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -983,9 +983,12 @@ static int write_section(git_filebuf *file, const char *key) if (dot == NULL) { git_buf_puts(&buf, key); } else { + char *escaped; git_buf_put(&buf, key, dot - key); - /* TODO: escape */ - git_buf_printf(&buf, " \"%s\"", dot + 1); + escaped = escape_value(dot + 1); + GITERR_CHECK_ALLOC(escaped); + git_buf_printf(&buf, " \"%s\"", escaped); + git__free(escaped); } git_buf_puts(&buf, "]\n"); diff --git a/tests-clar/config/stress.c b/tests-clar/config/stress.c index 3de1f7692..8fbc8b97c 100644 --- a/tests-clar/config/stress.c +++ b/tests-clar/config/stress.c @@ -59,3 +59,25 @@ void test_config_stress__comments(void) git_config_free(config); } + +void test_config_stress__escape_subsection_names(void) +{ + struct git_config_file *file; + git_config *config; + const char *str; + + cl_assert(git_path_exists("git-test-config")); + cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); + cl_git_pass(git_config_new(&config)); + cl_git_pass(git_config_add_file(config, file, 0)); + + cl_git_pass(git_config_set_string(config, "some.sec\\tion.other", "foo")); + git_config_free(config); + + cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); + cl_git_pass(git_config_new(&config)); + cl_git_pass(git_config_add_file(config, file, 0)); + + cl_git_pass(git_config_get_string(&str, config, "some.sec\\tion.other")); + cl_assert(!strcmp("foo", str)); +} From 14e1bc157a06d4513ce4193e6100a338432b3c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 21 Jul 2012 17:54:56 +0200 Subject: [PATCH 10/48] tests: plug a leak in the config stress --- tests-clar/config/stress.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests-clar/config/stress.c b/tests-clar/config/stress.c index 8fbc8b97c..6e7db6e8f 100644 --- a/tests-clar/config/stress.c +++ b/tests-clar/config/stress.c @@ -80,4 +80,5 @@ void test_config_stress__escape_subsection_names(void) cl_git_pass(git_config_get_string(&str, config, "some.sec\\tion.other")); cl_assert(!strcmp("foo", str)); + git_config_free(config); } From b3aaa7a7c887006d38b7262b73575d40f51beca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 21 Jul 2012 17:52:51 +0200 Subject: [PATCH 11/48] Add a struct for network callbacks Currently only update_tips is used, but it prepares the way for progress output during download. --- examples/network/fetch.c | 11 +++++++++-- include/git2/remote.h | 35 ++++++++++++++++++++++++++++++++++- include/git2/types.h | 1 + src/remote.c | 13 ++++++++++--- src/remote.h | 1 + 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/examples/network/fetch.c b/examples/network/fetch.c index d2752124d..73bfbddd0 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -4,6 +4,7 @@ #include #include #include +#include struct dl_data { git_remote *remote; @@ -39,7 +40,7 @@ exit: pthread_exit(&data->ret); } -int update_cb(const char *refname, const git_oid *a, const git_oid *b) +int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) { const char *action; char a_str[GIT_OID_HEXSZ+1], b_str[GIT_OID_HEXSZ+1]; @@ -65,6 +66,7 @@ int fetch(git_repository *repo, int argc, char **argv) git_indexer_stats stats; pthread_t worker; struct dl_data data; + git_remote_callbacks callbacks; // Figure out whether it's a named remote or a URL printf("Fetching %s\n", argv[1]); @@ -73,6 +75,11 @@ int fetch(git_repository *repo, int argc, char **argv) return -1; } + // Set up the callbacks (only update_tips for now) + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.update_tips = &update_cb; + git_remote_set_callbacks(remote, &callbacks); + // Set up the information for the background worker thread data.remote = remote; data.bytes = &bytes; @@ -101,7 +108,7 @@ int fetch(git_repository *repo, int argc, char **argv) // right commits. This may be needed even if there was no packfile // to download, which can happen e.g. when the branches have been // changed but all the neede objects are available locally. - if (git_remote_update_tips(remote, update_cb) < 0) + if (git_remote_update_tips(remote) < 0) return -1; git_remote_free(remote); diff --git a/include/git2/remote.h b/include/git2/remote.h index 5c01949d2..dc6642cc5 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -190,7 +190,7 @@ GIT_EXTERN(void) git_remote_free(git_remote *remote); * @param remote the remote to update * @param cb callback to run on each ref update. 'a' is the old value, 'b' is then new value */ -GIT_EXTERN(int) git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)); +GIT_EXTERN(int) git_remote_update_tips(git_remote *remote); /** * Return whether a string is a valid remote URL @@ -238,6 +238,39 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); +/** + * Argument to the completion callback which tells it which operation + * finished. + */ +typedef enum git_remote_completion_type { + GIT_REMOTE_COMPLETION_DOWNLOAD, + GIT_REMOTE_COMPLETION_INDEXING, + GIT_REMOTE_COMPLETION_ERROR, +} git_remote_completion_type; + +/** + * The callback settings structure + * + * Set the calbacks to be called by the remote. + */ +struct git_remote_callbacks { + int (*progress)(const char *str, void *data); + int (*completion)(git_remote_completion_type type, void *data); + int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data); + void *data; +}; + +/** + * Set the callbacks for a remote + * + * Note that the remote keeps its own copy of the data and you need to + * call this function again if you want to change the callbacks. + * + * @param remote the remote to configure + * @param callbacks a pointer to the user's callback settings + */ +GIT_EXTERN(void) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/types.h b/include/git2/types.h index 691903005..acd5a73bc 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -179,6 +179,7 @@ typedef struct git_refspec git_refspec; typedef struct git_remote git_remote; typedef struct git_remote_head git_remote_head; +typedef struct git_remote_callbacks git_remote_callbacks; /** @} */ GIT_END_DECL diff --git a/src/remote.c b/src/remote.c index 00e108a0a..e661fff86 100644 --- a/src/remote.c +++ b/src/remote.c @@ -331,7 +331,7 @@ int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats return git_fetch_download_pack(remote, bytes, stats); } -int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)) +int git_remote_update_tips(git_remote *remote) { int error = 0; unsigned int i = 0; @@ -381,8 +381,8 @@ int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, co git_reference_free(ref); - if (cb != NULL) { - if (cb(refname.ptr, &old, &head->oid) < 0) + if (remote->callbacks.update_tips != NULL) { + if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.data) < 0) goto on_error; } } @@ -525,3 +525,10 @@ void git_remote_check_cert(git_remote *remote, int check) remote->check_cert = check; } + +void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks) +{ + assert(remote && callbacks); + + memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks)); +} diff --git a/src/remote.h b/src/remote.h index 0949ad434..3360b6249 100644 --- a/src/remote.h +++ b/src/remote.h @@ -19,6 +19,7 @@ struct git_remote { struct git_refspec push; git_transport *transport; git_repository *repo; + git_remote_callbacks callbacks; unsigned int need_pack:1, check_cert; }; From 279b45b05b05e0dfc496701c4d22e5ea601d0ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 23 Jul 2012 21:22:53 +0200 Subject: [PATCH 12/48] revparse: don't allow an empty string Asking the library for "" used to give HEAD, but that's trying to impose a default at the wrong layer. Make it fail. --- src/revparse.c | 6 +++--- tests-clar/refs/revparse.c | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/revparse.c b/src/revparse.c index 6cfea0ca8..650d7a904 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -705,7 +705,7 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec *out = NULL; - do { + while (spec[pos]) { switch (spec[pos]) { case '^': if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) @@ -813,9 +813,9 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec pos++; identifier_len++; } - } while (spec[pos]); + } - if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0) + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) goto cleanup; *out = base_rev; diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c index 05a95652a..02acb8844 100644 --- a/tests-clar/refs/revparse.c +++ b/tests-clar/refs/revparse.c @@ -64,6 +64,8 @@ void test_refs_revparse__invalid_reference_name(void) cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense^1")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense~2")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "")); + } void test_refs_revparse__shas(void) @@ -74,7 +76,6 @@ void test_refs_revparse__shas(void) void test_refs_revparse__head(void) { - test_object("", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("HEAD^0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("HEAD~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); From 8d711074ac2d1ee6a6c5f9f1e974f932cb95b42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 22 Jul 2012 19:42:47 +0200 Subject: [PATCH 13/48] travis: build with both gcc and clang --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9d99d6f7..caead67b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,10 @@ language: erlang # Settings to try env: - - OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release" - - OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=ON" + - CC=gcc OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release" + - CC=clang OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release" + - CC=gcc OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=ON" + - CC=clang OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=ON" - CC=i586-mingw32msvc-gcc OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON" # Make sure CMake is installed From 7e48635d16f68397754bf11b101e165355c34d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 23 Jul 2012 21:56:06 +0200 Subject: [PATCH 14/48] revparse: initialize 'parsed' in case the user doesn't give a number with the @-notation --- src/revparse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/revparse.c b/src/revparse.c index 650d7a904..b0469286b 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -341,7 +341,7 @@ cleanup: static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, int identifier_len, git_repository* repo, const char *curly_braces_content) { bool is_numeric; - int parsed, error = -1; + int parsed = 0, error = -1; git_buf identifier = GIT_BUF_INIT; git_time_t timestamp; From 944d250f964698b33d9fa09e2e6af74b1dd84de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 24 Jul 2012 10:34:28 +0200 Subject: [PATCH 15/48] update_tips: report error if it fails to create a ref --- src/remote.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index 00e108a0a..e46249e12 100644 --- a/src/remote.c +++ b/src/remote.c @@ -377,7 +377,7 @@ int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, co continue; if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0) - break; + goto on_error; git_reference_free(ref); From 02a0d651d79b2108dd6b894b9a43f7682270ac51 Mon Sep 17 00:00:00 2001 From: yorah Date: Thu, 12 Jul 2012 16:31:59 +0200 Subject: [PATCH 16/48] Add git_buf_unescape and git__unescape to unescape all characters in a string (in-place) --- src/attr_file.c | 12 +----------- src/buffer.c | 4 ++++ src/buffer.h | 3 +++ src/util.c | 18 ++++++++++++++++++ src/util.h | 9 +++++++++ tests-clar/core/buffer.c | 20 ++++++++++++++++++++ 6 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/attr_file.c b/src/attr_file.c index 0dad09727..837c42d8e 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -426,17 +426,7 @@ int git_attr_fnmatch__parse( return -1; } else { /* strip '\' that might have be used for internal whitespace */ - char *to = spec->pattern; - for (scan = spec->pattern; *scan; to++, scan++) { - if (*scan == '\\') - scan++; /* skip '\' but include next char */ - if (to != scan) - *to = *scan; - } - if (to != scan) { - *to = '\0'; - spec->length = (to - spec->pattern); - } + spec->length = git__unescape(spec->pattern); } return 0; diff --git a/src/buffer.c b/src/buffer.c index 5d54ee1a5..b57998e1b 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -496,3 +496,7 @@ bool git_buf_is_binary(const git_buf *buf) return ((printable >> 7) < nonprintable); } +void git_buf_unescape(git_buf *buf) +{ + buf->size = git__unescape(buf->ptr); +} diff --git a/src/buffer.h b/src/buffer.h index 75f3b0e4f..17922e408 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -151,4 +151,7 @@ int git_buf_common_prefix(git_buf *buf, const git_strarray *strings); /* Check if buffer looks like it contains binary data */ bool git_buf_is_binary(const git_buf *buf); +/* Unescape all characters in a buffer */ +void git_buf_unescape(git_buf *buf); + #endif diff --git a/src/util.c b/src/util.c index 3093cd767..90bb3d02a 100644 --- a/src/util.c +++ b/src/util.c @@ -435,3 +435,21 @@ int git__parse_bool(int *out, const char *value) return -1; } + +size_t git__unescape(char *str) +{ + char *scan, *pos = str; + + for (scan = str; *scan; pos++, scan++) { + if (*scan == '\\' && *(scan + 1) != '\0') + scan++; /* skip '\' but include next char */ + if (pos != scan) + *pos = *scan; + } + + if (pos != scan) { + *pos = '\0'; + } + + return (pos - str); +} diff --git a/src/util.h b/src/util.h index a84dcab1e..905fc927f 100644 --- a/src/util.h +++ b/src/util.h @@ -238,4 +238,13 @@ extern int git__parse_bool(int *out, const char *value); */ int git__date_parse(git_time_t *out, const char *date); +/* + * Unescapes a string in-place. + * + * Edge cases behavior: + * - "jackie\" -> "jacky\" + * - "chan\\" -> "chan\" + */ +extern size_t git__unescape(char *str); + #endif /* INCLUDE_util_h__ */ diff --git a/tests-clar/core/buffer.c b/tests-clar/core/buffer.c index 21aaaed7e..b6274b012 100644 --- a/tests-clar/core/buffer.c +++ b/tests-clar/core/buffer.c @@ -658,3 +658,23 @@ void test_core_buffer__puts_escaped(void) git_buf_free(&a); } + +static void assert_unescape(char *expected, char *to_unescape) { + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_sets(&buf, to_unescape)); + git_buf_unescape(&buf); + cl_assert_equal_s(expected, buf.ptr); + cl_assert_equal_i(strlen(expected), buf.size); + + git_buf_free(&buf); +} + +void test_core_buffer__unescape(void) +{ + assert_unescape("Escaped\\", "Es\\ca\\ped\\"); + assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\"); + assert_unescape("\\", "\\"); + assert_unescape("\\", "\\\\"); + assert_unescape("", ""); +} From 151446ca605686b83b93952e8168cfc0b37cf4d3 Mon Sep 17 00:00:00 2001 From: aroben Date: Tue, 3 Jul 2012 17:46:07 +0200 Subject: [PATCH 17/48] Add a test for getting status of files containing brackets --- tests-clar/status/worktree.c | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index fed81e570..a92d076e9 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -517,6 +517,69 @@ void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void) cl_git_pass(p_unlink("my-index")); } +void test_status_worktree__bracket_in_filename(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + unsigned int status_flags; + + #define FILE_WITH_BRACKET "LICENSE[1].md" + + cl_git_pass(git_repository_init(&repo, "with_bracket", 0)); + cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n"); + + /* file is new to working directory */ + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, "LICENSE[1].md")); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* ignore the file */ + + cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_IGNORED); + + /* don't ignore the file */ + + cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* add the file to the index */ + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add(index, FILE_WITH_BRACKET, 0)); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + git_index_free(index); + git_repository_free(repo); +} void test_status_worktree__space_in_filename(void) { From e5e71f5e1db75075a81881f38b4ee0013fa966be Mon Sep 17 00:00:00 2001 From: yorah Date: Wed, 18 Jul 2012 16:26:11 +0200 Subject: [PATCH 18/48] Add more test coverage to match default git behavior for files containing brackets --- tests-clar/status/worktree.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index a92d076e9..1bdd8160a 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -523,8 +523,10 @@ void test_status_worktree__bracket_in_filename(void) git_index *index; status_entry_single result; unsigned int status_flags; + int error; #define FILE_WITH_BRACKET "LICENSE[1].md" + #define FILE_WITHOUT_BRACKET "LICENSE1.md" cl_git_pass(git_repository_init(&repo, "with_bracket", 0)); cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n"); @@ -536,7 +538,7 @@ void test_status_worktree__bracket_in_filename(void) cl_assert_equal_i(1, result.count); cl_assert(result.status == GIT_STATUS_WT_NEW); - cl_git_pass(git_status_file(&status_flags, repo, "LICENSE[1].md")); + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); cl_assert(status_flags == GIT_STATUS_WT_NEW); /* ignore the file */ @@ -577,6 +579,20 @@ void test_status_worktree__bracket_in_filename(void) cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + /* Create file without bracket */ + + cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n"); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md")); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + error = git_status_file(&status_flags, repo, FILE_WITH_BRACKET); + cl_git_fail(error); + cl_assert(error == GIT_EAMBIGUOUS); + git_index_free(index); git_repository_free(repo); } From ffbc689c8768c66cddf9ef3ab6c88c41ecf4c1ab Mon Sep 17 00:00:00 2001 From: yorah Date: Wed, 18 Jul 2012 16:26:55 +0200 Subject: [PATCH 19/48] Fix getting status of files containing brackets --- src/diff.c | 23 ++++++++++++++++------- src/status.c | 6 ++++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/diff.c b/src/diff.c index 09f319e6e..f08688e38 100644 --- a/src/diff.c +++ b/src/diff.c @@ -20,14 +20,21 @@ static char *diff_prefix_from_pathspec(const git_strarray *pathspec) return NULL; /* diff prefix will only be leading non-wildcards */ - for (scan = prefix.ptr; *scan && !git__iswildcard(*scan); ++scan); + for (scan = prefix.ptr; *scan; ++scan) { + if (git__iswildcard(*scan) && + (scan == prefix.ptr || (*(scan - 1) != '\\'))) + break; + } git_buf_truncate(&prefix, scan - prefix.ptr); - if (prefix.size > 0) - return git_buf_detach(&prefix); + if (prefix.size <= 0) { + git_buf_free(&prefix); + return NULL; + } - git_buf_free(&prefix); - return NULL; + git_buf_unescape(&prefix); + + return git_buf_detach(&prefix); } static bool diff_pathspec_is_interesting(const git_strarray *pathspec) @@ -54,7 +61,10 @@ static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) return true; git_vector_foreach(&diff->pathspec, i, match) { - int result = p_fnmatch(match->pattern, path, 0); + int result = strcmp(match->pattern, path); + + if (result != 0) + result = p_fnmatch(match->pattern, path, 0); /* if we didn't match, look for exact dirname prefix match */ if (result == FNM_NOMATCH && @@ -826,4 +836,3 @@ int git_diff_merge( return error; } - diff --git a/src/status.c b/src/status.c index e9ad3cfe4..633082c09 100644 --- a/src/status.c +++ b/src/status.c @@ -176,10 +176,12 @@ static int get_one_status(const char *path, unsigned int status, void *data) sfi->count++; sfi->status = status; - if (sfi->count > 1 || strcmp(sfi->expected, path) != 0) { + if (sfi->count > 1 || + (strcmp(sfi->expected, path) != 0 && + p_fnmatch(sfi->expected, path, 0) != 0)) { giterr_set(GITERR_INVALID, "Ambiguous path '%s' given to git_status_file", sfi->expected); - return -1; + return GIT_EAMBIGUOUS; } return 0; From a1773f9d89887d299248d15b43953d3fa494a025 Mon Sep 17 00:00:00 2001 From: yorah Date: Mon, 23 Jul 2012 18:16:09 +0200 Subject: [PATCH 20/48] Add flag to turn off pathspec testing for diff and status --- include/git2/diff.h | 1 + include/git2/status.h | 3 +++ src/diff.c | 5 ++-- src/status.c | 2 ++ tests-clar/status/worktree.c | 46 ++++++++++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index edec9957b..85727d969 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -46,6 +46,7 @@ enum { GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8), GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9), GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10), + GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11), }; /** diff --git a/include/git2/status.h b/include/git2/status.h index 69b6e47e0..9e7b5de4a 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -96,6 +96,8 @@ typedef enum { * 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_DISABLE_PATHSPEC_MATCH indicates that the given + * path will be treated as a literal path, and not as a pathspec. */ enum { @@ -104,6 +106,7 @@ enum { GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2), GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3), GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4), + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1 << 5), }; /** diff --git a/src/diff.c b/src/diff.c index f08688e38..2b1529d63 100644 --- a/src/diff.c +++ b/src/diff.c @@ -61,9 +61,10 @@ static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) return true; git_vector_foreach(&diff->pathspec, i, match) { - int result = strcmp(match->pattern, path); + int result = strcmp(match->pattern, path) ? FNM_NOMATCH : 0; - if (result != 0) + if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) && + result == FNM_NOMATCH) result = p_fnmatch(match->pattern, path, 0); /* if we didn't match, look for exact dirname prefix match */ diff --git a/src/status.c b/src/status.c index 633082c09..d78237689 100644 --- a/src/status.c +++ b/src/status.c @@ -99,6 +99,8 @@ int git_status_foreach_ext( diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; /* TODO: support EXCLUDE_SUBMODULES flag */ if (show != GIT_STATUS_SHOW_WORKDIR_ONLY && diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 1bdd8160a..bd57cf2b6 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -726,3 +726,49 @@ void test_status_worktree__filemode_changes(void) git_config_free(cfg); } + +int cb_status__expected_path(const char *p, unsigned int s, void *payload) +{ + const char *expected_path = (const char *)payload; + + GIT_UNUSED(s); + + if (payload == NULL) + cl_fail("Unexpected path"); + + cl_assert_equal_s(expected_path, p); + + return 0; +} + +void test_status_worktree__disable_pathspec_match(void) +{ + git_repository *repo; + git_status_options opts; + char *file_with_bracket = "LICENSE[1].md", + *imaginary_file_with_bracket = "LICENSE[1-2].md"; + + cl_git_pass(git_repository_init(&repo, "pathspec", 0)); + cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n"); + cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n"); + + memset(&opts, 0, sizeof(opts)); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + opts.pathspec.count = 1; + opts.pathspec.strings = &file_with_bracket; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__expected_path, + file_with_bracket) + ); + + /* Test passing a pathspec matching files in the workdir. */ + /* Must not match because pathspecs are disabled. */ + opts.pathspec.strings = &imaginary_file_with_bracket; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL) + ); + + git_repository_free(repo); +} From 326ca710a04cc43d3d5abba6ceb0452c126b7d1a Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 19 Jul 2012 15:32:58 +0200 Subject: [PATCH 21/48] branch: remove useless header --- src/branch.c | 3 ++- src/branch.h | 17 ----------------- tests-clar/refs/branches/create.c | 1 - tests-clar/refs/branches/delete.c | 1 - tests-clar/refs/branches/foreach.c | 1 - tests-clar/refs/branches/move.c | 1 - 6 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 src/branch.h diff --git a/src/branch.c b/src/branch.c index 671e42051..a59577d67 100644 --- a/src/branch.c +++ b/src/branch.c @@ -7,9 +7,10 @@ #include "common.h" #include "commit.h" -#include "branch.h" #include "tag.h" +#include "git2/branch.h" + static int retrieve_branch_reference( git_reference **branch_reference_out, git_repository *repo, diff --git a/src/branch.h b/src/branch.h deleted file mode 100644 index d0e5abc8b..000000000 --- a/src/branch.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_branch_h__ -#define INCLUDE_branch_h__ - -#include "git2/branch.h" - -struct git_branch { - char *remote; /* TODO: Make this a git_remote */ - char *merge; -}; - -#endif diff --git a/tests-clar/refs/branches/create.c b/tests-clar/refs/branches/create.c index ad7e1fd2c..4f5a61b40 100644 --- a/tests-clar/refs/branches/create.c +++ b/tests-clar/refs/branches/create.c @@ -1,6 +1,5 @@ #include "clar_libgit2.h" #include "refs.h" -#include "branch.h" static git_repository *repo; static git_oid branch_target_oid; diff --git a/tests-clar/refs/branches/delete.c b/tests-clar/refs/branches/delete.c index 03d3c56d7..699655f27 100644 --- a/tests-clar/refs/branches/delete.c +++ b/tests-clar/refs/branches/delete.c @@ -1,6 +1,5 @@ #include "clar_libgit2.h" #include "refs.h" -#include "branch.h" static git_repository *repo; static git_reference *fake_remote; diff --git a/tests-clar/refs/branches/foreach.c b/tests-clar/refs/branches/foreach.c index b6e973799..185ca36ba 100644 --- a/tests-clar/refs/branches/foreach.c +++ b/tests-clar/refs/branches/foreach.c @@ -1,6 +1,5 @@ #include "clar_libgit2.h" #include "refs.h" -#include "branch.h" static git_repository *repo; static git_reference *fake_remote; diff --git a/tests-clar/refs/branches/move.c b/tests-clar/refs/branches/move.c index 242e5cd01..258f74c3d 100644 --- a/tests-clar/refs/branches/move.c +++ b/tests-clar/refs/branches/move.c @@ -1,5 +1,4 @@ #include "clar_libgit2.h" -#include "branch.h" static git_repository *repo; From b308c11e4ee7d05df4906e04b4008615f41e069c Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 19 Jul 2012 15:39:16 +0200 Subject: [PATCH 22/48] branch: change git_branch_create() to make it return a reference --- include/git2/branch.h | 7 ++-- src/branch.c | 14 +++----- tests-clar/refs/branches/create.c | 56 +++++++++---------------------- 3 files changed, 23 insertions(+), 54 deletions(-) diff --git a/include/git2/branch.h b/include/git2/branch.h index 8884df15a..7442ece03 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -26,9 +26,9 @@ GIT_BEGIN_DECL * this target commit. If `force` is true and a reference * already exists with the given name, it'll be replaced. * - * @param oid_out Pointer where to store the OID of the target commit. + * The returned reference must be freed by the user. * - * @param repo Repository where to store the branch. + * @param ref_out Pointer where to store the underlying reference. * * @param branch_name Name for the branch; this name is * validated for consistency. It should also not conflict with @@ -46,8 +46,7 @@ GIT_BEGIN_DECL * pointing to the provided target commit. */ GIT_EXTERN(int) git_branch_create( - git_oid *oid_out, - git_repository *repo, + git_reference **ref_out, const char *branch_name, const git_object *target, int force); diff --git a/src/branch.c b/src/branch.c index a59577d67..789f52bb6 100644 --- a/src/branch.c +++ b/src/branch.c @@ -49,8 +49,7 @@ static int create_error_invalid(const char *msg) } int git_branch_create( - git_oid *oid_out, - git_repository *repo, + git_reference **ref_out, const char *branch_name, const git_object *target, int force) @@ -61,10 +60,7 @@ int git_branch_create( git_buf canonical_branch_name = GIT_BUF_INIT; int error = -1; - assert(repo && branch_name && target && oid_out); - - if (git_object_owner(target) != repo) - return create_error_invalid("The given target does not belong to this repository"); + assert(branch_name && target && ref_out); target_type = git_object_type(target); @@ -91,17 +87,17 @@ int git_branch_create( if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) goto cleanup; - if (git_reference_create_oid(&branch, repo, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0) + if (git_reference_create_oid(&branch, git_object_owner(commit), + git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0) goto cleanup; - git_oid_cpy(oid_out, git_reference_oid(branch)); + *ref_out = branch; error = 0; cleanup: if (target_type == GIT_OBJ_TAG) git_object_free(commit); - git_reference_free(branch); git_buf_free(&canonical_branch_name); return error; } diff --git a/tests-clar/refs/branches/create.c b/tests-clar/refs/branches/create.c index 4f5a61b40..d53904303 100644 --- a/tests-clar/refs/branches/create.c +++ b/tests-clar/refs/branches/create.c @@ -2,17 +2,21 @@ #include "refs.h" static git_repository *repo; -static git_oid branch_target_oid; static git_object *target; +static git_reference *branch; void test_refs_branches_create__initialize(void) { cl_fixture_sandbox("testrepo.git"); cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + branch = NULL; } void test_refs_branches_create__cleanup(void) { + git_reference_free(branch); + git_object_free(target); git_repository_free(repo); @@ -38,54 +42,24 @@ void test_refs_branches_create__can_create_a_local_branch(void) { retrieve_known_commit(&target, repo); - cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); - cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target))); -} - -void test_refs_branches_create__creating_a_local_branch_triggers_the_creation_of_a_new_direct_reference(void) -{ - git_reference *branch; - - retrieve_known_commit(&target, repo); - - cl_git_fail(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME)); - - cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); - - cl_git_pass(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME)); - cl_assert(git_reference_type(branch) == GIT_REF_OID); - - git_reference_free(branch); + cl_git_pass(git_branch_create(&branch, NEW_BRANCH_NAME, target, 0)); + cl_git_pass(git_oid_cmp(git_reference_oid(branch), git_object_id(target))); } void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with_an_existing_one(void) { retrieve_known_commit(&target, repo); - cl_git_fail(git_branch_create(&branch_target_oid, repo, "br2", target, 0)); + cl_git_fail(git_branch_create(&branch, "br2", target, 0)); } void test_refs_branches_create__can_force_create_over_an_existing_branch(void) { retrieve_known_commit(&target, repo); - cl_git_pass(git_branch_create(&branch_target_oid, repo, "br2", target, 1)); - cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target))); -} - -void test_refs_branches_create__can_not_create_a_branch_pointing_at_an_object_unknown_from_the_repository(void) -{ - git_repository *repo2; - - /* Open another instance of the same repository */ - cl_git_pass(git_repository_open(&repo2, cl_fixture("testrepo.git"))); - - /* Retrieve a commit object from this different repository */ - retrieve_known_commit(&target, repo2); - - cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); - - git_repository_free(repo2); + cl_git_pass(git_branch_create(&branch, "br2", target, 1)); + cl_git_pass(git_oid_cmp(git_reference_oid(branch), git_object_id(target))); + cl_assert_equal_s("refs/heads/br2", git_reference_name(branch)); } void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_it_to_its_commit(void) @@ -93,8 +67,8 @@ void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_i /* b25fa35 is a tag, pointing to another tag which points to a commit */ retrieve_target_from_oid(&target, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); - cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); - cl_git_pass(git_oid_streq(&branch_target_oid, "e90810b8df3e80c413d903f631643c716887138d")); + cl_git_pass(git_branch_create(&branch, NEW_BRANCH_NAME, target, 0)); + cl_git_pass(git_oid_streq(git_reference_oid(branch), "e90810b8df3e80c413d903f631643c716887138d")); } void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit_object(void) @@ -102,11 +76,11 @@ void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit /* 53fc32d is the tree of commit e90810b */ retrieve_target_from_oid(&target, repo, "53fc32d17276939fc79ed05badaef2db09990016"); - cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); + cl_git_fail(git_branch_create(&branch, NEW_BRANCH_NAME, target, 0)); git_object_free(target); /* 521d87c is an annotated tag pointing to a blob */ retrieve_target_from_oid(&target, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"); - cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); + cl_git_fail(git_branch_create(&branch, NEW_BRANCH_NAME, target, 0)); } From eed378b66960414942ac78840afbcb19bfffbf15 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 20 Jul 2012 16:19:04 +0200 Subject: [PATCH 23/48] branch: introduce git_branch_lookup() --- include/git2/branch.h | 24 +++++++++++++++++++++ src/branch.c | 11 ++++++++++ tests-clar/refs/branches/lookup.c | 35 +++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 tests-clar/refs/branches/lookup.c diff --git a/include/git2/branch.h b/include/git2/branch.h index 7442ece03..fb30aaa26 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -117,6 +117,30 @@ GIT_EXTERN(int) git_branch_move( const char *new_branch_name, int force); +/** + * Lookup a branch by its name in a repository. + * + * The generated reference must be freed by the user. + * + * @param branch_out pointer to the looked-up branch reference + * + * @param repo the repository to look up the branch + * + * @param branch_name Name of the branch to be looked-up; + * this name is validated for consistency. + * + * @param branch_type Type of the considered branch. This should + * be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE. + * + * @return 0 on success; GIT_ENOTFOUND when no matching branch + * exists, otherwise an error code. + */ +GIT_EXTERN(int) git_branch_lookup( + git_reference **branch_out, + git_repository *repo, + const char *branch_name, + git_branch_t branch_type); + /** @} */ GIT_END_DECL #endif diff --git a/src/branch.c b/src/branch.c index 789f52bb6..f0945b6c7 100644 --- a/src/branch.c +++ b/src/branch.c @@ -205,3 +205,14 @@ cleanup: return error; } + +int git_branch_lookup( + git_reference **ref_out, + git_repository *repo, + const char *branch_name, + git_branch_t branch_type) +{ + assert(ref_out && repo && branch_name); + + return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); +} diff --git a/tests-clar/refs/branches/lookup.c b/tests-clar/refs/branches/lookup.c new file mode 100644 index 000000000..2aabf9889 --- /dev/null +++ b/tests-clar/refs/branches/lookup.c @@ -0,0 +1,35 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *repo; +static git_reference *branch; + +void test_refs_branches_lookup__initialize(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + branch = NULL; +} + +void test_refs_branches_lookup__cleanup(void) +{ + git_reference_free(branch); + + git_repository_free(repo); +} + +void test_refs_branches_lookup__can_retrieve_a_local_branch(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); +} + +void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_REMOTE)); +} + +void test_refs_branches_lookup__trying_to_retrieve_an_unknown_branch_returns_ENOTFOUND(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "where/are/you", GIT_BRANCH_LOCAL)); + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "over/here", GIT_BRANCH_REMOTE)); +} From 88bcd5153f6dc9e0c7ebc73a1e87c0da17e8df28 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 20 Jul 2012 16:27:56 +0200 Subject: [PATCH 24/48] branch: introduce git_reference_is_branch() --- include/git2/refs.h | 10 ++++++++++ src/refs.c | 7 +++++++ tests-clar/refs/read.c | 16 ++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/include/git2/refs.h b/include/git2/refs.h index b119e90b1..ac876ebb0 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -384,6 +384,16 @@ GIT_EXTERN(int) git_reference_remote_tracking_from_branch( git_reference *branch_ref ); +/** + * Check if a reference is a local branch. + * + * @param ref A git reference + * + * @return 1 when the reference lives in the refs/heads + * namespace; 0 otherwise. + */ +GIT_EXTERN(int) git_reference_is_branch(git_reference *ref); + /** @} */ GIT_END_DECL #endif diff --git a/src/refs.c b/src/refs.c index b3c140bec..d08ea9604 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1888,3 +1888,10 @@ cleanup: git_buf_free(&buf); return error; } + +int git_reference_is_branch(git_reference *ref) +{ + assert(ref); + + return git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0; +} diff --git a/tests-clar/refs/read.c b/tests-clar/refs/read.c index ce4eefeba..1948e0a56 100644 --- a/tests-clar/refs/read.c +++ b/tests-clar/refs/read.c @@ -202,3 +202,19 @@ void test_refs_read__unfound_return_ENOTFOUND(void) cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/master")); cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master")); } + +static void assert_is_branch(const char *name, bool expected_branchness) +{ + git_reference *reference; + cl_git_pass(git_reference_lookup(&reference, g_repo, name)); + cl_assert_equal_i(expected_branchness, git_reference_is_branch(reference)); + git_reference_free(reference); +} + +void test_refs_read__can_determine_if_a_reference_is_a_local_branch(void) +{ + assert_is_branch("refs/heads/master", true); + assert_is_branch("refs/heads/packed", true); + assert_is_branch("refs/remotes/test/master", false); + assert_is_branch("refs/tags/e90810b", false); +} From abee7bd36a8a00e9578d3c94b1b7080f5b5c7dc8 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 20 Jul 2012 16:31:17 +0200 Subject: [PATCH 25/48] branch: slight git_branch_create() doc improvement --- include/git2/branch.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/git2/branch.h b/include/git2/branch.h index fb30aaa26..fcddb9332 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -28,7 +28,7 @@ GIT_BEGIN_DECL * * The returned reference must be freed by the user. * - * @param ref_out Pointer where to store the underlying reference. + * @param branch_out Pointer where to store the underlying reference. * * @param branch_name Name for the branch; this name is * validated for consistency. It should also not conflict with @@ -46,7 +46,7 @@ GIT_BEGIN_DECL * pointing to the provided target commit. */ GIT_EXTERN(int) git_branch_create( - git_reference **ref_out, + git_reference **branch_out, const char *branch_name, const git_object *target, int force); From bf9e8cc86b9c32946a395fd12a9b1a5cb71575a9 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 20 Jul 2012 16:34:08 +0200 Subject: [PATCH 26/48] branch: make git_branch_move() reference based --- include/git2/branch.h | 13 +++------ src/branch.c | 28 ++++++++++-------- tests-clar/refs/branches/move.c | 51 ++++++++++++++------------------- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/include/git2/branch.h b/include/git2/branch.h index fcddb9332..724cfba12 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -96,24 +96,19 @@ GIT_EXTERN(int) git_branch_foreach( ); /** - * Move/rename an existing branch reference. + * Move/rename an existing local branch reference. * - * @param repo Repository where lives the branch. - * - * @param old_branch_name Current name of the branch to be moved; - * this name is validated for consistency. + * @param branch Current underlying reference of the branch. * * @param new_branch_name Target name of the branch once the move * is performed; this name is validated for consistency. * * @param force Overwrite existing branch. * - * @return 0 on success, GIT_ENOTFOUND if the branch - * doesn't exist or an error code. + * @return 0 on success, or an error code. */ GIT_EXTERN(int) git_branch_move( - git_repository *repo, - const char *old_branch_name, + git_reference *branch, const char *new_branch_name, int force); diff --git a/src/branch.c b/src/branch.c index f0945b6c7..4a56fd1b9 100644 --- a/src/branch.c +++ b/src/branch.c @@ -180,27 +180,31 @@ int git_branch_foreach( return git_reference_foreach(repo, GIT_REF_LISTALL, &branch_foreach_cb, (void *)&filter); } -int git_branch_move(git_repository *repo, const char *old_branch_name, const char *new_branch_name, int force) +static int not_a_local_branch(git_reference *ref) { - git_reference *reference = NULL; - git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT; - int error = 0; + giterr_set(GITERR_INVALID, "Reference '%s' is not a local branch.", git_reference_name(ref)); + return -1; +} - if ((error = git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name)) < 0) - goto cleanup; +int git_branch_move( + git_reference *branch, + const char *new_branch_name, + int force) +{ + git_buf new_reference_name = GIT_BUF_INIT; + int error; - /* We need to be able to return GIT_ENOTFOUND */ - if ((error = git_reference_lookup(&reference, repo, git_buf_cstr(&old_reference_name))) < 0) - goto cleanup; + assert(branch && new_branch_name); + + if (!git_reference_is_branch(branch)) + return not_a_local_branch(branch); if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) goto cleanup; - error = git_reference_rename(reference, git_buf_cstr(&new_reference_name), force); + error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force); cleanup: - git_reference_free(reference); - git_buf_free(&old_reference_name); git_buf_free(&new_reference_name); return error; diff --git a/tests-clar/refs/branches/move.c b/tests-clar/refs/branches/move.c index 258f74c3d..6750473e1 100644 --- a/tests-clar/refs/branches/move.c +++ b/tests-clar/refs/branches/move.c @@ -1,71 +1,64 @@ #include "clar_libgit2.h" +#include "refs.h" static git_repository *repo; +static git_reference *ref; void test_refs_branches_move__initialize(void) { - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/heads/br2")); } void test_refs_branches_move__cleanup(void) { - git_repository_free(repo); - - cl_fixture_cleanup("testrepo.git"); + git_reference_free(ref); + cl_git_sandbox_cleanup(); } #define NEW_BRANCH_NAME "new-branch-on-the-block" void test_refs_branches_move__can_move_a_local_branch(void) { - cl_git_pass(git_branch_move(repo, "br2", NEW_BRANCH_NAME, 0)); + cl_git_pass(git_branch_move(ref, NEW_BRANCH_NAME, 0)); + cl_assert_equal_s(GIT_REFS_HEADS_DIR NEW_BRANCH_NAME, git_reference_name(ref)); } void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void) { /* Downward */ - cl_git_pass(git_branch_move(repo, "br2", "somewhere/" NEW_BRANCH_NAME, 0)); + cl_git_pass(git_branch_move(ref, "somewhere/" NEW_BRANCH_NAME, 0)); /* Upward */ - cl_git_pass(git_branch_move(repo, "somewhere/" NEW_BRANCH_NAME, "br2", 0)); + cl_git_pass(git_branch_move(ref, "br2", 0)); } void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void) { /* Downward */ - cl_git_pass(git_branch_move(repo, "br2", "br2/" NEW_BRANCH_NAME, 0)); + cl_git_pass(git_branch_move(ref, "br2/" NEW_BRANCH_NAME, 0)); /* Upward */ - cl_git_pass(git_branch_move(repo, "br2/" NEW_BRANCH_NAME, "br2", 0)); + cl_git_pass(git_branch_move(ref, "br2", 0)); } void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void) { - cl_git_fail(git_branch_move(repo, "br2", "master", 0)); + cl_git_fail(git_branch_move(ref, "master", 0)); } -void test_refs_branches_move__can_not_move_a_non_existing_branch(void) +void test_refs_branches_move__can_not_move_a_non_branch(void) { - cl_git_fail(git_branch_move(repo, "i-am-no-branch", NEW_BRANCH_NAME, 0)); + git_reference *tag; + + cl_git_pass(git_reference_lookup(&tag, repo, "refs/tags/e90810b")); + cl_git_fail(git_branch_move(tag, NEW_BRANCH_NAME, 0)); + + git_reference_free(tag); } void test_refs_branches_move__can_force_move_over_an_existing_branch(void) { - cl_git_pass(git_branch_move(repo, "br2", "master", 1)); -} - -void test_refs_branches_move__can_not_move_a_branch_through_its_canonical_name(void) -{ - cl_git_fail(git_branch_move(repo, "refs/heads/br2", NEW_BRANCH_NAME, 1)); -} - -void test_refs_branches_move__moving_a_non_exisiting_branch_returns_ENOTFOUND(void) -{ - int error; - - error = git_branch_move(repo, "where/am/I", NEW_BRANCH_NAME, 0); - cl_git_fail(error); - - cl_assert_equal_i(GIT_ENOTFOUND, error); + cl_git_pass(git_branch_move(ref, "master", 1)); } From fb910281d6598e2c235f6ec93384d4e08838d655 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 20 Jul 2012 16:38:54 +0200 Subject: [PATCH 27/48] branch: introduce git_branch_tracking() --- include/git2/branch.h | 16 +++++ src/branch.c | 68 ++++++++++++++++++ src/revparse.c | 2 +- tests-clar/network/remotelocal.c | 4 +- tests-clar/refs/branches/foreach.c | 4 +- tests-clar/refs/branches/tracking.c | 69 +++++++++++++++++++ tests-clar/refs/foreachglob.c | 4 +- tests-clar/resources/testrepo.git/config | 3 + .../testrepo.git/refs/heads/track-local | 1 + 9 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 tests-clar/refs/branches/tracking.c create mode 100644 tests-clar/resources/testrepo.git/refs/heads/track-local diff --git a/include/git2/branch.h b/include/git2/branch.h index 724cfba12..15894b709 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -136,6 +136,22 @@ GIT_EXTERN(int) git_branch_lookup( const char *branch_name, git_branch_t branch_type); +/** + * Return the reference supporting the remote tracking branch, + * given a local branch reference. + * + * @param tracking_out Pointer where to store the retrieved + * reference. + * + * @param branch Current underlying reference of the branch. + * + * @return 0 on success; GIT_ENOTFOUND when no remote tracking + * reference exists, otherwise an error code. + */ +GIT_EXTERN(int) git_branch_tracking( + git_reference **tracking_out, + git_reference *branch); + /** @} */ GIT_END_DECL #endif diff --git a/src/branch.c b/src/branch.c index 4a56fd1b9..d0ebb2dc3 100644 --- a/src/branch.c +++ b/src/branch.c @@ -8,6 +8,8 @@ #include "common.h" #include "commit.h" #include "tag.h" +#include "config.h" +#include "refspec.h" #include "git2/branch.h" @@ -220,3 +222,69 @@ int git_branch_lookup( return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); } + +int retrieve_tracking_configuration(const char **out, git_reference *branch, const char *format) +{ + git_config *config; + git_buf buf = GIT_BUF_INIT; + int error; + + if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0) + return -1; + + if (git_buf_printf(&buf, format, + git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) + return -1; + + error = git_config_get_string(out, config, git_buf_cstr(&buf)); + git_buf_free(&buf); + return error; +} + +int git_branch_tracking( + git_reference **tracking_out, + git_reference *branch) +{ + const char *remote_name, *merge_name; + git_buf buf = GIT_BUF_INIT; + int error = -1; + git_remote *remote = NULL; + const git_refspec *refspec; + + assert(tracking_out && branch); + + if (!git_reference_is_branch(branch)) + return not_a_local_branch(branch); + + if ((error = retrieve_tracking_configuration(&remote_name, branch, "branch.%s.remote")) < 0) + goto cleanup; + + if ((error = retrieve_tracking_configuration(&merge_name, branch, "branch.%s.merge")) < 0) + goto cleanup; + + if (strcmp(".", remote_name) != 0) { + if ((error = git_remote_load(&remote, git_reference_owner(branch), remote_name)) < 0) + goto cleanup; + + refspec = git_remote_fetchspec(remote); + if (refspec == NULL) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + if (git_refspec_transform_r(&buf, refspec, merge_name) < 0) + goto cleanup; + } else + if (git_buf_sets(&buf, merge_name) < 0) + goto cleanup; + + error = git_reference_lookup( + tracking_out, + git_reference_owner(branch), + git_buf_cstr(&buf)); + +cleanup: + git_remote_free(remote); + git_buf_free(&buf); + return error; +} diff --git a/src/revparse.c b/src/revparse.c index b0469286b..e2c0de612 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -328,7 +328,7 @@ static int retrieve_remote_tracking_reference(git_reference **base_ref, const ch *base_ref = NULL; } - if ((error = git_reference_remote_tracking_from_branch(&tracking, ref)) < 0) + if ((error = git_branch_tracking(&tracking, ref)) < 0) goto cleanup; *base_ref = tracking; diff --git a/tests-clar/network/remotelocal.c b/tests-clar/network/remotelocal.c index 5e20b4240..16e3fe2dd 100644 --- a/tests-clar/network/remotelocal.c +++ b/tests-clar/network/remotelocal.c @@ -107,7 +107,7 @@ void test_network_remotelocal__retrieve_advertised_references(void) cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); - cl_assert_equal_i(how_many_refs, 22); + cl_assert_equal_i(how_many_refs, 23); } void test_network_remotelocal__retrieve_advertised_references_from_spaced_repository(void) @@ -121,7 +121,7 @@ void test_network_remotelocal__retrieve_advertised_references_from_spaced_reposi cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); - cl_assert_equal_i(how_many_refs, 22); + cl_assert_equal_i(how_many_refs, 23); git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ remote = NULL; diff --git a/tests-clar/refs/branches/foreach.c b/tests-clar/refs/branches/foreach.c index 185ca36ba..794233cc9 100644 --- a/tests-clar/refs/branches/foreach.c +++ b/tests-clar/refs/branches/foreach.c @@ -47,7 +47,7 @@ static void assert_retrieval(unsigned int flags, unsigned int expected_count) void test_refs_branches_foreach__retrieve_all_branches(void) { - assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 10); + assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 11); } void test_refs_branches_foreach__retrieve_remote_branches(void) @@ -57,7 +57,7 @@ void test_refs_branches_foreach__retrieve_remote_branches(void) void test_refs_branches_foreach__retrieve_local_branches(void) { - assert_retrieval(GIT_BRANCH_LOCAL, 8); + assert_retrieval(GIT_BRANCH_LOCAL, 9); } struct expectations { diff --git a/tests-clar/refs/branches/tracking.c b/tests-clar/refs/branches/tracking.c new file mode 100644 index 000000000..8f7019437 --- /dev/null +++ b/tests-clar/refs/branches/tracking.c @@ -0,0 +1,69 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *repo; +static git_reference *branch; + +void test_refs_branches_tracking__initialize(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + branch = NULL; +} + +void test_refs_branches_tracking__cleanup(void) +{ + git_reference_free(branch); + + git_repository_free(repo); +} + +void test_refs_branches_tracking__can_retrieve_the_remote_tracking_reference_of_a_local_branch(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + + cl_git_pass(git_branch_tracking(&tracking, branch)); + + cl_assert_equal_s("refs/remotes/test/master", git_reference_name(tracking)); + + git_reference_free(branch); + git_reference_free(tracking); +} + +void test_refs_branches_tracking__can_retrieve_the_local_tracking_reference_of_a_local_branch(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/track-local")); + + cl_git_pass(git_branch_tracking(&tracking, branch)); + + cl_assert_equal_s("refs/heads/master", git_reference_name(tracking)); + + git_reference_free(branch); + git_reference_free(tracking); +} + +void test_refs_branches_tracking__cannot_retrieve_a_remote_tracking_reference_from_a_non_branch(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b")); + + cl_git_fail(git_branch_tracking(&tracking, branch)); + + git_reference_free(branch); +} + +void test_refs_branches_tracking__trying_to_retrieve_a_remote_tracking_reference_from_a_plain_local_branch_returns_GIT_ENOTFOUND(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/subtrees")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_tracking(&tracking, branch)); + + git_reference_free(branch); +} diff --git a/tests-clar/refs/foreachglob.c b/tests-clar/refs/foreachglob.c index d1412a94b..b024d36d4 100644 --- a/tests-clar/refs/foreachglob.c +++ b/tests-clar/refs/foreachglob.c @@ -46,7 +46,7 @@ static void assert_retrieval(const char *glob, unsigned int flags, int expected_ void test_refs_foreachglob__retrieve_all_refs(void) { /* 7 heads (including one packed head) + 1 note + 2 remotes + 6 tags */ - assert_retrieval("*", GIT_REF_LISTALL, 17); + assert_retrieval("*", GIT_REF_LISTALL, 18); } void test_refs_foreachglob__retrieve_remote_branches(void) @@ -56,7 +56,7 @@ void test_refs_foreachglob__retrieve_remote_branches(void) void test_refs_foreachglob__retrieve_local_branches(void) { - assert_retrieval("refs/heads/*", GIT_REF_LISTALL, 8); + assert_retrieval("refs/heads/*", GIT_REF_LISTALL, 9); } void test_refs_foreachglob__retrieve_partially_named_references(void) diff --git a/tests-clar/resources/testrepo.git/config b/tests-clar/resources/testrepo.git/config index b4fdac6c2..6b03dacb5 100644 --- a/tests-clar/resources/testrepo.git/config +++ b/tests-clar/resources/testrepo.git/config @@ -10,3 +10,6 @@ [branch "master"] remote = test merge = refs/heads/master +[branch "track-local"] + remote = . + merge = refs/heads/master diff --git a/tests-clar/resources/testrepo.git/refs/heads/track-local b/tests-clar/resources/testrepo.git/refs/heads/track-local new file mode 100644 index 000000000..f37febb2c --- /dev/null +++ b/tests-clar/resources/testrepo.git/refs/heads/track-local @@ -0,0 +1 @@ +9fd738e8f7967c078dceed8190330fc8648ee56a From ef4d795ec5f8cd39de72cfbc75565236205833a4 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 20 Jul 2012 16:39:22 +0200 Subject: [PATCH 28/48] refs: drop git_reference_remote_tracking_from_branch() --- include/git2/refs.h | 21 --------- src/refs.c | 74 -------------------------------- tests-clar/refs/remotetracking.c | 49 --------------------- 3 files changed, 144 deletions(-) delete mode 100644 tests-clar/refs/remotetracking.c diff --git a/include/git2/refs.h b/include/git2/refs.h index ac876ebb0..8dd8e3116 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -363,27 +363,6 @@ GIT_EXTERN(int) git_reference_foreach_glob( */ GIT_EXTERN(int) git_reference_has_log(git_reference *ref); - -/** - * Return the reference supporting the remote tracking branch, - * given a reference branch. - * - * The input reference has to be located in the `refs/heads` - * namespace. - * - * @param tracking_ref Pointer where to store the retrieved - * reference. - * - * @param branch_ref A git local branch reference. - * - * @return 0 on success; GIT_ENOTFOUND when no remote tracking - * reference exists, otherwise an error code. - */ -GIT_EXTERN(int) git_reference_remote_tracking_from_branch( - git_reference **tracking_ref, - git_reference *branch_ref -); - /** * Check if a reference is a local branch. * diff --git a/src/refs.c b/src/refs.c index d08ea9604..32f54fc31 100644 --- a/src/refs.c +++ b/src/refs.c @@ -11,7 +11,6 @@ #include "fileops.h" #include "pack.h" #include "reflog.h" -#include "config.h" #include #include @@ -1816,79 +1815,6 @@ int git_reference_has_log( return result; } -//TODO: How about also taking care of local tracking branches? -//cf. http://alblue.bandlem.com/2011/07/git-tip-of-week-tracking-branches.html -int git_reference_remote_tracking_from_branch( - git_reference **tracking_ref, - git_reference *branch_ref) -{ - git_config *config = NULL; - const char *name, *remote, *merge; - git_buf buf = GIT_BUF_INIT; - int error = -1; - - assert(tracking_ref && branch_ref); - - name = git_reference_name(branch_ref); - - if (git__prefixcmp(name, GIT_REFS_HEADS_DIR)) { - giterr_set( - GITERR_INVALID, - "Failed to retrieve tracking reference - '%s' is not a branch.", - name); - return -1; - } - - if (git_repository_config(&config, branch_ref->owner) < 0) - return -1; - - if (git_buf_printf( - &buf, - "branch.%s.remote", - name + strlen(GIT_REFS_HEADS_DIR)) < 0) - goto cleanup; - - if ((error = git_config_get_string(&remote, config, git_buf_cstr(&buf))) < 0) - goto cleanup; - - error = -1; - - git_buf_clear(&buf); - - //TODO: Is it ok to fail when no merge target is found? - if (git_buf_printf( - &buf, - "branch.%s.merge", - name + strlen(GIT_REFS_HEADS_DIR)) < 0) - goto cleanup; - - if (git_config_get_string(&merge, config, git_buf_cstr(&buf)) < 0) - goto cleanup; - - //TODO: Should we test this? - if (git__prefixcmp(merge, GIT_REFS_HEADS_DIR)) - goto cleanup; - - git_buf_clear(&buf); - - if (git_buf_printf( - &buf, - "refs/remotes/%s/%s", - remote, - merge + strlen(GIT_REFS_HEADS_DIR)) < 0) - goto cleanup; - - error = git_reference_lookup( - tracking_ref, - branch_ref->owner, - git_buf_cstr(&buf)); - -cleanup: - git_config_free(config); - git_buf_free(&buf); - return error; -} - int git_reference_is_branch(git_reference *ref) { assert(ref); diff --git a/tests-clar/refs/remotetracking.c b/tests-clar/refs/remotetracking.c deleted file mode 100644 index c4ec588ee..000000000 --- a/tests-clar/refs/remotetracking.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *g_repo; - -void test_refs_remotetracking__initialize(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); -} - -void test_refs_remotetracking__cleanup(void) -{ - git_repository_free(g_repo); -} - -void test_refs_remotetracking__unfound_returns_GIT_ENOTFOUND(void) -{ - git_reference *branch, *tracking; - - cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/subtrees")); - - cl_assert_equal_i(GIT_ENOTFOUND, git_reference_remote_tracking_from_branch(&tracking, branch)); - - git_reference_free(branch); -} - -void test_refs_remotetracking__retrieving_from_a_non_head_fails(void) -{ - git_reference *branch, *tracking; - - cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/tags/e90810b")); - - cl_git_fail(git_reference_remote_tracking_from_branch(&tracking, branch)); - - git_reference_free(branch); -} - -void test_refs_remotetracking__can_retrieve_a_remote_tracking_branch_reference(void) -{ - git_reference *branch, *tracking; - - cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/master")); - - cl_git_pass(git_reference_remote_tracking_from_branch(&tracking, branch)); - - cl_assert_equal_s("refs/remotes/test/master", git_reference_name(tracking)); - - git_reference_free(branch); - git_reference_free(tracking); -} From 786a17cd282cf81c76c45a8e62f2a1003235a673 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 20 Jul 2012 16:41:41 +0200 Subject: [PATCH 29/48] branch: enforce git_branch_delete() parameter checking --- src/branch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/branch.c b/src/branch.c index d0ebb2dc3..d11eca8da 100644 --- a/src/branch.c +++ b/src/branch.c @@ -110,6 +110,7 @@ int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_ git_reference *head = NULL; int error; + assert(repo && branch_name); assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE)); if ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0) From 1cb157184b6547b613a008455ba386425bb38a23 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 16 Jul 2012 15:14:29 +0200 Subject: [PATCH 30/48] tests: reorganize reflog tests --- tests-clar/refs/{ => reflog}/reflog.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) rename tests-clar/refs/{ => reflog}/reflog.c (93%) diff --git a/tests-clar/refs/reflog.c b/tests-clar/refs/reflog/reflog.c similarity index 93% rename from tests-clar/refs/reflog.c rename to tests-clar/refs/reflog/reflog.c index 05f3786bb..45da8c338 100644 --- a/tests-clar/refs/reflog.c +++ b/tests-clar/refs/reflog/reflog.c @@ -24,22 +24,22 @@ static void assert_signature(git_signature *expected, git_signature *actual) // Fixture setup and teardown -void test_refs_reflog__initialize(void) +void test_refs_reflog_reflog__initialize(void) { g_repo = cl_git_sandbox_init("testrepo.git"); } -void test_refs_reflog__cleanup(void) +void test_refs_reflog_reflog__cleanup(void) { cl_git_sandbox_cleanup(); } -void test_refs_reflog__write_then_read(void) +void test_refs_reflog_reflog__write_then_read(void) { // write a reflog for a given reference and ensure it can be read back - git_repository *repo2; + git_repository *repo2; git_reference *ref, *lookedup_ref; git_oid oid; git_signature *committer; @@ -94,7 +94,7 @@ void test_refs_reflog__write_then_read(void) git_reference_free(lookedup_ref); } -void test_refs_reflog__dont_write_bad(void) +void test_refs_reflog_reflog__dont_write_bad(void) { // avoid writing an obviously wrong reflog git_reference *ref; @@ -122,7 +122,7 @@ void test_refs_reflog__dont_write_bad(void) git_reference_free(ref); } -void test_refs_reflog__renaming_the_reference_moves_the_reflog(void) +void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) { git_reference *master; git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT; @@ -145,6 +145,7 @@ void test_refs_reflog__renaming_the_reference_moves_the_reflog(void) git_buf_free(&moved_log_path); git_buf_free(&master_log_path); } + static void assert_has_reflog(bool expected_result, const char *name) { git_reference *ref; @@ -156,7 +157,7 @@ static void assert_has_reflog(bool expected_result, const char *name) git_reference_free(ref); } -void test_refs_reflog__reference_has_reflog(void) +void test_refs_reflog_reflog__reference_has_reflog(void) { assert_has_reflog(true, "HEAD"); assert_has_reflog(true, "refs/heads/master"); From 7c458e3aee7b39bfec368456d494972fe9ae244b Mon Sep 17 00:00:00 2001 From: nulltoken Date: Tue, 17 Jul 2012 10:53:19 +0200 Subject: [PATCH 31/48] reflog: add GIT_OID_HEX_ZERO constant --- src/reflog.c | 2 +- src/reflog.h | 2 ++ tests-clar/refs/reflog/reflog.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/reflog.c b/src/reflog.c index 004ba936d..0e0758381 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -258,7 +258,7 @@ int git_reflog_write(git_reference *ref, const git_oid *oid_old, if (oid_old) git_oid_tostr(old, sizeof(old), oid_old); else - p_snprintf(old, sizeof(old), "%0*d", GIT_OID_HEXSZ, 0); + memmove(old, GIT_OID_HEX_ZERO, sizeof(old)); error = reflog_write(log_path.ptr, old, new, committer, msg); diff --git a/src/reflog.h b/src/reflog.h index 33cf0776c..fe2891909 100644 --- a/src/reflog.h +++ b/src/reflog.h @@ -17,6 +17,8 @@ #define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) +#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000" + struct git_reflog_entry { git_oid oid_old; git_oid oid_cur; diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c index 45da8c338..fb69dd2f1 100644 --- a/tests-clar/refs/reflog/reflog.c +++ b/tests-clar/refs/reflog/reflog.c @@ -73,7 +73,7 @@ void test_refs_reflog_reflog__write_then_read(void) entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 0); assert_signature(committer, entry->committer); git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_old); - cl_assert_equal_s("0000000000000000000000000000000000000000", oid_str); + cl_assert_equal_s(GIT_OID_HEX_ZERO, oid_str); git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_cur); cl_assert_equal_s(current_master_tip, oid_str); cl_assert(entry->msg == NULL); From 59341a5d5960b13801404d3690f6bcf27e91efa6 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 16 Jul 2012 18:31:22 +0200 Subject: [PATCH 32/48] reflog: introduce git_reflog_entry_drop() --- include/git2/reflog.h | 20 ++++++ src/reflog.c | 62 +++++++++++++++++-- tests-clar/refs/reflog/drop.c | 111 ++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 tests-clar/refs/reflog/drop.c diff --git a/include/git2/reflog.h b/include/git2/reflog.h index 8acba349b..7467e81ed 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -86,6 +86,26 @@ GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog); */ GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx); +/** + * Remove an entry from the reflog by its index + * + * To ensure there's no gap in the log history, set the `rewrite_previosu_entry` to 1. + * When deleting entry `n`, member old_oid of entry `n-1` (if any) will be updated with + * the value of memeber new_oid of entry `n+1`. + * + * @param reflog a previously loaded reflog. + * + * @param idx the position of the entry to remove. + * + * @param rewrite_previous_entry 1 to rewrite the history; 0 otherwise. + * + * @return 0 on success or an error code. + */ +GIT_EXTERN(int) git_reflog_entry_drop( + git_reflog *reflog, + unsigned int idx, + int rewrite_previous_entry); + /** * Get the old oid * diff --git a/src/reflog.c b/src/reflog.c index 0e0758381..8e9d973d3 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -160,6 +160,14 @@ fail: return -1; } +static void reflog_entry_free(git_reflog_entry *entry) +{ + git_signature_free(entry->committer); + + git__free(entry->msg); + git__free(entry); +} + void git_reflog_free(git_reflog *reflog) { unsigned int i; @@ -168,10 +176,7 @@ void git_reflog_free(git_reflog *reflog) for (i=0; i < reflog->entries.length; i++) { entry = git_vector_get(&reflog->entries, i); - git_signature_free(entry->committer); - - git__free(entry->msg); - git__free(entry); + reflog_entry_free(entry); } git_vector_free(&reflog->entries); @@ -370,3 +375,52 @@ char * git_reflog_entry_msg(const git_reflog_entry *entry) assert(entry); return entry->msg; } + +int git_reflog_entry_drop( + git_reflog *reflog, + unsigned int idx, + int rewrite_previous_entry) +{ + unsigned int entrycount; + git_reflog_entry *entry, *previous; + + assert(reflog); + + entrycount = git_reflog_entrycount(reflog); + + if (idx >= entrycount) + return GIT_ENOTFOUND; + + entry = git_vector_get(&reflog->entries, idx); + reflog_entry_free(entry); + + if (git_vector_remove(&reflog->entries, idx) < 0) + return -1; + + if (!rewrite_previous_entry) + return 0; + + /* No need to rewrite anything when removing the first entry */ + if (idx == 0) + return 0; + + /* There are no more entries in the log */ + if (entrycount == 1) + return 0; + + entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); + + /* If the last entry has just been removed... */ + if (idx == entrycount - 1) { + /* ...clear the oid_old member of the "new" last entry */ + if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) + return -1; + + return 0; + } + + previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + + return 0; +} diff --git a/tests-clar/refs/reflog/drop.c b/tests-clar/refs/reflog/drop.c new file mode 100644 index 000000000..be404947e --- /dev/null +++ b/tests-clar/refs/reflog/drop.c @@ -0,0 +1,111 @@ +#include "clar_libgit2.h" + +#include "reflog.h" + +static git_repository *g_repo; +static git_reflog *g_reflog; +static unsigned int entrycount; + +void test_refs_reflog_drop__initialize(void) +{ + git_reference *ref; + + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + + git_reflog_read(&g_reflog, ref); + entrycount = git_reflog_entrycount(g_reflog); + + git_reference_free(ref); +} + +void test_refs_reflog_drop__cleanup(void) +{ + git_reflog_free(g_reflog); + + cl_git_sandbox_cleanup(); +} + +void test_refs_reflog_drop__dropping_a_non_exisiting_entry_from_the_log_returns_ENOTFOUND(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_reflog_entry_drop(g_reflog, entrycount, 0)); + + cl_assert_equal_i(entrycount, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_drop_an_entry(void) +{ + cl_assert(entrycount > 4); + + cl_git_pass(git_reflog_entry_drop(g_reflog, 2, 0)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void) +{ + const git_reflog_entry *before_previous, *before_next; + const git_reflog_entry *after_next; + git_oid before_next_old_oid; + + cl_assert(entrycount > 4); + + before_previous = git_reflog_entry_byindex(g_reflog, 3); + before_next = git_reflog_entry_byindex(g_reflog, 1); + git_oid_cpy(&before_next_old_oid, &before_next->oid_old); + + cl_git_pass(git_reflog_entry_drop(g_reflog, 2, 1)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + + after_next = git_reflog_entry_byindex(g_reflog, 1); + + cl_assert_equal_i(0, git_oid_cmp(&before_next->oid_cur, &after_next->oid_cur)); + cl_assert(git_oid_cmp(&before_next_old_oid, &after_next->oid_old) != 0); + cl_assert_equal_i(0, git_oid_cmp(&before_previous->oid_cur, &after_next->oid_old)); +} + +void test_refs_reflog_drop__can_drop_the_first_entry(void) +{ + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_entry_drop(g_reflog, 0, 0)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_drop_the_last_entry(void) +{ + const git_reflog_entry *entry; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_entry_drop(g_reflog, entrycount - 1, 0)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + + entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) != 0); +} + +void test_refs_reflog_drop__can_drop_the_last_entry_and_rewrite_the_log_history(void) +{ + const git_reflog_entry *entry; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_entry_drop(g_reflog, entrycount - 1, 1)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + + entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); +} + +void test_refs_reflog_drop__can_drop_all_the_entries(void) +{ + cl_assert(--entrycount > 0); + + do { + cl_git_pass(git_reflog_entry_drop(g_reflog, --entrycount, 1)); + } while (entrycount > 0); + + cl_git_pass(git_reflog_entry_drop(g_reflog, 0, 1)); + + cl_assert_equal_i(0, git_reflog_entrycount(g_reflog)); +} From d284b3de631edeaa651bf3ee2c5963cb970016c4 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 16 Jul 2012 12:12:53 +0200 Subject: [PATCH 33/48] reflog: rename git_reflog_write() to git_reflog_append() --- include/git2/reflog.h | 4 ++-- src/reflog.c | 2 +- tests-clar/refs/reflog/reflog.c | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/git2/reflog.h b/include/git2/reflog.h index 7467e81ed..1de870bba 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -33,7 +33,7 @@ GIT_BEGIN_DECL GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref); /** - * Write a new reflog for the given reference + * Add a new entry to the reflog for the given reference * * If there is no reflog file for the given * reference yet, it will be created. @@ -48,7 +48,7 @@ GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref); * @param msg the reflog message * @return 0 or an error code */ -GIT_EXTERN(int) git_reflog_write(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg); +GIT_EXTERN(int) git_reflog_append(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg); /** * Rename the reflog for the given reference diff --git a/src/reflog.c b/src/reflog.c index 8e9d973d3..b2820cd3e 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -216,7 +216,7 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) return error; } -int git_reflog_write(git_reference *ref, const git_oid *oid_old, +int git_reflog_append(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg) { int error; diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c index fb69dd2f1..08b7754be 100644 --- a/tests-clar/refs/reflog/reflog.c +++ b/tests-clar/refs/reflog/reflog.c @@ -36,7 +36,7 @@ void test_refs_reflog_reflog__cleanup(void) -void test_refs_reflog_reflog__write_then_read(void) +void test_refs_reflog_reflog__append_then_read(void) { // write a reflog for a given reference and ensure it can be read back git_repository *repo2; @@ -55,10 +55,10 @@ void test_refs_reflog_reflog__write_then_read(void) cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); - cl_git_pass(git_reflog_write(ref, NULL, committer, NULL)); - cl_git_fail(git_reflog_write(ref, NULL, committer, "no ancestor NULL for an existing reflog")); - cl_git_fail(git_reflog_write(ref, NULL, committer, "no\nnewline")); - cl_git_pass(git_reflog_write(ref, &oid, committer, commit_msg)); + cl_git_pass(git_reflog_append(ref, NULL, committer, NULL)); + cl_git_fail(git_reflog_append(ref, NULL, committer, "no ancestor NULL for an existing reflog")); + cl_git_fail(git_reflog_append(ref, NULL, committer, "no\nnewline")); + cl_git_pass(git_reflog_append(ref, &oid, committer, commit_msg)); /* Reopen a new instance of the repository */ cl_git_pass(git_repository_open(&repo2, "testrepo.git")); @@ -94,7 +94,7 @@ void test_refs_reflog_reflog__write_then_read(void) git_reference_free(lookedup_ref); } -void test_refs_reflog_reflog__dont_write_bad(void) +void test_refs_reflog_reflog__dont_append_bad(void) { // avoid writing an obviously wrong reflog git_reference *ref; @@ -110,12 +110,12 @@ void test_refs_reflog_reflog__dont_write_bad(void) cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); /* Write the reflog for the new branch */ - cl_git_pass(git_reflog_write(ref, NULL, committer, NULL)); + cl_git_pass(git_reflog_append(ref, NULL, committer, NULL)); /* Try to update the reflog with wrong information: * It's no new reference, so the ancestor OID cannot * be NULL. */ - cl_git_fail(git_reflog_write(ref, NULL, committer, NULL)); + cl_git_fail(git_reflog_append(ref, NULL, committer, NULL)); git_signature_free(committer); From bd72425d16fce9771af7727029f7d8ea8c2e98d2 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 18 Jul 2012 20:12:45 +0200 Subject: [PATCH 34/48] reflog: introduce git_reflog_write() --- include/git2/reflog.h | 9 ++ src/reflog.c | 170 +++++++++++++++++++++----------- src/reflog.h | 1 + tests-clar/refs/reflog/drop.c | 19 ++++ tests-clar/refs/reflog/reflog.c | 8 +- 5 files changed, 146 insertions(+), 61 deletions(-) diff --git a/include/git2/reflog.h b/include/git2/reflog.h index 1de870bba..9d04688e2 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -32,6 +32,15 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref); +/** + * Write an existing in-memory reflog object back to disk + * using an atomic file lock. + * + * @param reflog an existing reflog object + * @return 0 or an error code + */ +GIT_EXTERN(int) git_reflog_write(git_reflog *reflog); + /** * Add a new entry to the reflog for the given reference * diff --git a/src/reflog.c b/src/reflog.c index b2820cd3e..dbac28aff 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -28,65 +28,83 @@ static int reflog_init(git_reflog **reflog, git_reference *ref) return -1; } + log->owner = git_reference_owner(ref); *reflog = log; return 0; } -static int reflog_write(const char *log_path, const char *oid_old, - const char *oid_new, const git_signature *committer, - const char *msg) +static int serialize_reflog_entry( + git_buf *buf, + const git_oid *oid_old, + const git_oid *oid_new, + const git_signature *committer, + const char *msg) { - int error; - git_buf log = GIT_BUF_INIT; - git_filebuf fbuf = GIT_FILEBUF_INIT; - bool trailing_newline = false; + char raw_old[GIT_OID_HEXSZ+1]; + char raw_new[GIT_OID_HEXSZ+1]; - assert(log_path && oid_old && oid_new && committer); + git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); + git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); + + git_buf_clear(buf); + + git_buf_puts(buf, raw_old); + git_buf_putc(buf, ' '); + git_buf_puts(buf, raw_new); + + git_signature__writebuf(buf, " ", committer); + + /* drop trailing LF */ + git_buf_rtrim(buf); if (msg) { const char *newline = strchr(msg, '\n'); - if (newline) { - if (*(newline + 1) == '\0') - trailing_newline = true; - else { - giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); - return -1; - } + + if (newline && newline[1] != '\0') { + giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); + return -1; } + + git_buf_putc(buf, '\t'); + git_buf_puts(buf, msg); + + /* drop potential trailing LF */ + git_buf_rtrim(buf); } - git_buf_puts(&log, oid_old); - git_buf_putc(&log, ' '); + git_buf_putc(buf, '\n'); - git_buf_puts(&log, oid_new); + return git_buf_oom(buf); +} - git_signature__writebuf(&log, " ", committer); - git_buf_truncate(&log, log.size - 1); /* drop LF */ +static int reflog_write(const char *log_path, const git_oid *oid_old, + const git_oid *oid_new, const git_signature *committer, + const char *msg) +{ + int error = -1; + git_buf log = GIT_BUF_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; - if (msg) { - git_buf_putc(&log, '\t'); - git_buf_puts(&log, msg); - } + assert(log_path && oid_old && oid_new && committer); - if (!trailing_newline) - git_buf_putc(&log, '\n'); + if (serialize_reflog_entry(&log, oid_old, oid_new, committer, msg) < 0) + goto cleanup; - if (git_buf_oom(&log)) { - git_buf_free(&log); - return -1; - } + if ((error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND)) < 0) + goto cleanup; - error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND); - if (!error) { - if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) - git_filebuf_cleanup(&fbuf); - else - error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); - } + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + goto cleanup; + error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); + goto success; + +cleanup: + git_filebuf_cleanup(&fbuf); + +success: git_buf_free(&log); - return error; } @@ -184,6 +202,12 @@ void git_reflog_free(git_reflog *reflog) git__free(reflog); } +static int retrieve_reflog_path(git_buf *path, git_reference *ref) +{ + return git_buf_join_n(path, '/', 3, + git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name); +} + int git_reflog_read(git_reflog **reflog, git_reference *ref) { int error; @@ -196,8 +220,7 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) if (reflog_init(&log, ref) < 0) return -1; - error = git_buf_join_n(&log_path, '/', 3, - ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + error = retrieve_reflog_path(&log_path, ref); if (!error) error = git_futils_readbuffer(&log_file, log_path.ptr); @@ -216,14 +239,53 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) return error; } +int git_reflog_write(git_reflog *reflog) +{ + int error = -1; + unsigned int i; + git_reflog_entry *entry; + git_buf log_path = GIT_BUF_INIT; + git_buf log = GIT_BUF_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; + + assert(reflog); + + + if (git_buf_join_n(&log_path, '/', 3, + git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0) + return -1; + + if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0) + goto cleanup; + + git_vector_foreach(&reflog->entries, i, entry) { + if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) + goto cleanup; + + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); + goto success; + +cleanup: + git_filebuf_cleanup(&fbuf); + +success: + git_buf_free(&log); + git_buf_free(&log_path); + return error; +} + int git_reflog_append(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg) { int error; - char old[GIT_OID_HEXSZ+1]; - char new[GIT_OID_HEXSZ+1]; + git_buf log_path = GIT_BUF_INIT; git_reference *r; + git_oid zero_oid; const git_oid *oid; if ((error = git_reference_resolve(&r, ref)) < 0) @@ -237,12 +299,7 @@ int git_reflog_append(git_reference *ref, const git_oid *oid_old, return -1; } - git_oid_tostr(new, GIT_OID_HEXSZ+1, oid); - - git_reference_free(r); - - error = git_buf_join_n(&log_path, '/', 3, - ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + error = retrieve_reflog_path(&log_path, ref); if (error < 0) goto cleanup; @@ -260,14 +317,14 @@ int git_reflog_append(git_reference *ref, const git_oid *oid_old, if (error < 0) goto cleanup; - if (oid_old) - git_oid_tostr(old, sizeof(old), oid_old); - else - memmove(old, GIT_OID_HEX_ZERO, sizeof(old)); - - error = reflog_write(log_path.ptr, old, new, committer, msg); + if (!oid_old) { + git_oid_fromstr(&zero_oid, GIT_OID_HEX_ZERO); + error = reflog_write(log_path.ptr, &zero_oid, oid, committer, msg); + } else + error = reflog_write(log_path.ptr, oid_old, oid, committer, msg); cleanup: + git_reference_free(r); git_buf_free(&log_path); return error; } @@ -281,7 +338,7 @@ int git_reflog_rename(git_reference *ref, const char *new_name) assert(ref && new_name); - if (git_buf_joinpath(&temp_path, ref->owner->path_repository, GIT_REFLOG_DIR) < 0) + if (git_buf_joinpath(&temp_path, git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR) < 0) return -1; if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->name) < 0) @@ -329,8 +386,7 @@ int git_reflog_delete(git_reference *ref) int error; git_buf path = GIT_BUF_INIT; - error = git_buf_join_n( - &path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + error = retrieve_reflog_path(&path, ref); if (!error && git_path_exists(path.ptr)) error = p_unlink(path.ptr); diff --git a/src/reflog.h b/src/reflog.h index fe2891909..3bbdf6e10 100644 --- a/src/reflog.h +++ b/src/reflog.h @@ -30,6 +30,7 @@ struct git_reflog_entry { struct git_reflog { char *ref_name; + git_repository *owner; git_vector entries; }; diff --git a/tests-clar/refs/reflog/drop.c b/tests-clar/refs/reflog/drop.c index be404947e..3aa99fe09 100644 --- a/tests-clar/refs/reflog/drop.c +++ b/tests-clar/refs/reflog/drop.c @@ -109,3 +109,22 @@ void test_refs_reflog_drop__can_drop_all_the_entries(void) cl_assert_equal_i(0, git_reflog_entrycount(g_reflog)); } + +void test_refs_reflog_drop__can_persist_deletion_on_disk(void) +{ + git_reference *ref; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reference_lookup(&ref, g_repo, g_reflog->ref_name)); + cl_git_pass(git_reflog_entry_drop(g_reflog, entrycount - 1, 1)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + cl_git_pass(git_reflog_write(g_reflog)); + + git_reflog_free(g_reflog); + + git_reflog_read(&g_reflog, ref); + git_reference_free(ref); + + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); +} diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c index 08b7754be..ac61f1343 100644 --- a/tests-clar/refs/reflog/reflog.c +++ b/tests-clar/refs/reflog/reflog.c @@ -7,7 +7,7 @@ static const char *new_ref = "refs/heads/test-reflog"; static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; -static const char *commit_msg = "commit: bla bla"; +#define commit_msg "commit: bla bla" static git_repository *g_repo; @@ -57,13 +57,13 @@ void test_refs_reflog_reflog__append_then_read(void) cl_git_pass(git_reflog_append(ref, NULL, committer, NULL)); cl_git_fail(git_reflog_append(ref, NULL, committer, "no ancestor NULL for an existing reflog")); - cl_git_fail(git_reflog_append(ref, NULL, committer, "no\nnewline")); - cl_git_pass(git_reflog_append(ref, &oid, committer, commit_msg)); + cl_git_fail(git_reflog_append(ref, NULL, committer, "no inner\nnewline")); + cl_git_pass(git_reflog_append(ref, &oid, committer, commit_msg "\n")); /* Reopen a new instance of the repository */ cl_git_pass(git_repository_open(&repo2, "testrepo.git")); - /* Lookup the preivously created branch */ + /* Lookup the previously created branch */ cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref)); /* Read and parse the reflog for this branch */ From ae8331784eb968169e03099a5803a236a6a5aed4 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 21 Jul 2012 12:32:02 +0200 Subject: [PATCH 35/48] reflog: prevent git_reflog_read() from chocking when no log exists yet --- include/git2/reflog.h | 4 ++++ src/reflog.c | 25 ++++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/include/git2/reflog.h b/include/git2/reflog.h index 9d04688e2..ae8bb8657 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -23,6 +23,10 @@ GIT_BEGIN_DECL /** * Read the reflog for the given reference * + * If there is no reflog file for the given + * reference yet, an empty reflog object will + * be returned. + * * The reflog must be freed manually by using * git_reflog_free(). * diff --git a/src/reflog.c b/src/reflog.c index dbac28aff..9007bd379 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -217,22 +217,29 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) *reflog = NULL; + assert(reflog && ref); + if (reflog_init(&log, ref) < 0) return -1; - error = retrieve_reflog_path(&log_path, ref); + if (retrieve_reflog_path(&log_path, ref) < 0) + goto cleanup; - if (!error) - error = git_futils_readbuffer(&log_file, log_path.ptr); + error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path)); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; - if (!error) - error = reflog_parse(log, log_file.ptr, log_file.size); + if ((error = reflog_parse(log, + git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0) + goto cleanup; - if (!error) - *reflog = log; - else - git_reflog_free(log); + *reflog = log; + goto success; +cleanup: + git_reflog_free(log); + +success: git_buf_free(&log_file); git_buf_free(&log_path); From 40c75652d075f87f20ddfbb715667f82644bc760 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 21 Jul 2012 12:33:46 +0200 Subject: [PATCH 36/48] reflog: prevent git_reflog_append() from persisting the reflog back to disk --- include/git2/reflog.h | 13 +-- src/reflog.c | 138 ++++++++++++++------------------ tests-clar/refs/reflog/reflog.c | 84 ++++++++----------- 3 files changed, 97 insertions(+), 138 deletions(-) diff --git a/include/git2/reflog.h b/include/git2/reflog.h index ae8bb8657..a314f94c2 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -46,22 +46,17 @@ GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref); GIT_EXTERN(int) git_reflog_write(git_reflog *reflog); /** - * Add a new entry to the reflog for the given reference - * - * If there is no reflog file for the given - * reference yet, it will be created. - * - * `oid_old` may be NULL in case it's a new reference. + * Add a new entry to the reflog. * * `msg` is optional and can be NULL. * - * @param ref the changed reference - * @param oid_old the OID the reference was pointing to + * @param reflog an existing reflog object + * @param new_oid the OID the reference is now pointing to * @param committer the signature of the committer * @param msg the reflog message * @return 0 or an error code */ -GIT_EXTERN(int) git_reflog_append(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg); +GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg); /** * Rename the reflog for the given reference diff --git a/src/reflog.c b/src/reflog.c index 9007bd379..ef0aa7eca 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -59,18 +59,8 @@ static int serialize_reflog_entry( git_buf_rtrim(buf); if (msg) { - const char *newline = strchr(msg, '\n'); - - if (newline && newline[1] != '\0') { - giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); - return -1; - } - git_buf_putc(buf, '\t'); git_buf_puts(buf, msg); - - /* drop potential trailing LF */ - git_buf_rtrim(buf); } git_buf_putc(buf, '\n'); @@ -78,34 +68,28 @@ static int serialize_reflog_entry( return git_buf_oom(buf); } -static int reflog_write(const char *log_path, const git_oid *oid_old, - const git_oid *oid_new, const git_signature *committer, - const char *msg) +static int reflog_entry_new(git_reflog_entry **entry) { - int error = -1; - git_buf log = GIT_BUF_INIT; - git_filebuf fbuf = GIT_FILEBUF_INIT; + git_reflog_entry *e; - assert(log_path && oid_old && oid_new && committer); + assert(entry); - if (serialize_reflog_entry(&log, oid_old, oid_new, committer, msg) < 0) - goto cleanup; + e = git__malloc(sizeof(git_reflog_entry)); + GITERR_CHECK_ALLOC(e); - if ((error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND)) < 0) - goto cleanup; + memset(e, 0, sizeof(git_reflog_entry)); - if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) - goto cleanup; + *entry = e; - error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); - goto success; + return 0; +} -cleanup: - git_filebuf_cleanup(&fbuf); +static void reflog_entry_free(git_reflog_entry *entry) +{ + git_signature_free(entry->committer); -success: - git_buf_free(&log); - return error; + git__free(entry->msg); + git__free(entry); } static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) @@ -123,8 +107,8 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) } while (0) while (buf_size > GIT_REFLOG_SIZE_MIN) { - entry = git__malloc(sizeof(git_reflog_entry)); - GITERR_CHECK_ALLOC(entry); + if (reflog_entry_new(&entry) < 0) + return -1; entry->committer = git__malloc(sizeof(git_signature)); GITERR_CHECK_ALLOC(entry->committer); @@ -171,21 +155,12 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) #undef seek_forward fail: - if (entry) { - git__free(entry->committer); - git__free(entry); - } + if (entry) + reflog_entry_free(entry); + return -1; } -static void reflog_entry_free(git_reflog_entry *entry) -{ - git_signature_free(entry->committer); - - git__free(entry->msg); - git__free(entry); -} - void git_reflog_free(git_reflog *reflog) { unsigned int i; @@ -285,55 +260,58 @@ success: return error; } -int git_reflog_append(git_reference *ref, const git_oid *oid_old, +int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) { - int error; + int count; + git_reflog_entry *entry; + const char *newline; - git_buf log_path = GIT_BUF_INIT; - git_reference *r; - git_oid zero_oid; - const git_oid *oid; + assert(reflog && new_oid && committer); - if ((error = git_reference_resolve(&r, ref)) < 0) - return error; - - oid = git_reference_oid(r); - if (oid == NULL) { - giterr_set(GITERR_REFERENCE, - "Failed to write reflog. Cannot resolve reference `%s`", r->name); - git_reference_free(r); + if (reflog_entry_new(&entry) < 0) return -1; - } - error = retrieve_reflog_path(&log_path, ref); - if (error < 0) + if ((entry->committer = git_signature_dup(committer)) == NULL) goto cleanup; - if (git_path_exists(log_path.ptr) == false) { - error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE); - } else if (git_path_isfile(log_path.ptr) == false) { - giterr_set(GITERR_REFERENCE, - "Failed to write reflog. `%s` is directory", log_path.ptr); - error = -1; - } else if (oid_old == NULL) { - giterr_set(GITERR_REFERENCE, - "Failed to write reflog. Old OID cannot be NULL for existing reference"); - error = -1; + if (msg != NULL) { + if ((entry->msg = git__strdup(msg)) == NULL) + goto cleanup; + + newline = strchr(msg, '\n'); + + if (newline) { + if (newline[1] != '\0') { + giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); + goto cleanup; + } + + entry->msg[newline - msg] = '\0'; + } } - if (error < 0) + + count = git_reflog_entrycount(reflog); + + if (count == 0) + git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); + else { + const git_reflog_entry *previous; + + previous = git_reflog_entry_byindex(reflog, count -1); + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + } + + git_oid_cpy(&entry->oid_cur, new_oid); + + if (git_vector_insert(&reflog->entries, entry) < 0) goto cleanup; - if (!oid_old) { - git_oid_fromstr(&zero_oid, GIT_OID_HEX_ZERO); - error = reflog_write(log_path.ptr, &zero_oid, oid, committer, msg); - } else - error = reflog_write(log_path.ptr, oid_old, oid, committer, msg); + return 0; cleanup: - git_reference_free(r); - git_buf_free(&log_path); - return error; + reflog_entry_free(entry); + return -1; } int git_reflog_rename(git_reference *ref, const char *new_name) diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c index ac61f1343..ed3b31563 100644 --- a/tests-clar/refs/reflog/reflog.c +++ b/tests-clar/refs/reflog/reflog.c @@ -34,8 +34,6 @@ void test_refs_reflog_reflog__cleanup(void) cl_git_sandbox_cleanup(); } - - void test_refs_reflog_reflog__append_then_read(void) { // write a reflog for a given reference and ensure it can be read back @@ -44,21 +42,21 @@ void test_refs_reflog_reflog__append_then_read(void) git_oid oid; git_signature *committer; git_reflog *reflog; - git_reflog_entry *entry; - char oid_str[GIT_OID_HEXSZ+1]; + const git_reflog_entry *entry; /* Create a new branch pointing at the HEAD */ git_oid_fromstr(&oid, current_master_tip); cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &oid, 0)); - git_reference_free(ref); - cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); - cl_git_pass(git_reflog_append(ref, NULL, committer, NULL)); - cl_git_fail(git_reflog_append(ref, NULL, committer, "no ancestor NULL for an existing reflog")); - cl_git_fail(git_reflog_append(ref, NULL, committer, "no inner\nnewline")); - cl_git_pass(git_reflog_append(ref, &oid, committer, commit_msg "\n")); + cl_git_pass(git_reflog_read(&reflog, ref)); + + cl_git_fail(git_reflog_append(reflog, &oid, committer, "no inner\nnewline")); + cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL)); + cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n")); + cl_git_pass(git_reflog_write(reflog)); + git_reflog_free(reflog); /* Reopen a new instance of the repository */ cl_git_pass(git_repository_open(&repo2, "testrepo.git")); @@ -68,22 +66,18 @@ void test_refs_reflog_reflog__append_then_read(void) /* Read and parse the reflog for this branch */ cl_git_pass(git_reflog_read(&reflog, lookedup_ref)); - cl_assert(reflog->entries.length == 2); + cl_assert_equal_i(2, git_reflog_entrycount(reflog)); - entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 0); + entry = git_reflog_entry_byindex(reflog, 0); assert_signature(committer, entry->committer); - git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_old); - cl_assert_equal_s(GIT_OID_HEX_ZERO, oid_str); - git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_cur); - cl_assert_equal_s(current_master_tip, oid_str); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); + cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0); cl_assert(entry->msg == NULL); - entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 1); + entry = git_reflog_entry_byindex(reflog, 1); assert_signature(committer, entry->committer); - git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_old); - cl_assert_equal_s(current_master_tip, oid_str); - git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_cur); - cl_assert_equal_s(current_master_tip, oid_str); + cl_assert(git_oid_cmp(&oid, &entry->oid_old) == 0); + cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0); cl_assert_equal_s(commit_msg, entry->msg); git_signature_free(committer); @@ -94,34 +88,6 @@ void test_refs_reflog_reflog__append_then_read(void) git_reference_free(lookedup_ref); } -void test_refs_reflog_reflog__dont_append_bad(void) -{ - // avoid writing an obviously wrong reflog - git_reference *ref; - git_oid oid; - git_signature *committer; - - /* Create a new branch pointing at the HEAD */ - git_oid_fromstr(&oid, current_master_tip); - cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &oid, 0)); - git_reference_free(ref); - cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); - - cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); - - /* Write the reflog for the new branch */ - cl_git_pass(git_reflog_append(ref, NULL, committer, NULL)); - - /* Try to update the reflog with wrong information: - * It's no new reference, so the ancestor OID cannot - * be NULL. */ - cl_git_fail(git_reflog_append(ref, NULL, committer, NULL)); - - git_signature_free(committer); - - git_reference_free(ref); -} - void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) { git_reference *master; @@ -163,3 +129,23 @@ void test_refs_reflog_reflog__reference_has_reflog(void) assert_has_reflog(true, "refs/heads/master"); assert_has_reflog(false, "refs/heads/subtrees"); } + +void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_returns_an_empty_one(void) +{ + git_reference *subtrees; + git_reflog *reflog; + git_buf subtrees_log_path = GIT_BUF_INIT; + + cl_git_pass(git_reference_lookup(&subtrees, g_repo, "refs/heads/subtrees")); + + git_buf_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, git_reference_name(subtrees)); + cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&subtrees_log_path))); + + cl_git_pass(git_reflog_read(&reflog, subtrees)); + + cl_assert_equal_i(0, git_reflog_entrycount(reflog)); + + git_reflog_free(reflog); + git_reference_free(subtrees); + git_buf_free(&subtrees_log_path); +} From c3be5c5af089683b6c61d1d37d8c2c40ff48e9a8 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 21 Jul 2012 19:19:46 +0200 Subject: [PATCH 37/48] reflog: keep the reflog name in sync with the reference name --- src/reflog.c | 22 ++++++++++++++++++++++ tests-clar/refs/reflog/reflog.c | 21 +++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/reflog.c b/src/reflog.c index ef0aa7eca..f841b2174 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -183,6 +183,18 @@ static int retrieve_reflog_path(git_buf *path, git_reference *ref) git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name); } +int create_new_reflog_file(const char *filepath) +{ + int fd; + + if ((fd = p_open(filepath, + O_WRONLY | O_CREAT | O_TRUNC, + GIT_REFLOG_FILE_MODE)) < 0) + return -1; + + return p_close(fd); +} + int git_reflog_read(git_reflog **reflog, git_reference *ref) { int error; @@ -204,6 +216,10 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) if (error < 0 && error != GIT_ENOTFOUND) goto cleanup; + if ((error == GIT_ENOTFOUND) && + ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0)) + goto cleanup; + if ((error = reflog_parse(log, git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0) goto cleanup; @@ -237,6 +253,12 @@ int git_reflog_write(git_reflog *reflog) git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0) return -1; + if (!git_path_isfile(git_buf_cstr(&log_path))) { + giterr_set(GITERR_INVALID, + "Log file for reference '%s' doesn't exist.", reflog->ref_name); + goto cleanup; + } + if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0) goto cleanup; diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c index ed3b31563..20f08f523 100644 --- a/tests-clar/refs/reflog/reflog.c +++ b/tests-clar/refs/reflog/reflog.c @@ -149,3 +149,24 @@ void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_re git_reference_free(subtrees); git_buf_free(&subtrees_log_path); } + +void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) +{ + git_reference *master; + git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT; + git_reflog *reflog; + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reflog_read(&reflog, master)); + + cl_git_pass(git_reflog_write(reflog)); + + cl_git_pass(git_reference_rename(master, "refs/moved", 0)); + + cl_git_fail(git_reflog_write(reflog)); + + git_reflog_free(reflog); + git_reference_free(master); + git_buf_free(&moved_log_path); + git_buf_free(&master_log_path); +} From 1d733b573af9e3a987be96b31be5ac11d3517f66 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 25 Jul 2012 09:00:58 +0200 Subject: [PATCH 38/48] odb: add some documentation to the foreach() test --- tests-clar/odb/foreach.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests-clar/odb/foreach.c b/tests-clar/odb/foreach.c index 525c70c09..e5d01eafd 100644 --- a/tests-clar/odb/foreach.c +++ b/tests-clar/odb/foreach.c @@ -29,8 +29,18 @@ static int foreach_cb(git_oid *oid, void *data) return 0; } +/* + * $ git --git-dir tests-clar/resources/testrepo.git count-objects --verbose + * count: 43 + * size: 3 + * in-pack: 1640 + * packs: 3 + * size-pack: 425 + * prune-packable: 0 + * garbage: 0 + */ void test_odb_foreach__foreach(void) { cl_git_pass(git_odb_foreach(_odb, foreach_cb, NULL)); - cl_assert(nobj == 1683); + cl_assert_equal_i(43 + 1640, nobj); /* count + in-pack */ } From 0aeae70553beb06d8df288232f1a1d345631f3cd Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Wed, 25 Jul 2012 17:01:50 +0200 Subject: [PATCH 39/48] tests-clar/status: fix missing-prototype warning --- 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 bd57cf2b6..d84cb77ed 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -727,7 +727,7 @@ void test_status_worktree__filemode_changes(void) git_config_free(cfg); } -int cb_status__expected_path(const char *p, unsigned int s, void *payload) +static int cb_status__expected_path(const char *p, unsigned int s, void *payload) { const char *expected_path = (const char *)payload; From cb020f0d9936f221c6bd6f873994e8978657cd28 Mon Sep 17 00:00:00 2001 From: Sascha Cunz Date: Wed, 25 Jul 2012 01:14:58 +0200 Subject: [PATCH 40/48] Remove unneccessary string transformation --- src/remote.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index e46249e12..c0819ffeb 100644 --- a/src/remote.c +++ b/src/remote.c @@ -179,7 +179,7 @@ int git_remote_save(const git_remote *remote) if (git_repository_config__weakptr(&config, remote->repo) < 0) return -1; - if (git_buf_printf(&buf, "remote.%s.%s", remote->name, "url") < 0) + if (git_buf_printf(&buf, "remote.%s.url", remote->name) < 0) return -1; if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) { From 3ed4b5012bbdba844ae1ffdff884a1eb630e9884 Mon Sep 17 00:00:00 2001 From: Sascha Cunz Date: Wed, 25 Jul 2012 01:32:31 +0200 Subject: [PATCH 41/48] Remotes: Load/Save for fetch.foo.pushurl --- src/remote.c | 32 ++++++++++++++++++++++++++++++++ src/remote.h | 1 + 2 files changed, 33 insertions(+) diff --git a/src/remote.c b/src/remote.c index c0819ffeb..bcc4ab5b4 100644 --- a/src/remote.c +++ b/src/remote.c @@ -130,6 +130,26 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) remote->url = git__strdup(val); GITERR_CHECK_ALLOC(remote->url); + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.pushurl", name) < 0) { + error = -1; + goto cleanup; + } + + error = git_config_get_string(&val, config, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) + error = 0; + + if (error < 0) { + error = -1; + goto cleanup; + } + + if (val) { + remote->pushurl = git__strdup(val); + GITERR_CHECK_ALLOC(remote->pushurl); + } + git_buf_clear(&buf); if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) { error = -1; @@ -187,6 +207,17 @@ int git_remote_save(const git_remote *remote) return -1; } + if (remote->pushurl) { + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0) + return -1; + + if (git_config_set_string(config, git_buf_cstr(&buf), remote->pushurl) < 0) { + git_buf_free(&buf); + return -1; + } + } + if (remote->fetch.src != NULL && remote->fetch.dst != NULL) { git_buf_clear(&buf); git_buf_clear(&value); @@ -429,6 +460,7 @@ void git_remote_free(git_remote *remote) git__free(remote->push.src); git__free(remote->push.dst); git__free(remote->url); + git__free(remote->pushurl); git__free(remote->name); git__free(remote); } diff --git a/src/remote.h b/src/remote.h index 0949ad434..abdaa5750 100644 --- a/src/remote.h +++ b/src/remote.h @@ -14,6 +14,7 @@ struct git_remote { char *name; char *url; + char *pushurl; git_vector refs; struct git_refspec fetch; struct git_refspec push; From 765015902ab346f4879acceaf8c8adf61f0e0ed5 Mon Sep 17 00:00:00 2001 From: Sascha Cunz Date: Wed, 25 Jul 2012 01:33:15 +0200 Subject: [PATCH 42/48] Remotes: Setter for url+pushurl; Getter for pushurl --- include/git2/remote.h | 30 ++++++++++++++++++++++++++++++ src/remote.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/include/git2/remote.h b/include/git2/remote.h index 5c01949d2..6d4b6cc20 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -79,6 +79,36 @@ GIT_EXTERN(const char *) git_remote_name(git_remote *remote); */ GIT_EXTERN(const char *) git_remote_url(git_remote *remote); +/** + * Get the remote's url for pushing + * + * @param remote the remote + * @return a pointer to the url or NULL if no special url for pushing is set + */ +GIT_EXTERN(const char *) git_remote_pushurl(git_remote *remote); + +/** + * Set the remote's url + * + * Existing connections will not be updated. + * + * @param remote the remote + * @param url the url to set + * @return 0 or an error value + */ +GIT_EXTERN(int) git_remote_set_url(git_remote *remote, const char* url); + +/** + * Set the remote's url for pushing + * + * Existing connections will not be updated. + * + * @param remote the remote + * @param url the url to set or NULL to clear the pushurl + * @return 0 or an error value + */ +GIT_EXTERN(int) git_remote_set_pushurl(git_remote *remote, const char* url); + /** * Set the remote's fetch refspec * diff --git a/src/remote.c b/src/remote.c index bcc4ab5b4..cdd593cdb 100644 --- a/src/remote.c +++ b/src/remote.c @@ -269,6 +269,38 @@ const char *git_remote_url(git_remote *remote) return remote->url; } +int git_remote_set_url(git_remote *remote, const char* url) +{ + assert(remote); + assert(url); + + git__free(remote->url); + remote->url = git__strdup(url); + GITERR_CHECK_ALLOC(remote->url); + + return 0; +} + +const char *git_remote_pushurl(git_remote *remote) +{ + assert(remote); + return remote->pushurl; +} + +int git_remote_set_pushurl(git_remote *remote, const char* url) +{ + assert(remote); + + git__free(remote->pushurl); + if (url) { + remote->pushurl = git__strdup(url); + GITERR_CHECK_ALLOC(remote->pushurl); + } else { + remote->pushurl = NULL; + } + return 0; +} + int git_remote_set_fetchspec(git_remote *remote, const char *spec) { git_refspec refspec; From 8689a69d097e1116e7fd2506b3cfd76b6e3b03bb Mon Sep 17 00:00:00 2001 From: Sascha Cunz Date: Wed, 25 Jul 2012 01:59:23 +0200 Subject: [PATCH 43/48] Tests: Test remote's pushurl --- tests-clar/network/remotes.c | 27 ++++++++++++++++++++++-- tests-clar/resources/testrepo.git/config | 5 +++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index eb7947dfb..61b29b85e 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -27,8 +27,26 @@ void test_network_remotes__cleanup(void) void test_network_remotes__parsing(void) { + git_remote *_remote2 = NULL; + cl_assert_equal_s(git_remote_name(_remote), "test"); cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert(git_remote_pushurl(_remote) == NULL); + + cl_git_pass(git_remote_load(&_remote2, _repo, "test_with_pushurl")); + cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl"); + cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2"); + cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2"); + git_remote_free(_remote2); +} + +void test_network_remotes__pushurl(void) +{ + cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/notlibgit2")); + cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/notlibgit2"); + + cl_git_pass(git_remote_set_pushurl(_remote, NULL)); + cl_assert(git_remote_pushurl(_remote) == NULL); } void test_network_remotes__parsing_ssh_remote(void) @@ -81,6 +99,7 @@ void test_network_remotes__save(void) cl_git_pass(git_remote_new(&_remote, _repo, "upstream", "git://github.com/libgit2/libgit2", NULL)); cl_git_pass(git_remote_set_fetchspec(_remote, "refs/heads/*:refs/remotes/upstream/*")); cl_git_pass(git_remote_set_pushspec(_remote, "refs/heads/*:refs/heads/*")); + cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/libgit2_push")); cl_git_pass(git_remote_save(_remote)); git_remote_free(_remote); _remote = NULL; @@ -98,6 +117,9 @@ void test_network_remotes__save(void) cl_assert(_refspec != NULL); cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); cl_assert_equal_s(git_refspec_dst(_refspec), "refs/heads/*"); + + cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/libgit2_push"); } void test_network_remotes__fnmatch(void) @@ -143,13 +165,13 @@ void test_network_remotes__list(void) git_config *cfg; cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 1); + cl_assert(list.count == 2); git_strarray_free(&list); cl_git_pass(git_repository_config(&cfg, _repo)); cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 2); + cl_assert(list.count == 3); git_strarray_free(&list); git_config_free(cfg); @@ -180,4 +202,5 @@ void test_network_remotes__add(void) cl_assert(!strcmp(git_refspec_src(_refspec), "refs/heads/*")); cl_assert(git_refspec_force(_refspec) == 1); cl_assert(!strcmp(git_refspec_dst(_refspec), "refs/remotes/addtest/*")); + cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2"); } diff --git a/tests-clar/resources/testrepo.git/config b/tests-clar/resources/testrepo.git/config index b4fdac6c2..c99d97153 100644 --- a/tests-clar/resources/testrepo.git/config +++ b/tests-clar/resources/testrepo.git/config @@ -7,6 +7,11 @@ url = git://github.com/libgit2/libgit2 fetch = +refs/heads/*:refs/remotes/test/* +[remote "test_with_pushurl"] + url = git://github.com/libgit2/fetchlibgit2 + pushurl = git://github.com/libgit2/pushlibgit2 + fetch = +refs/heads/*:refs/remotes/test_with_pushurl/* + [branch "master"] remote = test merge = refs/heads/master From 413d55638483678357ebcb8c26911cf944be95cc Mon Sep 17 00:00:00 2001 From: Sascha Cunz Date: Wed, 25 Jul 2012 02:10:35 +0200 Subject: [PATCH 44/48] Remotes: Save a cleaned pushurl (by deleting it from the config) --- src/remote.c | 17 +++++++++++++---- tests-clar/network/remotes.c | 9 +++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/remote.c b/src/remote.c index cdd593cdb..bee1ab65c 100644 --- a/src/remote.c +++ b/src/remote.c @@ -207,15 +207,24 @@ int git_remote_save(const git_remote *remote) return -1; } - if (remote->pushurl) { - git_buf_clear(&buf); - if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0) - return -1; + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0) + return -1; + if (remote->pushurl) { if (git_config_set_string(config, git_buf_cstr(&buf), remote->pushurl) < 0) { git_buf_free(&buf); return -1; } + } else { + int error = git_config_delete(config, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) { + error = 0; + } + if (error < 0) { + git_buf_free(&buf); + return -1; + } } if (remote->fetch.src != NULL && remote->fetch.dst != NULL) { diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index 61b29b85e..3d989c1b6 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -120,6 +120,15 @@ void test_network_remotes__save(void) cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/libgit2_push"); + + /* remove the pushurl again and see if we can save that too */ + cl_git_pass(git_remote_set_pushurl(_remote, NULL)); + cl_git_pass(git_remote_save(_remote)); + git_remote_free(_remote); + _remote = NULL; + + cl_git_pass(git_remote_load(&_remote, _repo, "upstream")); + cl_assert(git_remote_pushurl(_remote) == NULL); } void test_network_remotes__fnmatch(void) From eff5b4992731cd01f1bc6a457e8d2f86428a8b55 Mon Sep 17 00:00:00 2001 From: Sascha Cunz Date: Wed, 25 Jul 2012 02:34:12 +0200 Subject: [PATCH 45/48] Remotes: Use correct url in git_remote_connect --- src/remote.c | 21 ++++++++++++++++++++- src/remote.h | 2 ++ tests-clar/network/remotes.c | 12 ++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index bee1ab65c..b4a21a688 100644 --- a/src/remote.c +++ b/src/remote.c @@ -356,13 +356,32 @@ const git_refspec *git_remote_pushspec(git_remote *remote) return &remote->push; } +const char* git_remote__urlfordirection(git_remote *remote, int direction) +{ + assert(remote); + + if (direction == GIT_DIR_FETCH) { + return remote->url; + } + + if (direction == GIT_DIR_PUSH) { + return remote->pushurl ? remote->pushurl : remote->url; + } + + return NULL; +} + int git_remote_connect(git_remote *remote, int direction) { git_transport *t; assert(remote); - if (git_transport_new(&t, remote->url) < 0) + const char* url = git_remote__urlfordirection(remote, direction); + if (url == NULL ) + return -1; + + if (git_transport_new(&t, url) < 0) return -1; t->check_cert = remote->check_cert; diff --git a/src/remote.h b/src/remote.h index abdaa5750..623d40c87 100644 --- a/src/remote.h +++ b/src/remote.h @@ -24,4 +24,6 @@ struct git_remote { check_cert; }; +const char* git_remote__urlfordirection(struct git_remote *remote, int direction); + #endif diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index 3d989c1b6..f1d6f47c6 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -2,6 +2,7 @@ #include "buffer.h" #include "refspec.h" #include "transport.h" +#include "remote.h" static git_remote *_remote; static git_repository *_repo; @@ -33,10 +34,21 @@ void test_network_remotes__parsing(void) cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); cl_assert(git_remote_pushurl(_remote) == NULL); + cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIR_FETCH), + "git://github.com/libgit2/libgit2"); + cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIR_PUSH), + "git://github.com/libgit2/libgit2"); + cl_git_pass(git_remote_load(&_remote2, _repo, "test_with_pushurl")); cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl"); cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2"); cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2"); + + cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIR_FETCH), + "git://github.com/libgit2/fetchlibgit2"); + cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIR_PUSH), + "git://github.com/libgit2/pushlibgit2"); + git_remote_free(_remote2); } From c0c390255a70fb98f7ef9424c4ee53c471d7f22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 27 Jul 2012 02:37:15 +0200 Subject: [PATCH 46/48] remote: fix C99-ism --- src/remote.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index b4a21a688..c479c19c8 100644 --- a/src/remote.c +++ b/src/remote.c @@ -374,10 +374,11 @@ const char* git_remote__urlfordirection(git_remote *remote, int direction) int git_remote_connect(git_remote *remote, int direction) { git_transport *t; + const char *url; assert(remote); - const char* url = git_remote__urlfordirection(remote, direction); + url = git_remote__urlfordirection(remote, direction); if (url == NULL ) return -1; From b84f75c357208ce54c5cc35921ff0b4a1abbe7d2 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Fri, 27 Jul 2012 18:43:02 +0200 Subject: [PATCH 47/48] reflog: Rename `entry_drop` to `drop` --- include/git2/reflog.h | 2 +- src/reflog.c | 2 +- tests-clar/refs/reflog/drop.c | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/git2/reflog.h b/include/git2/reflog.h index a314f94c2..a73d1f7fd 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -109,7 +109,7 @@ GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog * * @return 0 on success or an error code. */ -GIT_EXTERN(int) git_reflog_entry_drop( +GIT_EXTERN(int) git_reflog_drop( git_reflog *reflog, unsigned int idx, int rewrite_previous_entry); diff --git a/src/reflog.c b/src/reflog.c index f841b2174..445e53942 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -439,7 +439,7 @@ char * git_reflog_entry_msg(const git_reflog_entry *entry) return entry->msg; } -int git_reflog_entry_drop( +int git_reflog_drop( git_reflog *reflog, unsigned int idx, int rewrite_previous_entry) diff --git a/tests-clar/refs/reflog/drop.c b/tests-clar/refs/reflog/drop.c index 3aa99fe09..86781c041 100644 --- a/tests-clar/refs/reflog/drop.c +++ b/tests-clar/refs/reflog/drop.c @@ -28,7 +28,7 @@ void test_refs_reflog_drop__cleanup(void) void test_refs_reflog_drop__dropping_a_non_exisiting_entry_from_the_log_returns_ENOTFOUND(void) { - cl_assert_equal_i(GIT_ENOTFOUND, git_reflog_entry_drop(g_reflog, entrycount, 0)); + cl_assert_equal_i(GIT_ENOTFOUND, git_reflog_drop(g_reflog, entrycount, 0)); cl_assert_equal_i(entrycount, git_reflog_entrycount(g_reflog)); } @@ -37,7 +37,7 @@ void test_refs_reflog_drop__can_drop_an_entry(void) { cl_assert(entrycount > 4); - cl_git_pass(git_reflog_entry_drop(g_reflog, 2, 0)); + cl_git_pass(git_reflog_drop(g_reflog, 2, 0)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); } @@ -53,7 +53,7 @@ void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void) before_next = git_reflog_entry_byindex(g_reflog, 1); git_oid_cpy(&before_next_old_oid, &before_next->oid_old); - cl_git_pass(git_reflog_entry_drop(g_reflog, 2, 1)); + cl_git_pass(git_reflog_drop(g_reflog, 2, 1)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); after_next = git_reflog_entry_byindex(g_reflog, 1); @@ -67,7 +67,7 @@ void test_refs_reflog_drop__can_drop_the_first_entry(void) { cl_assert(entrycount > 2); - cl_git_pass(git_reflog_entry_drop(g_reflog, 0, 0)); + cl_git_pass(git_reflog_drop(g_reflog, 0, 0)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); } @@ -77,7 +77,7 @@ void test_refs_reflog_drop__can_drop_the_last_entry(void) cl_assert(entrycount > 2); - cl_git_pass(git_reflog_entry_drop(g_reflog, entrycount - 1, 0)); + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 0)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); @@ -90,7 +90,7 @@ void test_refs_reflog_drop__can_drop_the_last_entry_and_rewrite_the_log_history( cl_assert(entrycount > 2); - cl_git_pass(git_reflog_entry_drop(g_reflog, entrycount - 1, 1)); + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); @@ -102,10 +102,10 @@ void test_refs_reflog_drop__can_drop_all_the_entries(void) cl_assert(--entrycount > 0); do { - cl_git_pass(git_reflog_entry_drop(g_reflog, --entrycount, 1)); + cl_git_pass(git_reflog_drop(g_reflog, --entrycount, 1)); } while (entrycount > 0); - cl_git_pass(git_reflog_entry_drop(g_reflog, 0, 1)); + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); cl_assert_equal_i(0, git_reflog_entrycount(g_reflog)); } @@ -117,7 +117,7 @@ void test_refs_reflog_drop__can_persist_deletion_on_disk(void) cl_assert(entrycount > 2); cl_git_pass(git_reference_lookup(&ref, g_repo, g_reflog->ref_name)); - cl_git_pass(git_reflog_entry_drop(g_reflog, entrycount - 1, 1)); + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); cl_git_pass(git_reflog_write(g_reflog)); From f0244463ad280664d2cac950fcc5d6ff550905d1 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Fri, 27 Jul 2012 18:49:37 +0200 Subject: [PATCH 48/48] branch: Add `repository` argument to `create` Yes, we can get the repository from the owner of the object, but having it marked explicitly makes the API more consistent. --- include/git2/branch.h | 1 + src/branch.c | 7 +++++-- src/reflog.c | 2 +- tests-clar/refs/branches/create.c | 12 ++++++------ 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/include/git2/branch.h b/include/git2/branch.h index 15894b709..2f46720af 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -47,6 +47,7 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_branch_create( git_reference **branch_out, + git_repository *repo, const char *branch_name, const git_object *target, int force); diff --git a/src/branch.c b/src/branch.c index d11eca8da..52fed67ad 100644 --- a/src/branch.c +++ b/src/branch.c @@ -52,6 +52,7 @@ static int create_error_invalid(const char *msg) int git_branch_create( git_reference **ref_out, + git_repository *repository, const char *branch_name, const git_object *target, int force) @@ -63,6 +64,7 @@ int git_branch_create( int error = -1; assert(branch_name && target && ref_out); + assert(git_object_owner(target) == repository); target_type = git_object_type(target); @@ -89,7 +91,7 @@ int git_branch_create( if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) goto cleanup; - if (git_reference_create_oid(&branch, git_object_owner(commit), + if (git_reference_create_oid(&branch, repository, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0) goto cleanup; @@ -224,7 +226,8 @@ int git_branch_lookup( return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); } -int retrieve_tracking_configuration(const char **out, git_reference *branch, const char *format) +static int retrieve_tracking_configuration( + const char **out, git_reference *branch, const char *format) { git_config *config; git_buf buf = GIT_BUF_INIT; diff --git a/src/reflog.c b/src/reflog.c index 445e53942..53cc25fa1 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -183,7 +183,7 @@ static int retrieve_reflog_path(git_buf *path, git_reference *ref) git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name); } -int create_new_reflog_file(const char *filepath) +static int create_new_reflog_file(const char *filepath) { int fd; diff --git a/tests-clar/refs/branches/create.c b/tests-clar/refs/branches/create.c index d53904303..fe72d4708 100644 --- a/tests-clar/refs/branches/create.c +++ b/tests-clar/refs/branches/create.c @@ -42,7 +42,7 @@ void test_refs_branches_create__can_create_a_local_branch(void) { retrieve_known_commit(&target, repo); - cl_git_pass(git_branch_create(&branch, NEW_BRANCH_NAME, target, 0)); + cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); cl_git_pass(git_oid_cmp(git_reference_oid(branch), git_object_id(target))); } @@ -50,14 +50,14 @@ void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with { retrieve_known_commit(&target, repo); - cl_git_fail(git_branch_create(&branch, "br2", target, 0)); + cl_git_fail(git_branch_create(&branch, repo, "br2", target, 0)); } void test_refs_branches_create__can_force_create_over_an_existing_branch(void) { retrieve_known_commit(&target, repo); - cl_git_pass(git_branch_create(&branch, "br2", target, 1)); + cl_git_pass(git_branch_create(&branch, repo, "br2", target, 1)); cl_git_pass(git_oid_cmp(git_reference_oid(branch), git_object_id(target))); cl_assert_equal_s("refs/heads/br2", git_reference_name(branch)); } @@ -67,7 +67,7 @@ void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_i /* b25fa35 is a tag, pointing to another tag which points to a commit */ retrieve_target_from_oid(&target, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); - cl_git_pass(git_branch_create(&branch, NEW_BRANCH_NAME, target, 0)); + cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); cl_git_pass(git_oid_streq(git_reference_oid(branch), "e90810b8df3e80c413d903f631643c716887138d")); } @@ -76,11 +76,11 @@ void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit /* 53fc32d is the tree of commit e90810b */ retrieve_target_from_oid(&target, repo, "53fc32d17276939fc79ed05badaef2db09990016"); - cl_git_fail(git_branch_create(&branch, NEW_BRANCH_NAME, target, 0)); + cl_git_fail(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); git_object_free(target); /* 521d87c is an annotated tag pointing to a blob */ retrieve_target_from_oid(&target, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"); - cl_git_fail(git_branch_create(&branch, NEW_BRANCH_NAME, target, 0)); + cl_git_fail(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); }