blob: 5eab5aef06da4f0765d10ab7c53bc76ff0863764 [file] [log] [blame]
/*
* Copyright (C) 2009-2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "commit.h"
#include "tag.h"
#include "config.h"
#include "refspec.h"
#include "git2/branch.h"
static int retrieve_branch_reference(
git_reference **branch_reference_out,
git_repository *repo,
const char *branch_name,
int is_remote)
{
git_reference *branch;
int error = -1;
char *prefix;
git_buf ref_name = GIT_BUF_INIT;
*branch_reference_out = NULL;
prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR;
if (git_buf_joinpath(&ref_name, prefix, branch_name) < 0)
goto cleanup;
if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) {
giterr_set(GITERR_REFERENCE,
"Cannot locate %s branch '%s'.", is_remote ? "remote-tracking" : "local", branch_name);
goto cleanup;
}
*branch_reference_out = branch;
cleanup:
git_buf_free(&ref_name);
return error;
}
static int not_a_local_branch(git_reference *ref)
{
giterr_set(GITERR_INVALID, "Reference '%s' is not a local branch.", git_reference_name(ref));
return -1;
}
int git_branch_create(
git_reference **ref_out,
git_repository *repository,
const char *branch_name,
const git_commit *commit,
int force)
{
git_reference *branch = NULL;
git_buf canonical_branch_name = GIT_BUF_INIT;
int error = -1;
assert(branch_name && commit && ref_out);
assert(git_object_owner((const git_object *)commit) == repository);
if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
goto cleanup;
error = git_reference_create_oid(&branch, repository,
git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force);
if (!error)
*ref_out = branch;
cleanup:
git_buf_free(&canonical_branch_name);
return error;
}
int git_branch_delete(git_reference *branch)
{
int is_head;
git_buf config_section = GIT_BUF_INIT;
int error = -1;
assert(branch);
if (!git_reference_is_branch(branch) &&
!git_reference_is_remote(branch)) {
giterr_set(GITERR_INVALID, "Reference '%s' is not a valid branch.", git_reference_name(branch));
return -1;
}
if ((is_head = git_branch_is_head(branch)) < 0)
return is_head;
if (is_head) {
giterr_set(GITERR_REFERENCE,
"Cannot delete branch '%s' as it is the current HEAD of the repository.", git_reference_name(branch));
return -1;
}
if (git_buf_printf(&config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
goto on_error;
if (git_config_rename_section(
git_reference_owner(branch),
git_buf_cstr(&config_section),
NULL) < 0)
goto on_error;
if (git_reference_delete(branch) < 0)
goto on_error;
error = 0;
on_error:
git_buf_free(&config_section);
return error;
}
typedef struct {
int (*branch_cb)(
const char *branch_name,
git_branch_t branch_type,
void *payload);
void *callback_payload;
unsigned int branch_type;
} branch_foreach_filter;
static int branch_foreach_cb(const char *branch_name, void *payload)
{
branch_foreach_filter *filter = (branch_foreach_filter *)payload;
if (filter->branch_type & GIT_BRANCH_LOCAL &&
git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0)
return filter->branch_cb(branch_name + strlen(GIT_REFS_HEADS_DIR), GIT_BRANCH_LOCAL, filter->callback_payload);
if (filter->branch_type & GIT_BRANCH_REMOTE &&
git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0)
return filter->branch_cb(branch_name + strlen(GIT_REFS_REMOTES_DIR), GIT_BRANCH_REMOTE, filter->callback_payload);
return 0;
}
int git_branch_foreach(
git_repository *repo,
unsigned int list_flags,
int (*branch_cb)(
const char *branch_name,
git_branch_t branch_type,
void *payload),
void *payload
)
{
branch_foreach_filter filter;
filter.branch_cb = branch_cb;
filter.branch_type = list_flags;
filter.callback_payload = payload;
return git_reference_foreach(repo, GIT_REF_LISTALL, &branch_foreach_cb, (void *)&filter);
}
int git_branch_move(
git_reference *branch,
const char *new_branch_name,
int force)
{
git_buf new_reference_name = GIT_BUF_INIT,
old_config_section = GIT_BUF_INIT,
new_config_section = GIT_BUF_INIT;
int error;
assert(branch && new_branch_name);
if (!git_reference_is_branch(branch))
return not_a_local_branch(branch);
if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
goto cleanup;
if (git_buf_printf(
&old_config_section,
"branch.%s",
git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
goto cleanup;
if ((error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force)) < 0)
goto cleanup;
if (git_buf_printf(&new_config_section, "branch.%s", new_branch_name) < 0)
goto cleanup;
if ((error = git_config_rename_section(
git_reference_owner(branch),
git_buf_cstr(&old_config_section),
git_buf_cstr(&new_config_section))) < 0)
goto cleanup;
cleanup:
git_buf_free(&new_reference_name);
git_buf_free(&old_config_section);
git_buf_free(&new_config_section);
return error;
}
int git_branch_lookup(
git_reference **ref_out,
git_repository *repo,
const char *branch_name,
git_branch_t branch_type)
{
assert(ref_out && repo && branch_name);
return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);
}
static 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 (remote_name == NULL || merge_name == NULL) {
error = GIT_ENOTFOUND;
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
|| refspec->src == NULL
|| refspec->dst == 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;
}
int git_branch_is_head(
git_reference *branch)
{
git_reference *head;
bool is_same = false;
int error;
assert(branch);
if (!git_reference_is_branch(branch))
return false;
error = git_repository_head(&head, git_reference_owner(branch));
if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
return false;
if (error < 0)
return -1;
is_same = strcmp(
git_reference_name(branch),
git_reference_name(head)) == 0;
git_reference_free(head);
return is_same;
}