]> git.ipfire.org Git - thirdparty/git.git/blobdiff - refs/files-backend.c
ref_iterator: keep track of whether the iterator output is ordered
[thirdparty/git.git] / refs / files-backend.c
index 0404f2c2333c0fadeb57d225deedc6f0d4afadad..35648c89fc7c152755f486905a94eae93900224e 100644 (file)
@@ -3,6 +3,7 @@
 #include "../refs.h"
 #include "refs-internal.h"
 #include "ref-cache.h"
+#include "packed-backend.h"
 #include "../iterator.h"
 #include "../dir-iterator.h"
 #include "../lockfile.h"
@@ -15,39 +16,6 @@ struct ref_lock {
        struct object_id old_oid;
 };
 
-/*
- * Return true if refname, which has the specified oid and flags, can
- * be resolved to an object in the database. If the referred-to object
- * does not exist, emit a warning and return false.
- */
-static int ref_resolves_to_object(const char *refname,
-                                 const struct object_id *oid,
-                                 unsigned int flags)
-{
-       if (flags & REF_ISBROKEN)
-               return 0;
-       if (!has_sha1_file(oid->hash)) {
-               error("%s does not point to a valid object!", refname);
-               return 0;
-       }
-       return 1;
-}
-
-struct packed_ref_cache {
-       struct ref_cache *cache;
-
-       /*
-        * Count of references to the data structure in this instance,
-        * including the pointer from files_ref_store::packed if any.
-        * The data will not be freed as long as the reference count
-        * is nonzero.
-        */
-       unsigned int referrers;
-
-       /* The metadata from when this packed-refs cache was read */
-       struct stat_validity validity;
-};
-
 /*
  * Future: need to be in "struct repository"
  * when doing a full libification.
@@ -58,54 +26,12 @@ struct files_ref_store {
 
        char *gitdir;
        char *gitcommondir;
-       char *packed_refs_path;
 
        struct ref_cache *loose;
-       struct packed_ref_cache *packed;
 
-       /*
-        * Lock used for the "packed-refs" file. Note that this (and
-        * thus the enclosing `files_ref_store`) must not be freed.
-        */
-       struct lock_file packed_refs_lock;
+       struct ref_store *packed_ref_store;
 };
 
-/*
- * Increment the reference count of *packed_refs.
- */
-static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs)
-{
-       packed_refs->referrers++;
-}
-
-/*
- * Decrease the reference count of *packed_refs.  If it goes to zero,
- * free *packed_refs and return true; otherwise return false.
- */
-static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
-{
-       if (!--packed_refs->referrers) {
-               free_ref_cache(packed_refs->cache);
-               stat_validity_clear(&packed_refs->validity);
-               free(packed_refs);
-               return 1;
-       } else {
-               return 0;
-       }
-}
-
-static void clear_packed_ref_cache(struct files_ref_store *refs)
-{
-       if (refs->packed) {
-               struct packed_ref_cache *packed_refs = refs->packed;
-
-               if (is_lock_file_locked(&refs->packed_refs_lock))
-                       die("BUG: packed-ref cache cleared while locked");
-               refs->packed = NULL;
-               release_packed_ref_cache(packed_refs);
-       }
-}
-
 static void clear_loose_ref_cache(struct files_ref_store *refs)
 {
        if (refs->loose) {
@@ -132,7 +58,8 @@ static struct ref_store *files_ref_store_create(const char *gitdir,
        get_common_dir_noenv(&sb, gitdir);
        refs->gitcommondir = strbuf_detach(&sb, NULL);
        strbuf_addf(&sb, "%s/packed-refs", refs->gitcommondir);
-       refs->packed_refs_path = strbuf_detach(&sb, NULL);
+       refs->packed_ref_store = packed_ref_store_create(sb.buf, flags);
+       strbuf_release(&sb);
 
        return ref_store;
 }
@@ -175,156 +102,6 @@ static struct files_ref_store *files_downcast(struct ref_store *ref_store,
        return refs;
 }
 
-/* The length of a peeled reference line in packed-refs, including EOL: */
-#define PEELED_LINE_LENGTH 42
-
-/*
- * The packed-refs header line that we write out.  Perhaps other
- * traits will be added later.  The trailing space is required.
- */
-static const char PACKED_REFS_HEADER[] =
-       "# pack-refs with: peeled fully-peeled \n";
-
-/*
- * Parse one line from a packed-refs file.  Write the SHA1 to sha1.
- * Return a pointer to the refname within the line (null-terminated),
- * or NULL if there was a problem.
- */
-static const char *parse_ref_line(struct strbuf *line, struct object_id *oid)
-{
-       const char *ref;
-
-       if (parse_oid_hex(line->buf, oid, &ref) < 0)
-               return NULL;
-       if (!isspace(*ref++))
-               return NULL;
-
-       if (isspace(*ref))
-               return NULL;
-
-       if (line->buf[line->len - 1] != '\n')
-               return NULL;
-       line->buf[--line->len] = 0;
-
-       return ref;
-}
-
-/*
- * Read from `packed_refs_file` into a newly-allocated
- * `packed_ref_cache` and return it. The return value will already
- * have its reference count incremented.
- *
- * A comment line of the form "# pack-refs with: " may contain zero or
- * more traits. We interpret the traits as follows:
- *
- *   No traits:
- *
- *      Probably no references are peeled. But if the file contains a
- *      peeled value for a reference, we will use it.
- *
- *   peeled:
- *
- *      References under "refs/tags/", if they *can* be peeled, *are*
- *      peeled in this file. References outside of "refs/tags/" are
- *      probably not peeled even if they could have been, but if we find
- *      a peeled value for such a reference we will use it.
- *
- *   fully-peeled:
- *
- *      All references in the file that can be peeled are peeled.
- *      Inversely (and this is more important), any references in the
- *      file for which no peeled value is recorded is not peelable. This
- *      trait should typically be written alongside "peeled" for
- *      compatibility with older clients, but we do not require it
- *      (i.e., "peeled" is a no-op if "fully-peeled" is set).
- */
-static struct packed_ref_cache *read_packed_refs(const char *packed_refs_file)
-{
-       FILE *f;
-       struct packed_ref_cache *packed_refs = xcalloc(1, sizeof(*packed_refs));
-       struct ref_entry *last = NULL;
-       struct strbuf line = STRBUF_INIT;
-       enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
-       struct ref_dir *dir;
-
-       acquire_packed_ref_cache(packed_refs);
-       packed_refs->cache = create_ref_cache(NULL, NULL);
-       packed_refs->cache->root->flag &= ~REF_INCOMPLETE;
-
-       f = fopen(packed_refs_file, "r");
-       if (!f) {
-               if (errno == ENOENT) {
-                       /*
-                        * This is OK; it just means that no
-                        * "packed-refs" file has been written yet,
-                        * which is equivalent to it being empty.
-                        */
-                       return packed_refs;
-               } else {
-                       die_errno("couldn't read %s", packed_refs_file);
-               }
-       }
-
-       stat_validity_update(&packed_refs->validity, fileno(f));
-
-       dir = get_ref_dir(packed_refs->cache->root);
-       while (strbuf_getwholeline(&line, f, '\n') != EOF) {
-               struct object_id oid;
-               const char *refname;
-               const char *traits;
-
-               if (skip_prefix(line.buf, "# pack-refs with:", &traits)) {
-                       if (strstr(traits, " fully-peeled "))
-                               peeled = PEELED_FULLY;
-                       else if (strstr(traits, " peeled "))
-                               peeled = PEELED_TAGS;
-                       /* perhaps other traits later as well */
-                       continue;
-               }
-
-               refname = parse_ref_line(&line, &oid);
-               if (refname) {
-                       int flag = REF_ISPACKED;
-
-                       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
-                               if (!refname_is_safe(refname))
-                                       die("packed refname is dangerous: %s", refname);
-                               oidclr(&oid);
-                               flag |= REF_BAD_NAME | REF_ISBROKEN;
-                       }
-                       last = create_ref_entry(refname, &oid, flag);
-                       if (peeled == PEELED_FULLY ||
-                           (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
-                               last->flag |= REF_KNOWS_PEELED;
-                       add_ref_entry(dir, last);
-                       continue;
-               }
-               if (last &&
-                   line.buf[0] == '^' &&
-                   line.len == PEELED_LINE_LENGTH &&
-                   line.buf[PEELED_LINE_LENGTH - 1] == '\n' &&
-                   !get_oid_hex(line.buf + 1, &oid)) {
-                       oidcpy(&last->u.value.peeled, &oid);
-                       /*
-                        * Regardless of what the file header said,
-                        * we definitely know the value of *this*
-                        * reference:
-                        */
-                       last->flag |= REF_KNOWS_PEELED;
-               }
-       }
-
-       fclose(f);
-       strbuf_release(&line);
-
-       return packed_refs;
-}
-
-static const char *files_packed_refs_path(struct files_ref_store *refs)
-{
-       return refs->packed_refs_path;
-}
-
 static void files_reflog_path(struct files_ref_store *refs,
                              struct strbuf *sb,
                              const char *refname)
@@ -370,70 +147,6 @@ static void files_ref_path(struct files_ref_store *refs,
        }
 }
 
-/*
- * Check that the packed refs cache (if any) still reflects the
- * contents of the file. If not, clear the cache.
- */
-static void validate_packed_ref_cache(struct files_ref_store *refs)
-{
-       if (refs->packed &&
-           !stat_validity_check(&refs->packed->validity,
-                                files_packed_refs_path(refs)))
-               clear_packed_ref_cache(refs);
-}
-
-/*
- * Get the packed_ref_cache for the specified files_ref_store,
- * creating and populating it if it hasn't been read before or if the
- * file has been changed (according to its `validity` field) since it
- * was last read. On the other hand, if we hold the lock, then assume
- * that the file hasn't been changed out from under us, so skip the
- * extra `stat()` call in `stat_validity_check()`.
- */
-static struct packed_ref_cache *get_packed_ref_cache(struct files_ref_store *refs)
-{
-       const char *packed_refs_file = files_packed_refs_path(refs);
-
-       if (!is_lock_file_locked(&refs->packed_refs_lock))
-               validate_packed_ref_cache(refs);
-
-       if (!refs->packed)
-               refs->packed = read_packed_refs(packed_refs_file);
-
-       return refs->packed;
-}
-
-static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache)
-{
-       return get_ref_dir(packed_ref_cache->cache->root);
-}
-
-static struct ref_dir *get_packed_refs(struct files_ref_store *refs)
-{
-       return get_packed_ref_dir(get_packed_ref_cache(refs));
-}
-
-/*
- * Add a reference to the in-memory packed reference cache.  This may
- * only be called while the packed-refs file is locked (see
- * lock_packed_refs()).  To actually write the packed-refs file, call
- * commit_packed_refs().
- */
-static void add_packed_ref(struct files_ref_store *refs,
-                          const char *refname, const struct object_id *oid)
-{
-       struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(refs);
-
-       if (!is_lock_file_locked(&refs->packed_refs_lock))
-               die("BUG: packed refs not locked");
-
-       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
-               die("Reference has invalid format: '%s'", refname);
-
-       add_ref_entry(get_packed_ref_dir(packed_ref_cache),
-                     create_ref_entry(refname, oid, REF_ISPACKED));
-}
-
 /*
  * Read the loose references from the namespace dirname into dir
  * (without recursing).  dirname must end with '/'.  dir must be the
@@ -556,39 +269,6 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs)
        return refs->loose;
 }
 
-/*
- * Return the ref_entry for the given refname from the packed
- * references.  If it does not exist, return NULL.
- */
-static struct ref_entry *get_packed_ref(struct files_ref_store *refs,
-                                       const char *refname)
-{
-       return find_ref_entry(get_packed_refs(refs), refname);
-}
-
-/*
- * A loose ref file doesn't exist; check for a packed ref.
- */
-static int resolve_packed_ref(struct files_ref_store *refs,
-                             const char *refname,
-                             unsigned char *sha1, unsigned int *flags)
-{
-       struct ref_entry *entry;
-
-       /*
-        * The loose reference file does not exist; check for a packed
-        * reference.
-        */
-       entry = get_packed_ref(refs, refname);
-       if (entry) {
-               hashcpy(sha1, entry->u.value.oid.hash);
-               *flags |= REF_ISPACKED;
-               return 0;
-       }
-       /* refname is not a packed reference. */
-       return -1;
-}
-
 static int files_read_raw_ref(struct ref_store *ref_store,
                              const char *refname, unsigned char *sha1,
                              struct strbuf *referent, unsigned int *type)
@@ -632,7 +312,8 @@ stat_ref:
        if (lstat(path, &st) < 0) {
                if (errno != ENOENT)
                        goto out;
-               if (resolve_packed_ref(refs, refname, sha1, type)) {
+               if (refs_read_raw_ref(refs->packed_ref_store, refname,
+                                     sha1, referent, type)) {
                        errno = ENOENT;
                        goto out;
                }
@@ -671,7 +352,8 @@ stat_ref:
                 * ref is supposed to be, there could still be a
                 * packed ref:
                 */
-               if (resolve_packed_ref(refs, refname, sha1, type)) {
+               if (refs_read_raw_ref(refs->packed_ref_store, refname,
+                                     sha1, referent, type)) {
                        errno = EISDIR;
                        goto out;
                }
@@ -855,7 +537,9 @@ retry:
        if (!lock->lk)
                lock->lk = xcalloc(1, sizeof(struct lock_file));
 
-       if (hold_lock_file_for_update(lock->lk, ref_file.buf, LOCK_NO_DEREF) < 0) {
+       if (hold_lock_file_for_update_timeout(
+                           lock->lk, ref_file.buf, LOCK_NO_DEREF,
+                           get_files_ref_lock_timeout_ms()) < 0) {
                if (errno == ENOENT && --attempts_remaining > 0) {
                        /*
                         * Maybe somebody just deleted one of the
@@ -950,11 +634,11 @@ retry:
 
                /*
                 * If the ref did not exist and we are creating it,
-                * make sure there is no existing ref that conflicts
-                * with refname:
+                * make sure there is no existing packed ref that
+                * conflicts with refname:
                 */
                if (refs_verify_refname_available(
-                                   &refs->base, refname,
+                                   refs->packed_ref_store, refname,
                                    extras, skip, err))
                        goto error_return;
        }
@@ -1001,15 +685,9 @@ static int files_peel_ref(struct ref_store *ref_store,
         * be expensive and (b) loose references anyway usually do not
         * have REF_KNOWS_PEELED.
         */
-       if (flag & REF_ISPACKED) {
-               struct ref_entry *r = get_packed_ref(refs, refname);
-               if (r) {
-                       if (peel_entry(r, 0))
-                               return -1;
-                       hashcpy(sha1, r->u.value.peeled.hash);
-                       return 0;
-               }
-       }
+       if (flag & REF_ISPACKED &&
+           !refs_peel_ref(refs->packed_ref_store, refname, sha1))
+               return 0;
 
        return peel_object(base, sha1);
 }
@@ -1017,7 +695,6 @@ static int files_peel_ref(struct ref_store *ref_store,
 struct files_ref_iterator {
        struct ref_iterator base;
 
-       struct packed_ref_cache *packed_ref_cache;
        struct ref_iterator *iter0;
        unsigned int flags;
 };
@@ -1070,7 +747,6 @@ static int files_ref_iterator_abort(struct ref_iterator *ref_iterator)
        if (iter->iter0)
                ok = ref_iterator_abort(iter->iter0);
 
-       release_packed_ref_cache(iter->packed_ref_cache);
        base_ref_iterator_free(ref_iterator);
        return ok;
 }
@@ -1086,7 +762,7 @@ static struct ref_iterator *files_ref_iterator_begin(
                const char *prefix, unsigned int flags)
 {
        struct files_ref_store *refs;
-       struct ref_iterator *loose_iter, *packed_iter;
+       struct ref_iterator *loose_iter, *packed_iter, *overlay_iter;
        struct files_ref_iterator *iter;
        struct ref_iterator *ref_iterator;
        unsigned int required_flags = REF_STORE_READ;
@@ -1096,10 +772,6 @@ static struct ref_iterator *files_ref_iterator_begin(
 
        refs = files_downcast(ref_store, required_flags, "ref_iterator_begin");
 
-       iter = xcalloc(1, sizeof(*iter));
-       ref_iterator = &iter->base;
-       base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable);
-
        /*
         * We must make sure that all loose refs are read before
         * accessing the packed-refs file; this avoids a race
@@ -1112,20 +784,36 @@ static struct ref_iterator *files_ref_iterator_begin(
         * (If they've already been read, that's OK; we only need to
         * guarantee that they're read before the packed refs, not
         * *how much* before.) After that, we call
-        * get_packed_ref_cache(), which internally checks whether the
-        * packed-ref cache is up to date with what is on disk, and
-        * re-reads it if not.
+        * packed_ref_iterator_begin(), which internally checks
+        * whether the packed-ref cache is up to date with what is on
+        * disk, and re-reads it if not.
         */
 
        loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs),
                                              prefix, 1);
 
-       iter->packed_ref_cache = get_packed_ref_cache(refs);
-       acquire_packed_ref_cache(iter->packed_ref_cache);
-       packed_iter = cache_ref_iterator_begin(iter->packed_ref_cache->cache,
-                                              prefix, 0);
+       /*
+        * The packed-refs file might contain broken references, for
+        * example an old version of a reference that points at an
+        * object that has since been garbage-collected. This is OK as
+        * long as there is a corresponding loose reference that
+        * overrides it, and we don't want to emit an error message in
+        * this case. So ask the packed_ref_store for all of its
+        * references, and (if needed) do our own check for broken
+        * ones in files_ref_iterator_advance(), after we have merged
+        * the packed and loose references.
+        */
+       packed_iter = refs_ref_iterator_begin(
+                       refs->packed_ref_store, prefix, 0,
+                       DO_FOR_EACH_INCLUDE_BROKEN);
+
+       overlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter);
 
-       iter->iter0 = overlay_ref_iterator_begin(loose_iter, packed_iter);
+       iter = xcalloc(1, sizeof(*iter));
+       ref_iterator = &iter->base;
+       base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable,
+                              overlay_iter->ordered);
+       iter->iter0 = overlay_iter;
        iter->flags = flags;
 
        return ref_iterator;
@@ -1181,7 +869,9 @@ static int create_reflock(const char *path, void *cb)
 {
        struct lock_file *lk = cb;
 
-       return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 0 ? -1 : 0;
+       return hold_lock_file_for_update_timeout(
+                       lk, path, LOCK_NO_DEREF,
+                       get_files_ref_lock_timeout_ms()) < 0 ? -1 : 0;
 }
 
 /*
@@ -1255,7 +945,7 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
         * our refname.
         */
        if (is_null_oid(&lock->old_oid) &&
-           refs_verify_refname_available(&refs->base, refname,
+           refs_verify_refname_available(refs->packed_ref_store, refname,
                                          extras, skip, err)) {
                last_errno = ENOTDIR;
                goto error_return;
@@ -1287,124 +977,6 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
        return lock;
 }
 
-/*
- * Write an entry to the packed-refs file for the specified refname.
- * If peeled is non-NULL, write it as the entry's peeled value.
- */
-static void write_packed_entry(FILE *fh, const char *refname,
-                              const unsigned char *sha1,
-                              const unsigned char *peeled)
-{
-       fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname);
-       if (peeled)
-               fprintf_or_die(fh, "^%s\n", sha1_to_hex(peeled));
-}
-
-/*
- * Lock the packed-refs file for writing. Flags is passed to
- * hold_lock_file_for_update(). Return 0 on success. On errors, set
- * errno appropriately and return a nonzero value.
- */
-static int lock_packed_refs(struct files_ref_store *refs, int flags)
-{
-       static int timeout_configured = 0;
-       static int timeout_value = 1000;
-       struct packed_ref_cache *packed_ref_cache;
-
-       files_assert_main_repository(refs, "lock_packed_refs");
-
-       if (!timeout_configured) {
-               git_config_get_int("core.packedrefstimeout", &timeout_value);
-               timeout_configured = 1;
-       }
-
-       if (hold_lock_file_for_update_timeout(
-                           &refs->packed_refs_lock, files_packed_refs_path(refs),
-                           flags, timeout_value) < 0)
-               return -1;
-
-       /*
-        * Now that we hold the `packed-refs` lock, make sure that our
-        * cache matches the current version of the file. Normally
-        * `get_packed_ref_cache()` does that for us, but that
-        * function assumes that when the file is locked, any existing
-        * cache is still valid. We've just locked the file, but it
-        * might have changed the moment *before* we locked it.
-        */
-       validate_packed_ref_cache(refs);
-
-       packed_ref_cache = get_packed_ref_cache(refs);
-       /* Increment the reference count to prevent it from being freed: */
-       acquire_packed_ref_cache(packed_ref_cache);
-       return 0;
-}
-
-/*
- * Write the current version of the packed refs cache from memory to
- * disk. The packed-refs file must already be locked for writing (see
- * lock_packed_refs()). Return zero on success. On errors, set errno
- * and return a nonzero value
- */
-static int commit_packed_refs(struct files_ref_store *refs)
-{
-       struct packed_ref_cache *packed_ref_cache =
-               get_packed_ref_cache(refs);
-       int ok, error = 0;
-       int save_errno = 0;
-       FILE *out;
-       struct ref_iterator *iter;
-
-       files_assert_main_repository(refs, "commit_packed_refs");
-
-       if (!is_lock_file_locked(&refs->packed_refs_lock))
-               die("BUG: packed-refs not locked");
-
-       out = fdopen_lock_file(&refs->packed_refs_lock, "w");
-       if (!out)
-               die_errno("unable to fdopen packed-refs descriptor");
-
-       fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
-
-       iter = cache_ref_iterator_begin(packed_ref_cache->cache, NULL, 0);
-       while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
-               struct object_id peeled;
-               int peel_error = ref_iterator_peel(iter, &peeled);
-
-               write_packed_entry(out, iter->refname, iter->oid->hash,
-                                  peel_error ? NULL : peeled.hash);
-       }
-
-       if (ok != ITER_DONE)
-               die("error while iterating over references");
-
-       if (commit_lock_file(&refs->packed_refs_lock)) {
-               save_errno = errno;
-               error = -1;
-       }
-       release_packed_ref_cache(packed_ref_cache);
-       errno = save_errno;
-       return error;
-}
-
-/*
- * Rollback the lockfile for the packed-refs file, and discard the
- * in-memory packed reference cache.  (The packed-refs file will be
- * read anew if it is needed again after this function is called.)
- */
-static void rollback_packed_refs(struct files_ref_store *refs)
-{
-       struct packed_ref_cache *packed_ref_cache =
-               get_packed_ref_cache(refs);
-
-       files_assert_main_repository(refs, "rollback_packed_refs");
-
-       if (!is_lock_file_locked(&refs->packed_refs_lock))
-               die("BUG: packed-refs not locked");
-       rollback_lock_file(&refs->packed_refs_lock);
-       release_packed_ref_cache(packed_ref_cache);
-       clear_packed_ref_cache(refs);
-}
-
 struct ref_to_prune {
        struct ref_to_prune *next;
        unsigned char sha1[20];
@@ -1487,11 +1059,17 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
        strbuf_release(&err);
 }
 
-static void prune_refs(struct files_ref_store *refs, struct ref_to_prune *r)
+/*
+ * Prune the loose versions of the references in the linked list
+ * `*refs_to_prune`, freeing the entries in the list as we go.
+ */
+static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_to_prune)
 {
-       while (r) {
+       while (*refs_to_prune) {
+               struct ref_to_prune *r = *refs_to_prune;
+               *refs_to_prune = r->next;
                prune_ref(refs, r);
-               r = r->next;
+               free(r);
        }
 }
 
@@ -1527,12 +1105,16 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
                files_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB,
                               "pack_refs");
        struct ref_iterator *iter;
-       struct ref_dir *packed_refs;
        int ok;
        struct ref_to_prune *refs_to_prune = NULL;
+       struct strbuf err = STRBUF_INIT;
+       struct ref_transaction *transaction;
 
-       lock_packed_refs(refs, LOCK_DIE_ON_ERROR);
-       packed_refs = get_packed_refs(refs);
+       transaction = ref_store_transaction_begin(refs->packed_ref_store, &err);
+       if (!transaction)
+               return -1;
+
+       packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err);
 
        iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL, 0);
        while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
@@ -1541,29 +1123,19 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
                 * in the packed ref cache. If the reference should be
                 * pruned, also add it to refs_to_prune.
                 */
-               struct ref_entry *packed_entry;
-
                if (!should_pack_ref(iter->refname, iter->oid, iter->flags,
                                     flags))
                        continue;
 
                /*
-                * Create an entry in the packed-refs cache equivalent
-                * to the one from the loose ref cache, except that
-                * we don't copy the peeled status, because we want it
-                * to be re-peeled.
+                * Add a reference creation for this reference to the
+                * packed-refs transaction:
                 */
-               packed_entry = find_ref_entry(packed_refs, iter->refname);
-               if (packed_entry) {
-                       /* Overwrite existing packed entry with info from loose entry */
-                       packed_entry->flag = REF_ISPACKED;
-                       oidcpy(&packed_entry->u.value.oid, iter->oid);
-               } else {
-                       packed_entry = create_ref_entry(iter->refname, iter->oid,
-                                                       REF_ISPACKED);
-                       add_ref_entry(packed_refs, packed_entry);
-               }
-               oidclr(&packed_entry->u.value.peeled);
+               if (ref_transaction_update(transaction, iter->refname,
+                                          iter->oid->hash, NULL,
+                                          REF_NODEREF, NULL, &err))
+                       die("failure preparing to create packed reference %s: %s",
+                           iter->refname, err.buf);
 
                /* Schedule the loose reference for pruning if requested. */
                if ((flags & PACK_REFS_PRUNE)) {
@@ -1577,67 +1149,16 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
        if (ok != ITER_DONE)
                die("error while iterating over references");
 
-       if (commit_packed_refs(refs))
-               die_errno("unable to overwrite old ref-pack file");
-
-       prune_refs(refs, refs_to_prune);
-       return 0;
-}
-
-/*
- * Rewrite the packed-refs file, omitting any refs listed in
- * 'refnames'. On error, leave packed-refs unchanged, write an error
- * message to 'err', and return a nonzero value.
- *
- * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
- */
-static int repack_without_refs(struct files_ref_store *refs,
-                              struct string_list *refnames, struct strbuf *err)
-{
-       struct ref_dir *packed;
-       struct string_list_item *refname;
-       int ret, needs_repacking = 0, removed = 0;
-
-       files_assert_main_repository(refs, "repack_without_refs");
-       assert(err);
-
-       /* Look for a packed ref */
-       for_each_string_list_item(refname, refnames) {
-               if (get_packed_ref(refs, refname->string)) {
-                       needs_repacking = 1;
-                       break;
-               }
-       }
+       if (ref_transaction_commit(transaction, &err))
+               die("unable to write new packed-refs: %s", err.buf);
 
-       /* Avoid locking if we have nothing to do */
-       if (!needs_repacking)
-               return 0; /* no refname exists in packed refs */
+       ref_transaction_free(transaction);
 
-       if (lock_packed_refs(refs, 0)) {
-               unable_to_lock_message(files_packed_refs_path(refs), errno, err);
-               return -1;
-       }
-       packed = get_packed_refs(refs);
+       packed_refs_unlock(refs->packed_ref_store);
 
-       /* Remove refnames from the cache */
-       for_each_string_list_item(refname, refnames)
-               if (remove_entry_from_dir(packed, refname->string) != -1)
-                       removed = 1;
-       if (!removed) {
-               /*
-                * All packed entries disappeared while we were
-                * acquiring the lock.
-                */
-               rollback_packed_refs(refs);
-               return 0;
-       }
-
-       /* Write what remains */
-       ret = commit_packed_refs(refs);
-       if (ret)
-               strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
-                           strerror(errno));
-       return ret;
+       prune_refs(refs, &refs_to_prune);
+       strbuf_release(&err);
+       return 0;
 }
 
 static int files_delete_refs(struct ref_store *ref_store, const char *msg,
@@ -1651,24 +1172,16 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg,
        if (!refnames->nr)
                return 0;
 
-       result = repack_without_refs(refs, refnames, &err);
-       if (result) {
-               /*
-                * If we failed to rewrite the packed-refs file, then
-                * it is unsafe to try to remove loose refs, because
-                * doing so might expose an obsolete packed value for
-                * a reference that might even point at an object that
-                * has been garbage collected.
-                */
-               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);
+       if (packed_refs_lock(refs->packed_ref_store, 0, &err))
+               goto error;
 
-               goto out;
+       if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) {
+               packed_refs_unlock(refs->packed_ref_store);
+               goto error;
        }
 
+       packed_refs_unlock(refs->packed_ref_store);
+
        for (i = 0; i < refnames->nr; i++) {
                const char *refname = refnames->items[i].string;
 
@@ -1676,9 +1189,24 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg,
                        result |= error(_("could not remove reference %s"), refname);
        }
 
-out:
        strbuf_release(&err);
        return result;
+
+error:
+       /*
+        * If we failed to rewrite the packed-refs file, then it is
+        * unsafe to try to remove loose refs, because doing so might
+        * expose an obsolete packed value for a reference that might
+        * even point at an object that has been garbage collected.
+        */
+       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);
+
+       strbuf_release(&err);
+       return -1;
 }
 
 /*
@@ -2558,7 +2086,7 @@ static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_st
        struct ref_iterator *ref_iterator = &iter->base;
        struct strbuf sb = STRBUF_INIT;
 
-       base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable);
+       base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable, 0);
        files_reflog_path(refs, &sb, NULL);
        iter->dir_iterator = dir_iterator_begin(sb.buf);
        iter->ref_store = ref_store;
@@ -2871,13 +2399,22 @@ static int lock_ref_for_update(struct files_ref_store *refs,
        return 0;
 }
 
+struct files_transaction_backend_data {
+       struct ref_transaction *packed_transaction;
+       int packed_refs_locked;
+};
+
 /*
  * Unlock any references in `transaction` that are still locked, and
  * mark the transaction closed.
  */
-static void files_transaction_cleanup(struct ref_transaction *transaction)
+static void files_transaction_cleanup(struct files_ref_store *refs,
+                                     struct ref_transaction *transaction)
 {
        size_t i;
+       struct files_transaction_backend_data *backend_data =
+               transaction->backend_data;
+       struct strbuf err = STRBUF_INIT;
 
        for (i = 0; i < transaction->nr; i++) {
                struct ref_update *update = transaction->updates[i];
@@ -2889,6 +2426,17 @@ static void files_transaction_cleanup(struct ref_transaction *transaction)
                }
        }
 
+       if (backend_data->packed_transaction &&
+           ref_transaction_abort(backend_data->packed_transaction, &err)) {
+               error("error aborting transaction: %s", err.buf);
+               strbuf_release(&err);
+       }
+
+       if (backend_data->packed_refs_locked)
+               packed_refs_unlock(refs->packed_ref_store);
+
+       free(backend_data);
+
        transaction->state = REF_TRANSACTION_CLOSED;
 }
 
@@ -2905,12 +2453,17 @@ static int files_transaction_prepare(struct ref_store *ref_store,
        char *head_ref = NULL;
        int head_type;
        struct object_id head_oid;
+       struct files_transaction_backend_data *backend_data;
+       struct ref_transaction *packed_transaction = NULL;
 
        assert(err);
 
        if (!transaction->nr)
                goto cleanup;
 
+       backend_data = xcalloc(1, sizeof(*backend_data));
+       transaction->backend_data = backend_data;
+
        /*
         * Fail if a refname appears more than once in the
         * transaction. (If we end up splitting up any updates using
@@ -2977,6 +2530,41 @@ static int files_transaction_prepare(struct ref_store *ref_store,
                                          head_ref, &affected_refnames, err);
                if (ret)
                        break;
+
+               if (update->flags & REF_DELETING &&
+                   !(update->flags & REF_LOG_ONLY) &&
+                   !(update->flags & REF_ISPRUNING)) {
+                       /*
+                        * This reference has to be deleted from
+                        * packed-refs if it exists there.
+                        */
+                       if (!packed_transaction) {
+                               packed_transaction = ref_store_transaction_begin(
+                                               refs->packed_ref_store, err);
+                               if (!packed_transaction) {
+                                       ret = TRANSACTION_GENERIC_ERROR;
+                                       goto cleanup;
+                               }
+
+                               backend_data->packed_transaction =
+                                       packed_transaction;
+                       }
+
+                       ref_transaction_add_update(
+                                       packed_transaction, update->refname,
+                                       update->flags & ~REF_HAVE_OLD,
+                                       update->new_oid.hash, update->old_oid.hash,
+                                       NULL);
+               }
+       }
+
+       if (packed_transaction) {
+               if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
+                       ret = TRANSACTION_GENERIC_ERROR;
+                       goto cleanup;
+               }
+               backend_data->packed_refs_locked = 1;
+               ret = ref_transaction_prepare(packed_transaction, err);
        }
 
 cleanup:
@@ -2984,7 +2572,7 @@ cleanup:
        string_list_clear(&affected_refnames, 0);
 
        if (ret)
-               files_transaction_cleanup(transaction);
+               files_transaction_cleanup(refs, transaction);
        else
                transaction->state = REF_TRANSACTION_PREPARED;
 
@@ -2999,9 +2587,10 @@ static int files_transaction_finish(struct ref_store *ref_store,
                files_downcast(ref_store, 0, "ref_transaction_finish");
        size_t i;
        int ret = 0;
-       struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
-       struct string_list_item *ref_to_delete;
        struct strbuf sb = STRBUF_INIT;
+       struct files_transaction_backend_data *backend_data;
+       struct ref_transaction *packed_transaction;
+
 
        assert(err);
 
@@ -3010,6 +2599,9 @@ static int files_transaction_finish(struct ref_store *ref_store,
                return 0;
        }
 
+       backend_data = transaction->backend_data;
+       packed_transaction = backend_data->packed_transaction;
+
        /* Perform updates first so live commits remain referenced */
        for (i = 0; i < transaction->nr; i++) {
                struct ref_update *update = transaction->updates[i];
@@ -3045,7 +2637,44 @@ static int files_transaction_finish(struct ref_store *ref_store,
                        }
                }
        }
-       /* Perform deletes now that updates are safely completed */
+
+       /*
+        * Now that updates are safely completed, we can perform
+        * deletes. First delete the reflogs of any references that
+        * will be deleted, since (in the unexpected event of an
+        * error) leaving a reference without a reflog is less bad
+        * than leaving a reflog without a reference (the latter is a
+        * mildly invalid repository state):
+        */
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
+               if (update->flags & REF_DELETING &&
+                   !(update->flags & REF_LOG_ONLY) &&
+                   !(update->flags & REF_ISPRUNING)) {
+                       strbuf_reset(&sb);
+                       files_reflog_path(refs, &sb, update->refname);
+                       if (!unlink_or_warn(sb.buf))
+                               try_remove_empty_parents(refs, update->refname,
+                                                        REMOVE_EMPTY_PARENTS_REFLOG);
+               }
+       }
+
+       /*
+        * Perform deletes now that updates are safely completed.
+        *
+        * First delete any packed versions of the references, while
+        * retaining the packed-refs lock:
+        */
+       if (packed_transaction) {
+               ret = ref_transaction_commit(packed_transaction, err);
+               ref_transaction_free(packed_transaction);
+               packed_transaction = NULL;
+               backend_data->packed_transaction = NULL;
+               if (ret)
+                       goto cleanup;
+       }
+
+       /* Now delete the loose versions of the references: */
        for (i = 0; i < transaction->nr; i++) {
                struct ref_update *update = transaction->updates[i];
                struct ref_lock *lock = update->backend_data;
@@ -3063,31 +2692,13 @@ static int files_transaction_finish(struct ref_store *ref_store,
                                }
                                update->flags |= REF_DELETED_LOOSE;
                        }
-
-                       if (!(update->flags & REF_ISPRUNING))
-                               string_list_append(&refs_to_delete,
-                                                  lock->ref_name);
                }
        }
 
-       if (repack_without_refs(refs, &refs_to_delete, err)) {
-               ret = TRANSACTION_GENERIC_ERROR;
-               goto cleanup;
-       }
-
-       /* Delete the reflogs of any references that were deleted: */
-       for_each_string_list_item(ref_to_delete, &refs_to_delete) {
-               strbuf_reset(&sb);
-               files_reflog_path(refs, &sb, ref_to_delete->string);
-               if (!unlink_or_warn(sb.buf))
-                       try_remove_empty_parents(refs, ref_to_delete->string,
-                                                REMOVE_EMPTY_PARENTS_REFLOG);
-       }
-
        clear_loose_ref_cache(refs);
 
 cleanup:
-       files_transaction_cleanup(transaction);
+       files_transaction_cleanup(refs, transaction);
 
        for (i = 0; i < transaction->nr; i++) {
                struct ref_update *update = transaction->updates[i];
@@ -3105,7 +2716,6 @@ cleanup:
        }
 
        strbuf_release(&sb);
-       string_list_clear(&refs_to_delete, 0);
        return ret;
 }
 
@@ -3113,7 +2723,10 @@ static int files_transaction_abort(struct ref_store *ref_store,
                                   struct ref_transaction *transaction,
                                   struct strbuf *err)
 {
-       files_transaction_cleanup(transaction);
+       struct files_ref_store *refs =
+               files_downcast(ref_store, 0, "ref_transaction_abort");
+
+       files_transaction_cleanup(refs, transaction);
        return 0;
 }
 
@@ -3135,6 +2748,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
        size_t i;
        int ret = 0;
        struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
+       struct ref_transaction *packed_transaction = NULL;
 
        assert(err);
 
@@ -3167,6 +2781,12 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
                                 &affected_refnames))
                die("BUG: initial ref transaction called with existing refs");
 
+       packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err);
+       if (!packed_transaction) {
+               ret = TRANSACTION_GENERIC_ERROR;
+               goto cleanup;
+       }
+
        for (i = 0; i < transaction->nr; i++) {
                struct ref_update *update = transaction->updates[i];
 
@@ -3179,32 +2799,31 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
                        ret = TRANSACTION_NAME_CONFLICT;
                        goto cleanup;
                }
+
+               /*
+                * Add a reference creation for this reference to the
+                * packed-refs transaction:
+                */
+               ref_transaction_add_update(packed_transaction, update->refname,
+                                          update->flags & ~REF_HAVE_OLD,
+                                          update->new_oid.hash, update->old_oid.hash,
+                                          NULL);
        }
 
-       if (lock_packed_refs(refs, 0)) {
-               strbuf_addf(err, "unable to lock packed-refs file: %s",
-                           strerror(errno));
+       if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
                ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
        }
 
-       for (i = 0; i < transaction->nr; i++) {
-               struct ref_update *update = transaction->updates[i];
-
-               if ((update->flags & REF_HAVE_NEW) &&
-                   !is_null_oid(&update->new_oid))
-                       add_packed_ref(refs, update->refname,
-                                      &update->new_oid);
-       }
-
-       if (commit_packed_refs(refs)) {
-               strbuf_addf(err, "unable to commit packed-refs file: %s",
-                           strerror(errno));
+       if (initial_ref_transaction_commit(packed_transaction, err)) {
                ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
        }
 
 cleanup:
+       if (packed_transaction)
+               ref_transaction_free(packed_transaction);
+       packed_refs_unlock(refs->packed_ref_store);
        transaction->state = REF_TRANSACTION_CLOSED;
        string_list_clear(&affected_refnames, 0);
        return ret;