Subject: [PATCH] fsck: do not loop infinitely when processing packs

From: "brian m. carlson" <[email-masked]>
Date: Sun, 22 Feb 2026 18:37:10 +0000

When we iterate over our packfiles in the fsck code, we do so twice.
The first time, we count the number of objects in all of the packs
together and later on, we iterate a second time, processing each pack
and verifying its integrity.

This would normally work fine, but if we have two packs and we're
processing the second, the verification process will open the pack to
read from it, which will place it at the beginning of the most recently
used list.  Since this same list is used for iteration, the pack we most
recently processed before this will then be behind the current pack in
the linked list, so when we next process the list, we will go back to
the first pack again and then loop forever.  This also makes our
progress indicator loop up to many thousands of percent, which is not
only nonsensical, but a clear indication that something has gone wrong.

Solve this by skipping our MRU updates when we're iterating over
packfiles, which avoids the reordering that causes problems.

Signed-off-by: brian m. carlson <[email-masked]>
---
I realize that t1050 may seem like a bizarre place to put this test.
However, I was debugging my sha256-interop branch and why the final test
calling `git fsck` was failing, so I placed a `git fsck` earlier in the
test to double-check and discovered the problem.  Since we already have
a natural testcase here, I thought I'd just place the test where we
already know it will trigger the problem.

 packfile.h       | 16 ++++++++++++++--
 t/t1050-large.sh |  4 ++++
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/packfile.h b/packfile.h
index acc5c55ad5..086d98c1a0 100644
--- a/packfile.h
+++ b/packfile.h
@@ -183,6 +183,7 @@ struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *stor
 struct repo_for_each_pack_data {
 	struct odb_source *source;
 	struct packfile_list_entry *entry;
+	struct repository *repo;
 };
 
 static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct repository *repo)
@@ -191,8 +192,13 @@ static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct
 
 	odb_prepare_alternates(repo->objects);
 
+	data.repo = repo;
+
 	for (struct odb_source *source = repo->objects->sources; source; source = source->next) {
-		struct packfile_list_entry *entry = packfile_store_get_packs(source->packfiles);
+		struct packfile_list_entry *entry;
+
+		source->packfiles->skip_mru_updates = true;
+		entry = packfile_store_get_packs(source->packfiles);
 		if (!entry)
 			continue;
 		data.source = source;
@@ -212,7 +218,10 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *
 		return;
 
 	for (source = data->source->next; source; source = source->next) {
-		struct packfile_list_entry *entry = packfile_store_get_packs(source->packfiles);
+		struct packfile_list_entry *entry;
+
+		source->packfiles->skip_mru_updates = true;
+		entry = packfile_store_get_packs(source->packfiles);
 		if (!entry)
 			continue;
 		data->source = source;
@@ -220,6 +229,9 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *
 		return;
 	}
 
+	for (struct odb_source *source = data->repo->objects->sources; source; source = source->next)
+		source->packfiles->skip_mru_updates = false;
+
 	data->source = NULL;
 	data->entry = NULL;
 }
diff --git a/t/t1050-large.sh b/t/t1050-large.sh
index 5be273611a..75e75e627c 100755
--- a/t/t1050-large.sh
+++ b/t/t1050-large.sh
@@ -160,6 +160,10 @@ test_expect_success 'hash-object' '
 	git hash-object large1
 '
 
+test_expect_success 'fsck does not loop forever' '
+	git fsck
+'
+
 test_expect_success 'cat-file a large file' '
 	git cat-file blob :large1 >/dev/null
 '