Commit ea8bac37 by Vicent Martí

Merge pull request #1450 from carlosmn/branch-upstream

Branch upstream configuration
parents acd40771 d59942c2
......@@ -171,11 +171,23 @@ GIT_EXTERN(int) git_branch_name(const char **out,
* @return 0 on success; GIT_ENOTFOUND when no remote tracking
* reference exists, otherwise an error code.
*/
GIT_EXTERN(int) git_branch_tracking(
GIT_EXTERN(int) git_branch_upstream(
git_reference **out,
git_reference *branch);
/**
* Set the upstream configuration for a given local branch
*
* @param branch the branch to configure
*
* @param upstream_name remote-tracking or local branch to set as
* upstream. Pass NULL to unset.
*
* @return 0 or an error code
*/
GIT_EXTERN(int) git_branch_set_upstream(git_reference *branch, const char *upstream_name);
/**
* Return the name of the reference supporting the remote tracking branch,
* given the name of a local branch reference.
*
......@@ -193,7 +205,7 @@ GIT_EXTERN(int) git_branch_tracking(
* including the trailing NUL byte; GIT_ENOTFOUND when no remote tracking
* reference exists, otherwise an error code.
*/
GIT_EXTERN(int) git_branch_tracking_name(
GIT_EXTERN(int) git_branch_upstream_name(
char *tracking_branch_name_out,
size_t buffer_size,
git_repository *repo,
......
......@@ -228,7 +228,7 @@ int git_branch_name(const char **out, git_reference *ref)
return 0;
}
static int retrieve_tracking_configuration(
static int retrieve_upstream_configuration(
const char **out,
git_repository *repo,
const char *canonical_branch_name,
......@@ -250,7 +250,7 @@ static int retrieve_tracking_configuration(
return error;
}
int git_branch_tracking__name(
int git_branch_upstream__name(
git_buf *tracking_name,
git_repository *repo,
const char *canonical_branch_name)
......@@ -266,11 +266,11 @@ int git_branch_tracking__name(
if (!git_reference__is_branch(canonical_branch_name))
return not_a_local_branch(canonical_branch_name);
if ((error = retrieve_tracking_configuration(
if ((error = retrieve_upstream_configuration(
&remote_name, repo, canonical_branch_name, "branch.%s.remote")) < 0)
goto cleanup;
if ((error = retrieve_tracking_configuration(
if ((error = retrieve_upstream_configuration(
&merge_name, repo, canonical_branch_name, "branch.%s.merge")) < 0)
goto cleanup;
......@@ -305,23 +305,16 @@ cleanup:
return error;
}
int git_branch_remote_name(
char *remote_name_out,
size_t buffer_size,
git_repository *repo,
const char *canonical_branch_name)
static int remote_name(git_buf *buf, git_repository *repo, const char *canonical_branch_name)
{
git_strarray remote_list = {0};
size_t i, remote_name_size;
size_t i;
git_remote *remote;
const git_refspec *fetchspec;
int error = 0;
char *remote_name = NULL;
assert(repo && canonical_branch_name);
if (remote_name_out && buffer_size)
*remote_name_out = '\0';
assert(buf && repo && canonical_branch_name);
/* Verify that this is a remote branch */
if (!git_reference__is_remote(canonical_branch_name)) {
......@@ -338,7 +331,7 @@ int git_branch_remote_name(
/* Find matching remotes */
for (i = 0; i < remote_list.count; i++) {
if ((error = git_remote_load(&remote, repo, remote_list.strings[i])) < 0)
goto cleanup;
continue;
fetchspec = git_remote_fetchspec(remote);
......@@ -362,23 +355,10 @@ int git_branch_remote_name(
}
if (remote_name) {
remote_name_size = strlen(remote_name) + 1;
error = (int) remote_name_size;
if (remote_name_out) {
if(remote_name_size > buffer_size) {
giterr_set(
GITERR_INVALID,
"Buffer too short to hold the remote name.");
error = GIT_ERROR;
goto cleanup;
}
memcpy(remote_name_out, remote_name, remote_name_size);
}
git_buf_clear(buf);
error = git_buf_puts(buf, remote_name);
} else {
error = GIT_ENOTFOUND;
goto cleanup;
}
cleanup:
......@@ -386,7 +366,24 @@ cleanup:
return error;
}
int git_branch_tracking_name(
int git_branch_remote_name(char *buffer, size_t buffer_len, git_repository *repo, const char *refname)
{
int ret;
git_buf buf = GIT_BUF_INIT;
if ((ret = remote_name(&buf, repo, refname)) < 0)
return ret;
if (buffer)
git_buf_copy_cstr(buffer, buffer_len, &buf);
ret = git_buf_len(&buf) + 1;
git_buf_free(&buf);
return ret;
}
int git_branch_upstream_name(
char *tracking_branch_name_out,
size_t buffer_size,
git_repository *repo,
......@@ -400,7 +397,7 @@ int git_branch_tracking_name(
if (tracking_branch_name_out && buffer_size)
*tracking_branch_name_out = '\0';
if ((error = git_branch_tracking__name(
if ((error = git_branch_upstream__name(
&buf, repo, canonical_branch_name)) < 0)
goto cleanup;
......@@ -422,14 +419,14 @@ cleanup:
return (int)error;
}
int git_branch_tracking(
int git_branch_upstream(
git_reference **tracking_out,
git_reference *branch)
{
int error;
git_buf tracking_name = GIT_BUF_INIT;
if ((error = git_branch_tracking__name(&tracking_name,
if ((error = git_branch_upstream__name(&tracking_name,
git_reference_owner(branch), git_reference_name(branch))) < 0)
return error;
......@@ -442,6 +439,120 @@ int git_branch_tracking(
return error;
}
static int unset_upstream(git_config *config, const char *shortname)
{
git_buf buf = GIT_BUF_INIT;
if (git_buf_printf(&buf, "branch.%s.remote", shortname) < 0)
return -1;
if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
goto on_error;
git_buf_clear(&buf);
if (git_buf_printf(&buf, "branch.%s.merge", shortname) < 0)
goto on_error;
if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
goto on_error;
git_buf_free(&buf);
return 0;
on_error:
git_buf_free(&buf);
return -1;
}
int git_branch_set_upstream(git_reference *branch, const char *upstream_name)
{
git_buf key = GIT_BUF_INIT, value = GIT_BUF_INIT;
git_reference *upstream;
git_repository *repo;
git_remote *remote = NULL;
git_config *config;
const char *name, *shortname;
int local;
const git_refspec *fetchspec;
name = git_reference_name(branch);
if (!git_reference__is_branch(name))
return not_a_local_branch(name);
if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)
return -1;
shortname = name + strlen(GIT_REFS_HEADS_DIR);
if (upstream_name == NULL)
return unset_upstream(config, shortname);
repo = git_reference_owner(branch);
/* First we need to figure out whether it's a branch or remote-tracking */
if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_LOCAL) == 0)
local = 1;
else if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_REMOTE) == 0)
local = 0;
else
return GIT_ENOTFOUND;
/*
* If it's local, the remote is "." and the branch name is
* simply the refname. Otherwise we need to figure out what
* the remote-tracking branch's name on the remote is and use
* that.
*/
if (local)
git_buf_puts(&value, ".");
else
remote_name(&value, repo, git_reference_name(upstream));
if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0)
goto on_error;
if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
goto on_error;
if (local) {
if (git_buf_puts(&value, git_reference_name(branch)) < 0)
goto on_error;
} else {
/* Get the remoe-tracking branch's refname in its repo */
if (git_remote_load(&remote, repo, git_buf_cstr(&value)) < 0)
goto on_error;
fetchspec = git_remote_fetchspec(remote);
git_buf_clear(&value);
if (git_refspec_transform_l(&value, fetchspec, git_reference_name(upstream)) < 0)
goto on_error;
git_remote_free(remote);
remote = NULL;
}
git_buf_clear(&key);
if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0)
goto on_error;
if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
goto on_error;
git_reference_free(upstream);
git_buf_free(&key);
git_buf_free(&value);
return 0;
on_error:
git_reference_free(upstream);
git_buf_free(&key);
git_buf_free(&value);
git_remote_free(remote);
return -1;
}
int git_branch_is_head(
git_reference *branch)
{
......
......@@ -9,7 +9,7 @@
#include "buffer.h"
int git_branch_tracking__name(
int git_branch_upstream__name(
git_buf *tracking_name,
git_repository *repo,
const char *canonical_branch_name);
......
......@@ -705,7 +705,7 @@ static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_ve
if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 ||
(!git_reference_is_branch(resolved_ref)) ||
(error = git_branch_tracking(&tracking_ref, resolved_ref)) < 0 ||
(error = git_branch_upstream(&tracking_ref, resolved_ref)) < 0 ||
(error = git_refspec_transform_l(&remote_name, &remote->fetch, git_reference_name(tracking_ref))) < 0) {
/* Not an error if HEAD is orphaned or no tracking branch */
if (error == GIT_ENOTFOUND)
......
......@@ -356,7 +356,7 @@ static int retrieve_remote_tracking_reference(git_reference **base_ref, const ch
goto cleanup;
}
if ((error = git_branch_tracking(&tracking, ref)) < 0)
if ((error = git_branch_upstream(&tracking, ref)) < 0)
goto cleanup;
*base_ref = tracking;
......
......@@ -1327,7 +1327,7 @@ static int lookup_head_remote(git_buf *url, git_repository *repo)
goto cleanup;
}
if ((error = git_branch_tracking(&remote, head)) < 0)
if ((error = git_branch_upstream(&remote, head)) < 0)
goto cleanup;
/* remote should refer to something like refs/remotes/ORIGIN/BRANCH */
......
......@@ -49,7 +49,7 @@ void test_clone_empty__can_clone_an_empty_local_repo_barely(void)
/* ...one can still retrieve the name of the remote tracking reference */
cl_assert_equal_i((int)strlen(expected_tracked_branch_name) + 1,
git_branch_tracking_name(buffer, 1024, g_repo_cloned, local_name));
git_branch_upstream_name(buffer, 1024, g_repo_cloned, local_name));
cl_assert_equal_s(expected_tracked_branch_name, buffer);
......
......@@ -42,7 +42,7 @@ void test_refs_branches_remote__insufficient_buffer_returns_error(void)
cl_git_fail_with(git_branch_remote_name(remotename,
expected_remote_name_length - 1, g_repo, remote_tracking_branch_name),
GIT_ERROR);
expected_remote_name_length);
}
void test_refs_branches_remote__no_matching_remote_returns_error(void)
......
......@@ -2,19 +2,19 @@
#include "refs.h"
static git_repository *repo;
static git_reference *branch, *tracking;
static git_reference *branch, *upstream;
void test_refs_branches_tracking__initialize(void)
void test_refs_branches_upstream__initialize(void)
{
cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
branch = NULL;
tracking = NULL;
upstream = NULL;
}
void test_refs_branches_tracking__cleanup(void)
void test_refs_branches_upstream__cleanup(void)
{
git_reference_free(tracking);
git_reference_free(upstream);
git_reference_free(branch);
branch = NULL;
......@@ -22,43 +22,43 @@ void test_refs_branches_tracking__cleanup(void)
repo = NULL;
}
void test_refs_branches_tracking__can_retrieve_the_remote_tracking_reference_of_a_local_branch(void)
void test_refs_branches_upstream__can_retrieve_the_remote_tracking_reference_of_a_local_branch(void)
{
cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
cl_git_pass(git_branch_tracking(&tracking, branch));
cl_git_pass(git_branch_upstream(&upstream, branch));
cl_assert_equal_s("refs/remotes/test/master", git_reference_name(tracking));
cl_assert_equal_s("refs/remotes/test/master", git_reference_name(upstream));
}
void test_refs_branches_tracking__can_retrieve_the_local_tracking_reference_of_a_local_branch(void)
void test_refs_branches_upstream__can_retrieve_the_local_upstream_reference_of_a_local_branch(void)
{
cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/track-local"));
cl_git_pass(git_branch_tracking(&tracking, branch));
cl_git_pass(git_branch_upstream(&upstream, branch));
cl_assert_equal_s("refs/heads/master", git_reference_name(tracking));
cl_assert_equal_s("refs/heads/master", git_reference_name(upstream));
}
void test_refs_branches_tracking__cannot_retrieve_a_remote_tracking_reference_from_a_non_branch(void)
void test_refs_branches_upstream__cannot_retrieve_a_remote_upstream_reference_from_a_non_branch(void)
{
cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b"));
cl_git_fail(git_branch_tracking(&tracking, branch));
cl_git_fail(git_branch_upstream(&upstream, branch));
}
void test_refs_branches_tracking__trying_to_retrieve_a_remote_tracking_reference_from_a_plain_local_branch_returns_GIT_ENOTFOUND(void)
void test_refs_branches_upstream__trying_to_retrieve_a_remote_tracking_reference_from_a_plain_local_branch_returns_GIT_ENOTFOUND(void)
{
cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/subtrees"));
cl_assert_equal_i(GIT_ENOTFOUND, git_branch_tracking(&tracking, branch));
cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch));
}
void test_refs_branches_tracking__trying_to_retrieve_a_remote_tracking_reference_from_a_branch_with_no_fetchspec_returns_GIT_ENOTFOUND(void)
void test_refs_branches_upstream__trying_to_retrieve_a_remote_tracking_reference_from_a_branch_with_no_fetchspec_returns_GIT_ENOTFOUND(void)
{
cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/cannot-fetch"));
cl_assert_equal_i(GIT_ENOTFOUND, git_branch_tracking(&tracking, branch));
cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch));
}
static void assert_merge_and_or_remote_key_missing(git_repository *repository, const git_commit *target, const char *entry_name)
......@@ -68,12 +68,12 @@ static void assert_merge_and_or_remote_key_missing(git_repository *repository, c
cl_assert_equal_i(GIT_OBJ_COMMIT, git_object_type((git_object*)target));
cl_git_pass(git_branch_create(&branch, repository, entry_name, (git_commit*)target, 0));
cl_assert_equal_i(GIT_ENOTFOUND, git_branch_tracking(&tracking, branch));
cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch));
git_reference_free(branch);
}
void test_refs_branches_tracking__retrieve_a_remote_tracking_reference_from_a_branch_with_no_remote_returns_GIT_ENOTFOUND(void)
void test_refs_branches_upstream__retrieve_a_remote_tracking_reference_from_a_branch_with_no_remote_returns_GIT_ENOTFOUND(void)
{
git_reference *head;
git_repository *repository;
......@@ -93,3 +93,38 @@ void test_refs_branches_tracking__retrieve_a_remote_tracking_reference_from_a_br
cl_git_sandbox_cleanup();
}
void test_refs_branches_upstream__set_unset_upstream(void)
{
git_reference *branch;
git_repository *repository;
const char *value;
git_config *config;
repository = cl_git_sandbox_init("testrepo.git");
cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/test"));
cl_git_pass(git_branch_set_upstream(branch, "test/master"));
cl_git_pass(git_repository_config(&config, repository));
cl_git_pass(git_config_get_string(&value, config, "branch.test.remote"));
cl_assert_equal_s(value, "test");
cl_git_pass(git_config_get_string(&value, config, "branch.test.merge"));
cl_assert_equal_s(value, "refs/heads/master");
cl_git_pass(git_branch_set_upstream(branch, NULL));
cl_git_fail_with(git_config_get_string(&value, config, "branch.test.merge"), GIT_ENOTFOUND);
cl_git_fail_with(git_config_get_string(&value, config, "branch.test.remote"), GIT_ENOTFOUND);
git_reference_free(branch);
cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/master"));
cl_git_pass(git_branch_set_upstream(branch, NULL));
cl_git_fail_with(git_config_get_string(&value, config, "branch.master.merge"), GIT_ENOTFOUND);
cl_git_fail_with(git_config_get_string(&value, config, "branch.master.remote"), GIT_ENOTFOUND);
git_reference_free(branch);
git_config_free(config);
cl_git_sandbox_cleanup();
}
......@@ -2,41 +2,41 @@
#include "branch.h"
static git_repository *repo;
static git_buf tracking_name;
static git_buf upstream_name;
void test_refs_branches_trackingname__initialize(void)
void test_refs_branches_upstreamname__initialize(void)
{
cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
git_buf_init(&tracking_name, 0);
git_buf_init(&upstream_name, 0);
}
void test_refs_branches_trackingname__cleanup(void)
void test_refs_branches_upstreamname__cleanup(void)
{
git_buf_free(&tracking_name);
git_buf_free(&upstream_name);
git_repository_free(repo);
repo = NULL;
}
void test_refs_branches_trackingname__can_retrieve_the_remote_tracking_reference_name_of_a_local_branch(void)
void test_refs_branches_upstreamname__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_git_pass(git_branch_upstream__name(
&upstream_name, repo, "refs/heads/master"));
cl_assert_equal_s("refs/remotes/test/master", git_buf_cstr(&tracking_name));
cl_assert_equal_s("refs/remotes/test/master", git_buf_cstr(&upstream_name));
}
void test_refs_branches_trackingname__can_retrieve_the_local_tracking_reference_name_of_a_local_branch(void)
void test_refs_branches_upstreamname__can_retrieve_the_local_upstream_reference_name_of_a_local_branch(void)
{
cl_git_pass(git_branch_tracking__name(
&tracking_name, repo, "refs/heads/track-local"));
cl_git_pass(git_branch_upstream__name(
&upstream_name, repo, "refs/heads/track-local"));
cl_assert_equal_s("refs/heads/master", git_buf_cstr(&tracking_name));
cl_assert_equal_s("refs/heads/master", git_buf_cstr(&upstream_name));
}
void test_refs_branches_trackingname__can_return_the_size_of_thelocal_tracking_reference_name_of_a_local_branch(void)
void test_refs_branches_upstreamname__can_return_the_size_of_thelocal_upstream_reference_name_of_a_local_branch(void)
{
cl_assert_equal_i((int)strlen("refs/heads/master") + 1,
git_branch_tracking_name(NULL, 0, repo, "refs/heads/track-local"));
git_branch_upstream_name(NULL, 0, repo, "refs/heads/track-local"));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment