]> 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 32e91ff74038503211b2cde43c7fab64cc8dfdbc..ceb72d4bd740b9496f284c2d654b69eb9eafe33d 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -2,11 +2,13 @@
  * The backend-independent part of the reference module.
  */
 
+#define USE_THE_REPOSITORY_VARIABLE
+
 #include "git-compat-util.h"
 #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 +21,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 +39,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 +55,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 +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;
 }
@@ -316,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))
@@ -412,7 +419,7 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)
                                         NULL, NULL);
 }
 
-static int for_each_filter_refs(const char *refname,
+static int for_each_filter_refs(const char *refname, const char *referent,
                                const struct object_id *oid,
                                int flags, void *data)
 {
@@ -422,38 +429,18 @@ static int for_each_filter_refs(const char *refname,
                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)
 {
@@ -463,8 +450,7 @@ static int warn_if_dangling_symref(const char *refname,
        if (!(flags & REF_ISSYMREF))
                return 0;
 
-       resolves_to = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
-                                             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)
@@ -477,28 +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;
-       refs_for_each_rawref(get_main_ref_store(the_repository),
-                            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;
-       refs_for_each_rawref(get_main_ref_store(the_repository),
-                            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)
@@ -525,7 +511,7 @@ int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_da
 
        strbuf_addf(&buf, "%sHEAD", get_git_namespace());
        if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))
-               ret = fn(buf.buf, &oid, flag, cb_data);
+               ret = fn(buf.buf, NULL, &oid, flag, cb_data);
        strbuf_release(&buf);
 
        return ret;
@@ -686,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
@@ -807,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/") ||
@@ -819,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;
 
@@ -828,56 +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_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,
@@ -954,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);
@@ -1234,6 +1201,12 @@ int ref_transaction_update(struct ref_transaction *transaction,
 {
        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) :
@@ -1243,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);
 
@@ -1265,43 +1245,57 @@ int ref_transaction_update(struct ref_transaction *transaction,
 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(), NULL, NULL, flags,
+                                     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,
-                                     NULL, NULL, flags,
+                                     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, NULL,
+                                     NULL, old_target,
                                      flags, NULL, err);
 }
 
@@ -1558,7 +1552,7 @@ 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;
 }
@@ -1594,53 +1588,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;
@@ -1648,8 +1601,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)
@@ -1670,12 +1622,12 @@ 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 refs_for_each_namespaced_ref(struct ref_store *refs,
@@ -1807,8 +1759,8 @@ static int refs_read_special_head(struct ref_store *ref_store,
                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);
@@ -1816,43 +1768,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);
 
@@ -1922,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;
@@ -1932,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;
@@ -1940,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)) {
@@ -1956,19 +1877,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);
 }
 
-int resolve_gitlink_ref(const char *submodule, const char *refname,
-                       struct object_id *oid)
+int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)
+{
+       return refs->be->remove_on_disk(refs, err);
+}
+
+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;
 
@@ -1978,80 +1904,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");
 
@@ -2059,6 +1941,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)
@@ -2067,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;
 }
@@ -2076,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;
@@ -2112,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;
 
@@ -2124,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:
@@ -2153,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;
 }
 
@@ -2189,14 +2075,14 @@ 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_update_symref(struct ref_store *refs, const char *ref,
@@ -2253,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;
 
@@ -2506,10 +2392,10 @@ 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 char *referent UNUSED,
                                     const struct object_id *oid UNUSED,
-                                    int flags,
+                                    int flags UNUSED,
                                     void *cb_data)
 {
        struct do_for_each_reflog_help *hp = cb_data;
@@ -2523,8 +2409,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);
+       return do_for_each_ref_iterator(iter, do_for_each_reflog_helper, &hp);
 }
 
 int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
@@ -2622,7 +2507,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);
@@ -2707,3 +2592,332 @@ int ref_update_check_old_target(const char *referent, struct ref_update *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);
+}
+