mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-06 09:41:04 +00:00

This is a significant reorganization of the diff code to break it into a set of more clearly distinct files and to document the new organization. Hopefully this will make the diff code easier to understand and to extend. This adds a new `git_diff_driver` object that looks of diff driver information from the attributes and the config so that things like function content in diff headers can be provided. The full driver spec is not implemented in the commit - this is focused on the reorganization of the code and putting the driver hooks in place. This also removes a few #includes from src/repository.h that were overbroad, but as a result required extra #includes in a variety of places since including src/repository.h no longer results in pulling in the whole world.
1044 lines
22 KiB
C
1044 lines
22 KiB
C
/*
|
|
* 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>
|
|
|
|
GIT__USE_STRMAP;
|
|
|
|
#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;
|
|
size_t namelen = strlen(name);
|
|
|
|
if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL)
|
|
return 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_REF_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_REF_OID;
|
|
git_oid_cpy(&ref->target.oid, oid);
|
|
|
|
if (peel != NULL)
|
|
git_oid_cpy(&ref->peel, peel);
|
|
|
|
return ref;
|
|
}
|
|
|
|
void git_reference_free(git_reference *reference)
|
|
{
|
|
if (reference == NULL)
|
|
return;
|
|
|
|
if (reference->type == GIT_REF_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)
|
|
{
|
|
return git_refdb_delete(ref->db, ref->name);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int git_reference_lookup_resolved(
|
|
git_reference **ref_out,
|
|
git_repository *repo,
|
|
const char *name,
|
|
int max_nesting)
|
|
{
|
|
char scan_name[GIT_REFNAME_MAX];
|
|
git_ref_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;
|
|
|
|
strncpy(scan_name, name, GIT_REFNAME_MAX);
|
|
scan_type = GIT_REF_SYMBOLIC;
|
|
|
|
if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
|
|
return -1;
|
|
|
|
if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0)
|
|
return error;
|
|
|
|
for (nesting = max_nesting;
|
|
nesting >= 0 && scan_type == GIT_REF_SYMBOLIC;
|
|
nesting--)
|
|
{
|
|
if (nesting != max_nesting) {
|
|
strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX);
|
|
git_reference_free(ref);
|
|
}
|
|
|
|
if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)
|
|
return error;
|
|
|
|
scan_type = ref->type;
|
|
}
|
|
|
|
if (scan_type != GIT_REF_OID && max_nesting != 0) {
|
|
giterr_set(GITERR_REFERENCE,
|
|
"Cannot resolve reference (>%u levels deep)", max_nesting);
|
|
git_reference_free(ref);
|
|
return -1;
|
|
}
|
|
|
|
*ref_out = ref;
|
|
return 0;
|
|
}
|
|
|
|
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 */
|
|
giterr_set(GITERR_REFERENCE,
|
|
"Could not use '%s' as valid reference name", git_buf_cstr(&name));
|
|
}
|
|
|
|
git_buf_free(&name);
|
|
git_buf_free(&refnamebuf);
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* Getters
|
|
*/
|
|
git_ref_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_REF_OID)
|
|
return NULL;
|
|
|
|
return &ref->target.oid;
|
|
}
|
|
|
|
const git_oid *git_reference_target_peel(const git_reference *ref)
|
|
{
|
|
assert(ref);
|
|
|
|
if (ref->type != GIT_REF_OID || 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_REF_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)
|
|
{
|
|
char normalized[GIT_REFNAME_MAX];
|
|
git_refdb *refdb;
|
|
git_reference *ref = NULL;
|
|
int error = 0;
|
|
|
|
if (ref_out)
|
|
*ref_out = NULL;
|
|
|
|
error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
error = git_repository_refdb__weakptr(&refdb, repo);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
if (oid != NULL) {
|
|
assert(symbolic == NULL);
|
|
ref = git_reference__alloc(normalized, oid, NULL);
|
|
} else {
|
|
ref = git_reference__alloc_symbolic(normalized, symbolic);
|
|
}
|
|
|
|
GITERR_CHECK_ALLOC(ref);
|
|
|
|
if ((error = git_refdb_write(refdb, ref, force)) < 0) {
|
|
git_reference_free(ref);
|
|
return error;
|
|
}
|
|
|
|
if (ref_out == NULL)
|
|
git_reference_free(ref);
|
|
else
|
|
*ref_out = ref;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_reference_create(
|
|
git_reference **ref_out,
|
|
git_repository *repo,
|
|
const char *name,
|
|
const git_oid *oid,
|
|
int force)
|
|
{
|
|
git_odb *odb;
|
|
int error = 0;
|
|
|
|
assert(repo && name && oid);
|
|
|
|
/* Sanity check the reference being created - target must exist. */
|
|
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
|
|
return error;
|
|
|
|
if (!git_odb_exists(odb, oid)) {
|
|
giterr_set(GITERR_REFERENCE,
|
|
"Target OID for the reference doesn't exist on the repository");
|
|
return -1;
|
|
}
|
|
|
|
return reference__create(ref_out, repo, name, oid, NULL, force);
|
|
}
|
|
|
|
int git_reference_symbolic_create(
|
|
git_reference **ref_out,
|
|
git_repository *repo,
|
|
const char *name,
|
|
const char *target,
|
|
int force)
|
|
{
|
|
char normalized[GIT_REFNAME_MAX];
|
|
int error = 0;
|
|
|
|
assert(repo && name && target);
|
|
|
|
if ((error = git_reference__normalize_name_lax(
|
|
normalized, sizeof(normalized), target)) < 0)
|
|
return error;
|
|
|
|
return reference__create(ref_out, repo, name, NULL, normalized, force);
|
|
}
|
|
|
|
int git_reference_set_target(
|
|
git_reference **out,
|
|
git_reference *ref,
|
|
const git_oid *id)
|
|
{
|
|
assert(out && ref && id);
|
|
|
|
if (ref->type != GIT_REF_OID) {
|
|
giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
|
|
return -1;
|
|
}
|
|
|
|
return git_reference_create(out, ref->db->repo, ref->name, id, 1);
|
|
}
|
|
|
|
int git_reference_symbolic_set_target(
|
|
git_reference **out,
|
|
git_reference *ref,
|
|
const char *target)
|
|
{
|
|
assert(out && ref && target);
|
|
|
|
if (ref->type != GIT_REF_SYMBOLIC) {
|
|
giterr_set(GITERR_REFERENCE,
|
|
"Cannot set symbolic target on a direct reference");
|
|
return -1;
|
|
}
|
|
|
|
return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1);
|
|
}
|
|
|
|
int git_reference_rename(
|
|
git_reference **out,
|
|
git_reference *ref,
|
|
const char *new_name,
|
|
int force)
|
|
{
|
|
unsigned int normalization_flags;
|
|
char normalized[GIT_REFNAME_MAX];
|
|
bool should_head_be_updated = false;
|
|
int error = 0;
|
|
int reference_has_log;
|
|
|
|
normalization_flags = ref->type == GIT_REF_SYMBOLIC ?
|
|
GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL;
|
|
|
|
if ((error = git_reference_normalize_name(
|
|
normalized, sizeof(normalized), new_name, normalization_flags)) < 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, new_name, force)) < 0)
|
|
return error;
|
|
|
|
/* Update HEAD it was poiting to the reference being renamed. */
|
|
if (should_head_be_updated &&
|
|
(error = git_repository_set_head(ref->db->repo, new_name)) < 0) {
|
|
giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference");
|
|
return error;
|
|
}
|
|
|
|
/* Rename the reflog file, if it exists. */
|
|
reference_has_log = git_reference_has_log(ref);
|
|
if (reference_has_log < 0)
|
|
return reference_has_log;
|
|
|
|
if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0)
|
|
return error;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
|
|
{
|
|
switch (git_reference_type(ref)) {
|
|
case GIT_REF_OID:
|
|
return git_reference_lookup(ref_out, ref->db->repo, ref->name);
|
|
|
|
case GIT_REF_SYMBOLIC:
|
|
return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1);
|
|
|
|
default:
|
|
giterr_set(GITERR_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 (git_reference_iterator_new(&iter, repo) < 0)
|
|
return -1;
|
|
|
|
while ((error = git_reference_next(&ref, iter)) == 0) {
|
|
if (callback(ref, payload)) {
|
|
error = GIT_EUSER;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (error == GIT_ITEROVER)
|
|
error = 0;
|
|
|
|
out:
|
|
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 (git_reference_iterator_new(&iter, repo) < 0)
|
|
return -1;
|
|
|
|
while ((error = git_reference_next_name(&refname, iter)) == 0) {
|
|
if (callback(refname, payload)) {
|
|
error = GIT_EUSER;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (error == GIT_ITEROVER)
|
|
error = 0;
|
|
|
|
out:
|
|
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 (git_reference_iterator_glob_new(&iter, repo, glob) < 0)
|
|
return -1;
|
|
|
|
while ((error = git_reference_next_name(&refname, iter)) == 0) {
|
|
if (callback(refname, payload)) {
|
|
error = GIT_EUSER;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (error == GIT_ITEROVER)
|
|
error = 0;
|
|
|
|
out:
|
|
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)
|
|
{
|
|
git_refdb_iterator_free(iter);
|
|
}
|
|
|
|
static int cb__reflist_add(const char *ref, void *data)
|
|
{
|
|
return git_vector_insert((git_vector *)data, git__strdup(ref));
|
|
}
|
|
|
|
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 **)ref_list.contents;
|
|
array->count = ref_list.length;
|
|
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;
|
|
}
|
|
|
|
int git_reference__normalize_name(
|
|
git_buf *buf,
|
|
const char *name,
|
|
unsigned int flags)
|
|
{
|
|
// Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100
|
|
|
|
char *current;
|
|
int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
|
|
unsigned int process_flags;
|
|
bool normalize = (buf != NULL);
|
|
assert(name);
|
|
|
|
process_flags = flags;
|
|
current = (char *)name;
|
|
|
|
if (*current == '/')
|
|
goto cleanup;
|
|
|
|
if (normalize)
|
|
git_buf_clear(buf);
|
|
|
|
while (true) {
|
|
segment_len = ensure_segment_validity(current);
|
|
if (segment_len < 0) {
|
|
if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
|
|
current[0] == '*' &&
|
|
(current[1] == '\0' || current[1] == '/')) {
|
|
/* Accept one wildcard as a full refname component. */
|
|
process_flags &= ~GIT_REF_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_REF_FORMAT_ALLOW_ONELEVEL))
|
|
goto cleanup;
|
|
|
|
if ((segments_count == 1 ) &&
|
|
!(flags & GIT_REF_FORMAT_REFSPEC_SHORTHAND) &&
|
|
!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
|
|
((flags & GIT_REF_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)
|
|
giterr_set(
|
|
GITERR_REFERENCE,
|
|
"The given reference name '%s' is not valid", name);
|
|
|
|
if (error && normalize)
|
|
git_buf_free(buf);
|
|
|
|
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) {
|
|
giterr_set(
|
|
GITERR_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_free(&buf);
|
|
return error;
|
|
}
|
|
|
|
int git_reference__normalize_name_lax(
|
|
char *buffer_out,
|
|
size_t out_size,
|
|
const char *name)
|
|
{
|
|
return git_reference_normalize_name(
|
|
buffer_out,
|
|
out_size,
|
|
name,
|
|
GIT_REF_FORMAT_ALLOW_ONELEVEL);
|
|
}
|
|
#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)
|
|
|
|
int git_reference_cmp(git_reference *ref1, git_reference *ref2)
|
|
{
|
|
git_ref_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_REF_SYMBOLIC) ? -1 : 1;
|
|
|
|
if (type1 == GIT_REF_SYMBOLIC)
|
|
return strcmp(ref1->target.symbolic, ref2->target.symbolic);
|
|
|
|
return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
|
|
}
|
|
|
|
static int reference__update_terminal(
|
|
git_repository *repo,
|
|
const char *ref_name,
|
|
const git_oid *oid,
|
|
int nesting)
|
|
{
|
|
git_reference *ref;
|
|
int error = 0;
|
|
|
|
if (nesting > MAX_NESTING_LEVEL) {
|
|
giterr_set(GITERR_REFERENCE, "Reference chain too deep (%d)", nesting);
|
|
return GIT_ENOTFOUND;
|
|
}
|
|
|
|
error = git_reference_lookup(&ref, repo, ref_name);
|
|
|
|
/* If we haven't found the reference at all, create a new reference. */
|
|
if (error == GIT_ENOTFOUND) {
|
|
giterr_clear();
|
|
return git_reference_create(NULL, repo, ref_name, oid, 0);
|
|
}
|
|
|
|
if (error < 0)
|
|
return error;
|
|
|
|
/* If the ref is a symbolic reference, follow its target. */
|
|
if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
|
|
error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid,
|
|
nesting+1);
|
|
git_reference_free(ref);
|
|
} else {
|
|
git_reference_free(ref);
|
|
error = git_reference_create(NULL, repo, ref_name, oid, 1);
|
|
}
|
|
|
|
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)
|
|
{
|
|
return reference__update_terminal(repo, ref_name, oid, 0);
|
|
}
|
|
|
|
int git_reference_has_log(
|
|
git_reference *ref)
|
|
{
|
|
git_buf path = GIT_BUF_INIT;
|
|
int result;
|
|
|
|
assert(ref);
|
|
|
|
if (git_buf_join_n(&path, '/', 3, ref->db->repo->path_repository,
|
|
GIT_REFLOG_DIR, ref->name) < 0)
|
|
return -1;
|
|
|
|
result = git_path_isfile(git_buf_cstr(&path));
|
|
git_buf_free(&path);
|
|
|
|
return result;
|
|
}
|
|
|
|
int git_reference__is_branch(const char *ref_name)
|
|
{
|
|
return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
|
|
}
|
|
|
|
int git_reference_is_branch(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(git_reference *ref)
|
|
{
|
|
assert(ref);
|
|
return git_reference__is_remote(ref->name);
|
|
}
|
|
|
|
static int peel_error(int error, git_reference *ref, const char* msg)
|
|
{
|
|
giterr_set(
|
|
GITERR_INVALID,
|
|
"The reference '%s' cannot be peeled - %s", git_reference_name(ref), msg);
|
|
return error;
|
|
}
|
|
|
|
int git_reference_peel(
|
|
git_object **peeled,
|
|
git_reference *ref,
|
|
git_otype target_type)
|
|
{
|
|
git_reference *resolved = NULL;
|
|
git_object *target = NULL;
|
|
int error;
|
|
|
|
assert(ref);
|
|
|
|
if (ref->type == GIT_REF_OID) {
|
|
resolved = ref;
|
|
} else {
|
|
if ((error = git_reference_resolve(&resolved, ref)) < 0)
|
|
return peel_error(error, ref, "Cannot resolve reference");
|
|
}
|
|
|
|
if (!git_oid_iszero(&resolved->peel)) {
|
|
error = git_object_lookup(&target,
|
|
git_reference_owner(ref), &resolved->peel, GIT_OBJ_ANY);
|
|
} else {
|
|
error = git_object_lookup(&target,
|
|
git_reference_owner(ref), &resolved->target.oid, GIT_OBJ_ANY);
|
|
}
|
|
|
|
if (error < 0) {
|
|
peel_error(error, ref, "Cannot retrieve reference target");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
|
|
error = git_object_dup(peeled, target);
|
|
else
|
|
error = git_object_peel(peeled, target, target_type);
|
|
|
|
cleanup:
|
|
git_object_free(target);
|
|
|
|
if (resolved != ref)
|
|
git_reference_free(resolved);
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_reference__is_valid_name(
|
|
const char *refname,
|
|
unsigned int flags)
|
|
{
|
|
int error;
|
|
|
|
error = git_reference__normalize_name(NULL, refname, flags) == 0;
|
|
giterr_clear();
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_reference_is_valid_name(
|
|
const char *refname)
|
|
{
|
|
return git_reference__is_valid_name(
|
|
refname,
|
|
GIT_REF_FORMAT_ALLOW_ONELEVEL);
|
|
}
|
|
|
|
const char *git_reference_shorthand(git_reference *ref)
|
|
{
|
|
const char *name = ref->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;
|
|
}
|