Commit d22db24fb75134f30c3a72af0bc47fc7f0a07f33

Carlos Martín Nieto 2014-05-21T09:32:35

remote: add api to guess the remote's default branch If the remote supports the symref protocol extension, then we return that, otherwise we guess with git's rules.

diff --git a/include/git2/remote.h b/include/git2/remote.h
index 07cd2e7..28771ac 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -623,6 +623,24 @@ GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name);
 */
 GIT_EXTERN(int) git_remote_delete(git_remote *remote);
 
+/**
+ * Retrieve the name of the remote's default branch
+ *
+ * The default branch of a repository is the branch which HEAD points
+ * to. If the remote does not support reporting this information
+ * directly, it performs the guess as git does; that is, if there are
+ * multiple branches which point to the same commit, the first one is
+ * chosen. If the master branch is a candidate, it wins.
+ *
+ * This function must only be called after connecting.
+ *
+ * @param out the buffern in which to store the reference name
+ * @param remote the remote
+ * @return 0, GIT_ENOTFOUND if the remote does not have any references
+ * or none of them point to HEAD's commit, or an error message.
+ */
+GIT_EXTERN(int) git_remote_default_branch(git_buf *out, git_remote *remote);
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/src/remote.c b/src/remote.c
index bdc4791..f2e2e2f 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -1885,3 +1885,50 @@ int git_remote_delete(git_remote *remote)
 
 	return 0;
 }
+
+int git_remote_default_branch(git_buf *out, git_remote *remote)
+{
+	const git_remote_head **heads;
+	const git_remote_head *guess = NULL;
+	const git_oid *head_id;
+	size_t heads_len, i;
+	int error;
+
+	if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0)
+		return error;
+
+	if (heads_len == 0)
+		return GIT_ENOTFOUND;
+
+	git_buf_sanitize(out);
+	/* the first one must be HEAD so if that has the symref info, we're done */
+	if (heads[0]->symref_target)
+		return git_buf_puts(out, heads[0]->symref_target);
+
+	/*
+	 * If there's no symref information, we have to look over them
+	 * and guess. We return the first match unless the master
+	 * branch is a candidate. Then we return the master branch.
+	 */
+	head_id = &heads[0]->oid;
+
+	for (i = 1; i < heads_len; i++) {
+		if (git_oid_cmp(head_id, &heads[i]->oid))
+			continue;
+
+		if (!guess) {
+			guess = heads[i];
+			continue;
+		}
+
+		if (!git__strcmp(GIT_REFS_HEADS_MASTER_FILE, heads[i]->name)) {
+			guess = heads[i];
+			break;
+		}
+	}
+
+	if (!guess)
+		return GIT_ENOTFOUND;
+
+	return git_buf_puts(out, guess->name);
+}
diff --git a/tests/network/remote/defaultbranch.c b/tests/network/remote/defaultbranch.c
new file mode 100644
index 0000000..fa3a329
--- /dev/null
+++ b/tests/network/remote/defaultbranch.c
@@ -0,0 +1,50 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "refspec.h"
+#include "remote.h"
+
+static git_remote *g_remote;
+static git_repository *g_repo_a, *g_repo_b;
+
+void test_network_remote_defaultbranch__initialize(void)
+{
+	g_repo_a = cl_git_sandbox_init("testrepo.git");
+	cl_git_pass(git_repository_init(&g_repo_b, "repo-b.git", true));
+	cl_git_pass(git_remote_create(&g_remote, g_repo_b, "origin", git_repository_path(g_repo_a)));
+}
+
+void test_network_remote_defaultbranch__cleanup(void)
+{
+	git_remote_free(g_remote);
+	git_repository_free(g_repo_b);
+
+	cl_git_sandbox_cleanup();
+	cl_fixture_cleanup("repo-b.git");
+}
+
+static void assert_default_branch(const char *should)
+{
+	git_buf name = GIT_BUF_INIT;
+
+	cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH));
+	cl_git_pass(git_remote_default_branch(&name, g_remote));
+	cl_assert_equal_s(should, name.ptr);
+	git_buf_free(&name);
+}
+
+void test_network_remote_defaultbranch__master(void)
+{
+	assert_default_branch("refs/heads/master");
+}
+
+void test_network_remote_defaultbranch__master_does_not_win(void)
+{
+	cl_git_pass(git_repository_set_head(g_repo_a, "refs/heads/not-good", NULL, NULL));
+	assert_default_branch("refs/heads/not-good");
+}
+
+void test_network_remote_defaultbranch__master_on_detached(void)
+{
+	cl_git_pass(git_repository_detach_head(g_repo_a, NULL, NULL));
+	assert_default_branch("refs/heads/master");
+}