From d6234163069e4ad44f6885234ac0fd36edcd292b Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Wed, 21 Oct 2026 20:49:32 +0200 Subject: [PATCH] Index: an empty listing is taken only when known index files are gone too --- lib/Repository/BlobIndex.php | 33 ++++++++++++++-------- tests/Unit/Repository/IndexListingTest.php | 16 +++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lib/Repository/BlobIndex.php b/lib/Repository/BlobIndex.php index e0d9c87..a14dd2c 100644 --- a/lib/Repository/BlobIndex.php +++ b/lib/Repository/BlobIndex.php @@ -99,7 +99,7 @@ final class BlobIndex { $this->downloaded = 0; $order = []; // pack ids in listing order: where a blob is in two packs, the last listed wins $fetch = []; - $known = $hadIndex || $cached !== [] || ($verify && ($this->cache?->all($this->repositoryId) ?? []) !== []); + $known = array_keys($this->loaded + $cached + ($verify ? ($this->cache?->all($this->repositoryId) ?? []) : [])); foreach ($this->listIndex($known) as $path) { $packId = basename($path); $present[$packId] = true; @@ -208,7 +208,7 @@ final class BlobIndex { public function refresh(): void { $fetch = []; $listed = 0; - foreach ($this->listIndex($this->loaded !== []) as $path) { + foreach ($this->listIndex(array_keys($this->loaded)) as $path) { $listed++; if (!isset($this->loaded[basename($path)])) { $fetch[] = $path; @@ -230,25 +230,36 @@ final class BlobIndex { } /** - * The index files on the location. An empty listing while index files are known ($known) is - * usually a connection that failed quietly; taken as is, it would empty the index and the - * cache, make a check report every blob missing, or make a cleanup wipe the cache. So the - * connection is checked (the repository's config must be there) and the folder listed once - * more: empty again on a working connection, the repository really is empty (another server - * removed its last snapshot and cleaned up). + * The index files on the location. An empty listing while index files are known ($known, + * pack ids) is usually a connection that failed quietly; taken as is, it would empty the + * index and the cache, make a check report every blob missing, or make a cleanup wipe the + * cache. So the connection is checked (the repository's config must be there) and the folder + * listed once more. Empty again, a few of the known index files are looked up one by one: + * only when they are gone too is the repository really empty (another server removed its + * last snapshot and cleaned up). * + * @param list $known * @return list * @throws BackendException when the location does not answer as it should */ - private function listIndex(bool $known): array { + private function listIndex(array $known): array { $paths = $this->backend->listFiles('index'); - if ($paths !== [] || !$known) { + if ($paths !== [] || $known === []) { return $paths; } if (!$this->backend->exists('config')) { throw new BackendException('The location listed no index files and has no repository config: not trusting that listing'); } - return $this->backend->listFiles('index'); + $paths = $this->backend->listFiles('index'); + if ($paths !== []) { + return $paths; + } + foreach (array_slice($known, 0, 3) as $packId) { + if ($this->backend->exists('index/' . $packId)) { + throw new BackendException('The location listed no index files, while index/' . $packId . ' is there: not trusting that listing'); + } + } + return []; } /** @return array> packId => blobs currently located in that pack */ diff --git a/tests/Unit/Repository/IndexListingTest.php b/tests/Unit/Repository/IndexListingTest.php index a0f3f97..1fade9a 100644 --- a/tests/Unit/Repository/IndexListingTest.php +++ b/tests/Unit/Repository/IndexListingTest.php @@ -115,6 +115,22 @@ class IndexListingTest extends TestCase { $this->assertSame(12, $again->restore($this->snapshot, $this->tempDir())); } + public function testAListingThatStaysEmptyWhileIndexFilesAreThereIsNotTaken(): void { + $cache = self::cache(); + $backend = new FlakyBackend(new LocalBackend($this->root)); + Repository::openWithKey($backend, $this->keys, $cache); + $cached = $cache->data; + $backend->emptyIndexListings = 5; // the config answers, the listing keeps lying + + try { + Repository::openWithKey($backend, $this->keys, $cache); + $this->fail('index files are there'); + } catch (BackendException $e) { + $this->assertStringContainsString('is there', $e->getMessage()); + } + $this->assertSame($cached, $cache->data); + } + public function testARepositoryThatReallyBecameEmptyIsTakenAsEmpty(): void { $cache = self::cache(); $backend = new FlakyBackend(new LocalBackend($this->root));