summaryrefslogtreecommitdiff
path: root/src/refdb_fs.c
diff options
context:
space:
mode:
authorVicent Marti <tanoku@gmail.com>2013-06-25 00:25:35 +0200
committerVicent Marti <tanoku@gmail.com>2013-06-25 00:25:35 +0200
commit29d7242b1dcd1f09a63417abd648a6217b85d301 (patch)
treededc3bc07a500770382ca4c517e4bb015e506c4b /src/refdb_fs.c
parenta50086d174658914d4d6462afbc83b02825b1f5b (diff)
parenteddc1f1ed78898a4ca41480045b1d0d5b075e773 (diff)
downloadlibgit2-29d7242b1dcd1f09a63417abd648a6217b85d301.tar.gz
Merge branch 'development'
Diffstat (limited to 'src/refdb_fs.c')
-rw-r--r--src/refdb_fs.c520
1 files changed, 387 insertions, 133 deletions
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index f00bd72a0..b9e283ac5 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -9,16 +9,18 @@
#include "hash.h"
#include "repository.h"
#include "fileops.h"
+#include "filebuf.h"
#include "pack.h"
#include "reflog.h"
-#include "config.h"
#include "refdb.h"
#include "refdb_fs.h"
+#include "iterator.h"
#include <git2/tag.h>
#include <git2/object.h>
#include <git2/refdb.h>
-#include <git2/refdb_backend.h>
+#include <git2/sys/refdb_backend.h>
+#include <git2/sys/refs.h>
GIT__USE_STRMAP;
@@ -26,8 +28,16 @@ GIT__USE_STRMAP;
#define MAX_NESTING_LEVEL 10
enum {
- GIT_PACKREF_HAS_PEEL = 1,
- GIT_PACKREF_WAS_LOOSE = 2
+ PACKREF_HAS_PEEL = 1,
+ PACKREF_WAS_LOOSE = 2,
+ PACKREF_CANNOT_PEEL = 4,
+ PACKREF_SHADOWED = 8,
+};
+
+enum {
+ PEELING_NONE = 0,
+ PEELING_STANDARD,
+ PEELING_FULL
};
struct packref {
@@ -41,10 +51,10 @@ typedef struct refdb_fs_backend {
git_refdb_backend parent;
git_repository *repo;
- const char *path;
- git_refdb *refdb;
+ char *path;
git_refcache refcache;
+ int peeling_mode;
} refdb_fs_backend;
static int reference_read(
@@ -62,7 +72,7 @@ static int reference_read(
/* Determine the full path of the file */
if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
return -1;
-
+
result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, NULL, updated);
git_buf_free(&path);
@@ -99,7 +109,7 @@ static int packed_parse_oid(
refname_len = refname_end - refname_begin;
- ref = git__malloc(sizeof(struct packref) + refname_len + 1);
+ ref = git__calloc(1, sizeof(struct packref) + refname_len + 1);
GITERR_CHECK_ALLOC(ref);
memcpy(ref->name, refname_begin, refname_len);
@@ -107,11 +117,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:
@@ -133,10 +140,6 @@ static int packed_parse_peel(
if (tag_ref == NULL)
goto corrupt;
- /* Ensure reference is a tag */
- if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)
- goto corrupt;
-
if (buffer + GIT_OID_HEXSZ > buffer_end)
goto corrupt;
@@ -155,6 +158,7 @@ static int packed_parse_peel(
goto corrupt;
}
+ tag_ref->flags |= PACKREF_HAS_PEEL;
*buffer_out = buffer;
return 0;
@@ -175,7 +179,10 @@ static int packed_load(refdb_fs_backend *backend)
ref_cache->packfile = git_strmap_alloc();
GITERR_CHECK_ALLOC(ref_cache->packfile);
}
-
+
+ if (backend->path == NULL)
+ return 0;
+
result = reference_read(&packfile, &ref_cache->packfile_time,
backend->path, GIT_PACKEDREFS_FILE, &updated);
@@ -193,7 +200,7 @@ static int packed_load(refdb_fs_backend *backend)
if (result < 0)
return -1;
-
+
if (!updated)
return 0;
@@ -206,6 +213,30 @@ static int packed_load(refdb_fs_backend *backend)
buffer_start = (const char *)packfile.ptr;
buffer_end = (const char *)(buffer_start) + packfile.size;
+ backend->peeling_mode = PEELING_NONE;
+
+ if (buffer_start[0] == '#') {
+ static const char *traits_header = "# pack-refs with: ";
+
+ if (git__prefixcmp(buffer_start, traits_header) == 0) {
+ char *traits = (char *)buffer_start + strlen(traits_header);
+ char *traits_end = strchr(traits, '\n');
+
+ if (traits_end == NULL)
+ goto parse_failed;
+
+ *traits_end = '\0';
+
+ if (strstr(traits, " fully-peeled ") != NULL) {
+ backend->peeling_mode = PEELING_FULL;
+ } else if (strstr(traits, " peeled ") != NULL) {
+ backend->peeling_mode = PEELING_STANDARD;
+ }
+
+ buffer_start = traits_end + 1;
+ }
+ }
+
while (buffer_start < buffer_end && buffer_start[0] == '#') {
buffer_start = strchr(buffer_start, '\n');
if (buffer_start == NULL)
@@ -224,6 +255,10 @@ static int packed_load(refdb_fs_backend *backend)
if (buffer_start[0] == '^') {
if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
goto parse_failed;
+ } else if (backend->peeling_mode == PEELING_FULL ||
+ (backend->peeling_mode == PEELING_STANDARD &&
+ git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) {
+ ref->flags |= PACKREF_CANNOT_PEEL;
}
git_strmap_insert(ref_cache->packfile, ref->name, ref, err);
@@ -241,7 +276,7 @@ parse_failed:
return -1;
}
-static int loose_parse_oid(git_oid *oid, git_buf *file_content)
+static int loose_parse_oid(git_oid *oid, const char *filename, git_buf *file_content)
{
size_t len;
const char *str;
@@ -263,7 +298,7 @@ static int loose_parse_oid(git_oid *oid, git_buf *file_content)
return 0;
corrupted:
- giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file: %s", filename);
return -1;
}
@@ -284,19 +319,19 @@ static int loose_lookup_to_packfile(
git_buf_rtrim(&ref_file);
name_len = strlen(name);
- ref = git__malloc(sizeof(struct packref) + name_len + 1);
+ ref = git__calloc(1, sizeof(struct packref) + name_len + 1);
GITERR_CHECK_ALLOC(ref);
memcpy(ref->name, name, name_len);
ref->name[name_len] = 0;
- if (loose_parse_oid(&ref->oid, &ref_file) < 0) {
+ if (loose_parse_oid(&ref->oid, name, &ref_file) < 0) {
git_buf_free(&ref_file);
git__free(ref);
return -1;
}
- ref->flags = GIT_PACKREF_WAS_LOOSE;
+ ref->flags = PACKREF_WAS_LOOSE;
*ref_out = ref;
git_buf_free(&ref_file);
@@ -430,12 +465,12 @@ static int loose_lookup(
goto done;
}
- *out = git_reference__alloc(backend->refdb, ref_name, NULL, target);
+ *out = git_reference__alloc_symbolic(ref_name, target);
} else {
- if ((error = loose_parse_oid(&oid, &ref_file)) < 0)
+ if ((error = loose_parse_oid(&oid, ref_name, &ref_file)) < 0)
goto done;
-
- *out = git_reference__alloc(backend->refdb, ref_name, &oid, NULL);
+
+ *out = git_reference__alloc(ref_name, &oid, NULL);
}
if (*out == NULL)
@@ -456,19 +491,19 @@ static int packed_map_entry(
if (packed_load(backend) < 0)
return -1;
-
+
/* Look up on the packfile */
packfile_refs = backend->refcache.packfile;
*pos = git_strmap_lookup_index(packfile_refs, ref_name);
-
+
if (!git_strmap_valid_index(packfile_refs, *pos)) {
giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref_name);
return GIT_ENOTFOUND;
}
*entry = git_strmap_value_at(packfile_refs, *pos);
-
+
return 0;
}
@@ -480,13 +515,14 @@ static int packed_lookup(
struct packref *entry;
khiter_t pos;
int error = 0;
-
+
if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0)
return error;
- if ((*out = git_reference__alloc(backend->refdb, ref_name, &entry->oid, NULL)) == NULL)
+ if ((*out = git_reference__alloc(ref_name,
+ &entry->oid, &entry->peel)) == NULL)
return -1;
-
+
return 0;
}
@@ -515,108 +551,239 @@ static int refdb_fs_backend__lookup(
return result;
}
-struct dirent_list_data {
- refdb_fs_backend *backend;
- size_t repo_path_len;
- unsigned int list_type:2;
+typedef struct {
+ git_reference_iterator parent;
- git_reference_foreach_cb callback;
- void *callback_payload;
- int callback_error;
-};
+ char *glob;
+ git_vector loose;
+ unsigned int loose_pos;
+ khiter_t packed_pos;
+} refdb_fs_iter;
-static git_ref_t loose_guess_rtype(const git_buf *full_path)
+static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
{
- git_buf ref_file = GIT_BUF_INIT;
- git_ref_t type;
+ refdb_fs_iter *iter = (refdb_fs_iter *) _iter;
+ char *loose_path;
+ size_t i;
+
+ git_vector_foreach(&iter->loose, i, loose_path) {
+ git__free(loose_path);
+ }
- type = GIT_REF_INVALID;
+ git_vector_free(&iter->loose);
- if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) {
- if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0)
- type = GIT_REF_SYMBOLIC;
- else
- type = GIT_REF_OID;
+ git__free(iter->glob);
+ git__free(iter);
+}
+
+static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
+{
+ git_strmap *packfile = backend->refcache.packfile;
+ git_buf path = GIT_BUF_INIT;
+ git_iterator *fsit;
+ const git_index_entry *entry = NULL;
+
+ if (!backend->path) /* do nothing if no path for loose refs */
+ return 0;
+
+ if (git_buf_printf(&path, "%s/refs", backend->path) < 0)
+ return -1;
+
+ if (git_iterator_for_filesystem(&fsit, git_buf_cstr(&path), 0, NULL, NULL) < 0)
+ return -1;
+
+ git_vector_init(&iter->loose, 8, NULL);
+ git_buf_sets(&path, GIT_REFS_DIR);
+
+ while (!git_iterator_advance(&entry, fsit)) {
+ const char *ref_name;
+ khiter_t pos;
+
+ git_buf_truncate(&path, strlen(GIT_REFS_DIR));
+ git_buf_puts(&path, entry->path);
+ ref_name = git_buf_cstr(&path);
+
+ if (git__suffixcmp(ref_name, ".lock") == 0 ||
+ (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0))
+ continue;
+
+ pos = git_strmap_lookup_index(packfile, ref_name);
+ if (git_strmap_valid_index(packfile, pos)) {
+ struct packref *ref = git_strmap_value_at(packfile, pos);
+ ref->flags |= PACKREF_SHADOWED;
+ }
+
+ git_vector_insert(&iter->loose, git__strdup(ref_name));
}
- git_buf_free(&ref_file);
- return type;
+ git_iterator_free(fsit);
+ git_buf_free(&path);
+
+ return 0;
}
-static int _dirent_loose_listall(void *_data, git_buf *full_path)
+static int refdb_fs_backend__iterator_next(
+ git_reference **out, git_reference_iterator *_iter)
{
- struct dirent_list_data *data = (struct dirent_list_data *)_data;
- const char *file_path = full_path->ptr + data->repo_path_len;
+ refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+ git_strmap *packfile = backend->refcache.packfile;
- if (git_path_isdir(full_path->ptr) == true)
- return git_path_direach(full_path, _dirent_loose_listall, _data);
+ while (iter->loose_pos < iter->loose.length) {
+ const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
- /* do not add twice a reference that exists already in the packfile */
- if (git_strmap_exists(data->backend->refcache.packfile, file_path))
- return 0;
+ if (loose_lookup(out, backend, path) == 0)
+ return 0;
- if (data->list_type != GIT_REF_LISTALL) {
- if ((data->list_type & loose_guess_rtype(full_path)) == 0)
- return 0; /* we are filtering out this reference */
+ giterr_clear();
}
- /* Locked references aren't returned */
- if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION))
+ while (iter->packed_pos < kh_end(packfile)) {
+ struct packref *ref = NULL;
+
+ while (!kh_exist(packfile, iter->packed_pos)) {
+ iter->packed_pos++;
+ if (iter->packed_pos == kh_end(packfile))
+ return GIT_ITEROVER;
+ }
+
+ ref = kh_val(packfile, iter->packed_pos);
+ iter->packed_pos++;
+
+ if (ref->flags & PACKREF_SHADOWED)
+ continue;
+
+ if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
+ continue;
+
+ *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
+ if (*out == NULL)
+ return -1;
+
+ return 0;
+ }
+
+ return GIT_ITEROVER;
+}
+
+static int refdb_fs_backend__iterator_next_name(
+ const char **out, git_reference_iterator *_iter)
+{
+ refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+ git_strmap *packfile = backend->refcache.packfile;
+
+ while (iter->loose_pos < iter->loose.length) {
+ const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
+
+ if (git_strmap_exists(packfile, path))
+ continue;
+
+ *out = path;
return 0;
+ }
+
+ while (iter->packed_pos < kh_end(packfile)) {
+ while (!kh_exist(packfile, iter->packed_pos)) {
+ iter->packed_pos++;
+ if (iter->packed_pos == kh_end(packfile))
+ return GIT_ITEROVER;
+ }
+
+ *out = kh_key(packfile, iter->packed_pos);
+ iter->packed_pos++;
- if (data->callback(file_path, data->callback_payload))
- data->callback_error = GIT_EUSER;
+ if (iter->glob && p_fnmatch(iter->glob, *out, 0) != 0)
+ continue;
+
+ return 0;
+ }
- return data->callback_error;
+ return GIT_ITEROVER;
}
-static int refdb_fs_backend__foreach(
- git_refdb_backend *_backend,
- unsigned int list_type,
- git_reference_foreach_cb callback,
- void *payload)
+static int refdb_fs_backend__iterator(
+ git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
{
+ refdb_fs_iter *iter;
refdb_fs_backend *backend;
- int result;
- struct dirent_list_data data;
- git_buf refs_path = GIT_BUF_INIT;
- const char *ref_name;
- void *ref = NULL;
-
- GIT_UNUSED(ref);
assert(_backend);
backend = (refdb_fs_backend *)_backend;
if (packed_load(backend) < 0)
return -1;
-
- /* list all the packed references first */
- if (list_type & GIT_REF_OID) {
- git_strmap_foreach(backend->refcache.packfile, ref_name, ref, {
- if (callback(ref_name, payload))
- return GIT_EUSER;
- });
+
+ iter = git__calloc(1, sizeof(refdb_fs_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ if (glob != NULL)
+ iter->glob = git__strdup(glob);
+
+ iter->parent.next = refdb_fs_backend__iterator_next;
+ iter->parent.next_name = refdb_fs_backend__iterator_next_name;
+ iter->parent.free = refdb_fs_backend__iterator_free;
+
+ if (iter_load_loose_paths(backend, iter) < 0) {
+ refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
+ return -1;
}
- /* now list the loose references, trying not to
- * duplicate the ref names already in the packed-refs file */
+ *out = (git_reference_iterator *)iter;
+ return 0;
+}
- data.repo_path_len = strlen(backend->path);
- data.list_type = list_type;
- data.backend = backend;
- data.callback = callback;
- data.callback_payload = payload;
- data.callback_error = 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;
+ }
+ }
- if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
+ 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;
- result = git_path_direach(&refs_path, _dirent_loose_listall, &data);
+ if (!force) {
+ int exists;
- git_buf_free(&refs_path);
+ 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;
+ }
+ }
- return data.callback_error ? GIT_EUSER : result;
+ 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)
@@ -627,8 +794,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)
@@ -678,14 +844,7 @@ static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
{
git_object *object;
- if (ref->flags & GIT_PACKREF_HAS_PEEL)
- return 0;
-
- /*
- * Only applies to tags, i.e. references
- * in the /refs/tags folder
- */
- if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0)
+ if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
return 0;
/*
@@ -706,7 +865,7 @@ static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
* Find the object pointed at by this tag
*/
git_oid_cpy(&ref->peel, git_tag_target_id(tag));
- ref->flags |= GIT_PACKREF_HAS_PEEL;
+ ref->flags |= PACKREF_HAS_PEEL;
/*
* The reference has now cached the resolved OID, and is
@@ -739,7 +898,7 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file)
* This obviously only applies to tags.
* The required peels have already been loaded into `ref->peel_target`.
*/
- if (ref->flags & GIT_PACKREF_HAS_PEEL) {
+ if (ref->flags & PACKREF_HAS_PEEL) {
char peel[GIT_OID_HEXSZ + 1];
git_oid_fmt(peel, &ref->peel);
peel[GIT_OID_HEXSZ] = 0;
@@ -776,7 +935,7 @@ static int packed_remove_loose(
for (i = 0; i < packing_list->length; ++i) {
struct packref *ref = git_vector_get(packing_list, i);
- if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)
+ if ((ref->flags & PACKREF_WAS_LOOSE) == 0)
continue;
if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0)
@@ -895,66 +1054,113 @@ 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;
- bool loose_deleted;
+ 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)) {
error = p_unlink(loose_path.ptr);
loose_deleted = 1;
}
-
+
git_buf_free(&loose_path);
if (error != 0)
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)
{
refdb_fs_backend *backend;
@@ -993,28 +1199,76 @@ static void refdb_fs_backend__free(git_refdb_backend *_backend)
backend = (refdb_fs_backend *)_backend;
refcache_free(&backend->refcache);
+ git__free(backend->path);
git__free(backend);
}
+static int setup_namespace(git_buf *path, git_repository *repo)
+{
+ char *parts, *start, *end;
+
+ /* Not all repositories have a path */
+ if (repo->path_repository == NULL)
+ return 0;
+
+ /* Load the path to the repo first */
+ git_buf_puts(path, repo->path_repository);
+
+ /* if the repo is not namespaced, nothing else to do */
+ if (repo->namespace == NULL)
+ return 0;
+
+ parts = end = git__strdup(repo->namespace);
+ if (parts == NULL)
+ return -1;
+
+ /**
+ * From `man gitnamespaces`:
+ * namespaces which include a / will expand to a hierarchy
+ * of namespaces; for example, GIT_NAMESPACE=foo/bar will store
+ * refs under refs/namespaces/foo/refs/namespaces/bar/
+ */
+ while ((start = git__strsep(&end, "/")) != NULL) {
+ git_buf_printf(path, "refs/namespaces/%s/", start);
+ }
+
+ git_buf_printf(path, "refs/namespaces/%s/refs", end);
+ git__free(parts);
+
+ /* Make sure that the folder with the namespace exists */
+ if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0)
+ return -1;
+
+ /* Return the root of the namespaced path, i.e. without the trailing '/refs' */
+ git_buf_rtruncate_at_char(path, '/');
+ return 0;
+}
+
int git_refdb_backend_fs(
git_refdb_backend **backend_out,
- git_repository *repository,
- git_refdb *refdb)
+ git_repository *repository)
{
+ git_buf path = GIT_BUF_INIT;
refdb_fs_backend *backend;
backend = git__calloc(1, sizeof(refdb_fs_backend));
GITERR_CHECK_ALLOC(backend);
backend->repo = repository;
- backend->path = repository->path_repository;
- backend->refdb = refdb;
+
+ if (setup_namespace(&path, repository) < 0) {
+ git__free(backend);
+ return -1;
+ }
+
+ backend->path = git_buf_detach(&path);
backend->parent.exists = &refdb_fs_backend__exists;
backend->parent.lookup = &refdb_fs_backend__lookup;
- backend->parent.foreach = &refdb_fs_backend__foreach;
+ 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;