ref_lock: stop leaking lock_files
[gitweb.git] / refs / files-backend.c
index 0404f2c2333c0fadeb57d225deedc6f0d4afadad..f3455609d658a55b20ca47026236cec17533ecca 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"
 
 struct ref_lock {
        char *ref_name;
-       struct lock_file *lk;
+       struct lock_file lk;
        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 @@ static int files_read_raw_ref(struct ref_store *ref_store,
        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 @@ static int files_read_raw_ref(struct ref_store *ref_store,
                 * 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;
                }
@@ -736,9 +418,7 @@ static int files_read_raw_ref(struct ref_store *ref_store,
 
 static void unlock_ref(struct ref_lock *lock)
 {
-       /* Do not free lock->lk -- atexit() still looks at them */
-       if (lock->lk)
-               rollback_lock_file(lock->lk);
+       rollback_lock_file(&lock->lk);
        free(lock->ref_name);
        free(lock);
 }
@@ -852,10 +532,9 @@ static int lock_raw_ref(struct files_ref_store *refs,
                goto error_return;
        }
 
-       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 +629,11 @@ static int lock_raw_ref(struct files_ref_store *refs,
 
                /*
                 * 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 +680,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 +690,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 +742,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;
 }
@@ -1112,18 +783,28 @@ 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);
 
        iter->iter0 = overlay_ref_iterator_begin(loose_iter, packed_iter);
        iter->flags = flags;
@@ -1181,7 +862,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,17 +938,15 @@ 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;
        }
 
-       lock->lk = xcalloc(1, sizeof(struct lock_file));
-
        lock->ref_name = xstrdup(refname);
 
-       if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) {
+       if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) {
                last_errno = errno;
                unable_to_lock_message(ref_file.buf, errno, err);
                goto error_return;
@@ -1287,124 +968,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];
@@ -1527,12 +1090,11 @@ 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;
 
-       lock_packed_refs(refs, LOCK_DIE_ON_ERROR);
-       packed_refs = get_packed_refs(refs);
+       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,8 +1103,6 @@ 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;
@@ -1553,17 +1113,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
                 * we don't copy the peeled status, because we want it
                 * to be re-peeled.
                 */
-               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);
+               add_packed_ref(refs->packed_ref_store, iter->refname, iter->oid);
 
                /* Schedule the loose reference for pruning if requested. */
                if ((flags & PACK_REFS_PRUNE)) {
@@ -1577,69 +1127,15 @@ 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");
+       if (commit_packed_refs(refs->packed_ref_store, &err))
+               die("unable to overwrite old ref-pack file: %s", err.buf);
+       packed_refs_unlock(refs->packed_ref_store);
 
        prune_refs(refs, refs_to_prune);
+       strbuf_release(&err);
        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;
-               }
-       }
-
-       /* Avoid locking if we have nothing to do */
-       if (!needs_repacking)
-               return 0; /* no refname exists in packed refs */
-
-       if (lock_packed_refs(refs, 0)) {
-               unable_to_lock_message(files_packed_refs_path(refs), errno, err);
-               return -1;
-       }
-       packed = get_packed_refs(refs);
-
-       /* 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;
-}
-
 static int files_delete_refs(struct ref_store *ref_store, const char *msg,
                             struct string_list *refnames, unsigned int flags)
 {
@@ -1651,24 +1147,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 (repack_without_refs(refs->packed_ref_store, refnames, &err)) {
+               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 +1164,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;
 }
 
 /*
@@ -1892,16 +1395,16 @@ static int files_rename_ref(struct ref_store *ref_store,
        return ret;
 }
 
-static int close_ref(struct ref_lock *lock)
+static int close_ref_gently(struct ref_lock *lock)
 {
-       if (close_lock_file(lock->lk))
+       if (close_lock_file_gently(&lock->lk))
                return -1;
        return 0;
 }
 
 static int commit_ref(struct ref_lock *lock)
 {
-       char *path = get_locked_file_path(lock->lk);
+       char *path = get_locked_file_path(&lock->lk);
        struct stat st;
 
        if (!lstat(path, &st) && S_ISDIR(st.st_mode)) {
@@ -1925,7 +1428,7 @@ static int commit_ref(struct ref_lock *lock)
                free(path);
        }
 
-       if (commit_lock_file(lock->lk))
+       if (commit_lock_file(&lock->lk))
                return -1;
        return 0;
 }
@@ -2117,12 +1620,12 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
                unlock_ref(lock);
                return -1;
        }
-       fd = get_lock_file_fd(lock->lk);
+       fd = get_lock_file_fd(&lock->lk);
        if (write_in_full(fd, oid_to_hex(oid), GIT_SHA1_HEXSZ) != GIT_SHA1_HEXSZ ||
            write_in_full(fd, &term, 1) != 1 ||
-           close_ref(lock) < 0) {
+           close_ref_gently(lock) < 0) {
                strbuf_addf(err,
-                           "couldn't write '%s'", get_lock_file_path(lock->lk));
+                           "couldn't write '%s'", get_lock_file_path(&lock->lk));
                unlock_ref(lock);
                return -1;
        }
@@ -2199,7 +1702,7 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
 {
        int ret = -1;
 #ifndef NO_SYMLINK_HEAD
-       char *ref_path = get_locked_file_path(lock->lk);
+       char *ref_path = get_locked_file_path(&lock->lk);
        unlink(ref_path);
        ret = symlink(target, ref_path);
        free(ref_path);
@@ -2235,14 +1738,14 @@ static int create_symref_locked(struct files_ref_store *refs,
                return 0;
        }
 
-       if (!fdopen_lock_file(lock->lk, "w"))
+       if (!fdopen_lock_file(&lock->lk, "w"))
                return error("unable to fdopen %s: %s",
-                            lock->lk->tempfile.filename.buf, strerror(errno));
+                            lock->lk.tempfile->filename.buf, strerror(errno));
 
        update_symref_reflog(refs, lock, refname, target, logmsg);
 
        /* no error check; commit_ref will check ferror */
-       fprintf(lock->lk->tempfile.fp, "ref: %s\n", target);
+       fprintf(lock->lk.tempfile->fp, "ref: %s\n", target);
        if (commit_ref(lock) < 0)
                return error("unable to write symref for %s: %s", refname,
                             strerror(errno));
@@ -2862,7 +2365,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
                 * the lockfile is still open. Close it to
                 * free up the file descriptor:
                 */
-               if (close_ref(lock)) {
+               if (close_ref_gently(lock)) {
                        strbuf_addf(err, "couldn't close '%s.lock'",
                                    update->refname);
                        return TRANSACTION_GENERIC_ERROR;
@@ -3070,11 +2573,19 @@ static int files_transaction_finish(struct ref_store *ref_store,
                }
        }
 
-       if (repack_without_refs(refs, &refs_to_delete, err)) {
+       if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
                ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
        }
 
+       if (repack_without_refs(refs->packed_ref_store, &refs_to_delete, err)) {
+               ret = TRANSACTION_GENERIC_ERROR;
+               packed_refs_unlock(refs->packed_ref_store);
+               goto cleanup;
+       }
+
+       packed_refs_unlock(refs->packed_ref_store);
+
        /* Delete the reflogs of any references that were deleted: */
        for_each_string_list_item(ref_to_delete, &refs_to_delete) {
                strbuf_reset(&sb);
@@ -3181,9 +2692,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
                }
        }
 
-       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;
        }
@@ -3193,18 +2702,17 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
 
                if ((update->flags & REF_HAVE_NEW) &&
                    !is_null_oid(&update->new_oid))
-                       add_packed_ref(refs, update->refname,
+                       add_packed_ref(refs->packed_ref_store, update->refname,
                                       &update->new_oid);
        }
 
-       if (commit_packed_refs(refs)) {
-               strbuf_addf(err, "unable to commit packed-refs file: %s",
-                           strerror(errno));
+       if (commit_packed_refs(refs->packed_ref_store, err)) {
                ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
        }
 
 cleanup:
+       packed_refs_unlock(refs->packed_ref_store);
        transaction->state = REF_TRANSACTION_CLOSED;
        string_list_clear(&affected_refnames, 0);
        return ret;
@@ -3333,16 +2841,17 @@ static int files_reflog_expire(struct ref_store *ref_store,
                        !(type & REF_ISSYMREF) &&
                        !is_null_oid(&cb.last_kept_oid);
 
-               if (close_lock_file(&reflog_lock)) {
+               if (close_lock_file_gently(&reflog_lock)) {
                        status |= error("couldn't write %s: %s", log_file,
                                        strerror(errno));
+                       rollback_lock_file(&reflog_lock);
                } else if (update &&
-                          (write_in_full(get_lock_file_fd(lock->lk),
+                          (write_in_full(get_lock_file_fd(&lock->lk),
                                oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) != GIT_SHA1_HEXSZ ||
-                           write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 ||
-                           close_ref(lock) < 0)) {
+                           write_str_in_full(get_lock_file_fd(&lock->lk), "\n") != 1 ||
+                           close_ref_gently(lock) < 0)) {
                        status |= error("couldn't write %s",
-                                       get_lock_file_path(lock->lk));
+                                       get_lock_file_path(&lock->lk));
                        rollback_lock_file(&reflog_lock);
                } else if (commit_lock_file(&reflog_lock)) {
                        status |= error("unable to write reflog '%s' (%s)",