summaryrefslogtreecommitdiff
path: root/src/libgit2/branch.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libgit2/branch.c')
-rw-r--r--src/libgit2/branch.c823
1 files changed, 823 insertions, 0 deletions
diff --git a/src/libgit2/branch.c b/src/libgit2/branch.c
new file mode 100644
index 000000000..5660a1dc4
--- /dev/null
+++ b/src/libgit2/branch.c
@@ -0,0 +1,823 @@
+/*
+ * 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.
+ */
+
+#include "branch.h"
+
+#include "buf.h"
+#include "commit.h"
+#include "tag.h"
+#include "config.h"
+#include "refspec.h"
+#include "refs.h"
+#include "remote.h"
+#include "annotated_commit.h"
+#include "worktree.h"
+
+#include "git2/branch.h"
+
+static int retrieve_branch_reference(
+ git_reference **branch_reference_out,
+ git_repository *repo,
+ const char *branch_name,
+ bool is_remote)
+{
+ git_reference *branch = NULL;
+ int error = 0;
+ char *prefix;
+ git_str ref_name = GIT_STR_INIT;
+
+ prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR;
+
+ if ((error = git_str_joinpath(&ref_name, prefix, branch_name)) < 0)
+ /* OOM */;
+ else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0)
+ git_error_set(
+ GIT_ERROR_REFERENCE, "cannot locate %s branch '%s'",
+ is_remote ? "remote-tracking" : "local", branch_name);
+
+ *branch_reference_out = branch; /* will be NULL on error */
+
+ git_str_dispose(&ref_name);
+ return error;
+}
+
+static int not_a_local_branch(const char *reference_name)
+{
+ git_error_set(
+ GIT_ERROR_INVALID,
+ "reference '%s' is not a local branch.", reference_name);
+ return -1;
+}
+
+static bool branch_name_follows_pattern(const char *branch_name)
+{
+ /*
+ * Discourage branch name starting with dash,
+ * https://github.com/git/git/commit/6348624010888b
+ * and discourage HEAD as branch name,
+ * https://github.com/git/git/commit/a625b092cc5994
+ */
+ return branch_name[0] != '-' && git__strcmp(branch_name, "HEAD");
+}
+
+static int create_branch(
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_commit *commit,
+ const char *from,
+ int force)
+{
+ int is_unmovable_head = 0;
+ git_reference *branch = NULL;
+ git_str canonical_branch_name = GIT_STR_INIT,
+ log_message = GIT_STR_INIT;
+ int error = -1;
+ int bare = git_repository_is_bare(repository);
+
+ GIT_ASSERT_ARG(branch_name);
+ GIT_ASSERT_ARG(commit);
+ GIT_ASSERT_ARG(ref_out);
+ GIT_ASSERT_ARG(git_commit_owner(commit) == repository);
+
+ if (!branch_name_follows_pattern(branch_name)) {
+ git_error_set(GIT_ERROR_REFERENCE, "'%s' is not a valid branch name", branch_name);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) {
+ error = git_branch_is_head(branch);
+ git_reference_free(branch);
+ branch = NULL;
+
+ if (error < 0)
+ goto cleanup;
+
+ is_unmovable_head = error;
+ }
+
+ if (is_unmovable_head && force) {
+ git_error_set(GIT_ERROR_REFERENCE, "cannot force update branch '%s' as it is "
+ "the current HEAD of the repository.", branch_name);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (git_str_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
+ goto cleanup;
+
+ if (git_str_printf(&log_message, "branch: Created from %s", from) < 0)
+ goto cleanup;
+
+ error = git_reference_create(&branch, repository,
+ git_str_cstr(&canonical_branch_name), git_commit_id(commit), force,
+ git_str_cstr(&log_message));
+
+ if (!error)
+ *ref_out = branch;
+
+cleanup:
+ git_str_dispose(&canonical_branch_name);
+ git_str_dispose(&log_message);
+ return error;
+}
+
+int git_branch_create(
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_commit *commit,
+ int force)
+{
+ char commit_id[GIT_OID_HEXSZ + 1];
+
+ git_oid_tostr(commit_id, GIT_OID_HEXSZ + 1, git_commit_id(commit));
+ return create_branch(ref_out, repository, branch_name, commit, commit_id, force);
+}
+
+int git_branch_create_from_annotated(
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_annotated_commit *commit,
+ int force)
+{
+ return create_branch(ref_out,
+ repository, branch_name, commit->commit, commit->description, force);
+}
+
+static int branch_is_checked_out(git_repository *worktree, void *payload)
+{
+ git_reference *branch = (git_reference *) payload;
+ git_reference *head = NULL;
+ int error;
+
+ if (git_repository_is_bare(worktree))
+ return 0;
+
+ if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ goto out;
+ }
+
+ if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC)
+ goto out;
+
+ error = !git__strcmp(head->target.symbolic, branch->name);
+
+out:
+ git_reference_free(head);
+ return error;
+}
+
+int git_branch_is_checked_out(const git_reference *branch)
+{
+ GIT_ASSERT_ARG(branch);
+
+ if (!git_reference_is_branch(branch))
+ return 0;
+ return git_repository_foreach_worktree(git_reference_owner(branch),
+ branch_is_checked_out, (void *)branch) == 1;
+}
+
+int git_branch_delete(git_reference *branch)
+{
+ int is_head;
+ git_str config_section = GIT_STR_INIT;
+ int error = -1;
+
+ GIT_ASSERT_ARG(branch);
+
+ if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) {
+ git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a valid branch.",
+ git_reference_name(branch));
+ return GIT_ENOTFOUND;
+ }
+
+ if ((is_head = git_branch_is_head(branch)) < 0)
+ return is_head;
+
+ if (is_head) {
+ git_error_set(GIT_ERROR_REFERENCE, "cannot delete branch '%s' as it is "
+ "the current HEAD of the repository.", git_reference_name(branch));
+ return -1;
+ }
+
+ if (git_reference_is_branch(branch) && git_branch_is_checked_out(branch)) {
+ git_error_set(GIT_ERROR_REFERENCE, "Cannot delete branch '%s' as it is "
+ "the current HEAD of a linked repository.", git_reference_name(branch));
+ return -1;
+ }
+
+ if (git_str_join(&config_section, '.', "branch",
+ git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ goto on_error;
+
+ if (git_config_rename_section(
+ git_reference_owner(branch), git_str_cstr(&config_section), NULL) < 0)
+ goto on_error;
+
+ error = git_reference_delete(branch);
+
+on_error:
+ git_str_dispose(&config_section);
+ return error;
+}
+
+typedef struct {
+ git_reference_iterator *iter;
+ unsigned int flags;
+} branch_iter;
+
+int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter)
+{
+ branch_iter *iter = (branch_iter *) _iter;
+ git_reference *ref;
+ int error;
+
+ while ((error = git_reference_next(&ref, iter->iter)) == 0) {
+ if ((iter->flags & GIT_BRANCH_LOCAL) &&
+ !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) {
+ *out = ref;
+ *out_type = GIT_BRANCH_LOCAL;
+
+ return 0;
+ } else if ((iter->flags & GIT_BRANCH_REMOTE) &&
+ !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) {
+ *out = ref;
+ *out_type = GIT_BRANCH_REMOTE;
+
+ return 0;
+ } else {
+ git_reference_free(ref);
+ }
+ }
+
+ return error;
+}
+
+int git_branch_iterator_new(
+ git_branch_iterator **out,
+ git_repository *repo,
+ git_branch_t list_flags)
+{
+ branch_iter *iter;
+
+ iter = git__calloc(1, sizeof(branch_iter));
+ GIT_ERROR_CHECK_ALLOC(iter);
+
+ iter->flags = list_flags;
+
+ if (git_reference_iterator_new(&iter->iter, repo) < 0) {
+ git__free(iter);
+ return -1;
+ }
+
+ *out = (git_branch_iterator *) iter;
+
+ return 0;
+}
+
+void git_branch_iterator_free(git_branch_iterator *_iter)
+{
+ branch_iter *iter = (branch_iter *) _iter;
+
+ if (iter == NULL)
+ return;
+
+ git_reference_iterator_free(iter->iter);
+ git__free(iter);
+}
+
+int git_branch_move(
+ git_reference **out,
+ git_reference *branch,
+ const char *new_branch_name,
+ int force)
+{
+ git_str new_reference_name = GIT_STR_INIT,
+ old_config_section = GIT_STR_INIT,
+ new_config_section = GIT_STR_INIT,
+ log_message = GIT_STR_INIT;
+ int error;
+
+ GIT_ASSERT_ARG(branch);
+ GIT_ASSERT_ARG(new_branch_name);
+
+ if (!git_reference_is_branch(branch))
+ return not_a_local_branch(git_reference_name(branch));
+
+ if ((error = git_str_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
+ goto done;
+
+ if ((error = git_str_printf(&log_message, "branch: renamed %s to %s",
+ git_reference_name(branch), git_str_cstr(&new_reference_name))) < 0)
+ goto done;
+
+ /* first update ref then config so failure won't trash config */
+
+ error = git_reference_rename(
+ out, branch, git_str_cstr(&new_reference_name), force,
+ git_str_cstr(&log_message));
+ if (error < 0)
+ goto done;
+
+ git_str_join(&old_config_section, '.', "branch",
+ git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR));
+ git_str_join(&new_config_section, '.', "branch", new_branch_name);
+
+ error = git_config_rename_section(
+ git_reference_owner(branch),
+ git_str_cstr(&old_config_section),
+ git_str_cstr(&new_config_section));
+
+done:
+ git_str_dispose(&new_reference_name);
+ git_str_dispose(&old_config_section);
+ git_str_dispose(&new_config_section);
+ git_str_dispose(&log_message);
+
+ return error;
+}
+
+int git_branch_lookup(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *branch_name,
+ git_branch_t branch_type)
+{
+ int error = -1;
+
+ GIT_ASSERT_ARG(ref_out);
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(branch_name);
+
+ switch (branch_type) {
+ case GIT_BRANCH_LOCAL:
+ case GIT_BRANCH_REMOTE:
+ error = retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);
+ break;
+ case GIT_BRANCH_ALL:
+ error = retrieve_branch_reference(ref_out, repo, branch_name, false);
+ if (error == GIT_ENOTFOUND)
+ error = retrieve_branch_reference(ref_out, repo, branch_name, true);
+ break;
+ default:
+ GIT_ASSERT(false);
+ }
+ return error;
+}
+
+int git_branch_name(
+ const char **out,
+ const git_reference *ref)
+{
+ const char *branch_name;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(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 {
+ git_error_set(GIT_ERROR_INVALID,
+ "reference '%s' is neither a local nor a remote branch.", ref->name);
+ return -1;
+ }
+ *out = branch_name;
+ return 0;
+}
+
+static int retrieve_upstream_configuration(
+ git_str *out,
+ const git_config *config,
+ const char *canonical_branch_name,
+ const char *format)
+{
+ git_str buf = GIT_STR_INIT;
+ int error;
+
+ if (git_str_printf(&buf, format,
+ canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ return -1;
+
+ error = git_config__get_string_buf(out, config, git_str_cstr(&buf));
+ git_str_dispose(&buf);
+ return error;
+}
+
+int git_branch_upstream_name(
+ git_buf *out,
+ git_repository *repo,
+ const char *refname)
+{
+ GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_name, repo, refname);
+}
+
+int git_branch__upstream_name(
+ git_str *out,
+ git_repository *repo,
+ const char *refname)
+{
+ git_str remote_name = GIT_STR_INIT;
+ git_str merge_name = GIT_STR_INIT;
+ git_str buf = GIT_STR_INIT;
+ int error = -1;
+ git_remote *remote = NULL;
+ const git_refspec *refspec;
+ git_config *config;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(refname);
+
+ if (!git_reference__is_branch(refname))
+ return not_a_local_branch(refname);
+
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ return error;
+
+ if ((error = retrieve_upstream_configuration(
+ &remote_name, config, refname, "branch.%s.remote")) < 0)
+ goto cleanup;
+
+ if ((error = retrieve_upstream_configuration(
+ &merge_name, config, refname, "branch.%s.merge")) < 0)
+ goto cleanup;
+
+ if (git_str_len(&remote_name) == 0 || git_str_len(&merge_name) == 0) {
+ git_error_set(GIT_ERROR_REFERENCE,
+ "branch '%s' does not have an upstream", refname);
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ if (strcmp(".", git_str_cstr(&remote_name)) != 0) {
+ if ((error = git_remote_lookup(&remote, repo, git_str_cstr(&remote_name))) < 0)
+ goto cleanup;
+
+ refspec = git_remote__matching_refspec(remote, git_str_cstr(&merge_name));
+ if (!refspec) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ if (git_refspec__transform(&buf, refspec, git_str_cstr(&merge_name)) < 0)
+ goto cleanup;
+ } else
+ if (git_str_set(&buf, git_str_cstr(&merge_name), git_str_len(&merge_name)) < 0)
+ goto cleanup;
+
+ git_str_swap(out, &buf);
+
+cleanup:
+ git_config_free(config);
+ git_remote_free(remote);
+ git_str_dispose(&remote_name);
+ git_str_dispose(&merge_name);
+ git_str_dispose(&buf);
+ return error;
+}
+
+static int git_branch_upstream_with_format(
+ git_str *out,
+ git_repository *repo,
+ const char *refname,
+ const char *format,
+ const char *format_name)
+{
+ git_config *cfg;
+ int error;
+
+ if (!git_reference__is_branch(refname))
+ return not_a_local_branch(refname);
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 ||
+ (error = retrieve_upstream_configuration(out, cfg, refname, format)) < 0)
+ return error;
+
+ if (git_str_len(out) == 0) {
+ git_error_set(GIT_ERROR_REFERENCE, "branch '%s' does not have an upstream %s", refname, format_name);
+ error = GIT_ENOTFOUND;
+ }
+
+ return error;
+}
+
+int git_branch_upstream_remote(
+ git_buf *out,
+ git_repository *repo,
+ const char *refname)
+{
+ GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_remote, repo, refname);
+}
+
+int git_branch__upstream_remote(
+ git_str *out,
+ git_repository *repo,
+ const char *refname)
+{
+ return git_branch_upstream_with_format(out, repo, refname, "branch.%s.remote", "remote");
+}
+
+int git_branch_upstream_merge(
+ git_buf *out,
+ git_repository *repo,
+ const char *refname)
+{
+ GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_merge, repo, refname);
+}
+
+int git_branch__upstream_merge(
+ git_str *out,
+ git_repository *repo,
+ const char *refname)
+{
+ return git_branch_upstream_with_format(out, repo, refname, "branch.%s.merge", "merge");
+}
+
+int git_branch_remote_name(
+ git_buf *out,
+ git_repository *repo,
+ const char *refname)
+{
+ GIT_BUF_WRAP_PRIVATE(out, git_branch__remote_name, repo, refname);
+}
+
+int git_branch__remote_name(
+ git_str *out,
+ git_repository *repo,
+ const char *refname)
+{
+ git_strarray remote_list = {0};
+ size_t i;
+ git_remote *remote;
+ const git_refspec *fetchspec;
+ int error = 0;
+ char *remote_name = NULL;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(refname);
+
+ /* Verify that this is a remote branch */
+ if (!git_reference__is_remote(refname)) {
+ git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a remote branch.",
+ refname);
+ error = GIT_ERROR;
+ goto cleanup;
+ }
+
+ /* Get the remotes */
+ if ((error = git_remote_list(&remote_list, repo)) < 0)
+ goto cleanup;
+
+ /* Find matching remotes */
+ for (i = 0; i < remote_list.count; i++) {
+ if ((error = git_remote_lookup(&remote, repo, remote_list.strings[i])) < 0)
+ continue;
+
+ fetchspec = git_remote__matching_dst_refspec(remote, refname);
+ if (fetchspec) {
+ /* 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);
+
+ git_error_set(GIT_ERROR_REFERENCE,
+ "reference '%s' is ambiguous", refname);
+ error = GIT_EAMBIGUOUS;
+ goto cleanup;
+ }
+ }
+
+ git_remote_free(remote);
+ }
+
+ if (remote_name) {
+ git_str_clear(out);
+ error = git_str_puts(out, remote_name);
+ } else {
+ git_error_set(GIT_ERROR_REFERENCE,
+ "could not determine remote for '%s'", refname);
+ error = GIT_ENOTFOUND;
+ }
+
+cleanup:
+ if (error < 0)
+ git_str_dispose(out);
+
+ git_strarray_dispose(&remote_list);
+ return error;
+}
+
+int git_branch_upstream(
+ git_reference **tracking_out,
+ const git_reference *branch)
+{
+ int error;
+ git_str tracking_name = GIT_STR_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_str_cstr(&tracking_name));
+
+ git_str_dispose(&tracking_name);
+ return error;
+}
+
+static int unset_upstream(git_config *config, const char *shortname)
+{
+ git_str buf = GIT_STR_INIT;
+
+ if (git_str_printf(&buf, "branch.%s.remote", shortname) < 0)
+ return -1;
+
+ if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_str_clear(&buf);
+ if (git_str_printf(&buf, "branch.%s.merge", shortname) < 0)
+ goto on_error;
+
+ if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_str_dispose(&buf);
+ return 0;
+
+on_error:
+ git_str_dispose(&buf);
+ return -1;
+}
+
+int git_branch_set_upstream(git_reference *branch, const char *branch_name)
+{
+ git_str key = GIT_STR_INIT, remote_name = GIT_STR_INIT, merge_refspec = GIT_STR_INIT;
+ git_reference *upstream;
+ git_repository *repo;
+ git_remote *remote = NULL;
+ git_config *config;
+ const char *refname, *shortname;
+ int local, error;
+ const git_refspec *fetchspec;
+
+ refname = git_reference_name(branch);
+ if (!git_reference__is_branch(refname))
+ return not_a_local_branch(refname);
+
+ if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)
+ return -1;
+
+ shortname = refname + strlen(GIT_REFS_HEADS_DIR);
+
+ /* We're unsetting, delegate and bail-out */
+ if (branch_name == NULL)
+ return unset_upstream(config, shortname);
+
+ repo = git_reference_owner(branch);
+
+ /* First we need to resolve name to a branch */
+ if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_LOCAL) == 0)
+ local = 1;
+ else if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_REMOTE) == 0)
+ local = 0;
+ else {
+ git_error_set(GIT_ERROR_REFERENCE,
+ "cannot set upstream for branch '%s'", shortname);
+ return GIT_ENOTFOUND;
+ }
+
+ /*
+ * If it's a local-tracking branch, its remote is "." (as "the local
+ * repository"), 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)
+ error = git_str_puts(&remote_name, ".");
+ else
+ error = git_branch__remote_name(&remote_name, repo, git_reference_name(upstream));
+
+ if (error < 0)
+ goto on_error;
+
+ /* Update the upstream branch config with the new name */
+ if (git_str_printf(&key, "branch.%s.remote", shortname) < 0)
+ goto on_error;
+
+ if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&remote_name)) < 0)
+ goto on_error;
+
+ if (local) {
+ /* A local branch uses the upstream refname directly */
+ if (git_str_puts(&merge_refspec, git_reference_name(upstream)) < 0)
+ goto on_error;
+ } else {
+ /* We transform the upstream branch name according to the remote's refspecs */
+ if (git_remote_lookup(&remote, repo, git_str_cstr(&remote_name)) < 0)
+ goto on_error;
+
+ fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream));
+ if (!fetchspec || git_refspec__rtransform(&merge_refspec, fetchspec, git_reference_name(upstream)) < 0)
+ goto on_error;
+
+ git_remote_free(remote);
+ remote = NULL;
+ }
+
+ /* Update the merge branch config with the refspec */
+ git_str_clear(&key);
+ if (git_str_printf(&key, "branch.%s.merge", shortname) < 0)
+ goto on_error;
+
+ if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&merge_refspec)) < 0)
+ goto on_error;
+
+ git_reference_free(upstream);
+ git_str_dispose(&key);
+ git_str_dispose(&remote_name);
+ git_str_dispose(&merge_refspec);
+
+ return 0;
+
+on_error:
+ git_reference_free(upstream);
+ git_str_dispose(&key);
+ git_str_dispose(&remote_name);
+ git_str_dispose(&merge_refspec);
+ git_remote_free(remote);
+
+ return -1;
+}
+
+int git_branch_is_head(
+ const git_reference *branch)
+{
+ git_reference *head;
+ bool is_same = false;
+ int error;
+
+ GIT_ASSERT_ARG(branch);
+
+ if (!git_reference_is_branch(branch))
+ return false;
+
+ error = git_repository_head(&head, git_reference_owner(branch));
+
+ if (error == GIT_EUNBORNBRANCH || 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;
+}
+
+int git_branch_name_is_valid(int *valid, const char *name)
+{
+ git_str ref_name = GIT_STR_INIT;
+ int error = 0;
+
+ GIT_ASSERT(valid);
+
+ *valid = 0;
+
+ if (!name || !branch_name_follows_pattern(name))
+ goto done;
+
+ if ((error = git_str_puts(&ref_name, GIT_REFS_HEADS_DIR)) < 0 ||
+ (error = git_str_puts(&ref_name, name)) < 0)
+ goto done;
+
+ error = git_reference_name_is_valid(valid, ref_name.ptr);
+
+done:
+ git_str_dispose(&ref_name);
+ return error;
+}