| /* |
| * 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 "fileops.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; |
| |
| #define DEFAULT_NESTING_LEVEL 5 |
| #define MAX_NESTING_LEVEL 10 |
| |
| 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; |
| |
| assert(name && target); |
| |
| 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; |
| |
| assert(name && oid); |
| |
| 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__set_name( |
| git_reference *ref, const char *name) |
| { |
| size_t namelen = strlen(name); |
| size_t reflen; |
| git_reference *rewrite = NULL; |
| |
| if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && |
| !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && |
| (rewrite = git__realloc(ref, reflen)) != NULL) |
| memcpy(rewrite->name, name, namelen + 1); |
| |
| 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 (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__cvar(&precompose, repo, GIT_CVAR_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 scan_name; |
| git_reference_t scan_type; |
| int error = 0, nesting; |
| git_reference *ref = NULL; |
| git_refdb *refdb; |
| |
| assert(ref_out && repo && name); |
| |
| *ref_out = NULL; |
| |
| if (max_nesting > MAX_NESTING_LEVEL) |
| max_nesting = MAX_NESTING_LEVEL; |
| else if (max_nesting < 0) |
| max_nesting = DEFAULT_NESTING_LEVEL; |
| |
| scan_type = GIT_REFERENCE_SYMBOLIC; |
| |
| if ((error = reference_normalize_for_repo(scan_name, repo, name, true)) < 0) |
| return error; |
| |
| if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) |
| return error; |
| |
| for (nesting = max_nesting; |
| nesting >= 0 && scan_type == GIT_REFERENCE_SYMBOLIC; |
| nesting--) |
| { |
| if (nesting != max_nesting) { |
| strncpy(scan_name, ref->target.symbolic, sizeof(scan_name)); |
| git_reference_free(ref); |
| } |
| |
| if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0) |
| return error; |
| |
| scan_type = ref->type; |
| } |
| |
| if (scan_type != GIT_REFERENCE_DIRECT && max_nesting != 0) { |
| git_error_set(GIT_ERROR_REFERENCE, |
| "cannot resolve reference (>%u levels deep)", max_nesting); |
| git_reference_free(ref); |
| return -1; |
| } |
| |
| *ref_out = ref; |
| return 0; |
| } |
| |
| int git_reference__read_head( |
| git_reference **out, |
| git_repository *repo, |
| const char *path) |
| { |
| git_buf reference = GIT_BUF_INIT; |
| char *name = NULL; |
| int error; |
| |
| if ((error = git_futils_readbuffer(&reference, path)) < 0) |
| goto out; |
| git_buf_rtrim(&reference); |
| |
| if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) == 0) { |
| git_buf_consume(&reference, reference.ptr + strlen(GIT_SYMREF)); |
| |
| name = git_path_basename(path); |
| |
| if ((*out = git_reference__alloc_symbolic(name, reference.ptr)) == NULL) { |
| error = -1; |
| goto out; |
| } |
| } else { |
| if ((error = git_reference_lookup(out, repo, reference.ptr)) < 0) |
| goto out; |
| } |
| |
| out: |
| git__free(name); |
| git_buf_dispose(&reference); |
| |
| return error; |
| } |
| |
| int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname) |
| { |
| int error = 0, i; |
| bool fallbackmode = true, foundvalid = false; |
| git_reference *ref; |
| git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_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_buf_puts(&name, refname); |
| else { |
| git_buf_puts(&name, GIT_HEAD_FILE); |
| fallbackmode = false; |
| } |
| |
| for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) { |
| |
| git_buf_clear(&refnamebuf); |
| |
| if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0) |
| goto cleanup; |
| |
| if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) { |
| error = GIT_EINVALIDSPEC; |
| continue; |
| } |
| foundvalid = true; |
| |
| error = git_reference_lookup_resolved(&ref, repo, git_buf_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_buf_cstr(&name)); |
| } |
| |
| if (error == GIT_ENOTFOUND) |
| git_error_set(GIT_ERROR_REFERENCE, "no reference found for shorthand '%s'", refname); |
| |
| git_buf_dispose(&name); |
| git_buf_dispose(&refnamebuf); |
| return error; |
| } |
| |
| /** |
| * Getters |
| */ |
| git_reference_t git_reference_type(const git_reference *ref) |
| { |
| assert(ref); |
| return ref->type; |
| } |
| |
| const char *git_reference_name(const git_reference *ref) |
| { |
| assert(ref); |
| return ref->name; |
| } |
| |
| git_repository *git_reference_owner(const git_reference *ref) |
| { |
| assert(ref); |
| return ref->db->repo; |
| } |
| |
| const git_oid *git_reference_target(const git_reference *ref) |
| { |
| assert(ref); |
| |
| if (ref->type != GIT_REFERENCE_DIRECT) |
| return NULL; |
| |
| return &ref->target.oid; |
| } |
| |
| const git_oid *git_reference_target_peel(const git_reference *ref) |
| { |
| assert(ref); |
| |
| if (ref->type != GIT_REFERENCE_DIRECT || git_oid_iszero(&ref->peel)) |
| return NULL; |
| |
| return &ref->peel; |
| } |
| |
| const char *git_reference_symbolic_target(const git_reference *ref) |
| { |
| assert(ref); |
| |
| 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; |
| |
| assert(repo && name); |
| assert(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) { |
| 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; |
| } |
| |
| int 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 = 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; |
| |
| assert(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; |
| |
| assert(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; |
| |
| assert(out && ref && 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; |
| |
| assert(out && ref && 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; |
| } rename_cb_data; |
| |
| static int update_wt_heads(git_repository *repo, const char *path, void *payload) |
| { |
| rename_cb_data *data = (rename_cb_data *) payload; |
| git_reference *head = NULL; |
| char *gitdir = NULL; |
| int error; |
| |
| if ((error = git_reference__read_head(&head, repo, path)) < 0) { |
| git_error_set(GIT_ERROR_REFERENCE, "could not read HEAD when renaming references"); |
| goto out; |
| } |
| |
| if ((gitdir = git_path_dirname(path)) == NULL) { |
| error = -1; |
| goto out; |
| } |
| |
| if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC || |
| git__strcmp(head->target.symbolic, data->old_name) != 0) { |
| error = 0; |
| goto out; |
| } |
| |
| /* Update HEAD it was pointing to the reference being renamed */ |
| if ((error = git_repository_create_head(gitdir, data->new_name)) < 0) { |
| git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference"); |
| goto out; |
| } |
| |
| out: |
| git_reference_free(head); |
| git__free(gitdir); |
| |
| return error; |
| } |
| |
| static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force, |
| const git_signature *signature, const char *message) |
| { |
| git_repository *repo; |
| git_refname_t normalized; |
| bool should_head_be_updated = false; |
| int error = 0; |
| |
| assert(ref && new_name && signature); |
| |
| repo = git_reference_owner(ref); |
| |
| if ((error = reference_normalize_for_repo( |
| normalized, repo, new_name, true)) < 0) |
| return error; |
| |
| /* Check if we have to update HEAD. */ |
| if ((error = git_branch_is_head(ref)) < 0) |
| return error; |
| |
| should_head_be_updated = (error > 0); |
| |
| if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0) |
| return error; |
| |
| /* Update HEAD if it was pointing to the reference being renamed */ |
| if (should_head_be_updated) { |
| error = git_repository_set_head(ref->db->repo, normalized); |
| } else { |
| rename_cb_data payload; |
| payload.old_name = ref->name; |
| memcpy(&payload.new_name, &normalized, sizeof(normalized)); |
| |
| error = git_repository_foreach_head(repo, update_wt_heads, &payload); |
| } |
| |
| return error; |
| } |
| |
| |
| int git_reference_rename( |
| git_reference **out, |
| git_reference *ref, |
| const char *new_name, |
| int force, |
| const char *log_message) |
| { |
| git_signature *who; |
| int error; |
| |
| assert(out && ref); |
| |
| if ((error = git_reference__log_signature(&who, ref->db->repo)) < 0) |
| return error; |
| |
| error = reference__rename(out, ref, new_name, force, who, log_message); |
| git_signature_free(who); |
| |
| 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; |
| |
| assert(array && 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 '[': |
| case '*': |
| return 0; |
| default: |
| return 1; |
| } |
| } |
| |
| static int ensure_segment_validity(const char *name) |
| { |
| 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 "@{" */ |
| |
| 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; |
| |
| assert(name && 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_buf *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_path_iconv_t ic = GIT_PATH_ICONV_INIT; |
| #endif |
| |
| assert(name); |
| |
| process_flags = flags; |
| current = (char *)name; |
| |
| if (validate && *current == '/') |
| goto cleanup; |
| |
| if (normalize) |
| git_buf_clear(buf); |
| |
| #ifdef GIT_USE_ICONV |
| if ((flags & GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE) != 0) { |
| size_t namelen = strlen(current); |
| if ((error = git_path_iconv_init_precompose(&ic)) < 0 || |
| (error = git_path_iconv(&ic, ¤t, &namelen)) < 0) |
| goto cleanup; |
| error = GIT_EINVALIDSPEC; |
| } |
| #endif |
| |
| if (!validate) { |
| git_buf_sets(buf, current); |
| |
| error = git_buf_oom(buf) ? -1 : 0; |
| goto cleanup; |
| } |
| |
| while (true) { |
| segment_len = ensure_segment_validity(current); |
| if (segment_len < 0) { |
| if ((process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && |
| current[0] == '*' && |
| (current[1] == '\0' || current[1] == '/')) { |
| /* Accept one wildcard as a full refname component. */ |
| process_flags &= ~GIT_REFERENCE_FORMAT_REFSPEC_PATTERN; |
| segment_len = 1; |
| } else |
| goto cleanup; |
| } |
| |
| if (segment_len > 0) { |
| if (normalize) { |
| size_t cur_len = git_buf_len(buf); |
| |
| git_buf_joinpath(buf, git_buf_cstr(buf), current); |
| git_buf_truncate(buf, |
| cur_len + segment_len + (segments_count ? 1 : 0)); |
| |
| if (git_buf_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_buf_dispose(buf); |
| |
| #ifdef GIT_USE_ICONV |
| git_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_buf buf = GIT_BUF_INIT; |
| int error; |
| |
| if ((error = git_reference__normalize_name(&buf, name, flags)) < 0) |
| goto cleanup; |
| |
| if (git_buf_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; |
| } |
| |
| git_buf_copy_cstr(buffer_out, buffer_size, &buf); |
| |
| error = 0; |
| |
| cleanup: |
| git_buf_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; |
| assert(ref1 && 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); |
| } |
| |
| /** |
| * Get the end of a chain of references. If the final one is not |
| * found, we return the reference just before that. |
| */ |
| static int get_terminal(git_reference **out, git_repository *repo, const char *ref_name, int nesting) |
| { |
| git_reference *ref; |
| int error = 0; |
| |
| if (nesting > MAX_NESTING_LEVEL) { |
| git_error_set(GIT_ERROR_REFERENCE, "reference chain too deep (%d)", nesting); |
| return GIT_ENOTFOUND; |
| } |
| |
| /* set to NULL to let the caller know that they're at the end of the chain */ |
| if ((error = git_reference_lookup(&ref, repo, ref_name)) < 0) { |
| *out = NULL; |
| return error; |
| } |
| |
| if (git_reference_type(ref) == GIT_REFERENCE_DIRECT) { |
| *out = ref; |
| error = 0; |
| } else { |
| error = get_terminal(out, repo, git_reference_symbolic_target(ref), nesting + 1); |
| if (error == GIT_ENOTFOUND && !*out) |
| *out = ref; |
| else |
| git_reference_free(ref); |
| } |
| |
| return error; |
| } |
| |
| /* |
| * 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; |
| const git_signature *to_use; |
| int error = 0; |
| |
| if (!sig && (error = git_reference__log_signature(&who, repo)) < 0) |
| return error; |
| |
| to_use = sig ? sig : who; |
| error = get_terminal(&ref, repo, ref_name, 0); |
| |
| /* found a dangling symref */ |
| if (error == GIT_ENOTFOUND && ref) { |
| assert(git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC); |
| git_error_clear(); |
| error = reference__create(&ref2, repo, ref->target.symbolic, oid, NULL, 0, to_use, |
| log_message, NULL, NULL); |
| } else if (error == GIT_ENOTFOUND) { |
| git_error_clear(); |
| error = reference__create(&ref2, repo, ref_name, oid, NULL, 0, to_use, |
| log_message, NULL, NULL); |
| } else if (error == 0) { |
| assert(git_reference_type(ref) == GIT_REFERENCE_DIRECT); |
| error = reference__create(&ref2, repo, ref->name, oid, NULL, 1, to_use, |
| log_message, &ref->target.oid, NULL); |
| } |
| |
| 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_buf reflog_msg = GIT_BUF_INIT; |
| const git_signature *who; |
| int error; |
| |
| if ((error = git_commit_lookup(&commit, repo, id)) < 0 || |
| (error = git_buf_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_buf_cstr(&reflog_msg), &ref->target.oid, NULL); |
| } |
| else |
| error = git_reference__update_terminal( |
| repo, ref_name, id, who, git_buf_cstr(&reflog_msg)); |
| |
| done: |
| git_reference_free(ref_new); |
| git_buf_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; |
| |
| assert(repo && 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; |
| |
| assert(repo && 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) |
| { |
| assert(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) |
| { |
| assert(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) |
| { |
| assert(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) |
| { |
| assert(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; |
| |
| assert(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_iszero(&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__is_valid_name(const char *refname, unsigned int flags) |
| { |
| if (git_reference__normalize_name(NULL, refname, flags) < 0) { |
| git_error_clear(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int git_reference_is_valid_name(const char *refname) |
| { |
| return git_reference__is_valid_name(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 avaiable, 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; |
| assert(unborn && ref && 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; |
| } |