]> git.proxmox.com Git - libgit2.git/blobdiff - src/refdb_fs.c
New upstream version 1.0.0+dfsg.1
[libgit2.git] / src / refdb_fs.c
index 8739d5b89d559bef385e3c3fd8df33c05fa49945..1e53b3af540b3cbf9de21e350857e3b5157fca18 100644 (file)
@@ -5,18 +5,21 @@
  * a Linking Exception. For full terms see the included COPYING file.
  */
 
+#include "refdb_fs.h"
+
 #include "refs.h"
 #include "hash.h"
 #include "repository.h"
-#include "fileops.h"
+#include "futils.h"
 #include "filebuf.h"
 #include "pack.h"
+#include "parse.h"
 #include "reflog.h"
 #include "refdb.h"
-#include "refdb_fs.h"
 #include "iterator.h"
 #include "sortedcache.h"
 #include "signature.h"
+#include "wildmatch.h"
 
 #include <git2/tag.h>
 #include <git2/object.h>
@@ -26,8 +29,6 @@
 #include <git2/sys/refs.h>
 #include <git2/sys/reflog.h>
 
-GIT__USE_STRMAP
-
 #define DEFAULT_NESTING_LEVEL  5
 #define MAX_NESTING_LEVEL              10
 
@@ -55,12 +56,16 @@ typedef struct refdb_fs_backend {
        git_refdb_backend parent;
 
        git_repository *repo;
-       char *path;
+       /* path to git directory */
+       char *gitpath;
+       /* path to common objects' directory */
+       char *commonpath;
 
        git_sortedcache *refcache;
        int peeling_mode;
        git_iterator_flag_t iterator_flags;
        uint32_t direach_flags;
+       int fsync;
 } refdb_fs_backend;
 
 static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name);
@@ -77,7 +82,7 @@ static int packed_reload(refdb_fs_backend *backend)
        git_buf packedrefs = GIT_BUF_INIT;
        char *scan, *eof, *eol;
 
-       if (!backend->path)
+       if (!backend->gitpath)
                return 0;
 
        error = git_sortedcache_lockandload(backend->refcache, &packedrefs);
@@ -91,7 +96,7 @@ static int packed_reload(refdb_fs_backend *backend)
        if (error <= 0) {
                if (error == GIT_ENOTFOUND) {
                        git_sortedcache_clear(backend->refcache, true);
-                       giterr_clear();
+                       git_error_clear();
                        error = 0;
                }
                return error;
@@ -180,16 +185,16 @@ static int packed_reload(refdb_fs_backend *backend)
        }
 
        git_sortedcache_wunlock(backend->refcache);
-       git_buf_free(&packedrefs);
+       git_buf_dispose(&packedrefs);
 
        return 0;
 
 parse_failed:
-       giterr_set(GITERR_REFERENCE, "Corrupted packed references file");
+       git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file");
 
        git_sortedcache_clear(backend->refcache, false);
        git_sortedcache_wunlock(backend->refcache);
-       git_buf_free(&packedrefs);
+       git_buf_dispose(&packedrefs);
 
        return -1;
 }
@@ -212,7 +217,7 @@ static int loose_parse_oid(
                return 0;
 
 corrupted:
-       giterr_set(GITERR_REFERENCE, "Corrupted loose reference file: %s", filename);
+       git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file: %s", filename);
        return -1;
 }
 
@@ -223,7 +228,7 @@ static int loose_readbuffer(git_buf *buf, const char *base, const char *path)
        /* build full path to file */
        if ((error = git_buf_joinpath(buf, base, path)) < 0 ||
                (error = git_futils_readbuffer(buf, buf->ptr)) < 0)
-               git_buf_free(buf);
+               git_buf_dispose(buf);
 
        return error;
 }
@@ -238,8 +243,8 @@ static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name)
        /* if we fail to load the loose reference, assume someone changed
         * the filesystem under us and skip it...
         */
-       if (loose_readbuffer(&ref_file, backend->path, name) < 0) {
-               giterr_clear();
+       if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) {
+               git_error_clear();
                goto done;
        }
 
@@ -263,7 +268,7 @@ static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name)
        git_sortedcache_wunlock(backend->refcache);
 
 done:
-       git_buf_free(&ref_file);
+       git_buf_dispose(&ref_file);
        return error;
 }
 
@@ -280,14 +285,14 @@ static int _dirent_loose_load(void *payload, git_buf *full_path)
                        full_path, backend->direach_flags, _dirent_loose_load, backend);
                /* Race with the filesystem, ignore it */
                if (error == GIT_ENOTFOUND) {
-                       giterr_clear();
+                       git_error_clear();
                        return 0;
                }
 
                return error;
        }
 
-       file_path = full_path->ptr + strlen(backend->path);
+       file_path = full_path->ptr + strlen(backend->gitpath);
 
        return loose_lookup_to_packfile(backend, file_path);
 }
@@ -303,7 +308,7 @@ static int packed_loadloose(refdb_fs_backend *backend)
        int error;
        git_buf refs_path = GIT_BUF_INIT;
 
-       if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
+       if (git_buf_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0)
                return -1;
 
        /*
@@ -314,7 +319,7 @@ static int packed_loadloose(refdb_fs_backend *backend)
        error = git_path_direach(
                &refs_path, backend->direach_flags, _dirent_loose_load, backend);
 
-       git_buf_free(&refs_path);
+       git_buf_dispose(&refs_path);
 
        return error;
 }
@@ -324,21 +329,33 @@ static int refdb_fs_backend__exists(
        git_refdb_backend *_backend,
        const char *ref_name)
 {
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        git_buf ref_path = GIT_BUF_INIT;
        int error;
 
        assert(backend);
 
-       if ((error = packed_reload(backend)) < 0 ||
-               (error = git_buf_joinpath(&ref_path, backend->path, ref_name)) < 0)
-               return error;
+       *exists = 0;
 
-       *exists = git_path_isfile(ref_path.ptr) ||
-               (git_sortedcache_lookup(backend->refcache, ref_name) != NULL);
+       if ((error = git_buf_joinpath(&ref_path, backend->gitpath, ref_name)) < 0)
+               goto out;
 
-       git_buf_free(&ref_path);
-       return 0;
+       if (git_path_isfile(ref_path.ptr)) {
+               *exists = 1;
+               goto out;
+       }
+
+       if ((error = packed_reload(backend)) < 0)
+               goto out;
+
+       if (git_sortedcache_lookup(backend->refcache, ref_name) != NULL) {
+               *exists = 1;
+               goto out;
+       }
+
+out:
+       git_buf_dispose(&ref_path);
+       return error;
 }
 
 static const char *loose_parse_symbolic(git_buf *file_content)
@@ -349,7 +366,7 @@ static const char *loose_parse_symbolic(git_buf *file_content)
        refname_start = (const char *)file_content->ptr;
 
        if (git_buf_len(file_content) < header_len + 1) {
-               giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
+               git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file");
                return NULL;
        }
 
@@ -362,6 +379,19 @@ static const char *loose_parse_symbolic(git_buf *file_content)
        return refname_start;
 }
 
+/*
+ * Returns whether a reference is stored per worktree or not.
+ * Per-worktree references are:
+ *
+ * - all pseudorefs, e.g. HEAD and MERGE_HEAD
+ * - all references stored inside of "refs/bisect/"
+ */
+static bool is_per_worktree_ref(const char *ref_name)
+{
+       return git__prefixcmp(ref_name, "refs/") != 0 ||
+           git__prefixcmp(ref_name, "refs/bisect/") == 0;
+}
+
 static int loose_lookup(
        git_reference **out,
        refdb_fs_backend *backend,
@@ -369,11 +399,17 @@ static int loose_lookup(
 {
        git_buf ref_file = GIT_BUF_INIT;
        int error = 0;
+       const char *ref_dir;
 
        if (out)
                *out = NULL;
 
-       if ((error = loose_readbuffer(&ref_file, backend->path, ref_name)) < 0)
+       if (is_per_worktree_ref(ref_name))
+               ref_dir = backend->gitpath;
+       else
+               ref_dir = backend->commonpath;
+
+       if ((error = loose_readbuffer(&ref_file, ref_dir, ref_name)) < 0)
                /* cannot read loose ref file - gah */;
        else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) {
                const char *target;
@@ -392,13 +428,13 @@ static int loose_lookup(
                        *out = git_reference__alloc(ref_name, &oid, NULL);
        }
 
-       git_buf_free(&ref_file);
+       git_buf_dispose(&ref_file);
        return error;
 }
 
 static int ref_error_notfound(const char *name)
 {
-       giterr_set(GITERR_REFERENCE, "Reference '%s' not found", name);
+       git_error_set(GIT_ERROR_REFERENCE, "reference '%s' not found", name);
        return GIT_ENOTFOUND;
 }
 
@@ -435,7 +471,7 @@ static int refdb_fs_backend__lookup(
        git_refdb_backend *_backend,
        const char *ref_name)
 {
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        int error;
 
        assert(backend);
@@ -446,7 +482,7 @@ static int refdb_fs_backend__lookup(
        /* only try to lookup this reference on the packfile if it
         * wasn't found on the loose refs; not if there was a critical error */
        if (error == GIT_ENOTFOUND) {
-               giterr_clear();
+               git_error_clear();
                error = packed_lookup(out, backend, ref_name);
        }
 
@@ -468,7 +504,7 @@ typedef struct {
 
 static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
 {
-       refdb_fs_iter *iter = (refdb_fs_iter *) _iter;
+       refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
 
        git_vector_free(&iter->loose);
        git_pool_clear(&iter->pool);
@@ -483,39 +519,63 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
        git_iterator *fsit = NULL;
        git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
        const git_index_entry *entry = NULL;
+       const char *ref_prefix = GIT_REFS_DIR;
+       size_t ref_prefix_len = strlen(ref_prefix);
 
-       if (!backend->path) /* do nothing if no path for loose refs */
+       if (!backend->commonpath) /* do nothing if no commonpath for loose refs */
                return 0;
 
        fsit_opts.flags = backend->iterator_flags;
 
-       if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 ||
-               (error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
-               git_buf_free(&path);
+       if (iter->glob) {
+               const char *last_sep = NULL;
+               const char *pos;
+               for (pos = iter->glob; *pos; ++pos) {
+                       switch (*pos) {
+                       case '?':
+                       case '*':
+                       case '[':
+                       case '\\':
+                               break;
+                       case '/':
+                               last_sep = pos;
+                               /* FALLTHROUGH */
+                       default:
+                               continue;
+                       }
+                       break;
+               }
+               if (last_sep) {
+                       ref_prefix = iter->glob;
+                       ref_prefix_len = (last_sep - ref_prefix) + 1;
+               }
+       }
+
+       if ((error = git_buf_printf(&path, "%s/", backend->commonpath)) < 0 ||
+               (error = git_buf_put(&path, ref_prefix, ref_prefix_len)) < 0) {
+               git_buf_dispose(&path);
                return error;
        }
 
-       error = git_buf_sets(&path, GIT_REFS_DIR);
+       if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
+               git_buf_dispose(&path);
+               return (iter->glob && error == GIT_ENOTFOUND)? 0 : error;
+       }
+
+       error = git_buf_sets(&path, ref_prefix);
 
        while (!error && !git_iterator_advance(&entry, fsit)) {
                const char *ref_name;
-               struct packref *ref;
                char *ref_dup;
 
-               git_buf_truncate(&path, strlen(GIT_REFS_DIR));
+               git_buf_truncate(&path, ref_prefix_len);
                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))
+                       (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0))
                        continue;
 
-               git_sortedcache_rlock(backend->refcache);
-               ref = git_sortedcache_lookup(backend->refcache, ref_name);
-               if (ref)
-                       ref->flags |= PACKREF_SHADOWED;
-               git_sortedcache_runlock(backend->refcache);
-
                ref_dup = git_pool_strdup(&iter->pool, ref_name);
                if (!ref_dup)
                        error = -1;
@@ -524,7 +584,7 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
        }
 
        git_iterator_free(fsit);
-       git_buf_free(&path);
+       git_buf_dispose(&path);
 
        return error;
 }
@@ -533,22 +593,22 @@ static int refdb_fs_backend__iterator_next(
        git_reference **out, git_reference_iterator *_iter)
 {
        int error = GIT_ITEROVER;
-       refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
-       refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+       refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent);
        struct packref *ref;
 
        while (iter->loose_pos < iter->loose.length) {
                const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
 
-               if (loose_lookup(out, backend, path) == 0)
+               if (loose_lookup(out, backend, path) == 0) {
+                       ref = git_sortedcache_lookup(iter->cache, path);
+                       if (ref)
+                               ref->flags |= PACKREF_SHADOWED;
+
                        return 0;
+               }
 
-               giterr_clear();
-       }
-
-       if (!iter->cache) {
-               if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
-                       return error;
+               git_error_clear();
        }
 
        error = GIT_ITEROVER;
@@ -559,7 +619,7 @@ static int refdb_fs_backend__iterator_next(
 
                if (ref->flags & PACKREF_SHADOWED)
                        continue;
-               if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
+               if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
                        continue;
 
                *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
@@ -574,24 +634,24 @@ static int refdb_fs_backend__iterator_next_name(
        const char **out, git_reference_iterator *_iter)
 {
        int error = GIT_ITEROVER;
-       refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
-       refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+       refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent);
        struct packref *ref;
 
        while (iter->loose_pos < iter->loose.length) {
                const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
+               struct packref *ref;
 
                if (loose_lookup(NULL, backend, path) == 0) {
+                       ref = git_sortedcache_lookup(iter->cache, path);
+                       if (ref)
+                               ref->flags |= PACKREF_SHADOWED;
+
                        *out = path;
                        return 0;
                }
 
-               giterr_clear();
-       }
-
-       if (!iter->cache) {
-               if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
-                       return error;
+               git_error_clear();
        }
 
        error = GIT_ITEROVER;
@@ -602,7 +662,7 @@ static int refdb_fs_backend__iterator_next_name(
 
                if (ref->flags & PACKREF_SHADOWED)
                        continue;
-               if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
+               if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
                        continue;
 
                *out = ref->name;
@@ -616,40 +676,44 @@ static int refdb_fs_backend__iterator_next_name(
 static int refdb_fs_backend__iterator(
        git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
 {
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
+       refdb_fs_iter *iter = NULL;
        int error;
-       refdb_fs_iter *iter;
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
 
        assert(backend);
 
-       if ((error = packed_reload(backend)) < 0)
-               return error;
-
        iter = git__calloc(1, sizeof(refdb_fs_iter));
-       GITERR_CHECK_ALLOC(iter);
+       GIT_ERROR_CHECK_ALLOC(iter);
 
        git_pool_init(&iter->pool, 1);
 
-       if (git_vector_init(&iter->loose, 8, NULL) < 0)
-               goto fail;
+       if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0)
+               goto out;
 
        if (glob != NULL &&
-               (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL)
-               goto fail;
+           (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) {
+               error = GIT_ERROR_NOMEMORY;
+               goto out;
+       }
+
+       if ((error = iter_load_loose_paths(backend, iter)) < 0)
+               goto out;
+
+       if ((error = packed_reload(backend)) < 0)
+               goto out;
+
+       if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
+               goto out;
 
        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)
-               goto fail;
-
        *out = (git_reference_iterator *)iter;
-       return 0;
-
-fail:
-       refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
-       return -1;
+out:
+       if (error)
+               refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
+       return error;
 }
 
 static bool ref_is_available(
@@ -690,8 +754,8 @@ static int reference_path_available(
                }
 
                if (exists) {
-                       giterr_set(GITERR_REFERENCE,
-                               "Failed to write reference '%s': a reference with "
+                       git_error_set(GIT_ERROR_REFERENCE,
+                               "failed to write reference '%s': a reference with "
                                "that name already exists.", new_ref);
                        return GIT_EEXISTS;
                }
@@ -704,8 +768,8 @@ static int reference_path_available(
 
                if (ref && !ref_is_available(old_ref, new_ref, ref->name)) {
                        git_sortedcache_runlock(backend->refcache);
-                       giterr_set(GITERR_REFERENCE,
-                               "Path to reference '%s' collides with existing one", new_ref);
+                       git_error_set(GIT_ERROR_REFERENCE,
+                               "path to reference '%s' collides with existing one", new_ref);
                        return -1;
                }
        }
@@ -716,31 +780,41 @@ static int reference_path_available(
 
 static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name)
 {
-       int error;
+       int error, filebuf_flags;
        git_buf ref_path = GIT_BUF_INIT;
+       const char *basedir;
 
        assert(file && backend && name);
 
-       if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
-               giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", name);
+       if (!git_path_isvalid(backend->repo, name, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
+               git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", name);
                return GIT_EINVALIDSPEC;
        }
 
+       if (is_per_worktree_ref(name))
+               basedir = backend->gitpath;
+       else
+               basedir = backend->commonpath;
+
        /* Remove a possibly existing empty directory hierarchy
         * which name would collide with the reference name
         */
-       if ((error = git_futils_rmdir_r(name, backend->path, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
+       if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
                return error;
 
-       if (git_buf_joinpath(&ref_path, backend->path, name) < 0)
+       if (git_buf_joinpath(&ref_path, basedir, name) < 0)
                return -1;
 
-       error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE);
+       filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS;
+       if (backend->fsync)
+               filebuf_flags |= GIT_FILEBUF_FSYNC;
+
+       error = git_filebuf_open(file, ref_path.ptr, filebuf_flags, GIT_REFS_FILE_MODE);
 
        if (error == GIT_EDIRECTORY)
-               giterr_set(GITERR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name);
+               git_error_set(GIT_ERROR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name);
 
-       git_buf_free(&ref_path);
+       git_buf_dispose(&ref_path);
        return error;
 }
 
@@ -748,12 +822,12 @@ static int loose_commit(git_filebuf *file, const git_reference *ref)
 {
        assert(file && ref);
 
-       if (ref->type == GIT_REF_OID) {
+       if (ref->type == GIT_REFERENCE_DIRECT) {
                char oid[GIT_OID_HEXSZ + 1];
                git_oid_nfmt(oid, sizeof(oid), &ref->target.oid);
 
                git_filebuf_printf(file, "%s\n", oid);
-       } else if (ref->type == GIT_REF_SYMBOLIC) {
+       } else if (ref->type == GIT_REFERENCE_SYMBOLIC) {
                git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic);
        } else {
                assert(0); /* don't let this happen */
@@ -766,10 +840,10 @@ static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const
 {
        int error;
        git_filebuf *lock;
-       refdb_fs_backend *backend = (refdb_fs_backend *) _backend;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
 
        lock = git__calloc(1, sizeof(git_filebuf));
-       GITERR_CHECK_ALLOC(lock);
+       GIT_ERROR_CHECK_ALLOC(lock);
 
        if ((error = loose_lock(lock, backend, refname)) < 0) {
                git__free(lock);
@@ -785,16 +859,17 @@ static int refdb_fs_backend__write_tail(
        const git_reference *ref,
        git_filebuf *file,
        int update_reflog,
-       const git_signature *who,
-       const char *message,
        const git_oid *old_id,
-       const char *old_target);
+       const char *old_target,
+       const git_signature *who,
+       const char *message);
 
 static int refdb_fs_backend__delete_tail(
        git_refdb_backend *_backend,
        git_filebuf *file,
        const char *ref_name,
-       const git_oid *old_id, const char *old_target);
+       const git_oid *old_id,
+       const char *old_target);
 
 static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, int success, int update_reflog,
                                    const git_reference *ref, const git_signature *sig, const char *message)
@@ -805,7 +880,7 @@ static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, i
        if (success == 2)
                error = refdb_fs_backend__delete_tail(backend, lock, ref->name, NULL, NULL);
        else if (success)
-               error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, sig, message, NULL, NULL);
+               error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, NULL, NULL, sig, message);
        else
                git_filebuf_cleanup(lock);
 
@@ -831,7 +906,7 @@ static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
        /*
         * Find the tagged object in the repository
         */
-       if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0)
+       if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJECT_ANY) < 0)
                return -1;
 
        /*
@@ -839,7 +914,7 @@ static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
         * if the ref is actually a 'weak' ref, we don't need to resolve
         * anything.
         */
-       if (git_object_type(object) == GIT_OBJ_TAG) {
+       if (git_object_type(object) == GIT_OBJECT_TAG) {
                git_tag *tag = (git_tag *)object;
 
                /*
@@ -927,8 +1002,8 @@ static int packed_remove_loose(refdb_fs_backend *backend)
                        continue;
 
                if (error < 0) {
-                       git_buf_free(&ref_content);
-                       giterr_set(GITERR_REFERENCE, "failed to lock loose reference '%s'", ref->name);
+                       git_buf_dispose(&ref_content);
+                       git_error_set(GIT_ERROR_REFERENCE, "failed to lock loose reference '%s'", ref->name);
                        return error;
                }
 
@@ -958,7 +1033,7 @@ static int packed_remove_loose(refdb_fs_backend *backend)
                p_unlink(lock.path_original);
        }
 
-       git_buf_free(&ref_content);
+       git_buf_dispose(&ref_content);
        git_filebuf_cleanup(&lock);
        return 0;
 }
@@ -970,15 +1045,18 @@ static int packed_write(refdb_fs_backend *backend)
 {
        git_sortedcache *refcache = backend->refcache;
        git_filebuf pack_file = GIT_FILEBUF_INIT;
-       int error;
+       int error, open_flags = 0;
        size_t i;
 
        /* lock the cache to updates while we do this */
        if ((error = git_sortedcache_wlock(refcache)) < 0)
                return error;
 
+       if (backend->fsync)
+               open_flags = GIT_FILEBUF_FSYNC;
+
        /* Open the file! */
-       if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE)) < 0)
+       if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), open_flags, GIT_PACKEDREFS_FILE_MODE)) < 0)
                goto fail;
 
        /* Packfiles have a header... apparently
@@ -1021,15 +1099,43 @@ fail:
        return error;
 }
 
+static int packed_delete(refdb_fs_backend *backend, const char *ref_name)
+{
+       size_t pack_pos;
+       int error, found = 0;
+
+       if ((error = packed_reload(backend)) < 0)
+               goto cleanup;
+
+       if ((error = git_sortedcache_wlock(backend->refcache)) < 0)
+               goto cleanup;
+
+       /* If a packed reference exists, remove it from the packfile and repack if necessary */
+       error = git_sortedcache_lookup_index(&pack_pos, backend->refcache, ref_name);
+       if (error == 0) {
+               error = git_sortedcache_remove(backend->refcache, pack_pos);
+               found = 1;
+       }
+       if (error == GIT_ENOTFOUND)
+               error = 0;
+
+       git_sortedcache_wunlock(backend->refcache);
+
+       if (found)
+               error = packed_write(backend);
+
+cleanup:
+       return error;
+}
+
 static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message);
 static int has_reflog(git_repository *repo, const char *name);
 
-/* We only write if it's under heads/, remotes/ or notes/ or if it already has a log */
 static int should_write_reflog(int *write, git_repository *repo, const char *name)
 {
        int error, logall;
 
-       error = git_repository__cvar(&logall, repo, GIT_CVAR_LOGALLREFUPDATES);
+       error = git_repository__configmap_lookup(&logall, repo, GIT_CONFIGMAP_LOGALLREFUPDATES);
        if (error < 0)
                return error;
 
@@ -1037,17 +1143,26 @@ static int should_write_reflog(int *write, git_repository *repo, const char *nam
        if (logall == GIT_LOGALLREFUPDATES_UNSET)
                logall = !git_repository_is_bare(repo);
 
-       if (!logall) {
+       *write = 0;
+       switch (logall) {
+       case GIT_LOGALLREFUPDATES_FALSE:
                *write = 0;
-       } else if (has_reflog(repo, name)) {
-               *write = 1;
-       } else if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR) ||
-                  !git__strcmp(name, GIT_HEAD_FILE) ||
-                  !git__prefixcmp(name, GIT_REFS_REMOTES_DIR) ||
-                  !git__prefixcmp(name, GIT_REFS_NOTES_DIR)) {
+               break;
+
+       case GIT_LOGALLREFUPDATES_TRUE:
+               /* Only write if it already has a log,
+                * or if it's under heads/, remotes/ or notes/
+                */
+               *write = has_reflog(repo, name) ||
+                       !git__prefixcmp(name, GIT_REFS_HEADS_DIR) ||
+                       !git__strcmp(name, GIT_HEAD_FILE) ||
+                       !git__prefixcmp(name, GIT_REFS_REMOTES_DIR) ||
+                       !git__prefixcmp(name, GIT_REFS_NOTES_DIR);
+               break;
+
+       case GIT_LOGALLREFUPDATES_ALWAYS:
                *write = 1;
-       } else {
-               *write = 0;
+               break;
        }
 
        return 0;
@@ -1068,19 +1183,19 @@ static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name,
                goto out;
 
        /* If the types don't match, there's no way the values do */
-       if (old_id && old_ref->type != GIT_REF_OID) {
+       if (old_id && old_ref->type != GIT_REFERENCE_DIRECT) {
                *cmp = -1;
                goto out;
        }
-       if (old_target && old_ref->type != GIT_REF_SYMBOLIC) {
+       if (old_target && old_ref->type != GIT_REFERENCE_SYMBOLIC) {
                *cmp = 1;
                goto out;
        }
 
-       if (old_id && old_ref->type == GIT_REF_OID)
+       if (old_id && old_ref->type == GIT_REFERENCE_DIRECT)
                *cmp = git_oid_cmp(old_id, &old_ref->target.oid);
 
-       if (old_target && old_ref->type == GIT_REF_SYMBOLIC)
+       if (old_target && old_ref->type == GIT_REFERENCE_SYMBOLIC)
                *cmp = git__strcmp(old_target, old_ref->target.symbolic);
 
 out:
@@ -1106,27 +1221,28 @@ out:
 static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message)
 {
        int error;
-       git_oid old_id = {{0}};
+       git_oid old_id;
        git_reference *tmp = NULL, *head = NULL, *peeled = NULL;
        const char *name;
 
-       if (ref->type == GIT_REF_SYMBOLIC)
+       if (ref->type == GIT_REFERENCE_SYMBOLIC)
                return 0;
 
        /* if we can't resolve, we use {0}*40 as old id */
-       git_reference_name_to_id(&old_id, backend->repo, ref->name);
+       if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0)
+               memset(&old_id, 0, sizeof(old_id));
 
        if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0)
                return error;
 
-       if (git_reference_type(head) == GIT_REF_OID)
+       if (git_reference_type(head) == GIT_REFERENCE_DIRECT)
                goto cleanup;
 
        if ((error = git_reference_lookup(&tmp, backend->repo, GIT_HEAD_FILE)) < 0)
                goto cleanup;
 
        /* Go down the symref chain until we find the branch */
-       while (git_reference_type(tmp) == GIT_REF_SYMBOLIC) {
+       while (git_reference_type(tmp) == GIT_REFERENCE_SYMBOLIC) {
                error = git_reference_lookup(&peeled, backend->repo, git_reference_symbolic_target(tmp));
                if (error < 0)
                        break;
@@ -1164,7 +1280,7 @@ static int refdb_fs_backend__write(
        const git_oid *old_id,
        const char *old_target)
 {
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        git_filebuf file = GIT_FILEBUF_INIT;
        int error = 0;
 
@@ -1177,7 +1293,7 @@ static int refdb_fs_backend__write(
        if ((error = loose_lock(&file, backend, ref->name)) < 0)
                return error;
 
-       return refdb_fs_backend__write_tail(_backend, ref, &file, true, who, message, old_id, old_target);
+       return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message);
 }
 
 static int refdb_fs_backend__write_tail(
@@ -1185,12 +1301,12 @@ static int refdb_fs_backend__write_tail(
        const git_reference *ref,
        git_filebuf *file,
        int update_reflog,
-       const git_signature *who,
-       const char *message,
        const git_oid *old_id,
-       const char *old_target)
+       const char *old_target,
+       const git_signature *who,
+       const char *message)
 {
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        int error = 0, cmp = 0, should_write;
        const char *new_target = NULL;
        const git_oid *new_id = NULL;
@@ -1199,12 +1315,12 @@ static int refdb_fs_backend__write_tail(
                goto on_error;
 
        if (cmp) {
-               giterr_set(GITERR_REFERENCE, "old reference value does not match");
+               git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match");
                error = GIT_EMODIFIED;
                goto on_error;
        }
 
-       if (ref->type == GIT_REF_SYMBOLIC)
+       if (ref->type == GIT_REFERENCE_SYMBOLIC)
                new_target = ref->target.symbolic;
        else
                new_id = &ref->target.oid;
@@ -1238,12 +1354,49 @@ on_error:
         return error;
 }
 
+static void refdb_fs_backend__prune_refs(
+       refdb_fs_backend *backend,
+       const char *ref_name,
+       const char *prefix)
+{
+       git_buf relative_path = GIT_BUF_INIT;
+       git_buf base_path = GIT_BUF_INIT;
+       size_t commonlen;
+
+       assert(backend && ref_name);
+
+       if (git_buf_sets(&relative_path, ref_name) < 0)
+               goto cleanup;
+
+       git_path_squash_slashes(&relative_path);
+       if ((commonlen = git_path_common_dirlen("refs/heads/", git_buf_cstr(&relative_path))) == strlen("refs/heads/") ||
+               (commonlen = git_path_common_dirlen("refs/tags/", git_buf_cstr(&relative_path))) == strlen("refs/tags/") ||
+               (commonlen = git_path_common_dirlen("refs/remotes/", git_buf_cstr(&relative_path))) == strlen("refs/remotes/")) {
+
+               git_buf_truncate(&relative_path, commonlen);
+
+               if (prefix) {
+                       if (git_buf_join3(&base_path, '/', backend->commonpath, prefix, git_buf_cstr(&relative_path)) < 0)
+                               goto cleanup;
+               } else {
+                       if (git_buf_joinpath(&base_path, backend->commonpath, git_buf_cstr(&relative_path)) < 0)
+                               goto cleanup;
+               }
+
+               git_futils_rmdir_r(ref_name + commonlen, git_buf_cstr(&base_path), GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_SKIP_ROOT);
+       }
+
+cleanup:
+       git_buf_dispose(&relative_path);
+       git_buf_dispose(&base_path);
+}
+
 static int refdb_fs_backend__delete(
        git_refdb_backend *_backend,
        const char *ref_name,
        const git_oid *old_id, const char *old_target)
 {
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        git_filebuf file = GIT_FILEBUF_INIT;
        int error = 0;
 
@@ -1260,65 +1413,80 @@ static int refdb_fs_backend__delete(
        return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target);
 }
 
+static int loose_delete(refdb_fs_backend *backend, const char *ref_name)
+{
+       git_buf loose_path = GIT_BUF_INIT;
+       int error = 0;
+
+       if (git_buf_joinpath(&loose_path, backend->commonpath, ref_name) < 0)
+               return -1;
+
+       error = p_unlink(loose_path.ptr);
+       if (error < 0 && errno == ENOENT)
+               error = GIT_ENOTFOUND;
+       else if (error != 0)
+               error = -1;
+
+       git_buf_dispose(&loose_path);
+
+       return error;
+}
+
 static int refdb_fs_backend__delete_tail(
        git_refdb_backend *_backend,
        git_filebuf *file,
        const char *ref_name,
        const git_oid *old_id, const char *old_target)
 {
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
-       git_buf loose_path = GIT_BUF_INIT;
-       size_t pack_pos;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        int error = 0, cmp = 0;
-       bool loose_deleted = 0;
+       bool packed_deleted = 0;
 
        error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target);
        if (error < 0)
                goto cleanup;
 
        if (cmp) {
-               giterr_set(GITERR_REFERENCE, "old reference value does not match");
+               git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match");
                error = GIT_EMODIFIED;
                goto cleanup;
        }
 
-       /* If a loose reference exists, remove it from the filesystem */
-       if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)
-               return -1;
-
-
-       error = p_unlink(loose_path.ptr);
-       if (error < 0 && errno == ENOENT)
-               error = 0;
-       else if (error < 0)
+       /*
+        * To ensure that an external observer will see either the current ref value
+        * (because the loose ref still exists), or a missing ref (after the packed-file is
+        * unlocked, there will be nothing left), we must ensure things happen in the
+        * following order:
+        *
+        * - the packed-ref file is locked and loaded, as well as a loose one, if it exists
+        * - we optimistically delete a packed ref, keeping track of whether it existed
+        * - we delete the loose ref, note that we have its .lock
+        * - the loose ref is "unlocked", then the packed-ref file is rewritten and unlocked
+        * - we should prune the path components if a loose ref was deleted
+        *
+        * Note that, because our packed backend doesn't expose its filesystem lock,
+        * we might not be able to guarantee that this is what actually happens (ie.
+        * as our current code never write packed-refs.lock, nothing stops observers
+        * from grabbing a "stale" value from there).
+        */
+       if ((error = packed_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND)
                goto cleanup;
-       else if (error == 0)
-               loose_deleted = 1;
 
-       if ((error = packed_reload(backend)) < 0)
-               goto cleanup;
+       if (error == 0)
+               packed_deleted = 1;
 
-       /* If a packed reference exists, remove it from the packfile and repack */
-       if ((error = git_sortedcache_wlock(backend->refcache)) < 0)
+       if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND)
                goto cleanup;
 
-       if (!(error = git_sortedcache_lookup_index(
-                       &pack_pos, backend->refcache, ref_name)))
-               error = git_sortedcache_remove(backend->refcache, pack_pos);
-
-       git_sortedcache_wunlock(backend->refcache);
-
        if (error == GIT_ENOTFOUND) {
-               error = loose_deleted ? 0 : ref_error_notfound(ref_name);
+               error = packed_deleted ? 0 : ref_error_notfound(ref_name);
                goto cleanup;
        }
 
-       error = packed_write(backend);
-
 cleanup:
-       git_buf_free(&loose_path);
        git_filebuf_cleanup(file);
-
+       if (error == 0)
+               refdb_fs_backend__prune_refs(backend, ref_name, "");
        return error;
 }
 
@@ -1333,8 +1501,8 @@ static int refdb_fs_backend__rename(
        const git_signature *who,
        const char *message)
 {
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
-       git_reference *old, *new;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
+       git_reference *old, *new = NULL;
        git_filebuf file = GIT_FILEBUF_INIT;
        int error;
 
@@ -1350,7 +1518,7 @@ static int refdb_fs_backend__rename(
                return error;
        }
 
-       new = git_reference__set_name(old, new_name);
+       new = git_reference__realloc(&old, new_name);
        if (!new) {
                git_reference_free(old);
                return -1;
@@ -1389,7 +1557,7 @@ static int refdb_fs_backend__rename(
 static int refdb_fs_backend__compress(git_refdb_backend *_backend)
 {
        int error;
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
 
        assert(backend);
 
@@ -1403,33 +1571,35 @@ static int refdb_fs_backend__compress(git_refdb_backend *_backend)
 
 static void refdb_fs_backend__free(git_refdb_backend *_backend)
 {
-       refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
 
        assert(backend);
 
        git_sortedcache_free(backend->refcache);
-       git__free(backend->path);
+       git__free(backend->gitpath);
+       git__free(backend->commonpath);
        git__free(backend);
 }
 
-static int setup_namespace(git_buf *path, git_repository *repo)
+static char *setup_namespace(git_repository *repo, const char *in)
 {
-       char *parts, *start, *end;
+       git_buf path = GIT_BUF_INIT;
+       char *parts, *start, *end, *out = NULL;
 
-       /* Not all repositories have a path */
-       if (repo->path_repository == NULL)
-               return 0;
+       if (!in)
+               goto done;
 
-       /* Load the path to the repo first */
-       git_buf_puts(path, repo->path_repository);
+       git_buf_puts(&path, in);
 
        /* if the repo is not namespaced, nothing else to do */
-       if (repo->namespace == NULL)
-               return 0;
+       if (repo->namespace == NULL) {
+               out = git_buf_detach(&path);
+               goto done;
+       }
 
        parts = end = git__strdup(repo->namespace);
        if (parts == NULL)
-               return -1;
+               goto done;
 
        /*
         * From `man gitnamespaces`:
@@ -1437,21 +1607,24 @@ static int setup_namespace(git_buf *path, git_repository *repo)
         *  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);
-       }
+       while ((start = git__strsep(&end, "/")) != NULL)
+               git_buf_printf(&path, "refs/namespaces/%s/", start);
 
-       git_buf_printf(path, "refs/namespaces/%s/refs", end);
+       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_relative(git_buf_cstr(path), repo->path_repository,
-                       0777, GIT_MKDIR_PATH, NULL) < 0)
-               return -1;
+       if (git_futils_mkdir_relative(git_buf_cstr(&path), in, 0777,
+                       GIT_MKDIR_PATH, NULL) < 0)
+               goto done;
 
-       /* Return root of the namespaced path, i.e. without the trailing '/refs' */
-       git_buf_rtruncate_at_char(path, '/');
-       return 0;
+       /* Return root of the namespaced gitpath, i.e. without the trailing '/refs' */
+       git_buf_rtruncate_at_char(&path, '/');
+       out = git_buf_detach(&path);
+
+done:
+       git_buf_dispose(&path);
+       return out;
 }
 
 static int reflog_alloc(git_reflog **reflog, const char *name)
@@ -1461,10 +1634,10 @@ static int reflog_alloc(git_reflog **reflog, const char *name)
        *reflog = NULL;
 
        log = git__calloc(1, sizeof(git_reflog));
-       GITERR_CHECK_ALLOC(log);
+       GIT_ERROR_CHECK_ALLOC(log);
 
        log->ref_name = git__strdup(name);
-       GITERR_CHECK_ALLOC(log->ref_name);
+       GIT_ERROR_CHECK_ALLOC(log->ref_name);
 
        if (git_vector_init(&log->entries, 0, NULL) < 0) {
                git__free(log->ref_name);
@@ -1479,70 +1652,57 @@ static int reflog_alloc(git_reflog **reflog, const char *name)
 
 static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
 {
-       const char *ptr;
-       git_reflog_entry *entry;
+       git_parse_ctx parser = GIT_PARSE_CTX_INIT;
 
-#define seek_forward(_increase) do { \
-       if (_increase >= buf_size) { \
-               giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \
-               goto fail; \
-       } \
-       buf += _increase; \
-       buf_size -= _increase; \
-       } while (0)
+       if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0)
+               return -1;
 
-       while (buf_size > GIT_REFLOG_SIZE_MIN) {
-               entry = git__calloc(1, sizeof(git_reflog_entry));
-               GITERR_CHECK_ALLOC(entry);
+       for (; parser.remain_len; git_parse_advance_line(&parser)) {
+               git_reflog_entry *entry;
+               const char *sig;
+               char c;
 
-               entry->committer = git__calloc(1, sizeof(git_signature));
-               GITERR_CHECK_ALLOC(entry->committer);
+               entry = git__calloc(1, sizeof(*entry));
+               GIT_ERROR_CHECK_ALLOC(entry);
+               entry->committer = git__calloc(1, sizeof(*entry->committer));
+               GIT_ERROR_CHECK_ALLOC(entry->committer);
 
-               if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0)
-                       goto fail;
-               seek_forward(GIT_OID_HEXSZ + 1);
+               if (git_parse_advance_oid(&entry->oid_old, &parser) < 0 ||
+                   git_parse_advance_expected(&parser, " ", 1) < 0 ||
+                   git_parse_advance_oid(&entry->oid_cur, &parser) < 0)
+                       goto next;
 
-               if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0)
-                       goto fail;
-               seek_forward(GIT_OID_HEXSZ + 1);
+               sig = parser.line;
+               while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n')
+                       git_parse_advance_chars(&parser, 1);
 
-               ptr = buf;
+               if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0)
+                       goto next;
 
-               /* Seek forward to the end of the signature. */
-               while (*buf && *buf != '\t' && *buf != '\n')
-                       seek_forward(1);
+               if (c == '\t') {
+                       size_t len;
+                       git_parse_advance_chars(&parser, 1);
 
-               if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0)
-                       goto fail;
-
-               if (*buf == '\t') {
-                       /* We got a message. Read everything till we reach LF. */
-                       seek_forward(1);
-                       ptr = buf;
+                       len = parser.line_len;
+                       if (parser.line[len - 1] == '\n')
+                               len--;
 
-                       while (*buf && *buf != '\n')
-                               seek_forward(1);
+                       entry->msg = git__strndup(parser.line, len);
+                       GIT_ERROR_CHECK_ALLOC(entry->msg);
+               }
 
-                       entry->msg = git__strndup(ptr, buf - ptr);
-                       GITERR_CHECK_ALLOC(entry->msg);
-               } else
-                       entry->msg = NULL;
+               if ((git_vector_insert(&log->entries, entry)) < 0) {
+                       git_reflog_entry__free(entry);
+                       return -1;
+               }
 
-               while (*buf && *buf == '\n' && buf_size > 1)
-                       seek_forward(1);
+               continue;
 
-               if (git_vector_insert(&log->entries, entry) < 0)
-                       goto fail;
+next:
+               git_reflog_entry__free(entry);
        }
 
        return 0;
-
-#undef seek_forward
-
-fail:
-       git_reflog_entry__free(entry);
-
-       return -1;
 }
 
 static int create_new_reflog_file(const char *filepath)
@@ -1562,7 +1722,9 @@ static int create_new_reflog_file(const char *filepath)
 
 GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name)
 {
-       return git_buf_join3(path, '/', repo->path_repository, GIT_REFLOG_DIR, name);
+       if (strcmp(name, GIT_HEAD_FILE) == 0)
+               return git_buf_join3(path, '/', repo->gitdir, GIT_REFLOG_DIR, name);
+       return git_buf_join3(path, '/', repo->commondir, GIT_REFLOG_DIR, name);
 }
 
 static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
@@ -1574,14 +1736,14 @@ static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *
 
        assert(_backend && name);
 
-       backend = (refdb_fs_backend *) _backend;
+       backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        repo = backend->repo;
 
        if ((error = retrieve_reflog_path(&path, repo, name)) < 0)
                return error;
 
        error = create_new_reflog_file(git_buf_cstr(&path));
-       git_buf_free(&path);
+       git_buf_dispose(&path);
 
        return error;
 }
@@ -1597,7 +1759,7 @@ static int has_reflog(git_repository *repo, const char *name)
        ret = git_path_isfile(git_buf_cstr(&path));
 
 cleanup:
-       git_buf_free(&path);
+       git_buf_dispose(&path);
        return ret;
 }
 
@@ -1607,7 +1769,7 @@ static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *nam
 
        assert(_backend && name);
 
-       backend = (refdb_fs_backend *) _backend;
+       backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
 
        return has_reflog(backend->repo, name);
 }
@@ -1623,7 +1785,7 @@ static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend,
 
        assert(out && _backend && name);
 
-       backend = (refdb_fs_backend *) _backend;
+       backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        repo = backend->repo;
 
        if (reflog_alloc(&log, name) < 0)
@@ -1639,7 +1801,7 @@ static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend,
        if ((error == GIT_ENOTFOUND) &&
                ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0))
                goto cleanup;
+
        if ((error = reflog_parse(log,
                git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0)
                goto cleanup;
@@ -1651,8 +1813,8 @@ cleanup:
        git_reflog_free(log);
 
 success:
-       git_buf_free(&log_file);
-       git_buf_free(&log_path);
+       git_buf_dispose(&log_file);
+       git_buf_dispose(&log_path);
 
        return error;
 }
@@ -1682,8 +1844,15 @@ static int serialize_reflog_entry(
        git_buf_rtrim(buf);
 
        if (msg) {
+               size_t i;
+
                git_buf_putc(buf, '\t');
                git_buf_puts(buf, msg);
+
+               for (i = 0; i < buf->size - 2; i++)
+                       if (buf->ptr[i] == '\n')
+                               buf->ptr[i] = ' ';
+               git_buf_rtrim(buf);
        }
 
        git_buf_putc(buf, '\n');
@@ -1699,8 +1868,8 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char
 
        repo = backend->repo;
 
-       if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
-               giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", refname);
+       if (!git_path_isvalid(backend->repo, refname, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
+               git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", refname);
                return GIT_EINVALIDSPEC;
        }
 
@@ -1708,8 +1877,8 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char
                return -1;
 
        if (!git_path_isfile(git_buf_cstr(&log_path))) {
-               giterr_set(GITERR_INVALID,
-                       "Log file for reference '%s' doesn't exist.", refname);
+               git_error_set(GIT_ERROR_INVALID,
+                       "log file for reference '%s' doesn't exist", refname);
                error = -1;
                goto cleanup;
        }
@@ -1717,7 +1886,7 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char
        error = git_filebuf_open(file, git_buf_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE);
 
 cleanup:
-       git_buf_free(&log_path);
+       git_buf_dispose(&log_path);
 
        return error;
 }
@@ -1733,7 +1902,7 @@ static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflo
 
        assert(_backend && reflog);
 
-       backend = (refdb_fs_backend *) _backend;
+       backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
 
        if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0)
                return -1;
@@ -1753,7 +1922,7 @@ cleanup:
        git_filebuf_cleanup(&fbuf);
 
 success:
-       git_buf_free(&log);
+       git_buf_dispose(&log);
 
        return error;
 }
@@ -1761,12 +1930,12 @@ success:
 /* Append to the reflog, must be called under reference lock */
 static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *who, const char *message)
 {
-       int error, is_symbolic;
+       int error, is_symbolic, open_flags;
        git_oid old_id = {{0}}, new_id = {{0}};
        git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
        git_repository *repo = backend->repo;
 
-       is_symbolic = ref->type == GIT_REF_SYMBOLIC;
+       is_symbolic = ref->type == GIT_REFERENCE_SYMBOLIC;
 
        /* "normal" symbolic updates do not write */
        if (is_symbolic &&
@@ -1774,7 +1943,7 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co
            !(old && new))
                return 0;
 
-       /* From here on is_symoblic also means that it's HEAD */
+       /* From here on is_symbolic also means that it's HEAD */
 
        if (old) {
                git_oid_cpy(&old_id, old);
@@ -1797,7 +1966,7 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co
                        if (error == GIT_ENOTFOUND)
                                return 0;
 
-                       giterr_clear();
+                       git_error_clear();
                }
        }
 
@@ -1820,7 +1989,7 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co
                        if (error == GIT_ENOTFOUND)
                                error = 0;
                } else if (git_path_isdir(git_buf_cstr(&path))) {
-                       giterr_set(GITERR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder",
+                       git_error_set(GIT_ERROR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder",
                                ref->name);
                        error = GIT_EDIRECTORY;
                }
@@ -1829,11 +1998,16 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co
                        goto cleanup;
        }
 
-       error = git_futils_writebuffer(&buf, git_buf_cstr(&path), O_WRONLY|O_CREAT|O_APPEND, GIT_REFLOG_FILE_MODE);
+       open_flags = O_WRONLY | O_CREAT | O_APPEND;
+
+       if (backend->fsync)
+               open_flags |= O_FSYNC;
+
+       error = git_futils_writebuffer(&buf, git_buf_cstr(&path), open_flags, GIT_REFLOG_FILE_MODE);
 
 cleanup:
-       git_buf_free(&buf);
-       git_buf_free(&path);
+       git_buf_dispose(&buf);
+       git_buf_dispose(&path);
 
        return error;
 }
@@ -1850,14 +2024,14 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_
 
        assert(_backend && old_name && new_name);
 
-       backend = (refdb_fs_backend *) _backend;
+       backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        repo = backend->repo;
 
        if ((error = git_reference__normalize_name(
-               &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0)
+               &normalized, new_name, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) < 0)
                        return error;
 
-       if (git_buf_joinpath(&temp_path, repo->path_repository, GIT_REFLOG_DIR) < 0)
+       if (git_buf_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0)
                return -1;
 
        if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0)
@@ -1889,12 +2063,12 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_
        p_close(fd);
 
        if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) {
-               giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);
+               git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name);
                error = -1;
                goto cleanup;
        }
 
-       if (git_path_isdir(git_buf_cstr(&new_path)) && 
+       if (git_path_isdir(git_buf_cstr(&new_path)) &&
                (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) {
                error = -1;
                goto cleanup;
@@ -1906,41 +2080,42 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_
        }
 
        if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) {
-               giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);
+               git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name);
                error = -1;
        }
 
 cleanup:
-       git_buf_free(&temp_path);
-       git_buf_free(&old_path);
-       git_buf_free(&new_path);
-       git_buf_free(&normalized);
+       git_buf_dispose(&temp_path);
+       git_buf_dispose(&old_path);
+       git_buf_dispose(&new_path);
+       git_buf_dispose(&normalized);
 
        return error;
 }
 
 static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name)
 {
-       int error;
+       refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
        git_buf path = GIT_BUF_INIT;
-
-       git_repository *repo;
-       refdb_fs_backend *backend;
+       int error;
 
        assert(_backend && name);
 
-       backend = (refdb_fs_backend *) _backend;
-       repo = backend->repo;
+       if ((error = retrieve_reflog_path(&path, backend->repo, name)) < 0)
+               goto out;
+
+       if (!git_path_exists(path.ptr))
+               goto out;
 
-       error = retrieve_reflog_path(&path, repo, name);
+       if ((error = p_unlink(path.ptr)) < 0)
+               goto out;
 
-       if (!error && git_path_exists(path.ptr))
-               error = p_unlink(path.ptr);
+       refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR);
 
-       git_buf_free(&path);
+out:
+       git_buf_dispose(&path);
 
        return error;
-
 }
 
 int git_refdb_backend_fs(
@@ -1948,35 +2123,51 @@ int git_refdb_backend_fs(
        git_repository *repository)
 {
        int t = 0;
-       git_buf path = GIT_BUF_INIT;
+       git_buf gitpath = GIT_BUF_INIT;
        refdb_fs_backend *backend;
 
        backend = git__calloc(1, sizeof(refdb_fs_backend));
-       GITERR_CHECK_ALLOC(backend);
+       GIT_ERROR_CHECK_ALLOC(backend);
+
+       if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0)
+               goto fail;
 
        backend->repo = repository;
 
-       if (setup_namespace(&path, repository) < 0)
-               goto fail;
+       if (repository->gitdir) {
+               backend->gitpath = setup_namespace(repository, repository->gitdir);
+
+               if (backend->gitpath == NULL)
+                       goto fail;
+       }
 
-       backend->path = git_buf_detach(&path);
+       if (repository->commondir) {
+               backend->commonpath = setup_namespace(repository, repository->commondir);
+
+               if (backend->commonpath == NULL)
+                       goto fail;
+       }
 
-       if (git_buf_joinpath(&path, backend->path, GIT_PACKEDREFS_FILE) < 0 ||
+       if (git_buf_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 ||
                git_sortedcache_new(
                        &backend->refcache, offsetof(struct packref, name),
-                       NULL, NULL, packref_cmp, git_buf_cstr(&path)) < 0)
+                       NULL, NULL, packref_cmp, git_buf_cstr(&gitpath)) < 0)
                goto fail;
 
-       git_buf_free(&path);
+       git_buf_dispose(&gitpath);
 
-       if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) {
+       if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_IGNORECASE) && t) {
                backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
                backend->direach_flags  |= GIT_PATH_DIR_IGNORE_CASE;
        }
-       if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_PRECOMPOSE) && t) {
+       if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_PRECOMPOSE) && t) {
                backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
                backend->direach_flags  |= GIT_PATH_DIR_PRECOMPOSE_UNICODE;
        }
+       if ((!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) ||
+               git_repository__fsync_gitdir)
+               backend->fsync = 1;
+       backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS;
 
        backend->parent.exists = &refdb_fs_backend__exists;
        backend->parent.lookup = &refdb_fs_backend__lookup;
@@ -1999,8 +2190,9 @@ int git_refdb_backend_fs(
        return 0;
 
 fail:
-       git_buf_free(&path);
-       git__free(backend->path);
+       git_buf_dispose(&gitpath);
+       git__free(backend->gitpath);
+       git__free(backend->commonpath);
        git__free(backend);
        return -1;
 }