Merge branch 'mh/packed-ref-store'
[git/git.git] / refs / packed-backend.c
index 4676dc3..412c850 100644 (file)
@@ -1,4 +1,5 @@
 #include "../cache.h"
+#include "../config.h"
 #include "../refs.h"
 #include "refs-internal.h"
 #include "ref-cache.h"
@@ -68,6 +69,13 @@ struct packed_ref_store {
         * thus the enclosing `packed_ref_store`) must not be freed.
         */
        struct lock_file lock;
+
+       /*
+        * Temporary file used when rewriting new contents to the
+        * "packed-refs" file. Note that this (and thus the enclosing
+        * `packed_ref_store`) must not be freed.
+        */
+       struct tempfile tempfile;
 };
 
 struct ref_store *packed_ref_store_create(const char *path,
@@ -126,8 +134,6 @@ static void clear_packed_ref_cache(struct packed_ref_store *refs)
        if (refs->cache) {
                struct packed_ref_cache *cache = refs->cache;
 
-               if (is_lock_file_locked(&refs->lock))
-                       die("BUG: packed-ref cache cleared while locked");
                refs->cache = NULL;
                release_packed_ref_cache(cache);
        }
@@ -224,6 +230,9 @@ static struct packed_ref_cache *read_packed_refs(const char *packed_refs_file)
                const char *refname;
                const char *traits;
 
+               if (!line.len || line.buf[line.len - 1] != '\n')
+                       die("unterminated line in %s: %s", packed_refs_file, line.buf);
+
                if (skip_prefix(line.buf, "# pack-refs with:", &traits)) {
                        if (strstr(traits, " fully-peeled "))
                                peeled = PEELED_FULLY;
@@ -248,9 +257,7 @@ static struct packed_ref_cache *read_packed_refs(const char *packed_refs_file)
                            (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
                                last->flag |= REF_KNOWS_PEELED;
                        add_ref_entry(dir, last);
-                       continue;
-               }
-               if (last &&
+               } else if (last &&
                    line.buf[0] == '^' &&
                    line.len == PEELED_LINE_LENGTH &&
                    line.buf[PEELED_LINE_LENGTH - 1] == '\n' &&
@@ -262,6 +269,9 @@ static struct packed_ref_cache *read_packed_refs(const char *packed_refs_file)
                         * reference:
                         */
                        last->flag |= REF_KNOWS_PEELED;
+               } else {
+                       strbuf_setlen(&line, line.len - 1);
+                       die("unexpected line in %s: %s", packed_refs_file, line.buf);
                }
        }
 
@@ -314,7 +324,7 @@ static struct ref_dir *get_packed_refs(struct packed_ref_store *refs)
 /*
  * Add or overwrite a reference in 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,
+ * (see packed_refs_lock()). To actually write the packed-refs file,
  * call commit_packed_refs().
  */
 void add_packed_ref(struct ref_store *ref_store,
@@ -493,22 +503,26 @@ static struct ref_iterator *packed_ref_iterator_begin(
 
 /*
  * 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.
+ * If peeled is non-NULL, write it as the entry's peeled value. On
+ * error, return a nonzero value and leave errno set at the value left
+ * by the failing call to `fprintf()`.
  */
-static void write_packed_entry(FILE *fh, const char *refname,
-                              const unsigned char *sha1,
-                              const unsigned char *peeled)
+static int 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));
+       if (fprintf(fh, "%s %s\n", sha1_to_hex(sha1), refname) < 0 ||
+           (peeled && fprintf(fh, "^%s\n", sha1_to_hex(peeled)) < 0))
+               return -1;
+
+       return 0;
 }
 
-int lock_packed_refs(struct ref_store *ref_store, int flags)
+int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
 {
        struct packed_ref_store *refs =
                packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN,
-                               "lock_packed_refs");
+                               "packed_refs_lock");
        static int timeout_configured = 0;
        static int timeout_value = 1000;
        struct packed_ref_cache *packed_ref_cache;
@@ -518,11 +532,23 @@ int lock_packed_refs(struct ref_store *ref_store, int flags)
                timeout_configured = 1;
        }
 
+       /*
+        * Note that we close the lockfile immediately because we
+        * don't write new content to it, but rather to a separate
+        * tempfile.
+        */
        if (hold_lock_file_for_update_timeout(
                            &refs->lock,
                            refs->path,
-                           flags, timeout_value) < 0)
+                           flags, timeout_value) < 0) {
+               unable_to_lock_message(refs->path, errno, err);
                return -1;
+       }
+
+       if (close_lock_file(&refs->lock)) {
+               strbuf_addf(err, "unable to close %s: %s", refs->path, strerror(errno));
+               return -1;
+       }
 
        /*
         * Now that we hold the `packed-refs` lock, make sure that our
@@ -540,6 +566,29 @@ int lock_packed_refs(struct ref_store *ref_store, int flags)
        return 0;
 }
 
+void packed_refs_unlock(struct ref_store *ref_store)
+{
+       struct packed_ref_store *refs = packed_downcast(
+                       ref_store,
+                       REF_STORE_READ | REF_STORE_WRITE,
+                       "packed_refs_unlock");
+
+       if (!is_lock_file_locked(&refs->lock))
+               die("BUG: packed_refs_unlock() called when not locked");
+       rollback_lock_file(&refs->lock);
+       release_packed_ref_cache(refs->cache);
+}
+
+int packed_refs_is_locked(struct ref_store *ref_store)
+{
+       struct packed_ref_store *refs = packed_downcast(
+                       ref_store,
+                       REF_STORE_READ | REF_STORE_WRITE,
+                       "packed_refs_is_locked");
+
+       return is_lock_file_locked(&refs->lock);
+}
+
 /*
  * The packed-refs header line that we write out.  Perhaps other
  * traits will be added later.  The trailing space is required.
@@ -550,73 +599,99 @@ static const char PACKED_REFS_HEADER[] =
 /*
  * 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.
+ * packed_refs_lock()). Return zero on success. On errors, rollback
+ * the lockfile, write an error message to `err`, and return a nonzero
+ * value.
  */
-int commit_packed_refs(struct ref_store *ref_store)
+int commit_packed_refs(struct ref_store *ref_store, struct strbuf *err)
 {
        struct packed_ref_store *refs =
                packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN,
                                "commit_packed_refs");
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(refs);
-       int ok, error = 0;
-       int save_errno = 0;
+       int ok;
+       int ret = -1;
+       struct strbuf sb = STRBUF_INIT;
        FILE *out;
        struct ref_iterator *iter;
+       char *packed_refs_path;
 
        if (!is_lock_file_locked(&refs->lock))
-               die("BUG: packed-refs not locked");
+               die("BUG: commit_packed_refs() called when unlocked");
 
-       out = fdopen_lock_file(&refs->lock, "w");
-       if (!out)
-               die_errno("unable to fdopen packed-refs descriptor");
+       /*
+        * If packed-refs is a symlink, we want to overwrite the
+        * symlinked-to file, not the symlink itself. Also, put the
+        * staging file next to it:
+        */
+       packed_refs_path = get_locked_file_path(&refs->lock);
+       strbuf_addf(&sb, "%s.new", packed_refs_path);
+       if (create_tempfile(&refs->tempfile, sb.buf) < 0) {
+               strbuf_addf(err, "unable to create file %s: %s",
+                           sb.buf, strerror(errno));
+               strbuf_release(&sb);
+               goto out;
+       }
+       strbuf_release(&sb);
 
-       fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
+       out = fdopen_tempfile(&refs->tempfile, "w");
+       if (!out) {
+               strbuf_addf(err, "unable to fdopen packed-refs tempfile: %s",
+                           strerror(errno));
+               goto error;
+       }
+
+       if (fprintf(out, "%s", PACKED_REFS_HEADER) < 0) {
+               strbuf_addf(err, "error writing to %s: %s",
+                           get_tempfile_path(&refs->tempfile), strerror(errno));
+               goto error;
+       }
 
        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 (write_packed_entry(out, iter->refname, iter->oid->hash,
+                                      peel_error ? NULL : peeled.hash)) {
+                       strbuf_addf(err, "error writing to %s: %s",
+                                   get_tempfile_path(&refs->tempfile),
+                                   strerror(errno));
+                       ref_iterator_abort(iter);
+                       goto error;
+               }
        }
 
-       if (ok != ITER_DONE)
-               die("error while iterating over references");
+       if (ok != ITER_DONE) {
+               strbuf_addf(err, "unable to rewrite packed-refs file: "
+                           "error iterating over old contents");
+               goto error;
+       }
 
-       if (commit_lock_file(&refs->lock)) {
-               save_errno = errno;
-               error = -1;
+       if (rename_tempfile(&refs->tempfile, packed_refs_path)) {
+               strbuf_addf(err, "error replacing %s: %s",
+                           refs->path, strerror(errno));
+               goto out;
        }
-       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 packed_ref_store *refs)
-{
-       struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(refs);
+       ret = 0;
+       goto out;
 
-       packed_assert_main_repository(refs, "rollback_packed_refs");
+error:
+       delete_tempfile(&refs->tempfile);
 
-       if (!is_lock_file_locked(&refs->lock))
-               die("BUG: packed-refs not locked");
-       rollback_lock_file(&refs->lock);
-       release_packed_ref_cache(packed_ref_cache);
-       clear_packed_ref_cache(refs);
+out:
+       free(packed_refs_path);
+       return ret;
 }
 
 /*
  * 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.
+ * message to 'err', and return a nonzero value. The packed refs lock
+ * must be held when calling this function; it will still be held when
+ * the function returns.
  *
  * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
  */
@@ -628,11 +703,14 @@ int repack_without_refs(struct ref_store *ref_store,
                                "repack_without_refs");
        struct ref_dir *packed;
        struct string_list_item *refname;
-       int ret, needs_repacking = 0, removed = 0;
+       int needs_repacking = 0, removed = 0;
 
        packed_assert_main_repository(refs, "repack_without_refs");
        assert(err);
 
+       if (!is_lock_file_locked(&refs->lock))
+               die("BUG: repack_without_refs called without holding lock");
+
        /* Look for a packed ref */
        for_each_string_list_item(refname, refnames) {
                if (get_packed_ref(refs, refname->string)) {
@@ -645,10 +723,6 @@ int repack_without_refs(struct ref_store *ref_store,
        if (!needs_repacking)
                return 0; /* no refname exists in packed refs */
 
-       if (lock_packed_refs(&refs->base, 0)) {
-               unable_to_lock_message(refs->path, errno, err);
-               return -1;
-       }
        packed = get_packed_refs(refs);
 
        /* Remove refnames from the cache */
@@ -660,16 +734,12 @@ int repack_without_refs(struct ref_store *ref_store,
                 * All packed entries disappeared while we were
                 * acquiring the lock.
                 */
-               rollback_packed_refs(refs);
+               clear_packed_ref_cache(refs);
                return 0;
        }
 
        /* Write what remains */
-       ret = commit_packed_refs(&refs->base);
-       if (ret)
-               strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
-                           strerror(errno));
-       return ret;
+       return commit_packed_refs(&refs->base, err);
 }
 
 static int packed_init_db(struct ref_store *ref_store, struct strbuf *err)