]> git.ipfire.org Git - thirdparty/git.git/blobdiff - refs.c
The fifteenth batch
[thirdparty/git.git] / refs.c
diff --git a/refs.c b/refs.c
index 55d2e0b2cb9e959443e98eb329fdf97eff9073a9..dd0d9c360f1ca6c83df17fdeaa491cc0b722a87e 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -6,7 +6,7 @@
 #include "advice.h"
 #include "config.h"
 #include "environment.h"
-#include "hashmap.h"
+#include "strmap.h"
 #include "gettext.h"
 #include "hex.h"
 #include "lockfile.h"
@@ -19,7 +19,6 @@
 #include "object-store-ll.h"
 #include "object.h"
 #include "path.h"
-#include "tag.h"
 #include "submodule.h"
 #include "worktree.h"
 #include "strvec.h"
@@ -38,14 +37,15 @@ static const struct ref_storage_be *refs_backends[] = {
        [REF_STORAGE_FORMAT_REFTABLE] = &refs_be_reftable,
 };
 
-static const struct ref_storage_be *find_ref_storage_backend(unsigned int ref_storage_format)
+static const struct ref_storage_be *find_ref_storage_backend(
+       enum ref_storage_format ref_storage_format)
 {
        if (ref_storage_format < ARRAY_SIZE(refs_backends))
                return refs_backends[ref_storage_format];
        return NULL;
 }
 
-unsigned int ref_storage_format_by_name(const char *name)
+enum ref_storage_format ref_storage_format_by_name(const char *name)
 {
        for (unsigned int i = 0; i < ARRAY_SIZE(refs_backends); i++)
                if (refs_backends[i] && !strcmp(refs_backends[i]->name, name))
@@ -53,7 +53,7 @@ unsigned int ref_storage_format_by_name(const char *name)
        return REF_STORAGE_FORMAT_UNKNOWN;
 }
 
-const char *ref_storage_format_to_name(unsigned int ref_storage_format)
+const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_format)
 {
        const struct ref_storage_be *be = find_ref_storage_backend(ref_storage_format);
        if (!be)
@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)
 {
        struct ref_namespace_info *info = &ref_namespace[namespace];
        if (info->ref_updated)
-               free(info->ref);
+               free((char *)info->ref);
        info->ref = ref;
        info->ref_updated = 1;
 }
@@ -384,14 +384,6 @@ char *refs_resolve_refdup(struct ref_store *refs,
        return xstrdup_or_null(result);
 }
 
-char *resolve_refdup(const char *refname, int resolve_flags,
-                    struct object_id *oid, int *flags)
-{
-       return refs_resolve_refdup(get_main_ref_store(the_repository),
-                                  refname, resolve_flags,
-                                  oid, flags);
-}
-
 /* The argument to for_each_filter_refs */
 struct for_each_ref_filter {
        const char *pattern;
@@ -400,19 +392,18 @@ struct for_each_ref_filter {
        void *cb_data;
 };
 
-int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags)
+int refs_read_ref_full(struct ref_store *refs, const char *refname,
+                      int resolve_flags, struct object_id *oid, int *flags)
 {
-       struct ref_store *refs = get_main_ref_store(the_repository);
-
        if (refs_resolve_ref_unsafe(refs, refname, resolve_flags,
                                    oid, flags))
                return 0;
        return -1;
 }
 
-int read_ref(const char *refname, struct object_id *oid)
+int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid)
 {
-       return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
+       return refs_read_ref_full(refs, refname, RESOLVE_REF_READING, oid, NULL);
 }
 
 int refs_ref_exists(struct ref_store *refs, const char *refname)
@@ -421,11 +412,6 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)
                                         NULL, NULL);
 }
 
-int ref_exists(const char *refname)
-{
-       return refs_ref_exists(get_main_ref_store(the_repository), refname);
-}
-
 static int for_each_filter_refs(const char *refname,
                                const struct object_id *oid,
                                int flags, void *data)
@@ -439,28 +425,8 @@ static int for_each_filter_refs(const char *refname,
        return filter->fn(refname, oid, flags, filter->cb_data);
 }
 
-enum peel_status peel_object(const struct object_id *name, struct object_id *oid)
-{
-       struct object *o = lookup_unknown_object(the_repository, name);
-
-       if (o->type == OBJ_NONE) {
-               int type = oid_object_info(the_repository, name, NULL);
-               if (type < 0 || !object_as_type(o, type, 0))
-                       return PEEL_INVALID;
-       }
-
-       if (o->type != OBJ_TAG)
-               return PEEL_NON_TAG;
-
-       o = deref_tag_noverify(o);
-       if (!o)
-               return PEEL_INVALID;
-
-       oidcpy(oid, &o->oid);
-       return PEEL_PEELED;
-}
-
 struct warn_if_dangling_data {
+       struct ref_store *refs;
        FILE *fp;
        const char *refname;
        const struct string_list *refnames;
@@ -477,7 +443,7 @@ static int warn_if_dangling_symref(const char *refname,
        if (!(flags & REF_ISSYMREF))
                return 0;
 
-       resolves_to = resolve_ref_unsafe(refname, 0, NULL, NULL);
+       resolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL);
        if (!resolves_to
            || (d->refname
                ? strcmp(resolves_to, d->refname)
@@ -490,26 +456,28 @@ static int warn_if_dangling_symref(const char *refname,
        return 0;
 }
 
-void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
+void refs_warn_dangling_symref(struct ref_store *refs, FILE *fp,
+                              const char *msg_fmt, const char *refname)
 {
-       struct warn_if_dangling_data data;
-
-       data.fp = fp;
-       data.refname = refname;
-       data.refnames = NULL;
-       data.msg_fmt = msg_fmt;
-       for_each_rawref(warn_if_dangling_symref, &data);
+       struct warn_if_dangling_data data = {
+               .refs = refs,
+               .fp = fp,
+               .refname = refname,
+               .msg_fmt = msg_fmt,
+       };
+       refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
 }
 
-void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)
+void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
+                               const char *msg_fmt, const struct string_list *refnames)
 {
-       struct warn_if_dangling_data data;
-
-       data.fp = fp;
-       data.refname = NULL;
-       data.refnames = refnames;
-       data.msg_fmt = msg_fmt;
-       for_each_rawref(warn_if_dangling_symref, &data);
+       struct warn_if_dangling_data data = {
+               .refs = refs,
+               .fp = fp,
+               .refnames = refnames,
+               .msg_fmt = msg_fmt,
+       };
+       refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
 }
 
 int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
@@ -517,32 +485,17 @@ int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
        return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data);
 }
 
-int for_each_tag_ref(each_ref_fn fn, void *cb_data)
-{
-       return refs_for_each_tag_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
 int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
        return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
 }
 
-int for_each_branch_ref(each_ref_fn fn, void *cb_data)
-{
-       return refs_for_each_branch_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
 int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
        return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
 }
 
-int for_each_remote_ref(each_ref_fn fn, void *cb_data)
-{
-       return refs_for_each_remote_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
-int head_ref_namespaced(each_ref_fn fn, void *cb_data)
+int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
        struct strbuf buf = STRBUF_INIT;
        int ret = 0;
@@ -550,7 +503,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
        int flag;
 
        strbuf_addf(&buf, "%sHEAD", get_git_namespace());
-       if (!read_ref_full(buf.buf, RESOLVE_REF_READING, &oid, &flag))
+       if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))
                ret = fn(buf.buf, &oid, flag, cb_data);
        strbuf_release(&buf);
 
@@ -583,8 +536,8 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
        strbuf_release(&normalized_pattern);
 }
 
-int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
-       const char *prefix, void *cb_data)
+int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
+                             const char *pattern, const char *prefix, void *cb_data)
 {
        struct strbuf real_pattern = STRBUF_INIT;
        struct for_each_ref_filter filter;
@@ -607,15 +560,16 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
        filter.prefix = prefix;
        filter.fn = fn;
        filter.cb_data = cb_data;
-       ret = for_each_ref(for_each_filter_refs, &filter);
+       ret = refs_for_each_ref(refs, for_each_filter_refs, &filter);
 
        strbuf_release(&real_pattern);
        return ret;
 }
 
-int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
+int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
+                          const char *pattern, void *cb_data)
 {
-       return for_each_glob_ref_in(fn, pattern, NULL, cb_data);
+       return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
 }
 
 const char *prettify_refname(const char *name)
@@ -711,16 +665,6 @@ char *repo_default_branch_name(struct repository *r, int quiet)
        return ret;
 }
 
-const char *git_default_branch_name(int quiet)
-{
-       static char *ret;
-
-       if (!ret)
-               ret = repo_default_branch_name(the_repository, quiet);
-
-       return ret;
-}
-
 /*
  * *string and *len will only be substituted, and *string returned (for
  * later free()ing) if the string passed in is a magic short-hand form
@@ -832,11 +776,6 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
        return logs_found;
 }
 
-int dwim_log(const char *str, int len, struct object_id *oid, char **log)
-{
-       return repo_dwim_log(the_repository, str, len, oid, log);
-}
-
 int is_per_worktree_ref(const char *refname)
 {
        return starts_with(refname, "refs/worktree/") ||
@@ -844,7 +783,22 @@ int is_per_worktree_ref(const char *refname)
               starts_with(refname, "refs/rewritten/");
 }
 
-static int is_pseudoref_syntax(const char *refname)
+int is_pseudo_ref(const char *refname)
+{
+       static const char * const pseudo_refs[] = {
+               "FETCH_HEAD",
+               "MERGE_HEAD",
+       };
+       size_t i;
+
+       for (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)
+               if (!strcmp(refname, pseudo_refs[i]))
+                       return 1;
+
+       return 0;
+}
+
+static int is_root_ref_syntax(const char *refname)
 {
        const char *c;
 
@@ -853,56 +807,37 @@ static int is_pseudoref_syntax(const char *refname)
                        return 0;
        }
 
-       /*
-        * HEAD is not a pseudoref, but it certainly uses the
-        * pseudoref syntax.
-        */
        return 1;
 }
 
-int is_pseudoref(struct ref_store *refs, const char *refname)
+int is_root_ref(const char *refname)
 {
-       static const char *const irregular_pseudorefs[] = {
+       static const char *const irregular_root_refs[] = {
+               "HEAD",
                "AUTO_MERGE",
                "BISECT_EXPECTED_REV",
                "NOTES_MERGE_PARTIAL",
                "NOTES_MERGE_REF",
                "MERGE_AUTOSTASH",
        };
-       struct object_id oid;
        size_t i;
 
-       if (!is_pseudoref_syntax(refname))
+       if (!is_root_ref_syntax(refname) ||
+           is_pseudo_ref(refname))
                return 0;
 
-       if (ends_with(refname, "_HEAD")) {
-               refs_resolve_ref_unsafe(refs, refname,
-                                       RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
-                                       &oid, NULL);
-               return !is_null_oid(&oid);
-       }
-
-       for (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)
-               if (!strcmp(refname, irregular_pseudorefs[i])) {
-                       refs_resolve_ref_unsafe(refs, refname,
-                                               RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
-                                               &oid, NULL);
-                       return !is_null_oid(&oid);
-               }
-
-       return 0;
-}
+       if (ends_with(refname, "_HEAD"))
+               return 1;
 
-int is_headref(struct ref_store *refs, const char *refname)
-{
-       if (!strcmp(refname, "HEAD"))
-               return refs_ref_exists(refs, refname);
+       for (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)
+               if (!strcmp(refname, irregular_root_refs[i]))
+                       return 1;
 
        return 0;
 }
 
 static int is_current_worktree_ref(const char *ref) {
-       return is_pseudoref_syntax(ref) || is_per_worktree_ref(ref);
+       return is_root_ref_syntax(ref) || is_per_worktree_ref(ref);
 }
 
 enum ref_worktree_type parse_worktree_ref(const char *maybe_worktree_ref,
@@ -979,7 +914,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
        transaction = ref_store_transaction_begin(refs, &err);
        if (!transaction ||
            ref_transaction_delete(transaction, refname, old_oid,
-                                  flags, msg, &err) ||
+                                  NULL, flags, msg, &err) ||
            ref_transaction_commit(transaction, &err)) {
                error("%s", err.buf);
                ref_transaction_free(transaction);
@@ -991,13 +926,6 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
        return 0;
 }
 
-int delete_ref(const char *msg, const char *refname,
-              const struct object_id *old_oid, unsigned int flags)
-{
-       return refs_delete_ref(get_main_ref_store(the_repository), msg, refname,
-                              old_oid, flags);
-}
-
 static void copy_reflog_msg(struct strbuf *sb, const char *msg)
 {
        char c;
@@ -1190,11 +1118,6 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
        return tr;
 }
 
-struct ref_transaction *ref_transaction_begin(struct strbuf *err)
-{
-       return ref_store_transaction_begin(get_main_ref_store(the_repository), err);
-}
-
 void ref_transaction_free(struct ref_transaction *transaction)
 {
        size_t i;
@@ -1217,6 +1140,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
 
        for (i = 0; i < transaction->nr; i++) {
                free(transaction->updates[i]->msg);
+               free((char *)transaction->updates[i]->new_target);
+               free((char *)transaction->updates[i]->old_target);
                free(transaction->updates[i]);
        }
        free(transaction->updates);
@@ -1228,6 +1153,7 @@ struct ref_update *ref_transaction_add_update(
                const char *refname, unsigned int flags,
                const struct object_id *new_oid,
                const struct object_id *old_oid,
+               const char *new_target, const char *old_target,
                const char *msg)
 {
        struct ref_update *update;
@@ -1235,16 +1161,24 @@ struct ref_update *ref_transaction_add_update(
        if (transaction->state != REF_TRANSACTION_OPEN)
                BUG("update called for transaction that is not open");
 
+       if (old_oid && old_target)
+               BUG("only one of old_oid and old_target should be non NULL");
+       if (new_oid && new_target)
+               BUG("only one of new_oid and new_target should be non NULL");
+
        FLEX_ALLOC_STR(update, refname, refname);
        ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
        transaction->updates[transaction->nr++] = update;
 
        update->flags = flags;
 
-       if (flags & REF_HAVE_NEW)
+       update->new_target = xstrdup_or_null(new_target);
+       update->old_target = xstrdup_or_null(old_target);
+       if ((flags & REF_HAVE_NEW) && new_oid)
                oidcpy(&update->new_oid, new_oid);
-       if (flags & REF_HAVE_OLD)
+       if ((flags & REF_HAVE_OLD) && old_oid)
                oidcpy(&update->old_oid, old_oid);
+
        update->msg = normalize_reflog_message(msg);
        return update;
 }
@@ -1253,11 +1187,19 @@ int ref_transaction_update(struct ref_transaction *transaction,
                           const char *refname,
                           const struct object_id *new_oid,
                           const struct object_id *old_oid,
+                          const char *new_target,
+                          const char *old_target,
                           unsigned int flags, const char *msg,
                           struct strbuf *err)
 {
        assert(err);
 
+       if ((flags & REF_FORCE_CREATE_REFLOG) &&
+           (flags & REF_SKIP_CREATE_REFLOG)) {
+               strbuf_addstr(err, _("refusing to force and skip creation of reflog"));
+               return -1;
+       }
+
        if (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&
            ((new_oid && !is_null_oid(new_oid)) ?
                     check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
@@ -1267,6 +1209,13 @@ int ref_transaction_update(struct ref_transaction *transaction,
                return -1;
        }
 
+       if (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&
+           is_pseudo_ref(refname)) {
+               strbuf_addf(err, _("refusing to update pseudoref '%s'"),
+                           refname);
+               return -1;
+       }
+
        if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)
                BUG("illegal flags 0x%x passed to ref_transaction_update()", flags);
 
@@ -1278,49 +1227,68 @@ int ref_transaction_update(struct ref_transaction *transaction,
        flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
 
        flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
+       flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
 
        ref_transaction_add_update(transaction, refname, flags,
-                                  new_oid, old_oid, msg);
+                                  new_oid, old_oid, new_target,
+                                  old_target, msg);
        return 0;
 }
 
 int ref_transaction_create(struct ref_transaction *transaction,
                           const char *refname,
                           const struct object_id *new_oid,
+                          const char *new_target,
                           unsigned int flags, const char *msg,
                           struct strbuf *err)
 {
-       if (!new_oid || is_null_oid(new_oid)) {
-               strbuf_addf(err, "'%s' has a null OID", refname);
+       if (new_oid && new_target)
+               BUG("create called with both new_oid and new_target set");
+       if ((!new_oid || is_null_oid(new_oid)) && !new_target) {
+               strbuf_addf(err, "'%s' has neither a valid OID nor a target", refname);
                return 1;
        }
        return ref_transaction_update(transaction, refname, new_oid,
-                                     null_oid(), flags, msg, err);
+                                     null_oid(), new_target, NULL, flags,
+                                     msg, err);
 }
 
 int ref_transaction_delete(struct ref_transaction *transaction,
                           const char *refname,
                           const struct object_id *old_oid,
-                          unsigned int flags, const char *msg,
+                          const char *old_target,
+                          unsigned int flags,
+                          const char *msg,
                           struct strbuf *err)
 {
        if (old_oid && is_null_oid(old_oid))
                BUG("delete called with old_oid set to zeros");
+       if (old_oid && old_target)
+               BUG("delete called with both old_oid and old_target set");
+       if (old_target && !(flags & REF_NO_DEREF))
+               BUG("delete cannot operate on symrefs with deref mode");
        return ref_transaction_update(transaction, refname,
                                      null_oid(), old_oid,
-                                     flags, msg, err);
+                                     NULL, old_target, flags,
+                                     msg, err);
 }
 
 int ref_transaction_verify(struct ref_transaction *transaction,
                           const char *refname,
                           const struct object_id *old_oid,
+                          const char *old_target,
                           unsigned int flags,
                           struct strbuf *err)
 {
-       if (!old_oid)
-               BUG("verify called with old_oid set to NULL");
+       if (!old_target && !old_oid)
+               BUG("verify called with old_oid and old_target set to NULL");
+       if (old_oid && old_target)
+               BUG("verify called with both old_oid and old_target set");
+       if (old_target && !(flags & REF_NO_DEREF))
+               BUG("verify cannot operate on symrefs with deref mode");
        return ref_transaction_update(transaction, refname,
                                      NULL, old_oid,
+                                     NULL, old_target,
                                      flags, NULL, err);
 }
 
@@ -1335,8 +1303,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
 
        t = ref_store_transaction_begin(refs, &err);
        if (!t ||
-           ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
-                                  &err) ||
+           ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
+                                  flags, msg, &err) ||
            ref_transaction_commit(t, &err)) {
                ret = 1;
                ref_transaction_free(t);
@@ -1363,15 +1331,6 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
        return 0;
 }
 
-int update_ref(const char *msg, const char *refname,
-              const struct object_id *new_oid,
-              const struct object_id *old_oid,
-              unsigned int flags, enum action_on_err onerr)
-{
-       return refs_update_ref(get_main_ref_store(the_repository), msg, refname, new_oid,
-                              old_oid, flags, onerr);
-}
-
 /*
  * Check that the string refname matches a rule of the form
  * "{prefix}%.*s{suffix}". So "foo/bar/baz" would match the rule
@@ -1473,12 +1432,6 @@ char *refs_shorten_unambiguous_ref(struct ref_store *refs,
        return xstrdup(refname);
 }
 
-char *shorten_unambiguous_ref(const char *refname, int strict)
-{
-       return refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-                                           refname, strict);
-}
-
 int parse_hide_refs_config(const char *var, const char *value, const char *section,
                           struct strvec *hide_refs)
 {
@@ -1597,11 +1550,6 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
        return 0;
 }
 
-int head_ref(each_ref_fn fn, void *cb_data)
-{
-       return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
 struct ref_iterator *refs_ref_iterator_begin(
                struct ref_store *refs,
                const char *prefix,
@@ -1633,53 +1581,12 @@ struct ref_iterator *refs_ref_iterator_begin(
        return iter;
 }
 
-/*
- * Call fn for each reference in the specified submodule for which the
- * refname begins with prefix. If trim is non-zero, then trim that
- * many characters off the beginning of each refname before passing
- * the refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to
- * include broken references in the iteration. If fn ever returns a
- * non-zero value, stop the iteration and return that value;
- * otherwise, return 0.
- */
-static int do_for_each_repo_ref(struct repository *r, const char *prefix,
-                               each_repo_ref_fn fn, int trim, int flags,
-                               void *cb_data)
-{
-       struct ref_iterator *iter;
-       struct ref_store *refs = get_main_ref_store(r);
-
-       if (!refs)
-               return 0;
-
-       iter = refs_ref_iterator_begin(refs, prefix, NULL, trim, flags);
-
-       return do_for_each_repo_ref_iterator(r, iter, fn, cb_data);
-}
-
-struct do_for_each_ref_help {
-       each_ref_fn *fn;
-       void *cb_data;
-};
-
-static int do_for_each_ref_helper(struct repository *r UNUSED,
-                                 const char *refname,
-                                 const struct object_id *oid,
-                                 int flags,
-                                 void *cb_data)
-{
-       struct do_for_each_ref_help *hp = cb_data;
-
-       return hp->fn(refname, oid, flags, hp->cb_data);
-}
-
 static int do_for_each_ref(struct ref_store *refs, const char *prefix,
                           const char **exclude_patterns,
                           each_ref_fn fn, int trim,
                           enum do_for_each_ref_flags flags, void *cb_data)
 {
        struct ref_iterator *iter;
-       struct do_for_each_ref_help hp = { fn, cb_data };
 
        if (!refs)
                return 0;
@@ -1687,8 +1594,7 @@ static int do_for_each_ref(struct ref_store *refs, const char *prefix,
        iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,
                                       flags);
 
-       return do_for_each_repo_ref_iterator(the_repository, iter,
-                                       do_for_each_ref_helper, &hp);
+       return do_for_each_ref_iterator(iter, fn, cb_data);
 }
 
 int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
@@ -1696,28 +1602,12 @@ int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
        return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
 }
 
-int for_each_ref(each_ref_fn fn, void *cb_data)
-{
-       return refs_for_each_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
 int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
                         each_ref_fn fn, void *cb_data)
 {
        return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
 }
 
-int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
-{
-       return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data);
-}
-
-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data)
-{
-       return do_for_each_ref(get_main_ref_store(the_repository),
-                              prefix, NULL, fn, 0, 0, cb_data);
-}
-
 int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
                             const char **exclude_patterns,
                             each_ref_fn fn, void *cb_data)
@@ -1725,22 +1615,22 @@ int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
        return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
 }
 
-int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
+int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
        const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
-       return do_for_each_repo_ref(r, git_replace_ref_base, fn,
-                                   strlen(git_replace_ref_base),
-                                   DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+       return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
+                              strlen(git_replace_ref_base),
+                              DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
-int for_each_namespaced_ref(const char **exclude_patterns,
-                           each_ref_fn fn, void *cb_data)
+int refs_for_each_namespaced_ref(struct ref_store *refs,
+                                const char **exclude_patterns,
+                                each_ref_fn fn, void *cb_data)
 {
        struct strbuf buf = STRBUF_INIT;
        int ret;
        strbuf_addf(&buf, "%srefs/", get_git_namespace());
-       ret = do_for_each_ref(get_main_ref_store(the_repository),
-                             buf.buf, exclude_patterns, fn, 0, 0, cb_data);
+       ret = do_for_each_ref(refs, buf.buf, exclude_patterns, fn, 0, 0, cb_data);
        strbuf_release(&buf);
        return ret;
 }
@@ -1751,11 +1641,6 @@ int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
                               DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
-int for_each_rawref(each_ref_fn fn, void *cb_data)
-{
-       return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
 int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
                                    void *cb_data)
 {
@@ -1876,43 +1761,12 @@ done:
        return result;
 }
 
-static int is_special_ref(const char *refname)
-{
-       /*
-        * Special references are refs that have different semantics compared
-        * to "normal" refs. These refs can thus not be stored in the ref
-        * backend, but must always be accessed via the filesystem. The
-        * following refs are special:
-        *
-        * - FETCH_HEAD may contain multiple object IDs, and each one of them
-        *   carries additional metadata like where it came from.
-        *
-        * - MERGE_HEAD may contain multiple object IDs when merging multiple
-        *   heads.
-        *
-        * Reading, writing or deleting references must consistently go either
-        * through the filesystem (special refs) or through the reference
-        * backend (normal ones).
-        */
-       static const char * const special_refs[] = {
-               "FETCH_HEAD",
-               "MERGE_HEAD",
-       };
-       size_t i;
-
-       for (i = 0; i < ARRAY_SIZE(special_refs); i++)
-               if (!strcmp(refname, special_refs[i]))
-                       return 1;
-
-       return 0;
-}
-
 int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
                      struct object_id *oid, struct strbuf *referent,
                      unsigned int *type, int *failure_errno)
 {
        assert(failure_errno);
-       if (is_special_ref(refname))
+       if (is_pseudo_ref(refname))
                return refs_read_special_head(ref_store, refname, oid, referent,
                                              type, failure_errno);
 
@@ -2016,26 +1870,24 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 }
 
 /* backend functions */
-int refs_init_db(struct ref_store *refs, int flags, struct strbuf *err)
+int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err)
 {
-       return refs->be->init_db(refs, flags, err);
+       return refs->be->create_on_disk(refs, flags, err);
 }
 
-const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
-                              struct object_id *oid, int *flags)
+int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)
 {
-       return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname,
-                                      resolve_flags, oid, flags);
+       return refs->be->remove_on_disk(refs, err);
 }
 
-int resolve_gitlink_ref(const char *submodule, const char *refname,
-                       struct object_id *oid)
+int repo_resolve_gitlink_ref(struct repository *r,
+                            const char *submodule, const char *refname,
+                            struct object_id *oid)
 {
        struct ref_store *refs;
        int flags;
 
-       refs = get_submodule_ref_store(submodule);
-
+       refs = repo_get_submodule_ref_store(r, submodule);
        if (!refs)
                return -1;
 
@@ -2045,80 +1897,36 @@ int resolve_gitlink_ref(const char *submodule, const char *refname,
        return 0;
 }
 
-struct ref_store_hash_entry
-{
-       struct hashmap_entry ent;
-
-       struct ref_store *refs;
-
-       /* NUL-terminated identifier of the ref store: */
-       char name[FLEX_ARRAY];
-};
-
-static int ref_store_hash_cmp(const void *cmp_data UNUSED,
-                             const struct hashmap_entry *eptr,
-                             const struct hashmap_entry *entry_or_key,
-                             const void *keydata)
-{
-       const struct ref_store_hash_entry *e1, *e2;
-       const char *name;
-
-       e1 = container_of(eptr, const struct ref_store_hash_entry, ent);
-       e2 = container_of(entry_or_key, const struct ref_store_hash_entry, ent);
-       name = keydata ? keydata : e2->name;
-
-       return strcmp(e1->name, name);
-}
-
-static struct ref_store_hash_entry *alloc_ref_store_hash_entry(
-               const char *name, struct ref_store *refs)
-{
-       struct ref_store_hash_entry *entry;
-
-       FLEX_ALLOC_STR(entry, name, name);
-       hashmap_entry_init(&entry->ent, strhash(name));
-       entry->refs = refs;
-       return entry;
-}
-
-/* A hashmap of ref_stores, stored by submodule name: */
-static struct hashmap submodule_ref_stores;
-
-/* A hashmap of ref_stores, stored by worktree id: */
-static struct hashmap worktree_ref_stores;
-
 /*
  * Look up a ref store by name. If that ref_store hasn't been
  * registered yet, return NULL.
  */
-static struct ref_store *lookup_ref_store_map(struct hashmap *map,
+static struct ref_store *lookup_ref_store_map(struct strmap *map,
                                              const char *name)
 {
-       struct ref_store_hash_entry *entry;
-       unsigned int hash;
+       struct strmap_entry *entry;
 
-       if (!map->tablesize)
+       if (!map->map.tablesize)
                /* It's initialized on demand in register_ref_store(). */
                return NULL;
 
-       hash = strhash(name);
-       entry = hashmap_get_entry_from_hash(map, hash, name,
-                                       struct ref_store_hash_entry, ent);
-       return entry ? entry->refs : NULL;
+       entry = strmap_get_entry(map, name);
+       return entry ? entry->value : NULL;
 }
 
 /*
  * Create, record, and return a ref_store instance for the specified
- * gitdir.
+ * gitdir using the given ref storage format.
  */
 static struct ref_store *ref_store_init(struct repository *repo,
+                                       enum ref_storage_format format,
                                        const char *gitdir,
                                        unsigned int flags)
 {
        const struct ref_storage_be *be;
        struct ref_store *refs;
 
-       be = find_ref_storage_backend(repo->ref_storage_format);
+       be = find_ref_storage_backend(format);
        if (!be)
                BUG("reference backend is unknown");
 
@@ -2126,6 +1934,12 @@ static struct ref_store *ref_store_init(struct repository *repo,
        return refs;
 }
 
+void ref_store_release(struct ref_store *ref_store)
+{
+       ref_store->be->release(ref_store);
+       free(ref_store->gitdir);
+}
+
 struct ref_store *get_main_ref_store(struct repository *r)
 {
        if (r->refs_private)
@@ -2134,7 +1948,8 @@ struct ref_store *get_main_ref_store(struct repository *r)
        if (!r->gitdir)
                BUG("attempting to get main_ref_store outside of repository");
 
-       r->refs_private = ref_store_init(r, r->gitdir, REF_STORE_ALL_CAPS);
+       r->refs_private = ref_store_init(r, r->ref_storage_format,
+                                        r->gitdir, REF_STORE_ALL_CAPS);
        r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private);
        return r->refs_private;
 }
@@ -2143,22 +1958,19 @@ struct ref_store *get_main_ref_store(struct repository *r)
  * Associate a ref store with a name. It is a fatal error to call this
  * function twice for the same name.
  */
-static void register_ref_store_map(struct hashmap *map,
+static void register_ref_store_map(struct strmap *map,
                                   const char *type,
                                   struct ref_store *refs,
                                   const char *name)
 {
-       struct ref_store_hash_entry *entry;
-
-       if (!map->tablesize)
-               hashmap_init(map, ref_store_hash_cmp, NULL, 0);
-
-       entry = alloc_ref_store_hash_entry(name, refs);
-       if (hashmap_put(map, &entry->ent))
+       if (!map->map.tablesize)
+               strmap_init(map);
+       if (strmap_put(map, name, refs))
                BUG("%s ref_store '%s' initialized twice", type, name);
 }
 
-struct ref_store *get_submodule_ref_store(const char *submodule)
+struct ref_store *repo_get_submodule_ref_store(struct repository *repo,
+                                              const char *submodule)
 {
        struct strbuf submodule_sb = STRBUF_INIT;
        struct ref_store *refs;
@@ -2179,7 +1991,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
                /* We need to strip off one or more trailing slashes */
                submodule = to_free = xmemdupz(submodule, len);
 
-       refs = lookup_ref_store_map(&submodule_ref_stores, submodule);
+       refs = lookup_ref_store_map(&repo->submodule_ref_stores, submodule);
        if (refs)
                goto done;
 
@@ -2191,20 +2003,16 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
                goto done;
 
        subrepo = xmalloc(sizeof(*subrepo));
-       /*
-        * NEEDSWORK: Make get_submodule_ref_store() work with arbitrary
-        * superprojects other than the_repository. This probably should be
-        * done by making it take a struct repository * parameter instead of a
-        * submodule path.
-        */
-       if (repo_submodule_init(subrepo, the_repository, submodule,
+
+       if (repo_submodule_init(subrepo, repo, submodule,
                                null_oid())) {
                free(subrepo);
                goto done;
        }
-       refs = ref_store_init(subrepo, submodule_sb.buf,
+       refs = ref_store_init(subrepo, the_repository->ref_storage_format,
+                             submodule_sb.buf,
                              REF_STORE_READ | REF_STORE_ODB);
-       register_ref_store_map(&submodule_ref_stores, "submodule",
+       register_ref_store_map(&repo->submodule_ref_stores, "submodule",
                               refs, submodule);
 
 done:
@@ -2220,25 +2028,29 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt)
        const char *id;
 
        if (wt->is_current)
-               return get_main_ref_store(the_repository);
+               return get_main_ref_store(wt->repo);
 
        id = wt->id ? wt->id : "/";
-       refs = lookup_ref_store_map(&worktree_ref_stores, id);
+       refs = lookup_ref_store_map(&wt->repo->worktree_ref_stores, id);
        if (refs)
                return refs;
 
-       if (wt->id)
-               refs = ref_store_init(the_repository,
-                                     git_common_path("worktrees/%s", wt->id),
-                                     REF_STORE_ALL_CAPS);
-       else
-               refs = ref_store_init(the_repository,
-                                     get_git_common_dir(),
-                                     REF_STORE_ALL_CAPS);
+       if (wt->id) {
+               struct strbuf common_path = STRBUF_INIT;
+               strbuf_git_common_path(&common_path, wt->repo,
+                                     "worktrees/%s", wt->id);
+               refs = ref_store_init(wt->repo, wt->repo->ref_storage_format,
+                                     common_path.buf, REF_STORE_ALL_CAPS);
+               strbuf_release(&common_path);
+       } else {
+               refs = ref_store_init(wt->repo, the_repository->ref_storage_format,
+                                     wt->repo->commondir, REF_STORE_ALL_CAPS);
+       }
 
        if (refs)
-               register_ref_store_map(&worktree_ref_stores, "worktree",
-                                      refs, id);
+               register_ref_store_map(&wt->repo->worktree_ref_stores,
+                                      "worktree", refs, id);
+
        return refs;
 }
 
@@ -2256,36 +2068,37 @@ int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)
        return refs->be->pack_refs(refs, opts);
 }
 
-int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)
+int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)
 {
        if (current_ref_iter &&
            (current_ref_iter->oid == base ||
             oideq(current_ref_iter->oid, base)))
                return ref_iterator_peel(current_ref_iter, peeled);
 
-       return peel_object(base, peeled) ? -1 : 0;
+       return peel_object(r, base, peeled) ? -1 : 0;
 }
 
-int refs_create_symref(struct ref_store *refs,
-                      const char *ref_target,
-                      const char *refs_heads_master,
-                      const char *logmsg)
+int refs_update_symref(struct ref_store *refs, const char *ref,
+                      const char *target, const char *logmsg)
 {
-       char *msg;
-       int retval;
+       struct ref_transaction *transaction;
+       struct strbuf err = STRBUF_INIT;
+       int ret = 0;
 
-       msg = normalize_reflog_message(logmsg);
-       retval = refs->be->create_symref(refs, ref_target, refs_heads_master,
-                                        msg);
-       free(msg);
-       return retval;
-}
+       transaction = ref_store_transaction_begin(refs, &err);
+       if (!transaction ||
+           ref_transaction_update(transaction, ref, NULL, NULL,
+                                  target, NULL, REF_NO_DEREF,
+                                  logmsg, &err) ||
+           ref_transaction_commit(transaction, &err)) {
+               ret = error("%s", err.buf);
+       }
 
-int create_symref(const char *ref_target, const char *refs_heads_master,
-                 const char *logmsg)
-{
-       return refs_create_symref(get_main_ref_store(the_repository), ref_target,
-                                 refs_heads_master, logmsg);
+       strbuf_release(&err);
+       if (transaction)
+               ref_transaction_free(transaction);
+
+       return ret;
 }
 
 int ref_update_reject_duplicates(struct string_list *refnames,
@@ -2338,10 +2151,22 @@ static int run_transaction_hook(struct ref_transaction *transaction,
                struct ref_update *update = transaction->updates[i];
 
                strbuf_reset(&buf);
-               strbuf_addf(&buf, "%s %s %s\n",
-                           oid_to_hex(&update->old_oid),
-                           oid_to_hex(&update->new_oid),
-                           update->refname);
+
+               if (!(update->flags & REF_HAVE_OLD))
+                       strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+               else if (update->old_target)
+                       strbuf_addf(&buf, "ref:%s ", update->old_target);
+               else
+                       strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
+
+               if (!(update->flags & REF_HAVE_NEW))
+                       strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+               else if (update->new_target)
+                       strbuf_addf(&buf, "ref:%s ", update->new_target);
+               else
+                       strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
+
+               strbuf_addf(&buf, "%s\n", update->refname);
 
                if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
                        if (errno != EPIPE) {
@@ -2560,8 +2385,7 @@ struct do_for_each_reflog_help {
        void *cb_data;
 };
 
-static int do_for_each_reflog_helper(struct repository *r UNUSED,
-                                    const char *refname,
+static int do_for_each_reflog_helper(const char *refname,
                                     const struct object_id *oid UNUSED,
                                     int flags,
                                     void *cb_data)
@@ -2577,13 +2401,7 @@ int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_dat
 
        iter = refs->be->reflog_iterator_begin(refs);
 
-       return do_for_each_repo_ref_iterator(the_repository, iter,
-                                            do_for_each_reflog_helper, &hp);
-}
-
-int for_each_reflog(each_reflog_fn fn, void *cb_data)
-{
-       return refs_for_each_reflog(get_main_ref_store(the_repository), fn, cb_data);
+       return do_for_each_ref_iterator(iter, do_for_each_reflog_helper, &hp);
 }
 
 int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
@@ -2595,58 +2413,28 @@ int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
                                                     fn, cb_data);
 }
 
-int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,
-                               void *cb_data)
-{
-       return refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository),
-                                               refname, fn, cb_data);
-}
-
 int refs_for_each_reflog_ent(struct ref_store *refs, const char *refname,
                             each_reflog_ent_fn fn, void *cb_data)
 {
        return refs->be->for_each_reflog_ent(refs, refname, fn, cb_data);
 }
 
-int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn,
-                       void *cb_data)
-{
-       return refs_for_each_reflog_ent(get_main_ref_store(the_repository), refname,
-                                       fn, cb_data);
-}
-
 int refs_reflog_exists(struct ref_store *refs, const char *refname)
 {
        return refs->be->reflog_exists(refs, refname);
 }
 
-int reflog_exists(const char *refname)
-{
-       return refs_reflog_exists(get_main_ref_store(the_repository), refname);
-}
-
 int refs_create_reflog(struct ref_store *refs, const char *refname,
                       struct strbuf *err)
 {
        return refs->be->create_reflog(refs, refname, err);
 }
 
-int safe_create_reflog(const char *refname, struct strbuf *err)
-{
-       return refs_create_reflog(get_main_ref_store(the_repository), refname,
-                                 err);
-}
-
 int refs_delete_reflog(struct ref_store *refs, const char *refname)
 {
        return refs->be->delete_reflog(refs, refname);
 }
 
-int delete_reflog(const char *refname)
-{
-       return refs_delete_reflog(get_main_ref_store(the_repository), refname);
-}
-
 int refs_reflog_expire(struct ref_store *refs,
                       const char *refname,
                       unsigned int flags,
@@ -2660,19 +2448,6 @@ int refs_reflog_expire(struct ref_store *refs,
                                       cleanup_fn, policy_cb_data);
 }
 
-int reflog_expire(const char *refname,
-                 unsigned int flags,
-                 reflog_expiry_prepare_fn prepare_fn,
-                 reflog_expiry_should_prune_fn should_prune_fn,
-                 reflog_expiry_cleanup_fn cleanup_fn,
-                 void *policy_cb_data)
-{
-       return refs_reflog_expire(get_main_ref_store(the_repository),
-                                 refname, flags,
-                                 prepare_fn, should_prune_fn,
-                                 cleanup_fn, policy_cb_data);
-}
-
 int initial_ref_transaction_commit(struct ref_transaction *transaction,
                                   struct strbuf *err)
 {
@@ -2724,7 +2499,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,
 
        for_each_string_list_item(item, refnames) {
                ret = ref_transaction_delete(transaction, item->string,
-                                            NULL, flags, msg, &err);
+                                            NULL, NULL, flags, msg, &err);
                if (ret) {
                        warning(_("could not delete reference %s: %s"),
                                item->string, err.buf);
@@ -2751,12 +2526,6 @@ out:
        return ret;
 }
 
-int delete_refs(const char *msg, struct string_list *refnames,
-               unsigned int flags)
-{
-       return refs_delete_refs(get_main_ref_store(the_repository), msg, refnames, flags);
-}
-
 int refs_rename_ref(struct ref_store *refs, const char *oldref,
                    const char *newref, const char *logmsg)
 {
@@ -2769,11 +2538,6 @@ int refs_rename_ref(struct ref_store *refs, const char *oldref,
        return retval;
 }
 
-int rename_ref(const char *oldref, const char *newref, const char *logmsg)
-{
-       return refs_rename_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
-}
-
 int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
                    const char *newref, const char *logmsg)
 {
@@ -2786,7 +2550,352 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
        return retval;
 }
 
-int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
+const char *ref_update_original_update_refname(struct ref_update *update)
+{
+       while (update->parent_update)
+               update = update->parent_update;
+
+       return update->refname;
+}
+
+int ref_update_has_null_new_value(struct ref_update *update)
+{
+       return !update->new_target && is_null_oid(&update->new_oid);
+}
+
+int ref_update_check_old_target(const char *referent, struct ref_update *update,
+                               struct strbuf *err)
+{
+       if (!update->old_target)
+               BUG("called without old_target set");
+
+       if (!strcmp(referent, update->old_target))
+               return 0;
+
+       if (!strcmp(referent, ""))
+               strbuf_addf(err, "verifying symref target: '%s': "
+                           "reference is missing but expected %s",
+                           ref_update_original_update_refname(update),
+                           update->old_target);
+       else
+               strbuf_addf(err, "verifying symref target: '%s': "
+                           "is at %s but expected %s",
+                           ref_update_original_update_refname(update),
+                           referent, update->old_target);
+       return -1;
+}
+
+struct migration_data {
+       struct ref_store *old_refs;
+       struct ref_transaction *transaction;
+       struct strbuf *errbuf;
+};
+
+static int migrate_one_ref(const char *refname, const struct object_id *oid,
+                          int flags, void *cb_data)
+{
+       struct migration_data *data = cb_data;
+       struct strbuf symref_target = STRBUF_INIT;
+       int ret;
+
+       if (flags & REF_ISSYMREF) {
+               ret = refs_read_symbolic_ref(data->old_refs, refname, &symref_target);
+               if (ret < 0)
+                       goto done;
+
+               ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(),
+                                            symref_target.buf, NULL,
+                                            REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf);
+               if (ret < 0)
+                       goto done;
+       } else {
+               ret = ref_transaction_create(data->transaction, refname, oid, NULL,
+                                            REF_SKIP_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION,
+                                            NULL, data->errbuf);
+               if (ret < 0)
+                       goto done;
+       }
+
+done:
+       strbuf_release(&symref_target);
+       return ret;
+}
+
+static int move_files(const char *from_path, const char *to_path, struct strbuf *errbuf)
+{
+       struct strbuf from_buf = STRBUF_INIT, to_buf = STRBUF_INIT;
+       size_t from_len, to_len;
+       DIR *from_dir;
+       int ret;
+
+       from_dir = opendir(from_path);
+       if (!from_dir) {
+               strbuf_addf(errbuf, "could not open source directory '%s': %s",
+                           from_path, strerror(errno));
+               ret = -1;
+               goto done;
+       }
+
+       strbuf_addstr(&from_buf, from_path);
+       strbuf_complete(&from_buf, '/');
+       from_len = from_buf.len;
+
+       strbuf_addstr(&to_buf, to_path);
+       strbuf_complete(&to_buf, '/');
+       to_len = to_buf.len;
+
+       while (1) {
+               struct dirent *ent;
+
+               errno = 0;
+               ent = readdir(from_dir);
+               if (!ent)
+                       break;
+
+               if (!strcmp(ent->d_name, ".") ||
+                   !strcmp(ent->d_name, ".."))
+                       continue;
+
+               strbuf_setlen(&from_buf, from_len);
+               strbuf_addstr(&from_buf, ent->d_name);
+
+               strbuf_setlen(&to_buf, to_len);
+               strbuf_addstr(&to_buf, ent->d_name);
+
+               ret = rename(from_buf.buf, to_buf.buf);
+               if (ret < 0) {
+                       strbuf_addf(errbuf, "could not link file '%s' to '%s': %s",
+                                   from_buf.buf, to_buf.buf, strerror(errno));
+                       goto done;
+               }
+       }
+
+       if (errno) {
+               strbuf_addf(errbuf, "could not read entry from directory '%s': %s",
+                           from_path, strerror(errno));
+               ret = -1;
+               goto done;
+       }
+
+       ret = 0;
+
+done:
+       strbuf_release(&from_buf);
+       strbuf_release(&to_buf);
+       if (from_dir)
+               closedir(from_dir);
+       return ret;
+}
+
+static int count_reflogs(const char *reflog UNUSED, void *payload)
+{
+       size_t *reflog_count = payload;
+       (*reflog_count)++;
+       return 0;
+}
+
+static int has_worktrees(void)
+{
+       struct worktree **worktrees = get_worktrees();
+       int ret = 0;
+       size_t i;
+
+       for (i = 0; worktrees[i]; i++) {
+               if (is_main_worktree(worktrees[i]))
+                       continue;
+               ret = 1;
+       }
+
+       free_worktrees(worktrees);
+       return ret;
+}
+
+int repo_migrate_ref_storage_format(struct repository *repo,
+                                   enum ref_storage_format format,
+                                   unsigned int flags,
+                                   struct strbuf *errbuf)
+{
+       struct ref_store *old_refs = NULL, *new_refs = NULL;
+       struct ref_transaction *transaction = NULL;
+       struct strbuf new_gitdir = STRBUF_INIT;
+       struct migration_data data;
+       size_t reflog_count = 0;
+       int did_migrate_refs = 0;
+       int ret;
+
+       if (repo->ref_storage_format == format) {
+               strbuf_addstr(errbuf, "current and new ref storage format are equal");
+               ret = -1;
+               goto done;
+       }
+
+       old_refs = get_main_ref_store(repo);
+
+       /*
+        * We do not have any interfaces that would allow us to write many
+        * reflog entries. Once we have them we can remove this restriction.
+        */
+       if (refs_for_each_reflog(old_refs, count_reflogs, &reflog_count) < 0) {
+               strbuf_addstr(errbuf, "cannot count reflogs");
+               ret = -1;
+               goto done;
+       }
+       if (reflog_count) {
+               strbuf_addstr(errbuf, "migrating reflogs is not supported yet");
+               ret = -1;
+               goto done;
+       }
+
+       /*
+        * Worktrees complicate the migration because every worktree has a
+        * separate ref storage. While it should be feasible to implement, this
+        * is pushed out to a future iteration.
+        *
+        * TODO: we should really be passing the caller-provided repository to
+        * `has_worktrees()`, but our worktree subsystem doesn't yet support
+        * that.
+        */
+       if (has_worktrees()) {
+               strbuf_addstr(errbuf, "migrating repositories with worktrees is not supported yet");
+               ret = -1;
+               goto done;
+       }
+
+       /*
+        * The overall logic looks like this:
+        *
+        *   1. Set up a new temporary directory and initialize it with the new
+        *      format. This is where all refs will be migrated into.
+        *
+        *   2. Enumerate all refs and write them into the new ref storage.
+        *      This operation is safe as we do not yet modify the main
+        *      repository.
+        *
+        *   3. If we're in dry-run mode then we are done and can hand over the
+        *      directory to the caller for inspection. If not, we now start
+        *      with the destructive part.
+        *
+        *   4. Delete the old ref storage from disk. As we have a copy of refs
+        *      in the new ref storage it's okay(ish) if we now get interrupted
+        *      as there is an equivalent copy of all refs available.
+        *
+        *   5. Move the new ref storage files into place.
+        *
+        *   6. Change the repository format to the new ref format.
+        */
+       strbuf_addf(&new_gitdir, "%s/%s", old_refs->gitdir, "ref_migration.XXXXXX");
+       if (!mkdtemp(new_gitdir.buf)) {
+               strbuf_addf(errbuf, "cannot create migration directory: %s",
+                           strerror(errno));
+               ret = -1;
+               goto done;
+       }
+
+       new_refs = ref_store_init(repo, format, new_gitdir.buf,
+                                 REF_STORE_ALL_CAPS);
+       ret = ref_store_create_on_disk(new_refs, 0, errbuf);
+       if (ret < 0)
+               goto done;
+
+       transaction = ref_store_transaction_begin(new_refs, errbuf);
+       if (!transaction)
+               goto done;
+
+       data.old_refs = old_refs;
+       data.transaction = transaction;
+       data.errbuf = errbuf;
+
+       /*
+        * We need to use the internal `do_for_each_ref()` here so that we can
+        * also include broken refs and symrefs. These would otherwise be
+        * skipped silently.
+        *
+        * Ideally, we would do this call while locking the old ref storage
+        * such that there cannot be any concurrent modifications. We do not
+        * have the infra for that though, and the "files" backend does not
+        * allow for a central lock due to its design. It's thus on the user to
+        * ensure that there are no concurrent writes.
+        */
+       ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
+                             DO_FOR_EACH_INCLUDE_ROOT_REFS | DO_FOR_EACH_INCLUDE_BROKEN,
+                             &data);
+       if (ret < 0)
+               goto done;
+
+       /*
+        * TODO: we might want to migrate to `initial_ref_transaction_commit()`
+        * here, which is more efficient for the files backend because it would
+        * write new refs into the packed-refs file directly. At this point,
+        * the files backend doesn't handle pseudo-refs and symrefs correctly
+        * though, so this requires some more work.
+        */
+       ret = ref_transaction_commit(transaction, errbuf);
+       if (ret < 0)
+               goto done;
+       did_migrate_refs = 1;
+
+       if (flags & REPO_MIGRATE_REF_STORAGE_FORMAT_DRYRUN) {
+               printf(_("Finished dry-run migration of refs, "
+                        "the result can be found at '%s'\n"), new_gitdir.buf);
+               ret = 0;
+               goto done;
+       }
+
+       /*
+        * Until now we were in the non-destructive phase, where we only
+        * populated the new ref store. From hereon though we are about
+        * to get hands by deleting the old ref store and then moving
+        * the new one into place.
+        *
+        * Assuming that there were no concurrent writes, the new ref
+        * store should have all information. So if we fail from hereon
+        * we may be in an in-between state, but it would still be able
+        * to recover by manually moving remaining files from the
+        * temporary migration directory into place.
+        */
+       ret = ref_store_remove_on_disk(old_refs, errbuf);
+       if (ret < 0)
+               goto done;
+
+       ret = move_files(new_gitdir.buf, old_refs->gitdir, errbuf);
+       if (ret < 0)
+               goto done;
+
+       if (rmdir(new_gitdir.buf) < 0)
+               warning_errno(_("could not remove temporary migration directory '%s'"),
+                             new_gitdir.buf);
+
+       /*
+        * We have migrated the repository, so we now need to adjust the
+        * repository format so that clients will use the new ref store.
+        * We also need to swap out the repository's main ref store.
+        */
+       initialize_repository_version(hash_algo_by_ptr(repo->hash_algo), format, 1);
+
+       free(new_refs->gitdir);
+       new_refs->gitdir = xstrdup(old_refs->gitdir);
+       repo->refs_private = new_refs;
+       ref_store_release(old_refs);
+
+       ret = 0;
+
+done:
+       if (ret && did_migrate_refs) {
+               strbuf_complete(errbuf, '\n');
+               strbuf_addf(errbuf, _("migrated refs can be found at '%s'"),
+                           new_gitdir.buf);
+       }
+
+       if (ret && new_refs)
+               ref_store_release(new_refs);
+       ref_transaction_free(transaction);
+       strbuf_release(&new_gitdir);
+       return ret;
+}
+
+int ref_update_expects_existing_old_ref(struct ref_update *update)
 {
-       return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
+       return (update->flags & REF_HAVE_OLD) &&
+               (!is_null_oid(&update->old_oid) || update->old_target);
 }
+