Commit 4e6e2ff2 by Vicent Marti

...Aaaand this works

parent ec24e542
......@@ -257,11 +257,6 @@ GIT_EXTERN(int) git_reference_set_target(
* The new name will be checked for validity.
* See `git_reference_create_symbolic()` for rules about valid names.
*
* On success, the given git_reference will be deleted from disk and a
* new `git_reference` will be returned.
*
* The reference will be immediately renamed in-memory and on disk.
*
* If the `force` flag is not enabled, and there's already
* a reference with the given name, the renaming will fail.
*
......@@ -277,7 +272,7 @@ GIT_EXTERN(int) git_reference_set_target(
*
*/
GIT_EXTERN(int) git_reference_rename(
git_reference **out,
git_reference **new_ref,
git_reference *ref,
const char *new_name,
int force);
......
......@@ -92,13 +92,18 @@ struct git_refdb_backend {
* Writes the given reference to the refdb. A refdb implementation
* must provide this function.
*/
int (*write)(git_refdb_backend *backend, const git_reference *ref);
int (*write)(git_refdb_backend *backend,
const git_reference *ref, int force);
int (*rename)(
git_reference **out, git_refdb_backend *backend,
const char *old_name, const char *new_name, int force);
/**
* Deletes the given reference from the refdb. A refdb implementation
* must provide this function.
*/
int (*delete)(git_refdb_backend *backend, const git_reference *ref);
int (*delete)(git_refdb_backend *backend, const char *ref_name);
/**
* Suggests that the given refdb compress or optimize its references.
......
......@@ -182,18 +182,21 @@ int git_branch_move(
if (!git_reference_is_branch(branch))
return not_a_local_branch(git_reference_name(branch));
if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0 ||
(error = git_buf_printf(&old_config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR))) < 0 ||
(error = git_buf_printf(&new_config_section, "branch.%s", new_branch_name)) < 0)
error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name);
if (error < 0)
goto done;
git_buf_printf(&old_config_section,
"branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR));
git_buf_printf(&new_config_section, "branch.%s", new_branch_name);
if ((error = git_config_rename_section(git_reference_owner(branch),
git_buf_cstr(&old_config_section),
git_buf_cstr(&new_config_section))) < 0)
goto done;
if ((error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force)) < 0)
goto done;
error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force);
done:
git_buf_free(&new_reference_name);
......
......@@ -165,14 +165,40 @@ void git_refdb_iterator_free(git_reference_iterator *iter)
iter->free(iter);
}
int git_refdb_write(git_refdb *db, const git_reference *ref)
int git_refdb_write(git_refdb *db, git_reference *ref, int force)
{
assert(db && db->backend);
return db->backend->write(db->backend, ref);
GIT_REFCOUNT_INC(db);
ref->db = db;
return db->backend->write(db->backend, ref, force);
}
int git_refdb_rename(
git_reference **out,
git_refdb *db,
const char *old_name,
const char *new_name,
int force)
{
int error;
assert(db && db->backend);
error = db->backend->rename(out, db->backend, old_name, new_name, force);
if (error < 0)
return error;
if (out) {
GIT_REFCOUNT_INC(db);
(*out)->db = db;
}
return 0;
}
int git_refdb_delete(struct git_refdb *db, const git_reference *ref)
int git_refdb_delete(struct git_refdb *db, const char *ref_name)
{
assert(db && db->backend);
return db->backend->delete(db->backend, ref);
return db->backend->delete(db->backend, ref_name);
}
......@@ -26,12 +26,19 @@ int git_refdb_lookup(
git_refdb *refdb,
const char *ref_name);
int git_refdb_rename(
git_reference **out,
git_refdb *db,
const char *old_name,
const char *new_name,
int force);
int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob);
int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter);
int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter);
void git_refdb_iterator_free(git_reference_iterator *iter);
int git_refdb_write(git_refdb *refdb, const git_reference *ref);
int git_refdb_delete(git_refdb *refdb, const git_reference *ref);
int git_refdb_write(git_refdb *refdb, git_reference *ref, int force);
int git_refdb_delete(git_refdb *refdb, const char *ref_name);
#endif
......@@ -116,11 +116,8 @@ static int packed_parse_oid(
git_oid_cpy(&ref->oid, &id);
ref->flags = 0;
*ref_out = ref;
*buffer_out = refname_end + 1;
return 0;
corrupt:
......@@ -735,6 +732,58 @@ static int refdb_fs_backend__iterator(
return 0;
}
static bool ref_is_available(
const char *old_ref, const char *new_ref, const char *this_ref)
{
if (old_ref == NULL || strcmp(old_ref, this_ref)) {
size_t reflen = strlen(this_ref);
size_t newlen = strlen(new_ref);
size_t cmplen = reflen < newlen ? reflen : newlen;
const char *lead = reflen < newlen ? new_ref : this_ref;
if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') {
return false;
}
}
return true;
}
static int reference_path_available(
refdb_fs_backend *backend,
const char *new_ref,
const char* old_ref,
int force)
{
struct packref *this_ref;
if (packed_load(backend) < 0)
return -1;
if (!force) {
int exists;
if (refdb_fs_backend__exists(&exists, (git_refdb_backend *)backend, new_ref) < 0)
return -1;
if (exists) {
giterr_set(GITERR_REFERENCE,
"Failed to write reference '%s': a reference with "
" that name already exists.", new_ref);
return GIT_EEXISTS;
}
}
git_strmap_foreach_value(backend->refcache.packfile, this_ref, {
if (!ref_is_available(old_ref, new_ref, this_ref->name)) {
giterr_set(GITERR_REFERENCE,
"The path to reference '%s' collides with an existing one", new_ref);
return -1;
}
});
return 0;
}
static int loose_write(refdb_fs_backend *backend, const git_reference *ref)
{
......@@ -744,8 +793,7 @@ static int loose_write(refdb_fs_backend *backend, const git_reference *ref)
/* Remove a possibly existing empty directory hierarchy
* which name would collide with the reference name
*/
if (git_futils_rmdir_r(ref->name, backend->path,
GIT_RMDIR_SKIP_NONEMPTY) < 0)
if (git_futils_rmdir_r(ref->name, backend->path, GIT_RMDIR_SKIP_NONEMPTY) < 0)
return -1;
if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0)
......@@ -1005,37 +1053,40 @@ cleanup_memory:
static int refdb_fs_backend__write(
git_refdb_backend *_backend,
const git_reference *ref)
const git_reference *ref,
int force)
{
refdb_fs_backend *backend;
int error;
assert(_backend);
backend = (refdb_fs_backend *)_backend;
error = reference_path_available(backend, ref->name, NULL, force);
if (error < 0)
return error;
return loose_write(backend, ref);
}
static int refdb_fs_backend__delete(
git_refdb_backend *_backend,
const git_reference *ref)
const char *ref_name)
{
refdb_fs_backend *backend;
git_repository *repo;
git_buf loose_path = GIT_BUF_INIT;
struct packref *pack_ref;
khiter_t pack_ref_pos;
int error = 0, pack_error;
int error = 0;
bool loose_deleted = 0;
assert(_backend);
assert(ref);
assert(ref_name);
backend = (refdb_fs_backend *)_backend;
repo = backend->repo;
/* If a loose reference exists, remove it from the filesystem */
if (git_buf_joinpath(&loose_path, repo->path_repository, ref->name) < 0)
if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)
return -1;
if (git_path_isfile(loose_path.ptr)) {
......@@ -1049,20 +1100,64 @@ static int refdb_fs_backend__delete(
return error;
/* If a packed reference exists, remove it from the packfile and repack */
error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref_name);
if (error == GIT_ENOTFOUND)
return loose_deleted ? 0 : GIT_ENOTFOUND;
if ((pack_error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref->name)) == 0) {
if (error == 0) {
git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos);
git__free(pack_ref);
error = packed_write(backend);
}
if (pack_error == GIT_ENOTFOUND)
error = loose_deleted ? 0 : GIT_ENOTFOUND;
else
error = pack_error;
return error;
}
static int refdb_fs_backend__rename(
git_reference **out,
git_refdb_backend *_backend,
const char *old_name,
const char *new_name,
int force)
{
refdb_fs_backend *backend;
git_reference *old, *new;
int error;
assert(_backend);
backend = (refdb_fs_backend *)_backend;
error = reference_path_available(backend, new_name, old_name, force);
if (error < 0)
return error;
error = refdb_fs_backend__lookup(&old, _backend, old_name);
if (error < 0)
return error;
error = refdb_fs_backend__delete(_backend, old_name);
if (error < 0) {
git_reference_free(old);
return error;
}
new = realloc(old, sizeof(git_reference) + strlen(new_name) + 1);
memcpy(new->name, new_name, strlen(new_name) + 1);
error = loose_write(backend, new);
if (error < 0) {
git_reference_free(new);
return error;
}
if (out) {
*out = new;
} else {
git_reference_free(new);
}
return 0;
}
static int refdb_fs_backend__compress(git_refdb_backend *_backend)
......@@ -1172,6 +1267,7 @@ int git_refdb_backend_fs(
backend->parent.iterator = &refdb_fs_backend__iterator;
backend->parent.write = &refdb_fs_backend__write;
backend->parent.delete = &refdb_fs_backend__delete;
backend->parent.rename = &refdb_fs_backend__rename;
backend->parent.compress = &refdb_fs_backend__compress;
backend->parent.free = &refdb_fs_backend__free;
......
......@@ -98,122 +98,9 @@ void git_reference_free(git_reference *reference)
git__free(reference);
}
struct reference_available_t {
const char *new_ref;
const char *old_ref;
int available;
};
static int _reference_available_cb(const char *refname, void *data)
{
struct reference_available_t *d;
assert(refname && data);
d = (struct reference_available_t *)data;
if (!d->old_ref || strcmp(d->old_ref, refname)) {
size_t reflen = strlen(refname);
size_t newlen = strlen(d->new_ref);
size_t cmplen = reflen < newlen ? reflen : newlen;
const char *lead = reflen < newlen ? d->new_ref : refname;
if (!strncmp(d->new_ref, refname, cmplen) && lead[cmplen] == '/') {
d->available = 0;
return -1;
}
}
return 0;
}
/**
* TODO: this should be part of the FS backend
*/
static int reference_path_available(
git_repository *repo,
const char *ref,
const char* old_ref)
{
int error;
struct reference_available_t data;
data.new_ref = ref;
data.old_ref = old_ref;
data.available = 1;
error = git_reference_foreach_name(repo, _reference_available_cb, (void *)&data);
if (error < 0)
return error;
if (!data.available) {
giterr_set(GITERR_REFERENCE,
"The path to reference '%s' collides with an existing one", ref);
return -1;
}
return 0;
}
/*
* Check if a reference could be written to disk, based on:
*
* - Whether a reference with the same name already exists,
* and we are allowing or disallowing overwrites
*
* - Whether the name of the reference would collide with
* an existing path
*/
static int reference_can_write(
git_repository *repo,
const char *refname,
const char *previous_name,
int force)
{
git_refdb *refdb;
if (git_repository_refdb__weakptr(&refdb, repo) < 0)
return -1;
/* see if the reference shares a path with an existing reference;
* if a path is shared, we cannot create the reference, even when forcing */
if (reference_path_available(repo, refname, previous_name) < 0)
return -1;
/* check if the reference actually exists, but only if we are not forcing
* the rename. If we are forcing, it's OK to overwrite */
if (!force) {
int exists;
if (git_refdb_exists(&exists, refdb, refname) < 0)
return -1;
/* We cannot proceed if the reference already exists and we're not forcing
* the rename; the existing one would be overwritten */
if (exists) {
giterr_set(GITERR_REFERENCE,
"A reference with that name (%s) already exists", refname);
return GIT_EEXISTS;
}
}
/* FIXME: if the reference exists and we are forcing, do we really need to
* remove the reference first?
*
* Two cases:
*
* - the reference already exists and is loose: not a problem, the file
* gets overwritten on disk
*
* - the reference already exists and is packed: we write a new one as
* loose, which by all means renders the packed one useless
*/
return 0;
}
int git_reference_delete(git_reference *ref)
{
return git_refdb_delete(ref->db, ref);
return git_refdb_delete(ref->db, ref->name);
}
int git_reference_lookup(git_reference **ref_out,
......@@ -420,23 +307,24 @@ static int reference__create(
if (ref_out)
*ref_out = NULL;
if ((error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name)) < 0 ||
(error = reference_can_write(repo, normalized, NULL, force)) < 0 ||
(error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
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(name, oid, NULL);
ref = git_reference__alloc(normalized, oid, NULL);
} else {
ref = git_reference__alloc_symbolic(name, symbolic);
ref = git_reference__alloc_symbolic(normalized, symbolic);
}
/* TODO: this needs to be written more explicitly */
GITERR_CHECK_ALLOC(ref);
ref->db = refdb;
if ((error = git_refdb_write(refdb, ref)) < 0) {
if ((error = git_refdb_write(refdb, ref, force)) < 0) {
git_reference_free(ref);
return error;
}
......@@ -533,77 +421,41 @@ int git_reference_rename(
unsigned int normalization_flags;
char normalized[GIT_REFNAME_MAX];
bool should_head_be_updated = false;
git_reference *result = NULL;
int error = 0;
int reference_has_log;
*out = NULL;
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 ||
(error = reference_can_write(ref->db->repo, normalized, ref->name, force)) < 0)
normalized, sizeof(normalized), new_name, normalization_flags)) < 0)
return error;
/*
* Create the new reference.
*/
if (ref->type == GIT_REF_OID) {
result = git_reference__alloc(new_name, &ref->target.oid, &ref->peel);
} else if (ref->type == GIT_REF_SYMBOLIC) {
result = git_reference__alloc_symbolic(new_name, ref->target.symbolic);
} else {
assert(0);
}
if (result == NULL)
return -1;
/* TODO: this is bad */
result->db = ref->db;
/* Check if we have to update HEAD. */
if ((error = git_branch_is_head(ref)) < 0)
goto on_error;
return error;
should_head_be_updated = (error > 0);
/* Now delete the old ref and save the new one. */
if ((error = git_refdb_delete(ref->db, ref)) < 0)
goto on_error;
/* Save the new reference. */
if ((error = git_refdb_write(ref->db, result)) < 0)
goto rollback;
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) {
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");
goto on_error;
return error;
}
/* Rename the reflog file, if it exists. */
reference_has_log = git_reference_has_log(ref);
if (reference_has_log < 0) {
error = reference_has_log;
goto on_error;
}
if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0)
goto on_error;
*out = result;
if (reference_has_log < 0)
return reference_has_log;
if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0)
return error;
rollback:
git_refdb_write(ref->db, ref);
on_error:
git_reference_free(result);
return error;
return 0;
}
int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
......
......@@ -1245,7 +1245,6 @@ static int rename_one_remote_reference(
{
int error = -1;
git_buf new_name = GIT_BUF_INIT;
git_reference *newref = NULL;
if (git_buf_printf(
&new_name,
......@@ -1254,11 +1253,9 @@ static int rename_one_remote_reference(
reference->name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name)) < 0)
return -1;
/* TODO: can we make this NULL? */
error = git_reference_rename(&newref, reference, git_buf_cstr(&new_name), 0);
error = git_reference_rename(NULL, reference, git_buf_cstr(&new_name), 0);
git_reference_free(reference);
git_reference_free(newref);
git_buf_free(&new_name);
return error;
}
......
#include "vector.h"
#include "util.h"
#include "testdb.h"
typedef struct refdb_test_backend {
git_refdb_backend parent;
git_repository *repo;
git_vector refs;
} refdb_test_backend;
typedef struct refdb_test_entry {
char *name;
git_ref_t type;
union {
git_oid oid;
char *symbolic;
} target;
} refdb_test_entry;
static int ref_name_cmp(const void *a, const void *b)
{
return strcmp(git_reference_name((git_reference *)a),
git_reference_name((git_reference *)b));
}
static int refdb_test_backend__exists(
int *exists,
git_refdb_backend *_backend,
const char *ref_name)
{
refdb_test_backend *backend;
refdb_test_entry *entry;
size_t i;
assert(_backend);
backend = (refdb_test_backend *)_backend;
*exists = 0;
git_vector_foreach(&backend->refs, i, entry) {
if (strcmp(entry->name, ref_name) == 0) {
*exists = 1;
break;
}
}
return 0;
}
static int refdb_test_backend__write(
git_refdb_backend *_backend,
const git_reference *ref)
{
refdb_test_backend *backend;
refdb_test_entry *entry;
assert(_backend);
backend = (refdb_test_backend *)_backend;
entry = git__calloc(1, sizeof(refdb_test_entry));
GITERR_CHECK_ALLOC(entry);
entry->name = git__strdup(git_reference_name(ref));
GITERR_CHECK_ALLOC(entry->name);
entry->type = git_reference_type(ref);
if (entry->type == GIT_REF_OID)
git_oid_cpy(&entry->target.oid, git_reference_target(ref));
else {
entry->target.symbolic = git__strdup(git_reference_symbolic_target(ref));
GITERR_CHECK_ALLOC(entry->target.symbolic);
}
git_vector_insert(&backend->refs, entry);
return 0;
}
static int refdb_test_backend__lookup(
git_reference **out,
git_refdb_backend *_backend,
const char *ref_name)
{
refdb_test_backend *backend;
refdb_test_entry *entry;
size_t i;
assert(_backend);
backend = (refdb_test_backend *)_backend;
git_vector_foreach(&backend->refs, i, entry) {
if (strcmp(entry->name, ref_name) == 0) {
if (entry->type == GIT_REF_OID) {
*out = git_reference__alloc(ref_name,
&entry->target.oid, NULL);
} else if (entry->type == GIT_REF_SYMBOLIC) {
*out = git_reference__alloc_symbolic(ref_name,
entry->target.symbolic);
}
if (*out == NULL)
return -1;
return 0;
}
}
return GIT_ENOTFOUND;
}
static void refdb_test_entry_free(refdb_test_entry *entry)
{
if (entry->type == GIT_REF_SYMBOLIC)
git__free(entry->target.symbolic);
git__free(entry->name);
git__free(entry);
}
static int refdb_test_backend__delete(
git_refdb_backend *_backend,
const git_reference *ref)
{
refdb_test_backend *backend;
refdb_test_entry *entry;
size_t i;
assert(_backend);
backend = (refdb_test_backend *)_backend;
git_vector_foreach(&backend->refs, i, entry) {
if (strcmp(entry->name, git_reference_name(ref)) == 0) {
git_vector_remove(&backend->refs, i);
refdb_test_entry_free(entry);
}
}
return GIT_ENOTFOUND;
}
static void refdb_test_backend__free(git_refdb_backend *_backend)
{
refdb_test_backend *backend;
refdb_test_entry *entry;
size_t i;
assert(_backend);
backend = (refdb_test_backend *)_backend;
git_vector_foreach(&backend->refs, i, entry)
refdb_test_entry_free(entry);
git_vector_free(&backend->refs);
git__free(backend);
}
int refdb_backend_test(
git_refdb_backend **backend_out,
git_repository *repo)
{
refdb_test_backend *backend;
backend = git__calloc(1, sizeof(refdb_test_backend));
GITERR_CHECK_ALLOC(backend);
git_vector_init(&backend->refs, 0, ref_name_cmp);
backend->repo = repo;
backend->parent.exists = &refdb_test_backend__exists;
backend->parent.lookup = &refdb_test_backend__lookup;
backend->parent.write = &refdb_test_backend__write;
backend->parent.delete = &refdb_test_backend__delete;
backend->parent.free = &refdb_test_backend__free;
*backend_out = (git_refdb_backend *)backend;
return 0;
}
#include <git2/errors.h>
#include <git2/repository.h>
#include <git2/refdb.h>
#include <git2/sys/refs.h>
#include <git2/sys/refdb_backend.h>
int refdb_backend_test(
git_refdb_backend **backend_out,
git_repository *repo);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment