Commit bf031581d3d87e44da447f0e5a95bd6a24e5bf34

nulltoken 2013-01-14T14:22:11

branch: Introduce git_branch_tracking_name()

diff --git a/include/git2/branch.h b/include/git2/branch.h
index 3bda431..70d609e 100644
--- a/include/git2/branch.h
+++ b/include/git2/branch.h
@@ -158,6 +158,30 @@ GIT_EXTERN(int) git_branch_tracking(
 		git_reference *branch);
 
 /**
+ * Return the name of the reference supporting the remote tracking branch,
+ * given the name of a local branch reference.
+ *
+ * @param tracking_branch_name_out The user-allocated buffer which will be
+ *     filled with the name of the reference. Pass NULL if you just want to
+ *     get the needed size of the name of the reference as the output value.
+ *
+ * @param buffer_size Size of the `out` buffer in bytes.
+ *
+ * @param repo the repository where the branches live
+ *
+ * @param canonical_branch_name name of the local branch.
+ *
+ * @return number of characters in the reference name
+ *     including the trailing NUL byte; GIT_ENOTFOUND when no remote tracking
+ *     reference exists, otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_tracking_name(
+	char *tracking_branch_name_out,
+	size_t buffer_size,
+	git_repository *repo,
+	const char *canonical_branch_name);
+
+/**
  * Determine if the current local branch is pointed at by HEAD.
  *
  * @param branch Current underlying reference of the branch.
diff --git a/src/branch.c b/src/branch.c
index 0d429b8..65c02b8 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -10,6 +10,7 @@
 #include "tag.h"
 #include "config.h"
 #include "refspec.h"
+#include "refs.h"
 
 #include "git2/branch.h"
 
@@ -44,9 +45,11 @@ cleanup:
 	return error;
 }
 
-static int not_a_local_branch(git_reference *ref)
+static int not_a_local_branch(const char *reference_name)
 {
-	giterr_set(GITERR_INVALID, "Reference '%s' is not a local branch.", git_reference_name(ref));
+	giterr_set(
+		GITERR_INVALID,
+		"Reference '%s' is not a local branch.", reference_name);
 	return -1;
 }
 
@@ -176,7 +179,7 @@ int git_branch_move(
 	assert(branch && new_branch_name);
 
 	if (!git_reference_is_branch(branch))
-		return not_a_local_branch(branch);
+		return not_a_local_branch(git_reference_name(branch));
 
 	if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
 		goto cleanup;
@@ -219,17 +222,20 @@ int git_branch_lookup(
 }
 
 static int retrieve_tracking_configuration(
-	const char **out, git_reference *branch, const char *format)
+	const char **out,
+	git_repository *repo,
+	const char *canonical_branch_name,
+	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)
+	if (git_repository_config__weakptr(&config, repo) < 0)
 		return -1;
 
 	if (git_buf_printf(&buf, format,
-		git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
+		canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
 			return -1;
 
 	error = git_config_get_string(out, config, git_buf_cstr(&buf));
@@ -237,9 +243,10 @@ static int retrieve_tracking_configuration(
 	return error;
 }
 
-int git_branch_tracking(
-		git_reference **tracking_out,
-		git_reference *branch)
+int git_branch_tracking__name(
+	git_buf *tracking_name,
+	git_repository *repo,
+	const char *canonical_branch_name)
 {
 	const char *remote_name, *merge_name;
 	git_buf buf = GIT_BUF_INIT;
@@ -247,16 +254,18 @@ int git_branch_tracking(
 	git_remote *remote = NULL;
 	const git_refspec *refspec;
 
-	assert(tracking_out && branch);
+	assert(tracking_name && canonical_branch_name);
 
-	if (!git_reference_is_branch(branch))
-		return not_a_local_branch(branch);
+	if (!git_reference__is_branch(canonical_branch_name))
+		return not_a_local_branch(canonical_branch_name);
 
-	if ((error = retrieve_tracking_configuration(&remote_name, branch, "branch.%s.remote")) < 0)
-		goto cleanup;
+	if ((error = retrieve_tracking_configuration(
+		&remote_name, repo, canonical_branch_name, "branch.%s.remote")) < 0)
+			goto cleanup;
 
-	if ((error = retrieve_tracking_configuration(&merge_name, branch, "branch.%s.merge")) < 0)
-		goto cleanup;
+	if ((error = retrieve_tracking_configuration(
+		&merge_name, repo, canonical_branch_name, "branch.%s.merge")) < 0)
+			goto cleanup;
 
 	if (!*remote_name || !*merge_name) {
 		error = GIT_ENOTFOUND;
@@ -264,7 +273,7 @@ int git_branch_tracking(
 	}
 
 	if (strcmp(".", remote_name) != 0) {
-		if ((error = git_remote_load(&remote, git_reference_owner(branch), remote_name)) < 0)
+		if ((error = git_remote_load(&remote, repo, remote_name)) < 0)
 			goto cleanup;
 
 		refspec = git_remote_fetchspec(remote);
@@ -281,10 +290,7 @@ int git_branch_tracking(
 		if (git_buf_sets(&buf, merge_name) < 0)
 			goto cleanup;
 
-	error = git_reference_lookup(
-		tracking_out,
-		git_reference_owner(branch),
-		git_buf_cstr(&buf));
+	error = git_buf_set(tracking_name, git_buf_cstr(&buf), git_buf_len(&buf));
 
 cleanup:
 	git_remote_free(remote);
@@ -292,6 +298,62 @@ cleanup:
 	return error;
 }
 
+int git_branch_tracking_name(
+	char *tracking_branch_name_out,
+	size_t buffer_size,
+	git_repository *repo,
+	const char *canonical_branch_name)
+{
+	git_buf buf = GIT_BUF_INIT;
+	int error;
+
+	assert(canonical_branch_name);
+
+	if (tracking_branch_name_out && buffer_size)
+		*tracking_branch_name_out = '\0';
+
+	if ((error = git_branch_tracking__name(
+		&buf, repo, canonical_branch_name)) < 0)
+			goto cleanup;
+
+	if (tracking_branch_name_out && buf.size + 1 > buffer_size) { /* +1 for NUL byte */
+		giterr_set(
+			GITERR_INVALID,
+			"Buffer too short to hold the tracked reference name.");
+		error = -1;
+		goto cleanup;
+	}
+
+	if (tracking_branch_name_out)
+		git_buf_copy_cstr(tracking_branch_name_out, buffer_size, &buf);
+
+	error = buf.size + 1;
+
+cleanup:
+	git_buf_free(&buf);
+	return (int)error;
+}
+
+int git_branch_tracking(
+		git_reference **tracking_out,
+		git_reference *branch)
+{
+	int error;
+	git_buf tracking_name = GIT_BUF_INIT;
+
+	if ((error = git_branch_tracking__name(&tracking_name,
+		git_reference_owner(branch), git_reference_name(branch))) < 0)
+			return error;
+
+	error = git_reference_lookup(
+		tracking_out,
+		git_reference_owner(branch),
+		git_buf_cstr(&tracking_name));
+
+	git_buf_free(&tracking_name);
+	return error;
+}
+
 int git_branch_is_head(
 		git_reference *branch)
 {
diff --git a/src/branch.h b/src/branch.h
new file mode 100644
index 0000000..8a26c4f
--- /dev/null
+++ b/src/branch.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_branch_h__
+#define INCLUDE_branch_h__
+
+#include "buffer.h"
+
+int git_branch_tracking__name(
+	git_buf *tracking_name,
+	git_repository *repo,
+	const char *canonical_branch_name);
+
+#endif
diff --git a/src/refs.c b/src/refs.c
index db8e998..4934a03 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -1905,10 +1905,15 @@ int git_reference_has_log(
 	return result;
 }
 
+int git_reference__is_branch(const char *ref_name)
+{
+	return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
+}
+
 int git_reference_is_branch(git_reference *ref)
 {
 	assert(ref);
-	return git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0;
+	return git_reference__is_branch(ref->name);
 }
 
 int git_reference_is_remote(git_reference *ref)
diff --git a/src/refs.h b/src/refs.h
index f5ed932..1228cea 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -69,6 +69,7 @@ int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const c
 int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags);
 int git_reference__is_valid_name(const char *refname, unsigned int flags);
 int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name);
+int git_reference__is_branch(const char *ref_name);
 
 /**
  * Lookup a reference by name and try to resolve to an OID.
diff --git a/tests-clar/clone/empty.c b/tests-clar/clone/empty.c
index 6523637..4e53557 100644
--- a/tests-clar/clone/empty.c
+++ b/tests-clar/clone/empty.c
@@ -33,10 +33,21 @@ static void cleanup_repository(void *path)
 
 void test_clone_empty__can_clone_an_empty_local_repo_barely(void)
 {
+	char *local_name = "refs/heads/master";
+	char tracking_name[1024];
+	git_reference *ref;
+
 	cl_set_cleanup(&cleanup_repository, "./empty");
 
 	g_options.bare = true;
 	cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options));
+
+	/* Although the HEAD is orphaned... */
+	cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned, local_name));
+
+	/* ...one can still retrieve the name of the remote tracking reference */
+	cl_assert_equal_i(strlen("refs/remotes/origin/master") + 1, 
+		git_branch_tracking_name(tracking_name, 1024, g_repo_cloned, local_name));
 }
 
 void test_clone_empty__can_clone_an_empty_local_repo(void)
diff --git a/tests-clar/refs/branches/trackingname.c b/tests-clar/refs/branches/trackingname.c
new file mode 100644
index 0000000..ea90583
--- /dev/null
+++ b/tests-clar/refs/branches/trackingname.c
@@ -0,0 +1,42 @@
+#include "clar_libgit2.h"
+#include "branch.h"
+
+static git_repository *repo;
+static git_buf tracking_name;
+
+void test_refs_branches_trackingname__initialize(void)
+{
+	cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+
+	git_buf_init(&tracking_name, 0);
+}
+
+void test_refs_branches_trackingname__cleanup(void)
+{
+	git_buf_free(&tracking_name);
+
+	git_repository_free(repo);
+	repo = NULL;
+}
+
+void test_refs_branches_trackingname__can_retrieve_the_remote_tracking_reference_name_of_a_local_branch(void)
+{
+	cl_git_pass(git_branch_tracking__name(
+		&tracking_name, repo, "refs/heads/master"));
+
+	cl_assert_equal_s("refs/remotes/test/master", git_buf_cstr(&tracking_name));
+}
+
+void test_refs_branches_trackingname__can_retrieve_the_local_tracking_reference_name_of_a_local_branch(void)
+{
+	cl_git_pass(git_branch_tracking__name(
+		&tracking_name, repo, "refs/heads/track-local"));
+
+	cl_assert_equal_s("refs/heads/master", git_buf_cstr(&tracking_name));
+}
+
+void test_refs_branches_trackingname__can_return_the_size_of_thelocal_tracking_reference_name_of_a_local_branch(void)
+{
+	cl_assert_equal_i(strlen("refs/heads/master") + 1,
+		git_branch_tracking_name(NULL, 0, repo, "refs/heads/track-local"));
+}