refs: do not invalidate the packed-refs cache unnecessarily
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index f33d22403ec7ec30a6a4125b9c66462f71262196..038e5c72d74bd11020a929204d136ccb076d4dc1 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -749,6 +749,21 @@ static int do_for_each_entry_in_dirs(struct ref_dir *dir1,
        }
 }
 
+/*
+ * Load all of the refs from the dir into our in-memory cache. The hard work
+ * of loading loose refs is done by get_ref_dir(), so we just need to recurse
+ * through all of the sub-directories. We do not even need to care about
+ * sorting, as traversal order does not matter to us.
+ */
+static void prime_ref_dir(struct ref_dir *dir)
+{
+       int i;
+       for (i = 0; i < dir->nr; i++) {
+               struct ref_entry *entry = dir->entries[i];
+               if (entry->flag & REF_DIR)
+                       prime_ref_dir(get_ref_dir(entry));
+       }
+}
 /*
  * Return true iff refname1 and refname2 conflict with each other.
  * Two reference names conflict if one of them exactly matches the
@@ -820,9 +835,14 @@ struct packed_ref_cache {
        /*
         * Iff the packed-refs file associated with this instance is
         * currently locked for writing, this points at the associated
-        * lock (which is owned by somebody else).
+        * lock (which is owned by somebody else).  The referrer count
+        * is also incremented when the file is locked and decremented
+        * when it is unlocked.
         */
        struct lock_file *lock;
+
+       /* The metadata from when this packed-refs cache was read */
+       struct stat_validity validity;
 };
 
 /*
@@ -860,6 +880,7 @@ static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
 {
        if (!--packed_refs->referrers) {
                free_ref_entry(packed_refs->root);
+               stat_validity_clear(&packed_refs->validity);
                free(packed_refs);
                return 1;
        } else {
@@ -1051,19 +1072,26 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
  */
 static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
 {
+       const char *packed_refs_file;
+
+       if (*refs->name)
+               packed_refs_file = git_path_submodule(refs->name, "packed-refs");
+       else
+               packed_refs_file = git_path("packed-refs");
+
+       if (refs->packed &&
+           !stat_validity_check(&refs->packed->validity, packed_refs_file))
+               clear_packed_ref_cache(refs);
+
        if (!refs->packed) {
-               const char *packed_refs_file;
                FILE *f;
 
                refs->packed = xcalloc(1, sizeof(*refs->packed));
                acquire_packed_ref_cache(refs->packed);
                refs->packed->root = create_dir_entry(refs, "", 0, 0);
-               if (*refs->name)
-                       packed_refs_file = git_path_submodule(refs->name, "packed-refs");
-               else
-                       packed_refs_file = git_path("packed-refs");
                f = fopen(packed_refs_file, "r");
                if (f) {
+                       stat_validity_update(&refs->packed->validity, fileno(f));
                        read_packed_refs(f, get_ref_dir(refs->packed->root));
                        fclose(f);
                }
@@ -1590,15 +1618,31 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
 static int do_for_each_entry(struct ref_cache *refs, const char *base,
                             each_ref_entry_fn fn, void *cb_data)
 {
-       struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(refs);
-       struct ref_dir *packed_dir = get_packed_ref_dir(packed_ref_cache);
-       struct ref_dir *loose_dir = get_loose_refs(refs);
+       struct packed_ref_cache *packed_ref_cache;
+       struct ref_dir *loose_dir;
+       struct ref_dir *packed_dir;
        int retval = 0;
 
+       /*
+        * We must make sure that all loose refs are read before accessing the
+        * packed-refs file; this avoids a race condition in which loose refs
+        * are migrated to the packed-refs file by a simultaneous process, but
+        * our in-memory view is from before the migration. get_packed_ref_cache()
+        * takes care of making sure our view is up to date with what is on
+        * disk.
+        */
+       loose_dir = get_loose_refs(refs);
+       if (base && *base) {
+               loose_dir = find_containing_dir(loose_dir, base, 0);
+       }
+       if (loose_dir)
+               prime_ref_dir(loose_dir);
+
+       packed_ref_cache = get_packed_ref_cache(refs);
        acquire_packed_ref_cache(packed_ref_cache);
+       packed_dir = get_packed_ref_dir(packed_ref_cache);
        if (base && *base) {
                packed_dir = find_containing_dir(packed_dir, base, 0);
-               loose_dir = find_containing_dir(loose_dir, base, 0);
        }
 
        if (packed_dir && loose_dir) {
@@ -2092,13 +2136,18 @@ int lock_packed_refs(int flags)
 {
        struct packed_ref_cache *packed_ref_cache;
 
-       /* Discard the old cache because it might be invalid: */
-       clear_packed_ref_cache(&ref_cache);
        if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
                return -1;
-       /* Read the current packed-refs while holding the lock: */
+       /*
+        * Get the current packed-refs while holding the lock.  If the
+        * packed-refs file has been modified since we last read it,
+        * this will automatically invalidate the cache and re-read
+        * the packed-refs file.
+        */
        packed_ref_cache = get_packed_ref_cache(&ref_cache);
        packed_ref_cache->lock = &packlock;
+       /* Increment the reference count to prevent it from being freed: */
+       acquire_packed_ref_cache(packed_ref_cache);
        return 0;
 }
 
@@ -2119,6 +2168,7 @@ int commit_packed_refs(void)
        if (commit_lock_file(packed_ref_cache->lock))
                error = -1;
        packed_ref_cache->lock = NULL;
+       release_packed_ref_cache(packed_ref_cache);
        return error;
 }
 
@@ -2131,6 +2181,7 @@ void rollback_packed_refs(void)
                die("internal error: packed-refs not locked");
        rollback_lock_file(packed_ref_cache->lock);
        packed_ref_cache->lock = NULL;
+       release_packed_ref_cache(packed_ref_cache);
        clear_packed_ref_cache(&ref_cache);
 }