]> git.ipfire.org Git - thirdparty/git.git/blobdiff - refs.c
The 19th batch
[thirdparty/git.git] / refs.c
diff --git a/refs.c b/refs.c
index e31dbcda5990b8f2e7143547ce134b7218041fd2..ceb72d4bd740b9496f284c2d654b69eb9eafe33d 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -2,40 +2,67 @@
  * The backend-independent part of the reference module.
  */
 
-#include "cache.h"
+#define USE_THE_REPOSITORY_VARIABLE
+
+#include "git-compat-util.h"
+#include "advice.h"
 #include "config.h"
-#include "hashmap.h"
+#include "environment.h"
+#include "strmap.h"
+#include "gettext.h"
+#include "hex.h"
 #include "lockfile.h"
 #include "iterator.h"
 #include "refs.h"
 #include "refs/refs-internal.h"
 #include "run-command.h"
 #include "hook.h"
-#include "object-store.h"
+#include "object-name.h"
+#include "object-store-ll.h"
 #include "object.h"
-#include "tag.h"
+#include "path.h"
 #include "submodule.h"
 #include "worktree.h"
 #include "strvec.h"
 #include "repository.h"
+#include "setup.h"
 #include "sigchain.h"
 #include "date.h"
 #include "commit.h"
+#include "wildmatch.h"
 
 /*
  * List of all available backends
  */
-static struct ref_storage_be *refs_backends = &refs_be_files;
+static const struct ref_storage_be *refs_backends[] = {
+       [REF_STORAGE_FORMAT_FILES] = &refs_be_files,
+       [REF_STORAGE_FORMAT_REFTABLE] = &refs_be_reftable,
+};
 
-static struct ref_storage_be *find_ref_storage_backend(const char *name)
+static const struct ref_storage_be *find_ref_storage_backend(
+       enum ref_storage_format ref_storage_format)
 {
-       struct ref_storage_be *be;
-       for (be = refs_backends; be; be = be->next)
-               if (!strcmp(be->name, name))
-                       return be;
+       if (ref_storage_format < ARRAY_SIZE(refs_backends))
+               return refs_backends[ref_storage_format];
        return NULL;
 }
 
+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))
+                       return i;
+       return REF_STORAGE_FORMAT_UNKNOWN;
+}
+
+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)
+               return "unknown";
+       return be->name;
+}
+
 /*
  * How to handle various characters in refnames:
  * 0: An acceptable character for refs
@@ -134,7 +161,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;
 }
@@ -291,6 +318,11 @@ int check_refname_format(const char *refname, int flags)
        return check_or_sanitize_refname(refname, flags, NULL);
 }
 
+int refs_fsck(struct ref_store *refs, struct fsck_options *o)
+{
+       return refs->be->fsck(refs, o);
+}
+
 void sanitize_refname_component(const char *refname, struct strbuf *out)
 {
        if (check_or_sanitize_refname(refname, REFNAME_ALLOW_ONELEVEL, out))
@@ -359,35 +391,26 @@ 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 filter_refs */
-struct ref_filter {
+/* The argument to for_each_filter_refs */
+struct for_each_ref_filter {
        const char *pattern;
        const char *prefix;
        each_ref_fn *fn;
        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)
@@ -396,52 +419,28 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)
                                         NULL, NULL);
 }
 
-int ref_exists(const char *refname)
+static int for_each_filter_refs(const char *refname, const char *referent,
+                               const struct object_id *oid,
+                               int flags, void *data)
 {
-       return refs_ref_exists(get_main_ref_store(the_repository), refname);
-}
-
-static int filter_refs(const char *refname, const struct object_id *oid,
-                          int flags, void *data)
-{
-       struct ref_filter *filter = (struct ref_filter *)data;
+       struct for_each_ref_filter *filter = data;
 
        if (wildmatch(filter->pattern, refname, 0))
                return 0;
        if (filter->prefix)
                skip_prefix(refname, filter->prefix, &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;
+       return filter->fn(refname, referent, oid, flags, filter->cb_data);
 }
 
 struct warn_if_dangling_data {
+       struct ref_store *refs;
        FILE *fp;
        const char *refname;
        const struct string_list *refnames;
        const char *msg_fmt;
 };
 
-static int warn_if_dangling_symref(const char *refname,
+static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED,
                                   const struct object_id *oid UNUSED,
                                   int flags, void *cb_data)
 {
@@ -451,7 +450,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)
@@ -464,26 +463,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)
@@ -491,32 +492,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;
@@ -524,8 +510,8 @@ 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))
-               ret = fn(buf.buf, &oid, flag, cb_data);
+       if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))
+               ret = fn(buf.buf, NULL, &oid, flag, cb_data);
        strbuf_release(&buf);
 
        return ret;
@@ -557,11 +543,11 @@ 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 ref_filter filter;
+       struct for_each_ref_filter filter;
        int ret;
 
        if (!prefix && !starts_with(pattern, "refs/"))
@@ -581,15 +567,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(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)
@@ -685,16 +672,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
@@ -806,11 +783,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/") ||
@@ -818,7 +790,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;
 
@@ -827,15 +814,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_root_ref(const char *refname)
+{
+       static const char *const irregular_root_refs[] = {
+               "HEAD",
+               "AUTO_MERGE",
+               "BISECT_EXPECTED_REV",
+               "NOTES_MERGE_PARTIAL",
+               "NOTES_MERGE_REF",
+               "MERGE_AUTOSTASH",
+       };
+       size_t i;
+
+       if (!is_root_ref_syntax(refname) ||
+           is_pseudo_ref(refname))
+               return 0;
+
+       if (ends_with(refname, "_HEAD"))
+               return 1;
+
+       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,
@@ -912,7 +921,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);
@@ -924,13 +933,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;
@@ -1013,55 +1015,40 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid,
                           const char *message, void *cb_data)
 {
        struct read_ref_at_cb *cb = cb_data;
-       int reached_count;
 
        cb->tz = tz;
        cb->date = timestamp;
 
-       /*
-        * It is not possible for cb->cnt == 0 on the first iteration because
-        * that special case is handled in read_ref_at().
-        */
-       if (cb->cnt > 0)
-               cb->cnt--;
-       reached_count = cb->cnt == 0 && !is_null_oid(ooid);
-       if (timestamp <= cb->at_time || reached_count) {
+       if (timestamp <= cb->at_time || cb->cnt == 0) {
                set_read_ref_cutoffs(cb, timestamp, tz, message);
                /*
                 * we have not yet updated cb->[n|o]oid so they still
                 * hold the values for the previous record.
                 */
-               if (!is_null_oid(&cb->ooid) && !oideq(&cb->ooid, noid))
-                       warning(_("log for ref %s has gap after %s"),
+               if (!is_null_oid(&cb->ooid)) {
+                       oidcpy(cb->oid, noid);
+                       if (!oideq(&cb->ooid, noid))
+                               warning(_("log for ref %s has gap after %s"),
                                        cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
-               if (reached_count)
-                       oidcpy(cb->oid, ooid);
-               else if (!is_null_oid(&cb->ooid) || cb->date == cb->at_time)
+               }
+               else if (cb->date == cb->at_time)
                        oidcpy(cb->oid, noid);
                else if (!oideq(noid, cb->oid))
                        warning(_("log for ref %s unexpectedly ended on %s"),
                                cb->refname, show_date(cb->date, cb->tz,
                                                       DATE_MODE(RFC2822)));
+               cb->reccnt++;
+               oidcpy(&cb->ooid, ooid);
+               oidcpy(&cb->noid, noid);
                cb->found_it = 1;
+               return 1;
        }
        cb->reccnt++;
        oidcpy(&cb->ooid, ooid);
        oidcpy(&cb->noid, noid);
-       return cb->found_it;
-}
-
-static int read_ref_at_ent_newest(struct object_id *ooid UNUSED,
-                                 struct object_id *noid,
-                                 const char *email UNUSED,
-                                 timestamp_t timestamp, int tz,
-                                 const char *message, void *cb_data)
-{
-       struct read_ref_at_cb *cb = cb_data;
-
-       set_read_ref_cutoffs(cb, timestamp, tz, message);
-       oidcpy(cb->oid, noid);
-       /* We just want the first entry */
-       return 1;
+       if (cb->cnt > 0)
+               cb->cnt--;
+       return 0;
 }
 
 static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid,
@@ -1073,7 +1060,7 @@ static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid
 
        set_read_ref_cutoffs(cb, timestamp, tz, message);
        oidcpy(cb->oid, ooid);
-       if (is_null_oid(cb->oid))
+       if (cb->at_time && is_null_oid(cb->oid))
                oidcpy(cb->oid, noid);
        /* We just want the first entry */
        return 1;
@@ -1096,14 +1083,24 @@ int read_ref_at(struct ref_store *refs, const char *refname,
        cb.cutoff_cnt = cutoff_cnt;
        cb.oid = oid;
 
-       if (cb.cnt == 0) {
-               refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent_newest, &cb);
-               return 0;
-       }
-
        refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent, &cb);
 
        if (!cb.reccnt) {
+               if (cnt == 0) {
+                       /*
+                        * The caller asked for ref@{0}, and we had no entries.
+                        * It's a bit subtle, but in practice all callers have
+                        * prepped the "oid" field with the current value of
+                        * the ref, which is the most reasonable fallback.
+                        *
+                        * We'll put dummy values into the out-parameters (so
+                        * they're not just uninitialized garbage), and the
+                        * caller can take our return value as a hint that
+                        * we did not find any such reflog.
+                        */
+                       set_read_ref_cutoffs(&cb, 0, 0, "empty reflog");
+                       return 1;
+               }
                if (flags & GET_OID_QUIETLY)
                        exit(128);
                else
@@ -1128,11 +1125,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;
@@ -1155,6 +1147,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);
@@ -1166,6 +1160,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;
@@ -1173,16 +1168,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;
 }
@@ -1191,11 +1194,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) :
@@ -1205,6 +1216,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);
 
@@ -1216,49 +1234,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);
 }
 
@@ -1273,8 +1310,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);
@@ -1301,74 +1338,68 @@ 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)
+/*
+ * Check that the string refname matches a rule of the form
+ * "{prefix}%.*s{suffix}". So "foo/bar/baz" would match the rule
+ * "foo/%.*s/baz", and return the string "bar".
+ */
+static const char *match_parse_rule(const char *refname, const char *rule,
+                                   size_t *len)
 {
-       return refs_update_ref(get_main_ref_store(the_repository), msg, refname, new_oid,
-                              old_oid, flags, onerr);
+       /*
+        * Check that rule matches refname up to the first percent in the rule.
+        * We can bail immediately if not, but otherwise we leave "rule" at the
+        * %-placeholder, and "refname" at the start of the potential matched
+        * name.
+        */
+       while (*rule != '%') {
+               if (!*rule)
+                       BUG("rev-parse rule did not have percent");
+               if (*refname++ != *rule++)
+                       return NULL;
+       }
+
+       /*
+        * Check that our "%" is the expected placeholder. This assumes there
+        * are no other percents (placeholder or quoted) in the string, but
+        * that is sufficient for our rev-parse rules.
+        */
+       if (!skip_prefix(rule, "%.*s", &rule))
+               return NULL;
+
+       /*
+        * And now check that our suffix (if any) matches.
+        */
+       if (!strip_suffix(refname, rule, len))
+               return NULL;
+
+       return refname; /* len set by strip_suffix() */
 }
 
 char *refs_shorten_unambiguous_ref(struct ref_store *refs,
                                   const char *refname, int strict)
 {
        int i;
-       static char **scanf_fmts;
-       static int nr_rules;
-       char *short_name;
        struct strbuf resolved_buf = STRBUF_INIT;
 
-       if (!nr_rules) {
-               /*
-                * Pre-generate scanf formats from ref_rev_parse_rules[].
-                * Generate a format suitable for scanf from a
-                * ref_rev_parse_rules rule by interpolating "%s" at the
-                * location of the "%.*s".
-                */
-               size_t total_len = 0;
-               size_t offset = 0;
-
-               /* the rule list is NULL terminated, count them first */
-               for (nr_rules = 0; ref_rev_parse_rules[nr_rules]; nr_rules++)
-                       /* -2 for strlen("%.*s") - strlen("%s"); +1 for NUL */
-                       total_len += strlen(ref_rev_parse_rules[nr_rules]) - 2 + 1;
-
-               scanf_fmts = xmalloc(st_add(st_mult(sizeof(char *), nr_rules), total_len));
-
-               offset = 0;
-               for (i = 0; i < nr_rules; i++) {
-                       assert(offset < total_len);
-                       scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;
-                       offset += xsnprintf(scanf_fmts[i], total_len - offset,
-                                           ref_rev_parse_rules[i], 2, "%s") + 1;
-               }
-       }
-
-       /* bail out if there are no rules */
-       if (!nr_rules)
-               return xstrdup(refname);
-
-       /* buffer for scanf result, at most refname must fit */
-       short_name = xstrdup(refname);
-
        /* skip first rule, it will always match */
-       for (i = nr_rules - 1; i > 0 ; --i) {
+       for (i = NUM_REV_PARSE_RULES - 1; i > 0 ; --i) {
                int j;
                int rules_to_fail = i;
-               int short_name_len;
+               const char *short_name;
+               size_t short_name_len;
 
-               if (1 != sscanf(refname, scanf_fmts[i], short_name))
+               short_name = match_parse_rule(refname, ref_rev_parse_rules[i],
+                                             &short_name_len);
+               if (!short_name)
                        continue;
 
-               short_name_len = strlen(short_name);
-
                /*
                 * in strict mode, all (except the matched one) rules
                 * must fail to resolve to a valid non-ambiguous ref
                 */
                if (strict)
-                       rules_to_fail = nr_rules;
+                       rules_to_fail = NUM_REV_PARSE_RULES;
 
                /*
                 * check if the short name resolves to a valid ref,
@@ -1388,7 +1419,8 @@ char *refs_shorten_unambiguous_ref(struct ref_store *refs,
                         */
                        strbuf_reset(&resolved_buf);
                        strbuf_addf(&resolved_buf, rule,
-                                   short_name_len, short_name);
+                                   cast_size_t_to_int(short_name_len),
+                                   short_name);
                        if (refs_ref_exists(refs, resolved_buf.buf))
                                break;
                }
@@ -1399,23 +1431,16 @@ char *refs_shorten_unambiguous_ref(struct ref_store *refs,
                 */
                if (j == rules_to_fail) {
                        strbuf_release(&resolved_buf);
-                       return short_name;
+                       return xmemdupz(short_name, short_name_len);
                }
        }
 
        strbuf_release(&resolved_buf);
-       free(short_name);
        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 string_list *hide_refs)
+                          struct strvec *hide_refs)
 {
        const char *key;
        if (!strcmp("transfer.hiderefs", var) ||
@@ -1426,22 +1451,23 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti
 
                if (!value)
                        return config_error_nonbool(var);
-               ref = xstrdup(value);
+
+               /* drop const to remove trailing '/' characters */
+               ref = (char *)strvec_push(hide_refs, value);
                len = strlen(ref);
                while (len && ref[len - 1] == '/')
                        ref[--len] = '\0';
-               string_list_append_nodup(hide_refs, ref);
        }
        return 0;
 }
 
 int ref_is_hidden(const char *refname, const char *refname_full,
-                 const struct string_list *hide_refs)
+                 const struct strvec *hide_refs)
 {
        int i;
 
        for (i = hide_refs->nr - 1; i >= 0; i--) {
-               const char *match = hide_refs->items[i].string;
+               const char *match = hide_refs->v[i];
                const char *subject;
                int neg = 0;
                const char *p;
@@ -1467,6 +1493,30 @@ int ref_is_hidden(const char *refname, const char *refname_full,
        return 0;
 }
 
+const char **hidden_refs_to_excludes(const struct strvec *hide_refs)
+{
+       const char **pattern;
+       for (pattern = hide_refs->v; *pattern; pattern++) {
+               /*
+                * We can't feed any excludes from hidden refs config
+                * sections, since later rules may override previous
+                * ones. For example, with rules "refs/foo" and
+                * "!refs/foo/bar", we should show "refs/foo/bar" (and
+                * everything underneath it), but the earlier exclusion
+                * would cause us to skip all of "refs/foo".  We
+                * likewise don't implement the namespace stripping
+                * required for '^' rules.
+                *
+                * Both are possible to do, but complicated, so avoid
+                * populating the jump list at all if we see either of
+                * these patterns.
+                */
+               if (**pattern == '!' || **pattern == '^')
+                       return NULL;
+       }
+       return hide_refs->v;
+}
+
 const char *find_descendant_ref(const char *dirname,
                                const struct string_list *extras,
                                const struct string_list *skip)
@@ -1502,19 +1552,16 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 
        if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING,
                                    &oid, &flag))
-               return fn("HEAD", &oid, flag, cb_data);
+               return fn("HEAD", NULL, &oid, flag, 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, int trim,
+               const char *prefix,
+               const char **exclude_patterns,
+               int trim,
                enum do_for_each_ref_flags flags)
 {
        struct ref_iterator *iter;
@@ -1530,8 +1577,7 @@ struct ref_iterator *refs_ref_iterator_begin(
                }
        }
 
-       iter = refs->be->iterator_begin(refs, prefix, flags);
-
+       iter = refs->be->iterator_begin(refs, prefix, exclude_patterns, flags);
        /*
         * `iterator_begin()` already takes care of prefix, but we
         * might need to do some trimming:
@@ -1539,130 +1585,74 @@ struct ref_iterator *refs_ref_iterator_begin(
        if (trim)
                iter = prefix_ref_iterator_begin(iter, "", trim);
 
-       /* Sanity check for subclasses: */
-       if (!iter->ordered)
-               BUG("reference iterator is not ordered");
-
        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, 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,
-                                 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;
 
-       iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
+       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)
 {
-       return do_for_each_ref(refs, "", 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);
+       return do_for_each_ref(refs, "", NULL, fn, 0, 0, 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, 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, fn, 0, 0, cb_data);
+       return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 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)
 {
-       return do_for_each_ref(refs, prefix, fn, 0, 0, cb_data);
+       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(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, 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;
 }
 
 int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref(refs, "", fn, 0,
+       return do_for_each_ref(refs, "", NULL, fn, 0,
                               DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
-int for_each_rawref(each_ref_fn fn, void *cb_data)
+int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
+                                   void *cb_data)
 {
-       return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data);
+       return do_for_each_ref(refs, "", NULL, fn, 0,
+                              DO_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);
 }
 
 static int qsort_strcmp(const void *va, const void *vb)
@@ -1726,6 +1716,7 @@ static void find_longest_prefixes(struct string_list *out,
 int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
                                      const char *namespace,
                                      const char **patterns,
+                                     const char **exclude_patterns,
                                      each_ref_fn fn, void *cb_data)
 {
        struct string_list prefixes = STRING_LIST_INIT_DUP;
@@ -1741,7 +1732,8 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
 
        for_each_string_list_item(prefix, &prefixes) {
                strbuf_addstr(&buf, prefix->string);
-               ret = refs_for_each_fullref_in(ref_store, buf.buf, fn, cb_data);
+               ret = refs_for_each_fullref_in(ref_store, buf.buf,
+                                              exclude_patterns, fn, cb_data);
                if (ret)
                        break;
                strbuf_setlen(&buf, namespace_len);
@@ -1762,11 +1754,13 @@ static int refs_read_special_head(struct ref_store *ref_store,
        int result = -1;
        strbuf_addf(&full_path, "%s/%s", ref_store->gitdir, refname);
 
-       if (strbuf_read_file(&content, full_path.buf, 0) < 0)
+       if (strbuf_read_file(&content, full_path.buf, 0) < 0) {
+               *failure_errno = errno;
                goto done;
+       }
 
-       result = parse_loose_ref_contents(content.buf, oid, referent, type,
-                                         failure_errno);
+       result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf,
+                                         oid, referent, type, failure_errno);
 
 done:
        strbuf_release(&full_path);
@@ -1779,10 +1773,9 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
                      unsigned int *type, int *failure_errno)
 {
        assert(failure_errno);
-       if (!strcmp(refname, "FETCH_HEAD") || !strcmp(refname, "MERGE_HEAD")) {
+       if (is_pseudo_ref(refname))
                return refs_read_special_head(ref_store, refname, oid, referent,
                                              type, failure_errno);
-       }
 
        return ref_store->be->read_raw_ref(ref_store, refname, oid, referent,
                                           type, failure_errno);
@@ -1818,7 +1811,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                        return NULL;
 
                /*
-                * dwim_ref() uses REF_ISBROKEN to distinguish between
+                * repo_dwim_ref() uses REF_ISBROKEN to distinguish between
                 * missing refs and refs that were present but invalid,
                 * to complain about the latter to stderr.
                 *
@@ -1850,7 +1843,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                            failure_errno != ENOTDIR)
                                return NULL;
 
-                       oidclr(oid);
+                       oidclr(oid, refs->repo->hash_algo);
                        if (*flags & REF_BAD_NAME)
                                *flags |= REF_ISBROKEN;
                        return refname;
@@ -1860,7 +1853,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 
                if (!(read_flags & REF_ISSYMREF)) {
                        if (*flags & REF_BAD_NAME) {
-                               oidclr(oid);
+                               oidclr(oid, refs->repo->hash_algo);
                                *flags |= REF_ISBROKEN;
                        }
                        return refname;
@@ -1868,7 +1861,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 
                refname = sb_refname.buf;
                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
-                       oidclr(oid);
+                       oidclr(oid, refs->repo->hash_algo);
                        return refname;
                }
                if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
@@ -1884,28 +1877,24 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 }
 
 /* backend functions */
-int refs_init_db(struct strbuf *err)
+int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err)
 {
-       struct ref_store *refs = get_main_ref_store(the_repository);
-
-       return refs->be->init_db(refs, 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;
 
@@ -1915,87 +1904,49 @@ 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 char *be_name = "files";
-       struct ref_storage_be *be = find_ref_storage_backend(be_name);
+       const struct ref_storage_be *be;
        struct ref_store *refs;
 
+       be = find_ref_storage_backend(format);
        if (!be)
-               BUG("reference backend %s is unknown", be_name);
+               BUG("reference backend is unknown");
 
        refs = be->init(repo, gitdir, flags);
        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)
@@ -2004,7 +1955,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;
 }
@@ -2013,22 +1965,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;
@@ -2049,7 +1998,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;
 
@@ -2061,20 +2010,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, subrepo->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:
@@ -2090,25 +2035,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, wt->repo->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;
 }
 
@@ -2121,41 +2070,42 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
 }
 
 /* backend functions */
-int refs_pack_refs(struct ref_store *refs, unsigned int flags)
+int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)
 {
-       return refs->be->pack_refs(refs, flags);
+       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,
@@ -2189,7 +2139,7 @@ static int run_transaction_hook(struct ref_transaction *transaction,
        const char *hook;
        int ret = 0, i;
 
-       hook = find_hook("reference-transaction");
+       hook = find_hook(transaction->ref_store->repo, "reference-transaction");
        if (!hook)
                return ret;
 
@@ -2208,10 +2158,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) {
@@ -2396,7 +2358,7 @@ int refs_verify_refname_available(struct ref_store *refs,
        strbuf_addstr(&dirname, refname + dirname.len);
        strbuf_addch(&dirname, '/');
 
-       iter = refs_ref_iterator_begin(refs, dirname.buf, 0,
+       iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,
                                       DO_FOR_EACH_INCLUDE_BROKEN);
        while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
                if (skip &&
@@ -2425,20 +2387,29 @@ cleanup:
        return ret;
 }
 
-int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+struct do_for_each_reflog_help {
+       each_reflog_fn *fn;
+       void *cb_data;
+};
+
+static int do_for_each_reflog_helper(const char *refname,
+                                    const char *referent UNUSED,
+                                    const struct object_id *oid UNUSED,
+                                    int flags UNUSED,
+                                    void *cb_data)
+{
+       struct do_for_each_reflog_help *hp = cb_data;
+       return hp->fn(refname, hp->cb_data);
+}
+
+int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data)
 {
        struct ref_iterator *iter;
-       struct do_for_each_ref_help hp = { fn, cb_data };
+       struct do_for_each_reflog_help hp = { fn, cb_data };
 
        iter = refs->be->reflog_iterator_begin(refs);
 
-       return do_for_each_repo_ref_iterator(the_repository, iter,
-                                            do_for_each_ref_helper, &hp);
-}
-
-int for_each_reflog(each_ref_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,
@@ -2450,58 +2421,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,
@@ -2515,19 +2456,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)
 {
@@ -2555,19 +2483,55 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
 int refs_delete_refs(struct ref_store *refs, const char *logmsg,
                     struct string_list *refnames, unsigned int flags)
 {
+       struct ref_transaction *transaction;
+       struct strbuf err = STRBUF_INIT;
+       struct string_list_item *item;
+       int ret = 0, failures = 0;
        char *msg;
-       int retval;
+
+       if (!refnames->nr)
+               return 0;
 
        msg = normalize_reflog_message(logmsg);
-       retval = refs->be->delete_refs(refs, msg, refnames, flags);
-       free(msg);
-       return retval;
-}
 
-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);
+       /*
+        * Since we don't check the references' old_oids, the
+        * individual updates can't fail, so we can pack all of the
+        * updates into a single transaction.
+        */
+       transaction = ref_store_transaction_begin(refs, &err);
+       if (!transaction) {
+               ret = error("%s", err.buf);
+               goto out;
+       }
+
+       for_each_string_list_item(item, refnames) {
+               ret = ref_transaction_delete(transaction, item->string,
+                                            NULL, NULL, flags, msg, &err);
+               if (ret) {
+                       warning(_("could not delete reference %s: %s"),
+                               item->string, err.buf);
+                       strbuf_reset(&err);
+                       failures = 1;
+               }
+       }
+
+       ret = ref_transaction_commit(transaction, &err);
+       if (ret) {
+               if (refnames->nr == 1)
+                       error(_("could not delete reference %s: %s"),
+                             refnames->items[0].string, err.buf);
+               else
+                       error(_("could not delete references: %s"), err.buf);
+       }
+
+out:
+       if (!ret && failures)
+               ret = -1;
+       ref_transaction_free(transaction);
+       strbuf_release(&err);
+       free(msg);
+       return ret;
 }
 
 int refs_rename_ref(struct ref_store *refs, const char *oldref,
@@ -2582,11 +2546,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)
 {
@@ -2599,7 +2558,366 @@ 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)
 {
-       return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
+       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 char *referent UNUSED, 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;
+       }
+
+       /*
+        * Release the new ref store such that any potentially-open files will
+        * be closed. This is required for platforms like Cygwin, where
+        * renaming an open file results in EPERM.
+        */
+       ref_store_release(new_refs);
+       FREE_AND_NULL(new_refs);
+
+       /*
+        * 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);
+
+       /*
+        * Unset the old ref store and release it. `get_main_ref_store()` will
+        * make sure to lazily re-initialize the repository's ref store with
+        * the new format.
+        */
+       ref_store_release(old_refs);
+       FREE_AND_NULL(old_refs);
+       repo->refs_private = NULL;
+
+       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 (new_refs) {
+               ref_store_release(new_refs);
+               free(new_refs);
+       }
+       ref_transaction_free(transaction);
+       strbuf_release(&new_gitdir);
+       return ret;
+}
+
+int ref_update_expects_existing_old_ref(struct ref_update *update)
+{
+       return (update->flags & REF_HAVE_OLD) &&
+               (!is_null_oid(&update->old_oid) || update->old_target);
+}
+