summaryrefslogtreecommitdiff
path: root/src/libgit2/refs.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libgit2/refs.c')
-rw-r--r--src/libgit2/refs.c1395
1 files changed, 1395 insertions, 0 deletions
diff --git a/src/libgit2/refs.c b/src/libgit2/refs.c
new file mode 100644
index 000000000..5c875b95b
--- /dev/null
+++ b/src/libgit2/refs.c
@@ -0,0 +1,1395 @@
+/*
+ * 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 "refs.h"
+
+#include "hash.h"
+#include "repository.h"
+#include "futils.h"
+#include "filebuf.h"
+#include "pack.h"
+#include "reflog.h"
+#include "refdb.h"
+
+#include <git2/tag.h>
+#include <git2/object.h>
+#include <git2/oid.h>
+#include <git2/branch.h>
+#include <git2/refs.h>
+#include <git2/refdb.h>
+#include <git2/sys/refs.h>
+#include <git2/signature.h>
+#include <git2/commit.h>
+
+bool git_reference__enable_symbolic_ref_target_validation = true;
+
+enum {
+ GIT_PACKREF_HAS_PEEL = 1,
+ GIT_PACKREF_WAS_LOOSE = 2
+};
+
+static git_reference *alloc_ref(const char *name)
+{
+ git_reference *ref = NULL;
+ size_t namelen = strlen(name), reflen;
+
+ if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) &&
+ !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) &&
+ (ref = git__calloc(1, reflen)) != NULL)
+ memcpy(ref->name, name, namelen + 1);
+
+ return ref;
+}
+
+git_reference *git_reference__alloc_symbolic(
+ const char *name, const char *target)
+{
+ git_reference *ref;
+
+ GIT_ASSERT_ARG_WITH_RETVAL(name, NULL);
+ GIT_ASSERT_ARG_WITH_RETVAL(target, NULL);
+
+ ref = alloc_ref(name);
+ if (!ref)
+ return NULL;
+
+ ref->type = GIT_REFERENCE_SYMBOLIC;
+
+ if ((ref->target.symbolic = git__strdup(target)) == NULL) {
+ git__free(ref);
+ return NULL;
+ }
+
+ return ref;
+}
+
+git_reference *git_reference__alloc(
+ const char *name,
+ const git_oid *oid,
+ const git_oid *peel)
+{
+ git_reference *ref;
+
+ GIT_ASSERT_ARG_WITH_RETVAL(name, NULL);
+ GIT_ASSERT_ARG_WITH_RETVAL(oid, NULL);
+
+ ref = alloc_ref(name);
+ if (!ref)
+ return NULL;
+
+ ref->type = GIT_REFERENCE_DIRECT;
+ git_oid_cpy(&ref->target.oid, oid);
+
+ if (peel != NULL)
+ git_oid_cpy(&ref->peel, peel);
+
+ return ref;
+}
+
+git_reference *git_reference__realloc(
+ git_reference **ptr_to_ref, const char *name)
+{
+ size_t namelen, reflen;
+ git_reference *rewrite = NULL;
+
+ GIT_ASSERT_ARG_WITH_RETVAL(ptr_to_ref, NULL);
+ GIT_ASSERT_ARG_WITH_RETVAL(name, NULL);
+
+ namelen = strlen(name);
+
+ if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) &&
+ !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) &&
+ (rewrite = git__realloc(*ptr_to_ref, reflen)) != NULL)
+ memcpy(rewrite->name, name, namelen + 1);
+
+ *ptr_to_ref = NULL;
+
+ return rewrite;
+}
+
+int git_reference_dup(git_reference **dest, git_reference *source)
+{
+ if (source->type == GIT_REFERENCE_SYMBOLIC)
+ *dest = git_reference__alloc_symbolic(source->name, source->target.symbolic);
+ else
+ *dest = git_reference__alloc(source->name, &source->target.oid, &source->peel);
+
+ GIT_ERROR_CHECK_ALLOC(*dest);
+
+ (*dest)->db = source->db;
+ GIT_REFCOUNT_INC((*dest)->db);
+
+ return 0;
+}
+
+void git_reference_free(git_reference *reference)
+{
+ if (reference == NULL)
+ return;
+
+ if (reference->type == GIT_REFERENCE_SYMBOLIC)
+ git__free(reference->target.symbolic);
+
+ if (reference->db)
+ GIT_REFCOUNT_DEC(reference->db, git_refdb__free);
+
+ git__free(reference);
+}
+
+int git_reference_delete(git_reference *ref)
+{
+ const git_oid *old_id = NULL;
+ const char *old_target = NULL;
+
+ if (!strcmp(ref->name, "HEAD")) {
+ git_error_set(GIT_ERROR_REFERENCE, "cannot delete HEAD");
+ return GIT_ERROR;
+ }
+
+ if (ref->type == GIT_REFERENCE_DIRECT)
+ old_id = &ref->target.oid;
+ else
+ old_target = ref->target.symbolic;
+
+ return git_refdb_delete(ref->db, ref->name, old_id, old_target);
+}
+
+int git_reference_remove(git_repository *repo, const char *name)
+{
+ git_refdb *db;
+ int error;
+
+ if ((error = git_repository_refdb__weakptr(&db, repo)) < 0)
+ return error;
+
+ return git_refdb_delete(db, name, NULL, NULL);
+}
+
+int git_reference_lookup(git_reference **ref_out,
+ git_repository *repo, const char *name)
+{
+ return git_reference_lookup_resolved(ref_out, repo, name, 0);
+}
+
+int git_reference_name_to_id(
+ git_oid *out, git_repository *repo, const char *name)
+{
+ int error;
+ git_reference *ref;
+
+ if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0)
+ return error;
+
+ git_oid_cpy(out, git_reference_target(ref));
+ git_reference_free(ref);
+ return 0;
+}
+
+static int reference_normalize_for_repo(
+ git_refname_t out,
+ git_repository *repo,
+ const char *name,
+ bool validate)
+{
+ int precompose;
+ unsigned int flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL;
+
+ if (!git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) &&
+ precompose)
+ flags |= GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE;
+
+ if (!validate)
+ flags |= GIT_REFERENCE_FORMAT__VALIDATION_DISABLE;
+
+ return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags);
+}
+
+int git_reference_lookup_resolved(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ int max_nesting)
+{
+ git_refname_t normalized;
+ git_refdb *refdb;
+ int error = 0;
+
+ GIT_ASSERT_ARG(ref_out);
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(name);
+
+ if ((error = reference_normalize_for_repo(normalized, repo, name, true)) < 0 ||
+ (error = git_repository_refdb__weakptr(&refdb, repo)) < 0 ||
+ (error = git_refdb_resolve(ref_out, refdb, normalized, max_nesting)) < 0)
+ return error;
+
+ /*
+ * The resolved reference may be a symbolic reference in case its
+ * target doesn't exist. If the user asked us to resolve (e.g.
+ * `max_nesting != 0`), then we need to return an error in case we got
+ * a symbolic reference back.
+ */
+ if (max_nesting && git_reference_type(*ref_out) == GIT_REFERENCE_SYMBOLIC) {
+ git_reference_free(*ref_out);
+ *ref_out = NULL;
+ return GIT_ENOTFOUND;
+ }
+
+ return 0;
+}
+
+int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname)
+{
+ int error = 0, i, valid;
+ bool fallbackmode = true, foundvalid = false;
+ git_reference *ref;
+ git_str refnamebuf = GIT_STR_INIT, name = GIT_STR_INIT;
+
+ static const char *formatters[] = {
+ "%s",
+ GIT_REFS_DIR "%s",
+ GIT_REFS_TAGS_DIR "%s",
+ GIT_REFS_HEADS_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
+ NULL
+ };
+
+ if (*refname)
+ git_str_puts(&name, refname);
+ else {
+ git_str_puts(&name, GIT_HEAD_FILE);
+ fallbackmode = false;
+ }
+
+ for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {
+
+ git_str_clear(&refnamebuf);
+
+ if ((error = git_str_printf(&refnamebuf, formatters[i], git_str_cstr(&name))) < 0 ||
+ (error = git_reference_name_is_valid(&valid, git_str_cstr(&refnamebuf))) < 0)
+ goto cleanup;
+
+ if (!valid) {
+ error = GIT_EINVALIDSPEC;
+ continue;
+ }
+ foundvalid = true;
+
+ error = git_reference_lookup_resolved(&ref, repo, git_str_cstr(&refnamebuf), -1);
+
+ if (!error) {
+ *out = ref;
+ error = 0;
+ goto cleanup;
+ }
+
+ if (error != GIT_ENOTFOUND)
+ goto cleanup;
+ }
+
+cleanup:
+ if (error && !foundvalid) {
+ /* never found a valid reference name */
+ git_error_set(GIT_ERROR_REFERENCE,
+ "could not use '%s' as valid reference name", git_str_cstr(&name));
+ }
+
+ if (error == GIT_ENOTFOUND)
+ git_error_set(GIT_ERROR_REFERENCE, "no reference found for shorthand '%s'", refname);
+
+ git_str_dispose(&name);
+ git_str_dispose(&refnamebuf);
+ return error;
+}
+
+/**
+ * Getters
+ */
+git_reference_t git_reference_type(const git_reference *ref)
+{
+ GIT_ASSERT_ARG(ref);
+ return ref->type;
+}
+
+const char *git_reference_name(const git_reference *ref)
+{
+ GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
+ return ref->name;
+}
+
+git_repository *git_reference_owner(const git_reference *ref)
+{
+ GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
+ return ref->db->repo;
+}
+
+const git_oid *git_reference_target(const git_reference *ref)
+{
+ GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
+
+ if (ref->type != GIT_REFERENCE_DIRECT)
+ return NULL;
+
+ return &ref->target.oid;
+}
+
+const git_oid *git_reference_target_peel(const git_reference *ref)
+{
+ GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
+
+ if (ref->type != GIT_REFERENCE_DIRECT || git_oid_is_zero(&ref->peel))
+ return NULL;
+
+ return &ref->peel;
+}
+
+const char *git_reference_symbolic_target(const git_reference *ref)
+{
+ GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
+
+ if (ref->type != GIT_REFERENCE_SYMBOLIC)
+ return NULL;
+
+ return ref->target.symbolic;
+}
+
+static int reference__create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const git_oid *oid,
+ const char *symbolic,
+ int force,
+ const git_signature *signature,
+ const char *log_message,
+ const git_oid *old_id,
+ const char *old_target)
+{
+ git_refname_t normalized;
+ git_refdb *refdb;
+ git_reference *ref = NULL;
+ int error = 0;
+
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(name);
+ GIT_ASSERT_ARG(symbolic || signature);
+
+ if (ref_out)
+ *ref_out = NULL;
+
+ error = reference_normalize_for_repo(normalized, repo, name, true);
+ if (error < 0)
+ return error;
+
+ error = git_repository_refdb__weakptr(&refdb, repo);
+ if (error < 0)
+ return error;
+
+ if (oid != NULL) {
+ GIT_ASSERT(symbolic == NULL);
+
+ if (!git_object__is_valid(repo, oid, GIT_OBJECT_ANY)) {
+ git_error_set(GIT_ERROR_REFERENCE,
+ "target OID for the reference doesn't exist on the repository");
+ return -1;
+ }
+
+ ref = git_reference__alloc(normalized, oid, NULL);
+ } else {
+ git_refname_t normalized_target;
+
+ error = reference_normalize_for_repo(normalized_target, repo,
+ symbolic, git_reference__enable_symbolic_ref_target_validation);
+
+ if (error < 0)
+ return error;
+
+ ref = git_reference__alloc_symbolic(normalized, normalized_target);
+ }
+
+ GIT_ERROR_CHECK_ALLOC(ref);
+
+ if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) {
+ git_reference_free(ref);
+ return error;
+ }
+
+ if (ref_out == NULL)
+ git_reference_free(ref);
+ else
+ *ref_out = ref;
+
+ return 0;
+}
+
+static int refs_configured_ident(git_signature **out, const git_repository *repo)
+{
+ if (repo->ident_name && repo->ident_email)
+ return git_signature_now(out, repo->ident_name, repo->ident_email);
+
+ /* if not configured let us fall-through to the next method */
+ return -1;
+}
+
+int git_reference__log_signature(git_signature **out, git_repository *repo)
+{
+ int error;
+ git_signature *who;
+
+ if(((error = refs_configured_ident(&who, repo)) < 0) &&
+ ((error = git_signature_default(&who, repo)) < 0) &&
+ ((error = git_signature_now(&who, "unknown", "unknown")) < 0))
+ return error;
+
+ *out = who;
+ return 0;
+}
+
+int git_reference_create_matching(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const git_oid *id,
+ int force,
+ const git_oid *old_id,
+ const char *log_message)
+
+{
+ int error;
+ git_signature *who = NULL;
+
+ GIT_ASSERT_ARG(id);
+
+ if ((error = git_reference__log_signature(&who, repo)) < 0)
+ return error;
+
+ error = reference__create(
+ ref_out, repo, name, id, NULL, force, who, log_message, old_id, NULL);
+
+ git_signature_free(who);
+ return error;
+}
+
+int git_reference_create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const git_oid *id,
+ int force,
+ const char *log_message)
+{
+ return git_reference_create_matching(ref_out, repo, name, id, force, NULL, log_message);
+}
+
+int git_reference_symbolic_create_matching(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const char *target,
+ int force,
+ const char *old_target,
+ const char *log_message)
+{
+ int error;
+ git_signature *who = NULL;
+
+ GIT_ASSERT_ARG(target);
+
+ if ((error = git_reference__log_signature(&who, repo)) < 0)
+ return error;
+
+ error = reference__create(
+ ref_out, repo, name, NULL, target, force, who, log_message, NULL, old_target);
+
+ git_signature_free(who);
+ return error;
+}
+
+int git_reference_symbolic_create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const char *target,
+ int force,
+ const char *log_message)
+{
+ return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, log_message);
+}
+
+static int ensure_is_an_updatable_direct_reference(git_reference *ref)
+{
+ if (ref->type == GIT_REFERENCE_DIRECT)
+ return 0;
+
+ git_error_set(GIT_ERROR_REFERENCE, "cannot set OID on symbolic reference");
+ return -1;
+}
+
+int git_reference_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const git_oid *id,
+ const char *log_message)
+{
+ int error;
+ git_repository *repo;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(ref);
+ GIT_ASSERT_ARG(id);
+
+ repo = ref->db->repo;
+
+ if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
+ return error;
+
+ return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, log_message);
+}
+
+static int ensure_is_an_updatable_symbolic_reference(git_reference *ref)
+{
+ if (ref->type == GIT_REFERENCE_SYMBOLIC)
+ return 0;
+
+ git_error_set(GIT_ERROR_REFERENCE, "cannot set symbolic target on a direct reference");
+ return -1;
+}
+
+int git_reference_symbolic_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const char *target,
+ const char *log_message)
+{
+ int error;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(ref);
+ GIT_ASSERT_ARG(target);
+
+ if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0)
+ return error;
+
+ return git_reference_symbolic_create_matching(
+ out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message);
+}
+
+typedef struct {
+ const char *old_name;
+ git_refname_t new_name;
+} refs_update_head_payload;
+
+static int refs_update_head(git_repository *worktree, void *_payload)
+{
+ refs_update_head_payload *payload = (refs_update_head_payload *)_payload;
+ git_reference *head = NULL, *updated = NULL;
+ int error;
+
+ if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0)
+ goto out;
+
+ if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC ||
+ git__strcmp(git_reference_symbolic_target(head), payload->old_name) != 0)
+ goto out;
+
+ /* Update HEAD if it was pointing to the reference being renamed */
+ if ((error = git_reference_symbolic_set_target(&updated, head, payload->new_name, NULL)) < 0) {
+ git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference");
+ goto out;
+ }
+
+out:
+ git_reference_free(updated);
+ git_reference_free(head);
+ return error;
+}
+
+int git_reference_rename(
+ git_reference **out,
+ git_reference *ref,
+ const char *new_name,
+ int force,
+ const char *log_message)
+{
+ refs_update_head_payload payload;
+ git_signature *signature = NULL;
+ git_repository *repo;
+ int error;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(ref);
+
+ repo = git_reference_owner(ref);
+
+ if ((error = git_reference__log_signature(&signature, repo)) < 0 ||
+ (error = reference_normalize_for_repo(payload.new_name, repo, new_name, true)) < 0 ||
+ (error = git_refdb_rename(out, ref->db, ref->name, payload.new_name, force, signature, log_message)) < 0)
+ goto out;
+
+ payload.old_name = ref->name;
+
+ /* We may have to update any HEAD that was pointing to the renamed reference. */
+ if ((error = git_repository_foreach_worktree(repo, refs_update_head, &payload)) < 0)
+ goto out;
+
+out:
+ git_signature_free(signature);
+ return error;
+}
+
+int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
+{
+ switch (git_reference_type(ref)) {
+ case GIT_REFERENCE_DIRECT:
+ return git_reference_lookup(ref_out, ref->db->repo, ref->name);
+
+ case GIT_REFERENCE_SYMBOLIC:
+ return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1);
+
+ default:
+ git_error_set(GIT_ERROR_REFERENCE, "invalid reference");
+ return -1;
+ }
+}
+
+int git_reference_foreach(
+ git_repository *repo,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ git_reference_iterator *iter;
+ git_reference *ref;
+ int error;
+
+ if ((error = git_reference_iterator_new(&iter, repo)) < 0)
+ return error;
+
+ while (!(error = git_reference_next(&ref, iter))) {
+ if ((error = callback(ref, payload)) != 0) {
+ git_error_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_reference_iterator_free(iter);
+ return error;
+}
+
+int git_reference_foreach_name(
+ git_repository *repo,
+ git_reference_foreach_name_cb callback,
+ void *payload)
+{
+ git_reference_iterator *iter;
+ const char *refname;
+ int error;
+
+ if ((error = git_reference_iterator_new(&iter, repo)) < 0)
+ return error;
+
+ while (!(error = git_reference_next_name(&refname, iter))) {
+ if ((error = callback(refname, payload)) != 0) {
+ git_error_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_reference_iterator_free(iter);
+ return error;
+}
+
+int git_reference_foreach_glob(
+ git_repository *repo,
+ const char *glob,
+ git_reference_foreach_name_cb callback,
+ void *payload)
+{
+ git_reference_iterator *iter;
+ const char *refname;
+ int error;
+
+ if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0)
+ return error;
+
+ while (!(error = git_reference_next_name(&refname, iter))) {
+ if ((error = callback(refname, payload)) != 0) {
+ git_error_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_reference_iterator_free(iter);
+ return error;
+}
+
+int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo)
+{
+ git_refdb *refdb;
+
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
+
+ return git_refdb_iterator(out, refdb, NULL);
+}
+
+int git_reference_iterator_glob_new(
+ git_reference_iterator **out, git_repository *repo, const char *glob)
+{
+ git_refdb *refdb;
+
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
+
+ return git_refdb_iterator(out, refdb, glob);
+}
+
+int git_reference_next(git_reference **out, git_reference_iterator *iter)
+{
+ return git_refdb_iterator_next(out, iter);
+}
+
+int git_reference_next_name(const char **out, git_reference_iterator *iter)
+{
+ return git_refdb_iterator_next_name(out, iter);
+}
+
+void git_reference_iterator_free(git_reference_iterator *iter)
+{
+ if (iter == NULL)
+ return;
+
+ git_refdb_iterator_free(iter);
+}
+
+static int cb__reflist_add(const char *ref, void *data)
+{
+ char *name = git__strdup(ref);
+ GIT_ERROR_CHECK_ALLOC(name);
+ return git_vector_insert((git_vector *)data, name);
+}
+
+int git_reference_list(
+ git_strarray *array,
+ git_repository *repo)
+{
+ git_vector ref_list;
+
+ GIT_ASSERT_ARG(array);
+ GIT_ASSERT_ARG(repo);
+
+ array->strings = NULL;
+ array->count = 0;
+
+ if (git_vector_init(&ref_list, 8, NULL) < 0)
+ return -1;
+
+ if (git_reference_foreach_name(
+ repo, &cb__reflist_add, (void *)&ref_list) < 0) {
+ git_vector_free(&ref_list);
+ return -1;
+ }
+
+ array->strings = (char **)git_vector_detach(&array->count, NULL, &ref_list);
+
+ return 0;
+}
+
+static int is_valid_ref_char(char ch)
+{
+ if ((unsigned) ch <= ' ')
+ return 0;
+
+ switch (ch) {
+ case '~':
+ case '^':
+ case ':':
+ case '\\':
+ case '?':
+ case '[':
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+static int ensure_segment_validity(const char *name, char may_contain_glob)
+{
+ const char *current = name;
+ char prev = '\0';
+ const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
+ int segment_len;
+
+ if (*current == '.')
+ return -1; /* Refname starts with "." */
+
+ for (current = name; ; current++) {
+ if (*current == '\0' || *current == '/')
+ break;
+
+ if (!is_valid_ref_char(*current))
+ return -1; /* Illegal character in refname */
+
+ if (prev == '.' && *current == '.')
+ return -1; /* Refname contains ".." */
+
+ if (prev == '@' && *current == '{')
+ return -1; /* Refname contains "@{" */
+
+ if (*current == '*') {
+ if (!may_contain_glob)
+ return -1;
+ may_contain_glob = 0;
+ }
+
+ prev = *current;
+ }
+
+ segment_len = (int)(current - name);
+
+ /* A refname component can not end with ".lock" */
+ if (segment_len >= lock_len &&
+ !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))
+ return -1;
+
+ return segment_len;
+}
+
+static bool is_all_caps_and_underscore(const char *name, size_t len)
+{
+ size_t i;
+ char c;
+
+ GIT_ASSERT_ARG(name);
+ GIT_ASSERT_ARG(len > 0);
+
+ for (i = 0; i < len; i++)
+ {
+ c = name[i];
+ if ((c < 'A' || c > 'Z') && c != '_')
+ return false;
+ }
+
+ if (*name == '_' || name[len - 1] == '_')
+ return false;
+
+ return true;
+}
+
+/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */
+int git_reference__normalize_name(
+ git_str *buf,
+ const char *name,
+ unsigned int flags)
+{
+ const char *current;
+ int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
+ unsigned int process_flags;
+ bool normalize = (buf != NULL);
+ bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0;
+
+#ifdef GIT_USE_ICONV
+ git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT;
+#endif
+
+ GIT_ASSERT_ARG(name);
+
+ process_flags = flags;
+ current = (char *)name;
+
+ if (validate && *current == '/')
+ goto cleanup;
+
+ if (normalize)
+ git_str_clear(buf);
+
+#ifdef GIT_USE_ICONV
+ if ((flags & GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE) != 0) {
+ size_t namelen = strlen(current);
+ if ((error = git_fs_path_iconv_init_precompose(&ic)) < 0 ||
+ (error = git_fs_path_iconv(&ic, &current, &namelen)) < 0)
+ goto cleanup;
+ error = GIT_EINVALIDSPEC;
+ }
+#endif
+
+ if (!validate) {
+ git_str_sets(buf, current);
+
+ error = git_str_oom(buf) ? -1 : 0;
+ goto cleanup;
+ }
+
+ while (true) {
+ char may_contain_glob = process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN;
+
+ segment_len = ensure_segment_validity(current, may_contain_glob);
+ if (segment_len < 0)
+ goto cleanup;
+
+ if (segment_len > 0) {
+ /*
+ * There may only be one glob in a pattern, thus we reset
+ * the pattern-flag in case the current segment has one.
+ */
+ if (memchr(current, '*', segment_len))
+ process_flags &= ~GIT_REFERENCE_FORMAT_REFSPEC_PATTERN;
+
+ if (normalize) {
+ size_t cur_len = git_str_len(buf);
+
+ git_str_joinpath(buf, git_str_cstr(buf), current);
+ git_str_truncate(buf,
+ cur_len + segment_len + (segments_count ? 1 : 0));
+
+ if (git_str_oom(buf)) {
+ error = -1;
+ goto cleanup;
+ }
+ }
+
+ segments_count++;
+ }
+
+ /* No empty segment is allowed when not normalizing */
+ if (segment_len == 0 && !normalize)
+ goto cleanup;
+
+ if (current[segment_len] == '\0')
+ break;
+
+ current += segment_len + 1;
+ }
+
+ /* A refname can not be empty */
+ if (segment_len == 0 && segments_count == 0)
+ goto cleanup;
+
+ /* A refname can not end with "." */
+ if (current[segment_len - 1] == '.')
+ goto cleanup;
+
+ /* A refname can not end with "/" */
+ if (current[segment_len - 1] == '/')
+ goto cleanup;
+
+ if ((segments_count == 1 ) && !(flags & GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL))
+ goto cleanup;
+
+ if ((segments_count == 1 ) &&
+ !(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) &&
+ !(is_all_caps_and_underscore(name, (size_t)segment_len) ||
+ ((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
+ goto cleanup;
+
+ if ((segments_count > 1)
+ && (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ if (error == GIT_EINVALIDSPEC)
+ git_error_set(
+ GIT_ERROR_REFERENCE,
+ "the given reference name '%s' is not valid", name);
+
+ if (error && normalize)
+ git_str_dispose(buf);
+
+#ifdef GIT_USE_ICONV
+ git_fs_path_iconv_clear(&ic);
+#endif
+
+ return error;
+}
+
+int git_reference_normalize_name(
+ char *buffer_out,
+ size_t buffer_size,
+ const char *name,
+ unsigned int flags)
+{
+ git_str buf = GIT_STR_INIT;
+ int error;
+
+ if ((error = git_reference__normalize_name(&buf, name, flags)) < 0)
+ goto cleanup;
+
+ if (git_str_len(&buf) > buffer_size - 1) {
+ git_error_set(
+ GIT_ERROR_REFERENCE,
+ "the provided buffer is too short to hold the normalization of '%s'", name);
+ error = GIT_EBUFS;
+ goto cleanup;
+ }
+
+ if ((error = git_str_copy_cstr(buffer_out, buffer_size, &buf)) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_str_dispose(&buf);
+ return error;
+}
+
+#define GIT_REFERENCE_TYPEMASK (GIT_REFERENCE_DIRECT | GIT_REFERENCE_SYMBOLIC)
+
+int git_reference_cmp(
+ const git_reference *ref1,
+ const git_reference *ref2)
+{
+ git_reference_t type1, type2;
+
+ GIT_ASSERT_ARG(ref1);
+ GIT_ASSERT_ARG(ref2);
+
+ type1 = git_reference_type(ref1);
+ type2 = git_reference_type(ref2);
+
+ /* let's put symbolic refs before OIDs */
+ if (type1 != type2)
+ return (type1 == GIT_REFERENCE_SYMBOLIC) ? -1 : 1;
+
+ if (type1 == GIT_REFERENCE_SYMBOLIC)
+ return strcmp(ref1->target.symbolic, ref2->target.symbolic);
+
+ return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
+}
+
+/*
+ * Starting with the reference given by `ref_name`, follows symbolic
+ * references until a direct reference is found and updated the OID
+ * on that direct reference to `oid`.
+ */
+int git_reference__update_terminal(
+ git_repository *repo,
+ const char *ref_name,
+ const git_oid *oid,
+ const git_signature *sig,
+ const char *log_message)
+{
+ git_reference *ref = NULL, *ref2 = NULL;
+ git_signature *who = NULL;
+ git_refdb *refdb = NULL;
+ const git_signature *to_use;
+ int error = 0;
+
+ if (!sig && (error = git_reference__log_signature(&who, repo)) < 0)
+ goto out;
+
+ to_use = sig ? sig : who;
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ goto out;
+
+ if ((error = git_refdb_resolve(&ref, refdb, ref_name, -1)) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ git_error_clear();
+ error = reference__create(&ref2, repo, ref_name, oid, NULL, 0, to_use,
+ log_message, NULL, NULL);
+ }
+ goto out;
+ }
+
+ /* In case the resolved reference is symbolic, then it's a dangling symref. */
+ if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) {
+ error = reference__create(&ref2, repo, ref->target.symbolic, oid, NULL, 0, to_use,
+ log_message, NULL, NULL);
+ } else {
+ error = reference__create(&ref2, repo, ref->name, oid, NULL, 1, to_use,
+ log_message, &ref->target.oid, NULL);
+ }
+
+out:
+ git_reference_free(ref2);
+ git_reference_free(ref);
+ git_signature_free(who);
+ return error;
+}
+
+static const char *commit_type(const git_commit *commit)
+{
+ unsigned int count = git_commit_parentcount(commit);
+
+ if (count >= 2)
+ return " (merge)";
+ else if (count == 0)
+ return " (initial)";
+ else
+ return "";
+}
+
+int git_reference__update_for_commit(
+ git_repository *repo,
+ git_reference *ref,
+ const char *ref_name,
+ const git_oid *id,
+ const char *operation)
+{
+ git_reference *ref_new = NULL;
+ git_commit *commit = NULL;
+ git_str reflog_msg = GIT_STR_INIT;
+ const git_signature *who;
+ int error;
+
+ if ((error = git_commit_lookup(&commit, repo, id)) < 0 ||
+ (error = git_str_printf(&reflog_msg, "%s%s: %s",
+ operation ? operation : "commit",
+ commit_type(commit),
+ git_commit_summary(commit))) < 0)
+ goto done;
+
+ who = git_commit_committer(commit);
+
+ if (ref) {
+ if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
+ return error;
+
+ error = reference__create(&ref_new, repo, ref->name, id, NULL, 1, who,
+ git_str_cstr(&reflog_msg), &ref->target.oid, NULL);
+ }
+ else
+ error = git_reference__update_terminal(
+ repo, ref_name, id, who, git_str_cstr(&reflog_msg));
+
+done:
+ git_reference_free(ref_new);
+ git_str_dispose(&reflog_msg);
+ git_commit_free(commit);
+ return error;
+}
+
+int git_reference_has_log(git_repository *repo, const char *refname)
+{
+ int error;
+ git_refdb *refdb;
+
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(refname);
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
+
+ return git_refdb_has_log(refdb, refname);
+}
+
+int git_reference_ensure_log(git_repository *repo, const char *refname)
+{
+ int error;
+ git_refdb *refdb;
+
+ GIT_ASSERT_ARG(repo);
+ GIT_ASSERT_ARG(refname);
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
+
+ return git_refdb_ensure_log(refdb, refname);
+}
+
+int git_reference__is_branch(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
+}
+
+int git_reference_is_branch(const git_reference *ref)
+{
+ GIT_ASSERT_ARG(ref);
+ return git_reference__is_branch(ref->name);
+}
+
+int git_reference__is_remote(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;
+}
+
+int git_reference_is_remote(const git_reference *ref)
+{
+ GIT_ASSERT_ARG(ref);
+ return git_reference__is_remote(ref->name);
+}
+
+int git_reference__is_tag(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0;
+}
+
+int git_reference_is_tag(const git_reference *ref)
+{
+ GIT_ASSERT_ARG(ref);
+ return git_reference__is_tag(ref->name);
+}
+
+int git_reference__is_note(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0;
+}
+
+int git_reference_is_note(const git_reference *ref)
+{
+ GIT_ASSERT_ARG(ref);
+ return git_reference__is_note(ref->name);
+}
+
+static int peel_error(int error, const git_reference *ref, const char *msg)
+{
+ git_error_set(
+ GIT_ERROR_INVALID,
+ "the reference '%s' cannot be peeled - %s", git_reference_name(ref), msg);
+ return error;
+}
+
+int git_reference_peel(
+ git_object **peeled,
+ const git_reference *ref,
+ git_object_t target_type)
+{
+ const git_reference *resolved = NULL;
+ git_reference *allocated = NULL;
+ git_object *target = NULL;
+ int error;
+
+ GIT_ASSERT_ARG(ref);
+
+ if (ref->type == GIT_REFERENCE_DIRECT) {
+ resolved = ref;
+ } else {
+ if ((error = git_reference_resolve(&allocated, ref)) < 0)
+ return peel_error(error, ref, "Cannot resolve reference");
+
+ resolved = allocated;
+ }
+
+ /*
+ * If we try to peel an object to a tag, we cannot use
+ * the fully peeled object, as that will always resolve
+ * to a commit. So we only want to use the peeled value
+ * if it is not zero and the target is not a tag.
+ */
+ if (target_type != GIT_OBJECT_TAG && !git_oid_is_zero(&resolved->peel)) {
+ error = git_object_lookup(&target,
+ git_reference_owner(ref), &resolved->peel, GIT_OBJECT_ANY);
+ } else {
+ error = git_object_lookup(&target,
+ git_reference_owner(ref), &resolved->target.oid, GIT_OBJECT_ANY);
+ }
+
+ if (error < 0) {
+ peel_error(error, ref, "Cannot retrieve reference target");
+ goto cleanup;
+ }
+
+ if (target_type == GIT_OBJECT_ANY && git_object_type(target) != GIT_OBJECT_TAG)
+ error = git_object_dup(peeled, target);
+ else
+ error = git_object_peel(peeled, target, target_type);
+
+cleanup:
+ git_object_free(target);
+ git_reference_free(allocated);
+
+ return error;
+}
+
+int git_reference__name_is_valid(
+ int *valid,
+ const char *refname,
+ unsigned int flags)
+{
+ int error;
+
+ GIT_ASSERT(valid && refname);
+
+ *valid = 0;
+
+ error = git_reference__normalize_name(NULL, refname, flags);
+
+ if (!error)
+ *valid = 1;
+ else if (error == GIT_EINVALIDSPEC)
+ error = 0;
+
+ return error;
+}
+
+int git_reference_name_is_valid(int *valid, const char *refname)
+{
+ return git_reference__name_is_valid(valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL);
+}
+
+const char *git_reference__shorthand(const char *name)
+{
+ if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR))
+ return name + strlen(GIT_REFS_HEADS_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR))
+ return name + strlen(GIT_REFS_TAGS_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR))
+ return name + strlen(GIT_REFS_REMOTES_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_DIR))
+ return name + strlen(GIT_REFS_DIR);
+
+ /* No shorthands are available, so just return the name. */
+ return name;
+}
+
+const char *git_reference_shorthand(const git_reference *ref)
+{
+ return git_reference__shorthand(ref->name);
+}
+
+int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo)
+{
+ int error;
+ git_reference *tmp_ref;
+
+ GIT_ASSERT_ARG(unborn);
+ GIT_ASSERT_ARG(ref);
+ GIT_ASSERT_ARG(repo);
+
+ if (ref->type == GIT_REFERENCE_DIRECT) {
+ *unborn = 0;
+ return 0;
+ }
+
+ error = git_reference_lookup_resolved(&tmp_ref, repo, ref->name, -1);
+ git_reference_free(tmp_ref);
+
+ if (error != 0 && error != GIT_ENOTFOUND)
+ return error;
+ else if (error == GIT_ENOTFOUND && git__strcmp(ref->name, GIT_HEAD_FILE) == 0)
+ *unborn = true;
+ else
+ *unborn = false;
+
+ return 0;
+}
+
+/* Deprecated functions */
+
+#ifndef GIT_DEPRECATE_HARD
+
+int git_reference_is_valid_name(const char *refname)
+{
+ int valid = 0;
+
+ git_reference__name_is_valid(&valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL);
+
+ return valid;
+}
+
+#endif