fix: restore release assets through a scratch file

This commit is contained in:
Abdessamad Derraz committed 2026-10-06 08:58:40 +02:00
1 parent 6c9a41327b
commit 098b2f0b70
2 files changed
+40 -2

No files matched your search

+20 -2
View File
@@ -15,6 +15,7 @@ import json
import os
import shutil
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
@@ -270,6 +271,24 @@ def _load_gitignored_large_files() -> set[str]:
}
def _restore(cached: str, path: str) -> None:
"""Copy a fetched asset to its registered path, whole or not at all.
An interrupted copy at the final name is a truncated file the next scan
hashes and publishes; a scratch beside it is swapped in only once full.
"""
directory = os.path.dirname(path)
os.makedirs(directory, exist_ok=True)
fd, scratch = tempfile.mkstemp(dir=directory, prefix=".restore-")
os.close(fd)
try:
shutil.copy2(cached, scratch)
os.replace(scratch, path)
finally:
if os.path.exists(scratch):
os.remove(scratch)
def _preserve_large_file_entries(files: dict, db_path: str) -> int:
"""Keep the entries of release assets the checkout does not hold.
@@ -309,8 +328,7 @@ def _preserve_large_file_entries(files: dict, db_path: str) -> int:
expected_md5=entry.get("md5", ""),
)
if cached and not os.path.exists(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
shutil.copy2(cached, path)
_restore(cached, path)
files[sha1] = entry
count += 1
return count
+20
View File
@@ -19,6 +19,7 @@ import threading
import unittest
import urllib.error
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
@@ -296,6 +297,25 @@ class PreservedLargeFileEntries(unittest.TestCase):
self.assertEqual(files["b" * 40]["path"], "bios/Sony/PS3/FW.PUP")
self.assertEqual((self.tmp / "bios/Sony/PS3/FW.PUP").read_bytes(), b"firmware")
def test_an_interrupted_restore_leaves_no_file_behind(self):
cache = self.tmp / "cache" / "FW.PUP"
cache.parent.mkdir()
cache.write_bytes(b"firmware")
common.fetch_large_file = lambda *_a, **_k: str(cache)
db_path = self._write_db(
{"b" * 40: {"name": "FW.PUP", "path": "bios/Sony/PS3/FW.PUP"}}
)
def interrupted(_src, dst, *_a, **_k):
Path(dst).write_bytes(b"firm")
raise KeyboardInterrupt
with mock.patch.object(self.generate_db.shutil, "copy2", interrupted), \
self.assertRaises(KeyboardInterrupt):
self.generate_db._preserve_large_file_entries({}, db_path)
directory = self.tmp / "bios/Sony/PS3"
self.assertEqual(list(directory.iterdir()), [])
def test_a_file_sharing_only_the_name_is_not_preserved(self):
common.fetch_large_file = lambda *_a, **_k: "/cache/large/FW.PUP"
db_path = self._write_db(