refs: introduce an iterator interface
[gitweb.git] / refs / files-backend.c
index bbf96ad83a5abc494fbbd99d2bd9cceef6252e97..395056b67598f84120a7fa9443fdf4328b1c0e20 100644 (file)
@@ -1,6 +1,7 @@
 #include "../cache.h"
 #include "../refs.h"
 #include "refs-internal.h"
+#include "../iterator.h"
 #include "../lockfile.h"
 #include "../object.h"
 #include "../dir.h"
@@ -513,21 +514,34 @@ static void sort_ref_dir(struct ref_dir *dir)
 }
 
 /*
- * Return true iff the reference described by entry can be resolved to
- * an object in the database.  Emit a warning if the referred-to
- * object does not exist.
+ * 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(struct ref_entry *entry)
+static int ref_resolves_to_object(const char *refname,
+                                 const struct object_id *oid,
+                                 unsigned int flags)
 {
-       if (entry->flag & REF_ISBROKEN)
+       if (flags & REF_ISBROKEN)
                return 0;
-       if (!has_sha1_file(entry->u.value.oid.hash)) {
-               error("%s does not point to a valid object!", entry->name);
+       if (!has_sha1_file(oid->hash)) {
+               error("%s does not point to a valid object!", refname);
                return 0;
        }
        return 1;
 }
 
+/*
+ * Return true if the reference described by entry can be resolved to
+ * an object in the database; otherwise, emit a warning and return
+ * false.
+ */
+static int entry_resolves_to_object(struct ref_entry *entry)
+{
+       return ref_resolves_to_object(entry->name,
+                                     &entry->u.value.oid, entry->flag);
+}
+
 /*
  * current_ref is a performance hack: when iterating over references
  * using the for_each_ref*() functions, current_ref is set to the
@@ -542,7 +556,7 @@ static struct ref_entry *current_ref;
 typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data);
 
 struct ref_entry_cb {
-       const char *base;
+       const char *prefix;
        int trim;
        int flags;
        each_ref_fn *fn;
@@ -559,11 +573,11 @@ static int do_one_ref(struct ref_entry *entry, void *cb_data)
        struct ref_entry *old_current_ref;
        int retval;
 
-       if (!starts_with(entry->name, data->base))
+       if (!starts_with(entry->name, data->prefix))
                return 0;
 
        if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
-             !ref_resolves_to_object(entry))
+             !entry_resolves_to_object(entry))
                return 0;
 
        /* Store the old value, in case this is a recursive call: */
@@ -691,6 +705,153 @@ static void prime_ref_dir(struct ref_dir *dir)
        }
 }
 
+/*
+ * A level in the reference hierarchy that is currently being iterated
+ * through.
+ */
+struct cache_ref_iterator_level {
+       /*
+        * The ref_dir being iterated over at this level. The ref_dir
+        * is sorted before being stored here.
+        */
+       struct ref_dir *dir;
+
+       /*
+        * The index of the current entry within dir (which might
+        * itself be a directory). If index == -1, then the iteration
+        * hasn't yet begun. If index == dir->nr, then the iteration
+        * through this level is over.
+        */
+       int index;
+};
+
+/*
+ * Represent an iteration through a ref_dir in the memory cache. The
+ * iteration recurses through subdirectories.
+ */
+struct cache_ref_iterator {
+       struct ref_iterator base;
+
+       /*
+        * The number of levels currently on the stack. This is always
+        * at least 1, because when it becomes zero the iteration is
+        * ended and this struct is freed.
+        */
+       size_t levels_nr;
+
+       /* The number of levels that have been allocated on the stack */
+       size_t levels_alloc;
+
+       /*
+        * A stack of levels. levels[0] is the uppermost level that is
+        * being iterated over in this iteration. (This is not
+        * necessary the top level in the references hierarchy. If we
+        * are iterating through a subtree, then levels[0] will hold
+        * the ref_dir for that subtree, and subsequent levels will go
+        * on from there.)
+        */
+       struct cache_ref_iterator_level *levels;
+};
+
+static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+       struct cache_ref_iterator *iter =
+               (struct cache_ref_iterator *)ref_iterator;
+
+       while (1) {
+               struct cache_ref_iterator_level *level =
+                       &iter->levels[iter->levels_nr - 1];
+               struct ref_dir *dir = level->dir;
+               struct ref_entry *entry;
+
+               if (level->index == -1)
+                       sort_ref_dir(dir);
+
+               if (++level->index == level->dir->nr) {
+                       /* This level is exhausted; pop up a level */
+                       if (--iter->levels_nr == 0)
+                               return ref_iterator_abort(ref_iterator);
+
+                       continue;
+               }
+
+               entry = dir->entries[level->index];
+
+               if (entry->flag & REF_DIR) {
+                       /* push down a level */
+                       ALLOC_GROW(iter->levels, iter->levels_nr + 1,
+                                  iter->levels_alloc);
+
+                       level = &iter->levels[iter->levels_nr++];
+                       level->dir = get_ref_dir(entry);
+                       level->index = -1;
+               } else {
+                       iter->base.refname = entry->name;
+                       iter->base.oid = &entry->u.value.oid;
+                       iter->base.flags = entry->flag;
+                       return ITER_OK;
+               }
+       }
+}
+
+static enum peel_status peel_entry(struct ref_entry *entry, int repeel);
+
+static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
+                                  struct object_id *peeled)
+{
+       struct cache_ref_iterator *iter =
+               (struct cache_ref_iterator *)ref_iterator;
+       struct cache_ref_iterator_level *level;
+       struct ref_entry *entry;
+
+       level = &iter->levels[iter->levels_nr - 1];
+
+       if (level->index == -1)
+               die("BUG: peel called before advance for cache iterator");
+
+       entry = level->dir->entries[level->index];
+
+       if (peel_entry(entry, 0))
+               return -1;
+       hashcpy(peeled->hash, entry->u.value.peeled.hash);
+       return 0;
+}
+
+static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+       struct cache_ref_iterator *iter =
+               (struct cache_ref_iterator *)ref_iterator;
+
+       free(iter->levels);
+       base_ref_iterator_free(ref_iterator);
+       return ITER_DONE;
+}
+
+static struct ref_iterator_vtable cache_ref_iterator_vtable = {
+       cache_ref_iterator_advance,
+       cache_ref_iterator_peel,
+       cache_ref_iterator_abort
+};
+
+static struct ref_iterator *cache_ref_iterator_begin(struct ref_dir *dir)
+{
+       struct cache_ref_iterator *iter;
+       struct ref_iterator *ref_iterator;
+       struct cache_ref_iterator_level *level;
+
+       iter = xcalloc(1, sizeof(*iter));
+       ref_iterator = &iter->base;
+       base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable);
+       ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
+
+       iter->levels_nr = 1;
+       level = &iter->levels[0];
+       level->index = -1;
+       level->dir = dir;
+
+       return ref_iterator;
+}
+
 struct nonmatching_ref_data {
        const struct string_list *skip;
        const char *conflicting_refname;
@@ -954,15 +1115,26 @@ static struct ref_cache *lookup_ref_cache(const char *submodule)
 
 /*
  * Return a pointer to a ref_cache for the specified submodule. For
- * the main repository, use submodule==NULL. The returned structure
- * will be allocated and initialized but not necessarily populated; it
- * should not be freed.
+ * the main repository, use submodule==NULL; such a call cannot fail.
+ * For a submodule, the submodule must exist and be a nonbare
+ * repository, otherwise return NULL.
+ *
+ * The returned structure will be allocated and initialized but not
+ * necessarily populated; it should not be freed.
  */
 static struct ref_cache *get_ref_cache(const char *submodule)
 {
        struct ref_cache *refs = lookup_ref_cache(submodule);
-       if (!refs)
-               refs = create_ref_cache(submodule);
+
+       if (!refs) {
+               struct strbuf submodule_sb = STRBUF_INIT;
+
+               strbuf_addstr(&submodule_sb, submodule);
+               if (is_nonbare_repository_dir(&submodule_sb))
+                       refs = create_ref_cache(submodule);
+               strbuf_release(&submodule_sb);
+       }
+
        return refs;
 }
 
@@ -1341,13 +1513,10 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sh
                return -1;
 
        strbuf_add(&submodule, path, len);
-       refs = lookup_ref_cache(submodule.buf);
+       refs = get_ref_cache(submodule.buf);
        if (!refs) {
-               if (!is_nonbare_repository_dir(&submodule)) {
-                       strbuf_release(&submodule);
-                       return -1;
-               }
-               refs = create_ref_cache(submodule.buf);
+               strbuf_release(&submodule);
+               return -1;
        }
        strbuf_release(&submodule);
 
@@ -1822,14 +1991,147 @@ int peel_ref(const char *refname, unsigned char *sha1)
        return peel_object(base, sha1);
 }
 
+struct files_ref_iterator {
+       struct ref_iterator base;
+
+       struct packed_ref_cache *packed_ref_cache;
+       struct ref_iterator *iter0;
+       unsigned int flags;
+};
+
+static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+       struct files_ref_iterator *iter =
+               (struct files_ref_iterator *)ref_iterator;
+       int ok;
+
+       while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
+               if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+                   !ref_resolves_to_object(iter->iter0->refname,
+                                           iter->iter0->oid,
+                                           iter->iter0->flags))
+                       continue;
+
+               iter->base.refname = iter->iter0->refname;
+               iter->base.oid = iter->iter0->oid;
+               iter->base.flags = iter->iter0->flags;
+               return ITER_OK;
+       }
+
+       iter->iter0 = NULL;
+       if (ref_iterator_abort(ref_iterator) != ITER_DONE)
+               ok = ITER_ERROR;
+
+       return ok;
+}
+
+static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,
+                                  struct object_id *peeled)
+{
+       struct files_ref_iterator *iter =
+               (struct files_ref_iterator *)ref_iterator;
+
+       return ref_iterator_peel(iter->iter0, peeled);
+}
+
+static int files_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+       struct files_ref_iterator *iter =
+               (struct files_ref_iterator *)ref_iterator;
+       int ok = ITER_DONE;
+
+       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;
+}
+
+static struct ref_iterator_vtable files_ref_iterator_vtable = {
+       files_ref_iterator_advance,
+       files_ref_iterator_peel,
+       files_ref_iterator_abort
+};
+
+struct ref_iterator *files_ref_iterator_begin(
+               const char *submodule,
+               const char *prefix, unsigned int flags)
+{
+       struct ref_cache *refs = get_ref_cache(submodule);
+       struct ref_dir *loose_dir, *packed_dir;
+       struct ref_iterator *loose_iter, *packed_iter;
+       struct files_ref_iterator *iter;
+       struct ref_iterator *ref_iterator;
+
+       if (!refs)
+               return empty_ref_iterator_begin();
+
+       if (ref_paranoia < 0)
+               ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0);
+       if (ref_paranoia)
+               flags |= DO_FOR_EACH_INCLUDE_BROKEN;
+
+       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
+        * condition if loose refs are migrated to the packed-refs
+        * file by a simultaneous process, but our in-memory view is
+        * from before the migration. We ensure this as follows:
+        * First, we call prime_ref_dir(), which pre-reads the loose
+        * references for the subtree into the cache. (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.
+        */
+
+       loose_dir = get_loose_refs(refs);
+
+       if (prefix && *prefix)
+               loose_dir = find_containing_dir(loose_dir, prefix, 0);
+
+       if (loose_dir) {
+               prime_ref_dir(loose_dir);
+               loose_iter = cache_ref_iterator_begin(loose_dir);
+       } else {
+               /* There's nothing to iterate over. */
+               loose_iter = empty_ref_iterator_begin();
+       }
+
+       iter->packed_ref_cache = get_packed_ref_cache(refs);
+       acquire_packed_ref_cache(iter->packed_ref_cache);
+       packed_dir = get_packed_ref_dir(iter->packed_ref_cache);
+
+       if (prefix && *prefix)
+               packed_dir = find_containing_dir(packed_dir, prefix, 0);
+
+       if (packed_dir) {
+               packed_iter = cache_ref_iterator_begin(packed_dir);
+       } else {
+               /* There's nothing to iterate over. */
+               packed_iter = empty_ref_iterator_begin();
+       }
+
+       iter->iter0 = overlay_ref_iterator_begin(loose_iter, packed_iter);
+       iter->flags = flags;
+
+       return ref_iterator;
+}
+
 /*
  * Call fn for each reference in the specified ref_cache, omitting
- * references not in the containing_dir of base.  fn is called for all
- * references, including broken ones.  If fn ever returns a non-zero
+ * references not in the containing_dir of prefix. Call fn for all
+ * references, including broken ones. If fn ever returns a non-zero
  * value, stop the iteration and return that value; otherwise, return
  * 0.
  */
-static int do_for_each_entry(struct ref_cache *refs, const char *base,
+static int do_for_each_entry(struct ref_cache *refs, const char *prefix,
                             each_ref_entry_fn fn, void *cb_data)
 {
        struct packed_ref_cache *packed_ref_cache;
@@ -1846,8 +2148,8 @@ static int do_for_each_entry(struct ref_cache *refs, const char *base,
         * disk.
         */
        loose_dir = get_loose_refs(refs);
-       if (base && *base) {
-               loose_dir = find_containing_dir(loose_dir, base, 0);
+       if (prefix && *prefix) {
+               loose_dir = find_containing_dir(loose_dir, prefix, 0);
        }
        if (loose_dir)
                prime_ref_dir(loose_dir);
@@ -1855,8 +2157,8 @@ static int do_for_each_entry(struct ref_cache *refs, const char *base,
        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);
+       if (prefix && *prefix) {
+               packed_dir = find_containing_dir(packed_dir, prefix, 0);
        }
 
        if (packed_dir && loose_dir) {
@@ -1878,23 +2180,17 @@ static int do_for_each_entry(struct ref_cache *refs, const char *base,
        return retval;
 }
 
-/*
- * Call fn for each reference in the specified ref_cache for which the
- * refname begins with base.  If trim is non-zero, then trim that many
- * characters off the beginning of each refname before passing the
- * refname to fn.  flags can be DO_FOR_EACH_INCLUDE_BROKEN to include
- * broken references in the iteration.  If fn ever returns a non-zero
- * value, stop the iteration and return that value; otherwise, return
- * 0.
- */
-int do_for_each_ref(const char *submodule, const char *base,
+int do_for_each_ref(const char *submodule, const char *prefix,
                    each_ref_fn fn, int trim, int flags, void *cb_data)
 {
        struct ref_entry_cb data;
        struct ref_cache *refs;
 
        refs = get_ref_cache(submodule);
-       data.base = base;
+       if (!refs)
+               return 0;
+
+       data.prefix = prefix;
        data.trim = trim;
        data.flags = flags;
        data.fn = fn;
@@ -1905,7 +2201,7 @@ int do_for_each_ref(const char *submodule, const char *base,
        if (ref_paranoia)
                data.flags |= DO_FOR_EACH_INCLUDE_BROKEN;
 
-       return do_for_each_entry(refs, base, do_one_ref, &data);
+       return do_for_each_entry(refs, prefix, do_one_ref, &data);
 }
 
 /*
@@ -2226,7 +2522,7 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
                return 0;
 
        /* Do not pack symbolic or broken refs: */
-       if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry))
+       if ((entry->flag & REF_ISSYMREF) || !entry_resolves_to_object(entry))
                return 0;
 
        /* Add a packed ref cache entry equivalent to the loose entry. */
@@ -2412,7 +2708,7 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
        return 0;
 }
 
-int delete_refs(struct string_list *refnames)
+int delete_refs(struct string_list *refnames, unsigned int flags)
 {
        struct strbuf err = STRBUF_INIT;
        int i, result = 0;
@@ -2441,7 +2737,7 @@ int delete_refs(struct string_list *refnames)
        for (i = 0; i < refnames->nr; i++) {
                const char *refname = refnames->items[i].string;
 
-               if (delete_ref(refname, NULL, 0))
+               if (delete_ref(refname, NULL, flags))
                        result |= error(_("could not remove reference %s"), refname);
        }