Commit fb910281d6598e2c235f6ec93384d4e08838d655

nulltoken 2012-07-20T16:38:54

branch: introduce git_branch_tracking()

diff --git a/include/git2/branch.h b/include/git2/branch.h
index 724cfba..15894b7 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 4a56fd1..d0ebb2d 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 b046928..e2c0de6 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 5e20b42..16e3fe2 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 185ca36..794233c 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 0000000..8f70194
--- /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 d1412a9..b024d36 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 b4fdac6..6b03dac 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 0000000..f37febb
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/refs/heads/track-local
@@ -0,0 +1 @@
+9fd738e8f7967c078dceed8190330fc8648ee56a