Skip to content

Commit d22db24

Browse files
committed
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.
1 parent 04865aa commit d22db24

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed

include/git2/remote.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,24 @@ GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name);
623623
*/
624624
GIT_EXTERN(int) git_remote_delete(git_remote *remote);
625625

626+
/**
627+
* Retrieve the name of the remote's default branch
628+
*
629+
* The default branch of a repository is the branch which HEAD points
630+
* to. If the remote does not support reporting this information
631+
* directly, it performs the guess as git does; that is, if there are
632+
* multiple branches which point to the same commit, the first one is
633+
* chosen. If the master branch is a candidate, it wins.
634+
*
635+
* This function must only be called after connecting.
636+
*
637+
* @param out the buffern in which to store the reference name
638+
* @param remote the remote
639+
* @return 0, GIT_ENOTFOUND if the remote does not have any references
640+
* or none of them point to HEAD's commit, or an error message.
641+
*/
642+
GIT_EXTERN(int) git_remote_default_branch(git_buf *out, git_remote *remote);
643+
626644
/** @} */
627645
GIT_END_DECL
628646
#endif

src/remote.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,3 +1885,50 @@ int git_remote_delete(git_remote *remote)
18851885

18861886
return 0;
18871887
}
1888+
1889+
int git_remote_default_branch(git_buf *out, git_remote *remote)
1890+
{
1891+
const git_remote_head **heads;
1892+
const git_remote_head *guess = NULL;
1893+
const git_oid *head_id;
1894+
size_t heads_len, i;
1895+
int error;
1896+
1897+
if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0)
1898+
return error;
1899+
1900+
if (heads_len == 0)
1901+
return GIT_ENOTFOUND;
1902+
1903+
git_buf_sanitize(out);
1904+
/* the first one must be HEAD so if that has the symref info, we're done */
1905+
if (heads[0]->symref_target)
1906+
return git_buf_puts(out, heads[0]->symref_target);
1907+
1908+
/*
1909+
* If there's no symref information, we have to look over them
1910+
* and guess. We return the first match unless the master
1911+
* branch is a candidate. Then we return the master branch.
1912+
*/
1913+
head_id = &heads[0]->oid;
1914+
1915+
for (i = 1; i < heads_len; i++) {
1916+
if (git_oid_cmp(head_id, &heads[i]->oid))
1917+
continue;
1918+
1919+
if (!guess) {
1920+
guess = heads[i];
1921+
continue;
1922+
}
1923+
1924+
if (!git__strcmp(GIT_REFS_HEADS_MASTER_FILE, heads[i]->name)) {
1925+
guess = heads[i];
1926+
break;
1927+
}
1928+
}
1929+
1930+
if (!guess)
1931+
return GIT_ENOTFOUND;
1932+
1933+
return git_buf_puts(out, guess->name);
1934+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include "clar_libgit2.h"
2+
#include "buffer.h"
3+
#include "refspec.h"
4+
#include "remote.h"
5+
6+
static git_remote *g_remote;
7+
static git_repository *g_repo_a, *g_repo_b;
8+
9+
void test_network_remote_defaultbranch__initialize(void)
10+
{
11+
g_repo_a = cl_git_sandbox_init("testrepo.git");
12+
cl_git_pass(git_repository_init(&g_repo_b, "repo-b.git", true));
13+
cl_git_pass(git_remote_create(&g_remote, g_repo_b, "origin", git_repository_path(g_repo_a)));
14+
}
15+
16+
void test_network_remote_defaultbranch__cleanup(void)
17+
{
18+
git_remote_free(g_remote);
19+
git_repository_free(g_repo_b);
20+
21+
cl_git_sandbox_cleanup();
22+
cl_fixture_cleanup("repo-b.git");
23+
}
24+
25+
static void assert_default_branch(const char *should)
26+
{
27+
git_buf name = GIT_BUF_INIT;
28+
29+
cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH));
30+
cl_git_pass(git_remote_default_branch(&name, g_remote));
31+
cl_assert_equal_s(should, name.ptr);
32+
git_buf_free(&name);
33+
}
34+
35+
void test_network_remote_defaultbranch__master(void)
36+
{
37+
assert_default_branch("refs/heads/master");
38+
}
39+
40+
void test_network_remote_defaultbranch__master_does_not_win(void)
41+
{
42+
cl_git_pass(git_repository_set_head(g_repo_a, "refs/heads/not-good", NULL, NULL));
43+
assert_default_branch("refs/heads/not-good");
44+
}
45+
46+
void test_network_remote_defaultbranch__master_on_detached(void)
47+
{
48+
cl_git_pass(git_repository_detach_head(g_repo_a, NULL, NULL));
49+
assert_default_branch("refs/heads/master");
50+
}

0 commit comments

Comments
 (0)