From 3766588ec1236fad2429a5bb4517d3a29c7c853b Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:16:28 +0200 Subject: [PATCH] fix: keep tree entries out of the asset store --- scripts/largefiles.py | 6 ++++++ scripts/packresolve.py | 4 ++++ tests/test_audit_regressions.py | 23 +++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/scripts/largefiles.py b/scripts/largefiles.py index fd52a015..b0eb21b8 100644 --- a/scripts/largefiles.py +++ b/scripts/largefiles.py @@ -126,6 +126,10 @@ def fetch_large_file( turns it into the asset to fetch, and a bare name shared by several registered paths tries each of their assets until one verifies. """ + if not name or name.endswith("/"): + # A directory entry names a tree, never one asset: asked for + # "caesar3/", the store answered with .cache/large/ itself. + return None if registered is None: registered = load_registered_paths() for asset in asset_candidates(name, registered): @@ -153,6 +157,8 @@ def _fetch_asset( with contextlib.suppress(FileNotFoundError): os.unlink(path) + if os.path.isdir(cached): + return None if os.path.exists(cached): try: hashes = compute_hashes(cached) if (expected_sha1 or expected_md5) else {} diff --git a/scripts/packresolve.py b/scripts/packresolve.py index 67267a2c..cf16c4bd 100644 --- a/scripts/packresolve.py +++ b/scripts/packresolve.py @@ -210,6 +210,10 @@ def resolve_file( # Large files from GitHub release assets -tried when local file is # missing OR has a hash mismatch (wrong variant on disk) name = file_entry.get("name", "") + if file_entry.get("type") == "directory" or name.endswith("/"): + # A tree the collection does not hold is missing, as verify says; + # the asset store holds files, not trees. + return (path, status) if path else (None, "not_found") declared = _declared_hashes(file_entry) if file_entry.get("unsourceable") and not any(declared.values()): # The asset store is addressed by file name: with no hash to check, diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index 7fa25dc6..7a67ba4f 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -3780,5 +3780,28 @@ class SignaturesCarryTheirTypes(unittest.TestCase): self.assertEqual(self._missing(ROOT / "scripts" / name), []) + +class ATreeNobodyHoldsIsMissingNotAnAsset(unittest.TestCase): + """julius's caesar3/ directory, unexpanded, went to the asset store, + which answered with .cache/large/ itself: the emulator pack died on + IsADirectoryError where verify said MISSING.""" + + def test_directory_entries_never_reach_the_asset_store(self): + from scripts import largefiles, packresolve + + db = {"files": {}, "indexes": {"by_name": {}, "by_md5": {}, "by_crc32": {}, + "by_path_suffix": {}}} + for entry in ({"name": "caesar3/", "type": "directory"}, {"name": "caesar3/"}): + with self.subTest(entry=entry): + self.assertEqual( + packresolve.resolve_file(entry, db, "bios", {}, offline=True), + (None, "not_found"), + ) + self.assertIsNone(largefiles.fetch_large_file("caesar3/", offline=True)) + with tempfile.TemporaryDirectory(dir=TMP_ROOT) as tmp: + Path(tmp, "sub").mkdir() + self.assertIsNone(largefiles._fetch_asset("sub", tmp, "", "", offline=True)) + + if __name__ == "__main__": unittest.main()