git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] refs/files: keep root refs out of loose ref cache

From
Andy Koppe <andy.koppe@gmail.com>
Date
Oct 14, 2026, 15:32 UTC
Message-ID
<20261014153208.2194727-1-andy.koppe@gmail.com>

With the files backend, root refs such as HEAD or ORIG_HEAD are only added to the loose ref cache if REFS_FOR_EACH_INCLUDE_ROOT_REFS is passed to the iteration that creates the cache. If the cache has already been created by an earlier iteration, root refs are silently omitted. Conversely, once they have been added, any later iteration also yields them, whether or not it asked for them. This has been the case since d0f00c1ac17 (refs: introduce `refs_for_each_include_root_refs()`).

For example, `git for-each-ref --include-root-refs --merged=main` omits root refs that are merged into main, because looking up the given commit loads the replace refs, which populates the loose ref cache first. The reftable backend is not affected.

Fix this by keeping the root refs out of the shared loose ref cache. Instead, read them into a separate ref cache whenever an iteration asks for them, and overlay its iterator on the loose ref iterator. The cache is freed when the iteration finishes.

Signed-off-by: Andy Koppe <andy.koppe@gmail.com>
---
 refs/files-backend.c           | 38 +++++++++++++++++++++++++++-------
 t/t6302-for-each-ref-filter.sh | 12 +++++++++++
 2 files changed, 42 insertions(+), 8 deletions(-)
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 71628550f2..74409878b6 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -481,8 +481,21 @@ static void add_root_refs(struct files_ref_store *refs,
 	for_each_root_ref(refs, fill_root_ref, &data);
 }
 
-static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs,
-					     unsigned int flags)
+/*
+ * Create a separate ref cache containing only the root refs. These are kept
+ * out of the loose ref cache, which may already exist and is shared by
+ * iterations that should not include them.
+ */
+static struct ref_cache *create_root_ref_cache(struct files_ref_store *refs)
+{
+	struct ref_cache *cache = create_ref_cache(&refs->base, NULL);
+
+	cache->root->flag &= ~REF_INCOMPLETE;
+	add_root_refs(refs, get_ref_dir(cache->root));
+	return cache;
+}
+
+static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs)
 {
 	if (!refs->loose) {
 		struct ref_dir *dir;
@@ -499,9 +512,6 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs,
 
 		dir = get_ref_dir(refs->loose->root);
 
-		if (flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS)
-			add_root_refs(refs, dir);
-
 		/*
 		 * Add an incomplete entry for "refs/" (to be filled
 		 * lazily):
@@ -1004,6 +1014,7 @@ struct files_ref_iterator {
 	struct ref_iterator base;
 
 	struct ref_iterator *iter0;
+	struct ref_cache *root_refs;
 	struct repository *repo;
 	unsigned int flags;
 };
@@ -1053,6 +1064,7 @@ static void files_ref_iterator_release(struct ref_iterator *ref_iterator)
 	struct files_ref_iterator *iter =
 		(struct files_ref_iterator *)ref_iterator;
 	ref_iterator_free(iter->iter0);
+	free_ref_cache(iter->root_refs);
 }
 
 static struct ref_iterator_vtable files_ref_iterator_vtable = {
@@ -1068,6 +1080,7 @@ static struct ref_iterator *files_ref_iterator_begin(
 {
 	struct files_ref_store *refs;
 	struct ref_iterator *loose_iter, *packed_iter, *overlay_iter;
+	struct ref_cache *root_refs = NULL;
 	struct files_ref_iterator *iter;
 	struct ref_iterator *ref_iterator;
 	unsigned int required_flags = REF_STORE_READ;
@@ -1094,9 +1107,17 @@ static struct ref_iterator *files_ref_iterator_begin(
 	 * disk, and re-reads it if not.
 	 */
 
-	loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs, flags),
+	loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs),
 					      prefix, ref_store->repo, 1);
 
+	if (flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS) {
+		root_refs = create_root_ref_cache(refs);
+		loose_iter = overlay_ref_iterator_begin(
+				cache_ref_iterator_begin(root_refs, prefix,
+							 ref_store->repo, 0),
+				loose_iter);
+	}
+
 	/*
 	 * The packed-refs file might contain broken references, for
 	 * example an old version of a reference that points at an
@@ -1118,6 +1139,7 @@ static struct ref_iterator *files_ref_iterator_begin(
 	ref_iterator = &iter->base;
 	base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable);
 	iter->iter0 = overlay_iter;
+	iter->root_refs = root_refs;
 	iter->repo = ref_store->repo;
 	iter->flags = flags;
 
@@ -1490,7 +1512,7 @@ static int should_pack_refs(struct files_ref_store *refs,
 	if (limit < 16)
 		limit = 16;
 
-	iter = cache_ref_iterator_begin(get_loose_ref_cache(refs, 0), NULL,
+	iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL,
 					refs->base.repo, 0);
 	while ((ret = ref_iterator_advance(iter)) == ITER_OK) {
 		if (should_pack_ref(refs, &iter->ref, opts))
@@ -1530,7 +1552,7 @@ static int files_optimize(struct ref_store *ref_store,
 
 	packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err);
 
-	iter = cache_ref_iterator_begin(get_loose_ref_cache(refs, 0), NULL,
+	iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL,
 					refs->base.repo, 0);
 	while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
 		/*
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
index 7f060d97bf..a2fc257f98 100755
--- a/t/t6302-for-each-ref-filter.sh
+++ b/t/t6302-for-each-ref-filter.sh
@@ -96,6 +96,18 @@ test_expect_success '--include-root-refs omits dangling symrefs' '
 	)
 '
 
+test_expect_success '--include-root-refs with --merged' '
+	cat >expect <<-\EOF &&
+	HEAD
+	ORIG_HEAD
+	EOF
+	test_when_finished "git update-ref -d ORIG_HEAD" &&
+	git update-ref ORIG_HEAD main &&
+	git for-each-ref --format="%(refname)" --include-root-refs \
+		--merged=main "*HEAD" >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'filtering with --points-at' '
 	cat >expect <<-\EOF &&
 	refs/heads/main
-- 
2.56.0
Next: Patrick Steinhardt
Message 1 of 13 in “refs/files: keep root refs out of loose ref cache”
  1. refs/files: keep root refs out of loose ref cacheAndy Koppe, Oct 14, 2026
  2. Patrick SteinhardtOct 15, 2026
  3. Junio C HamanoOct 15, 2026
  4. Andy KoppeOct 15, 2026
  5. Patrick SteinhardtOct 16, 2026
  6. Junio C HamanoOct 16, 2026
  7. refs/files: keep root refs out of loose ref cacheAndy Koppe, Oct 17, 2026
  8. Patrick SteinhardtOct 19, 2026
  9. Andy KoppeOct 19, 2026
  10. refs/files: keep root refs out of loose ref cacheAndy Koppe, Oct 19, 2026
  11. Patrick SteinhardtOct 20, 2026
  12. Junio C HamanoOct 21, 2026
  13. Patrick SteinhardtOct 21, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.