Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/branch.c')
-rw-r--r--src/branch.c569
1 files changed, 472 insertions, 97 deletions
diff --git a/src/branch.c b/src/branch.c
index 5d5a24038..e7088790e 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,8 +7,12 @@
#include "common.h"
#include "commit.h"
-#include "branch.h"
#include "tag.h"
+#include "config.h"
+#include "refspec.h"
+#include "refs.h"
+
+#include "git2/branch.h"
static int retrieve_branch_reference(
git_reference **branch_reference_out,
@@ -41,168 +45,539 @@ cleanup:
return error;
}
-static int create_error_invalid(const char *msg)
+static int not_a_local_branch(const char *reference_name)
{
- giterr_set(GITERR_INVALID, "Cannot create branch - %s", msg);
+ giterr_set(
+ GITERR_INVALID,
+ "Reference '%s' is not a local branch.", reference_name);
return -1;
}
int git_branch_create(
- git_oid *oid_out,
- git_repository *repo,
- const char *branch_name,
- const git_object *target,
- int force)
-{
- git_otype target_type = GIT_OBJ_BAD;
- git_object *commit = NULL;
+ 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(repo && branch_name && target && oid_out);
-
- if (git_object_owner(target) != repo)
- return create_error_invalid("The given target does not belong to this repository");
-
- target_type = git_object_type(target);
-
- switch (target_type)
- {
- case GIT_OBJ_TAG:
- if (git_tag_peel(&commit, (git_tag *)target) < 0)
- goto cleanup;
-
- if (git_object_type(commit) != GIT_OBJ_COMMIT) {
- create_error_invalid("The given target does not resolve to a commit");
- goto cleanup;
- }
- break;
-
- case GIT_OBJ_COMMIT:
- commit = (git_object *)target;
- break;
-
- default:
- return create_error_invalid("Only git_tag and git_commit objects are valid targets.");
- }
+ 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;
- if (git_reference_create_oid(&branch, repo, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0)
- goto cleanup;
+ error = git_reference_create(&branch, repository,
+ git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force);
- git_oid_cpy(oid_out, git_reference_oid(branch));
- error = 0;
+ if (!error)
+ *ref_out = branch;
cleanup:
- if (target_type == GIT_OBJ_TAG)
- git_object_free(commit);
-
- git_reference_free(branch);
git_buf_free(&canonical_branch_name);
return error;
}
-int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_t branch_type)
+int git_branch_delete(git_reference *branch)
{
- git_reference *branch = NULL;
- git_reference *head = NULL;
- int error;
+ int is_head;
+ git_buf config_section = GIT_BUF_INIT;
+ int error = -1;
+
+ assert(branch);
- assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE));
+ 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 ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0)
- return error;
+ if ((is_head = git_branch_is_head(branch)) < 0)
+ return is_head;
- if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {
- giterr_set(GITERR_REFERENCE, "Cannot locate HEAD.");
- goto on_error;
+ 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_reference_type(head) == GIT_REF_SYMBOLIC)
- && (strcmp(git_reference_target(head), git_reference_name(branch)) == 0)) {
- giterr_set(GITERR_REFERENCE,
- "Cannot delete branch '%s' as it is the current HEAD of the repository.", branch_name);
+ 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;
- git_reference_free(head);
- return 0;
+ error = 0;
on_error:
- git_reference_free(head);
- git_reference_free(branch);
- return -1;
+ git_buf_free(&config_section);
+ return error;
}
typedef struct {
- git_vector *branchlist;
+ git_branch_foreach_cb branch_cb;
+ void *callback_payload;
unsigned int branch_type;
-} branch_filter_data;
+} branch_foreach_filter;
-static int branch_list_cb(const char *branch_name, void *payload)
+static int branch_foreach_cb(const char *branch_name, void *payload)
{
- branch_filter_data *filter = (branch_filter_data *)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_LOCAL && git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0)
- || (filter->branch_type & GIT_BRANCH_REMOTE && git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0))
- return git_vector_insert(filter->branchlist, git__strdup(branch_name));
+ 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_list(git_strarray *branch_names, git_repository *repo, unsigned int list_flags)
+int git_branch_foreach(
+ git_repository *repo,
+ unsigned int list_flags,
+ git_branch_foreach_cb branch_cb,
+ 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 **out,
+ 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;
- branch_filter_data filter;
- git_vector branchlist;
- assert(branch_names && repo);
+ assert(branch && new_branch_name);
+
+ if (!git_reference_is_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 ||
+ (error = git_buf_printf(&old_config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR))) < 0 ||
+ (error = git_buf_printf(&new_config_section, "branch.%s", new_branch_name)) < 0)
+ goto done;
+
+ if ((error = git_config_rename_section(git_reference_owner(branch),
+ git_buf_cstr(&old_config_section),
+ git_buf_cstr(&new_config_section))) < 0)
+ goto done;
+
+ if ((error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force)) < 0)
+ goto done;
+
+done:
+ git_buf_free(&new_reference_name);
+ git_buf_free(&old_config_section);
+ git_buf_free(&new_config_section);
- if (git_vector_init(&branchlist, 8, NULL) < 0)
+ 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);
+}
+
+int git_branch_name(const char **out, git_reference *ref)
+{
+ const char *branch_name;
+
+ assert(out && ref);
+
+ branch_name = ref->name;
+
+ if (git_reference_is_branch(ref)) {
+ branch_name += strlen(GIT_REFS_HEADS_DIR);
+ } else if (git_reference_is_remote(ref)) {
+ branch_name += strlen(GIT_REFS_REMOTES_DIR);
+ } else {
+ giterr_set(GITERR_INVALID,
+ "Reference '%s' is neither a local nor a remote branch.", ref->name);
return -1;
+ }
+ *out = branch_name;
+ return 0;
+}
- filter.branchlist = &branchlist;
- filter.branch_type = list_flags;
+static int retrieve_upstream_configuration(
+ 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;
- error = git_reference_foreach(repo, GIT_REF_LISTALL, &branch_list_cb, (void *)&filter);
- if (error < 0) {
- git_vector_free(&branchlist);
+ if (git_repository_config__weakptr(&config, repo) < 0)
return -1;
+
+ if (git_buf_printf(&buf, format,
+ canonical_branch_name + 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_upstream__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;
+ int error = -1;
+ git_remote *remote = NULL;
+ const git_refspec *refspec;
+
+ assert(tracking_name && canonical_branch_name);
+
+ if (!git_reference__is_branch(canonical_branch_name))
+ return not_a_local_branch(canonical_branch_name);
+
+ if ((error = retrieve_upstream_configuration(
+ &remote_name, repo, canonical_branch_name, "branch.%s.remote")) < 0)
+ goto cleanup;
+
+ if ((error = retrieve_upstream_configuration(
+ &merge_name, repo, canonical_branch_name, "branch.%s.merge")) < 0)
+ goto cleanup;
+
+ if (!*remote_name || !*merge_name) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
}
- branch_names->strings = (char **)branchlist.contents;
- branch_names->count = branchlist.length;
- return 0;
+ if (strcmp(".", remote_name) != 0) {
+ if ((error = git_remote_load(&remote, repo, 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_buf_set(tracking_name, git_buf_cstr(&buf), git_buf_len(&buf));
+
+cleanup:
+ git_remote_free(remote);
+ git_buf_free(&buf);
+ return error;
}
-int git_branch_move(git_repository *repo, const char *old_branch_name, const char *new_branch_name, int force)
+static int remote_name(git_buf *buf, git_repository *repo, const char *canonical_branch_name)
{
- git_reference *reference = NULL;
- git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT;
+ git_strarray remote_list = {0};
+ size_t i;
+ git_remote *remote;
+ const git_refspec *fetchspec;
int error = 0;
+ char *remote_name = NULL;
+
+ assert(buf && repo && canonical_branch_name);
- if ((error = git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name)) < 0)
+ /* Verify that this is a remote branch */
+ if (!git_reference__is_remote(canonical_branch_name)) {
+ giterr_set(GITERR_INVALID, "Reference '%s' is not a remote branch.",
+ canonical_branch_name);
+ error = GIT_ERROR;
goto cleanup;
+ }
- /* We need to be able to return GIT_ENOTFOUND */
- if ((error = git_reference_lookup(&reference, repo, git_buf_cstr(&old_reference_name))) < 0)
+ /* Get the remotes */
+ if ((error = git_remote_list(&remote_list, repo)) < 0)
goto cleanup;
- if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
+ /* Find matching remotes */
+ for (i = 0; i < remote_list.count; i++) {
+ if ((error = git_remote_load(&remote, repo, remote_list.strings[i])) < 0)
+ continue;
+
+ fetchspec = git_remote_fetchspec(remote);
+
+ /* Defensivly check that we have a fetchspec */
+ if (fetchspec &&
+ git_refspec_dst_matches(fetchspec, canonical_branch_name)) {
+ /* If we have not already set out yet, then set
+ * it to the matching remote name. Otherwise
+ * multiple remotes match this reference, and it
+ * is ambiguous. */
+ if (!remote_name) {
+ remote_name = remote_list.strings[i];
+ } else {
+ git_remote_free(remote);
+ error = GIT_EAMBIGUOUS;
+ goto cleanup;
+ }
+ }
+
+ git_remote_free(remote);
+ }
+
+ if (remote_name) {
+ git_buf_clear(buf);
+ error = git_buf_puts(buf, remote_name);
+ } else {
+ error = GIT_ENOTFOUND;
+ }
+
+cleanup:
+ git_strarray_free(&remote_list);
+ return error;
+}
+
+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,
+ 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_upstream__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 = git_reference_rename(reference, git_buf_cstr(&new_reference_name), force);
+ error = (int)buf.size + 1;
cleanup:
- git_reference_free(reference);
- git_buf_free(&old_reference_name);
- git_buf_free(&new_reference_name);
+ git_buf_free(&buf);
+ return (int)error;
+}
+
+int git_branch_upstream(
+ git_reference **tracking_out,
+ git_reference *branch)
+{
+ int error;
+ git_buf tracking_name = GIT_BUF_INIT;
+
+ if ((error = git_branch_upstream__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;
}
+
+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)
+{
+ 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;
+}