From 8f17ed801f211e8481b2e0903da547c9e329b010 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Tue, 10 Jul 2012 20:52:56 +0200 Subject: [PATCH 1/7] revparse: simplify the parsing of described object --- src/revparse.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/revparse.c b/src/revparse.c index f9859b18b..10b8376f9 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -104,7 +104,16 @@ cleanup: return error; } -extern int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec); +static int maybe_sha_or_abbrev(git_object**out, git_repository *repo, const char *spec) +{ + git_oid oid; + size_t speclen = strlen(spec); + + if (git_oid_fromstrn(&oid, spec, speclen) < 0) + return GIT_ENOTFOUND; + + return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY); +} static int maybe_describe(git_object**out, git_repository *repo, const char *spec) { @@ -123,21 +132,10 @@ static int maybe_describe(git_object**out, git_repository *repo, const char *spe if (!match) return GIT_ENOTFOUND; - return revparse_lookup_object(out, repo, substr+2); + return maybe_sha_or_abbrev(out, repo, substr+2); } -static int maybe_sha_or_abbrev(git_object**out, git_repository *repo, const char *spec) -{ - git_oid oid; - size_t speclen = strlen(spec); - - if (git_oid_fromstrn(&oid, spec, speclen) < 0) - return GIT_ENOTFOUND; - - return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY); -} - -int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec) +static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec) { int error; git_reference *ref; From 2b92a154b66f213b664e44544048a2df7708b9de Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 11 Jul 2012 11:20:20 +0200 Subject: [PATCH 2/7] commit: reduce code duplication --- src/commit.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/commit.c b/src/commit.c index a3baf9d4e..5acbbc39b 100644 --- a/src/commit.c +++ b/src/commit.c @@ -229,30 +229,29 @@ GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length) GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid); - int git_commit_tree(git_tree **tree_out, git_commit *commit) { assert(commit); return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_oid); } -int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) -{ - git_oid *parent_oid; - assert(commit); - - parent_oid = git_vector_get(&commit->parent_oids, n); - if (parent_oid == NULL) { - giterr_set(GITERR_INVALID, "Parent %u does not exist", n); - return GIT_ENOTFOUND; - } - - return git_commit_lookup(parent, commit->object.repo, parent_oid); -} - const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n) { assert(commit); return git_vector_get(&commit->parent_oids, n); } + +int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) +{ + const git_oid *parent_oid; + assert(commit); + + parent_oid = git_commit_parent_oid(commit, n); + if (parent_oid == NULL) { + giterr_set(GITERR_INVALID, "Parent %u does not exist", n); + return GIT_ENOTFOUND; + } + + return git_commit_lookup(parent, commit->object.repo, parent_oid); +} From b1aca6eae084ebe59c5a092314e94019c59ecec6 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 11 Jul 2012 16:14:12 +0200 Subject: [PATCH 3/7] commit: introduce git_commit_nth_gen_ancestor() --- include/git2/commit.h | 19 +++++++++++++ src/commit.c | 34 +++++++++++++++++++++++ tests-clar/commit/parent.c | 57 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 tests-clar/commit/parent.c diff --git a/include/git2/commit.h b/include/git2/commit.h index 640adf5c2..5b6da520d 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -178,6 +178,25 @@ GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsig */ GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned int n); +/** + * Get the commit object that is the th generation ancestor + * of the named commit object, following only the first parents. + * The returned commit has to be freed by the caller. + * + * Passing `0` as the generation number returns another instance of the + * base commit itself. + * + * @param ancestor Pointer where to store the ancestor commit + * @param commit a previously loaded commit. + * @param n the requested generation + * @return 0 on success; GIT_ENOTFOUND if no matching ancestor exists + * or an error code + */ +int git_commit_nth_gen_ancestor( + git_commit **ancestor, + const git_commit *commit, + unsigned int n); + /** * Create a new commit in the repository using `git_object` * instances as parameters. diff --git a/src/commit.c b/src/commit.c index 5acbbc39b..32c47944b 100644 --- a/src/commit.c +++ b/src/commit.c @@ -255,3 +255,37 @@ int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) return git_commit_lookup(parent, commit->object.repo, parent_oid); } + +int git_commit_nth_gen_ancestor( + git_commit **ancestor, + const git_commit *commit, + unsigned int n) +{ + git_commit *current, *parent; + int error; + + assert(ancestor && commit); + + current = (git_commit *)commit; + + if (n == 0) + return git_commit_lookup( + ancestor, + commit->object.repo, + git_object_id((const git_object *)commit)); + + while (n--) { + error = git_commit_parent(&parent, (git_commit *)current, 0); + + if (current != commit) + git_commit_free(current); + + if (error < 0) + return error; + + current = parent; + } + + *ancestor = parent; + return 0; +} diff --git a/tests-clar/commit/parent.c b/tests-clar/commit/parent.c new file mode 100644 index 000000000..a00757732 --- /dev/null +++ b/tests-clar/commit/parent.c @@ -0,0 +1,57 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_commit *commit; + +void test_commit_parent__initialize(void) +{ + git_oid oid; + + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + + git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); +} + +void test_commit_parent__cleanup(void) +{ + git_commit_free(commit); + git_repository_free(_repo); +} + +static void assert_nth_gen_parent(unsigned int gen, const char *expected_oid) +{ + git_commit *parent = NULL; + int error; + + error = git_commit_nth_gen_ancestor(&parent, commit, gen); + + if (expected_oid != NULL) { + cl_assert_equal_i(0, error); + cl_assert_equal_i(0, git_oid_streq(git_commit_id(parent), expected_oid)); + } else + cl_assert_equal_i(GIT_ENOTFOUND, error); + + git_commit_free(parent); +} + +/* + * $ git show be35~0 + * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644 + * + * $ git show be35~1 + * commit 9fd738e8f7967c078dceed8190330fc8648ee56a + * + * $ git show be35~3 + * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644 + * + * $ git show be35~42 + * fatal: ambiguous argument 'be35~42': unknown revision or path not in the working tree. + */ +void test_commit_parent__can_retrieve_nth_generation_parent(void) +{ + assert_nth_gen_parent(0, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + assert_nth_gen_parent(1, "9fd738e8f7967c078dceed8190330fc8648ee56a"); + assert_nth_gen_parent(3, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + assert_nth_gen_parent(42, NULL); +} From 2d012c0c72e1c7ae6e418340a7bb4ab9dc0288bd Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 11 Jul 2012 16:52:02 +0200 Subject: [PATCH 4/7] revparse: deploy git_commit_nth_gen_ancestor() --- src/revparse.c | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/revparse.c b/src/revparse.c index 10b8376f9..670f67e1f 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -522,8 +522,7 @@ static int handle_caret_syntax(git_object **out, git_repository *repo, git_objec static int handle_linear_syntax(git_object **out, git_object *obj, const char *movement) { - git_commit *commit1, *commit2; - int i, n; + int n; /* Dereference until we reach a commit. */ if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { @@ -537,26 +536,8 @@ static int handle_linear_syntax(git_object **out, git_object *obj, const char *m } else if (git__strtol32(&n, movement, NULL, 0) < 0) { return GIT_ERROR; } - commit1 = (git_commit*)obj; - /* "~0" just returns the input */ - if (n == 0) { - *out = obj; - return 0; - } - - for (i=0; i Date: Wed, 11 Jul 2012 23:47:58 +0200 Subject: [PATCH 5/7] refs: readonly tests don't need a sandboxed repo --- tests-clar/refs/read.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-clar/refs/read.c b/tests-clar/refs/read.c index 6838ead74..ce4eefeba 100644 --- a/tests-clar/refs/read.c +++ b/tests-clar/refs/read.c @@ -16,12 +16,12 @@ static git_repository *g_repo; void test_refs_read__initialize(void) { - g_repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); } void test_refs_read__cleanup(void) { - cl_git_sandbox_cleanup(); + git_repository_free(g_repo); } void test_refs_read__loose_tag(void) From 84f18e358742b77bfc815f2a360a41f3f1b9abd7 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 12 Jul 2012 00:44:07 +0200 Subject: [PATCH 6/7] refs: introduce 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 insertions(+) create mode 100644 tests-clar/refs/remotetracking.c diff --git a/include/git2/refs.h b/include/git2/refs.h index 7f6eb0e9b..b119e90b1 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -363,6 +363,27 @@ 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 +); + /** @} */ GIT_END_DECL #endif diff --git a/src/refs.c b/src/refs.c index e8f9fc8dc..13022c7a5 100644 --- a/src/refs.c +++ b/src/refs.c @@ -11,6 +11,7 @@ #include "fileops.h" #include "pack.h" #include "reflog.h" +#include "config.h" #include #include @@ -1811,3 +1812,76 @@ 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; +} diff --git a/tests-clar/refs/remotetracking.c b/tests-clar/refs/remotetracking.c new file mode 100644 index 000000000..c4ec588ee --- /dev/null +++ b/tests-clar/refs/remotetracking.c @@ -0,0 +1,49 @@ +#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 12595ab8f91a71e5a596a883b31789d5317e9ec2 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 12 Jul 2012 00:52:01 +0200 Subject: [PATCH 7/7] revparse: deploy git_reference_remote_tracking_from_branch() --- src/revparse.c | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/src/revparse.c b/src/revparse.c index 670f67e1f..2b03c86b4 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -249,32 +249,12 @@ static int walk_ref_history(git_object **out, git_repository *repo, const char * date_error = git__date_parse(×tamp, git_buf_cstr(&datebuf)); /* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */ - if (!git__prefixcmp(git_reference_name(disambiguated), GIT_REFS_HEADS_DIR) && - (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}"))) { - git_config *cfg; - if (!git_repository_config(&cfg, repo)) { - /* Is the ref a tracking branch? */ - const char *remote; - git_buf_clear(&buf); - git_buf_printf(&buf, "branch.%s.remote", - git_reference_name(disambiguated) + strlen(GIT_REFS_HEADS_DIR)); - - if (!git_config_get_string(&remote, cfg, git_buf_cstr(&buf))) { - /* Yes. Find the first merge target name. */ - const char *mergetarget; - git_buf_clear(&buf); - git_buf_printf(&buf, "branch.%s.merge", - git_reference_name(disambiguated) + strlen(GIT_REFS_HEADS_DIR)); - - if (!git_config_get_string(&mergetarget, cfg, git_buf_cstr(&buf)) && - !git__prefixcmp(mergetarget, "refs/heads/")) { - /* Success. Look up the target and fetch the object. */ - git_buf_clear(&buf); - git_buf_printf(&buf, "refs/remotes/%s/%s", remote, mergetarget+11); - retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf)); - } - } - git_config_free(cfg); + 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); } }