feat(operations): cargo cache force purge ("empty all")
purge_cargo_cache gains force: bool. The default age-based purge only ever removed entries past the configured retention window, which read as a lie when a user expected "purge" to clear everything -- force=True sets the cutoff a day in the future so every existing entry is treated as expired, backing the web UI's explicit "Empty all" action. A disabled cache stays untouched either way.
This commit is contained in:
@@ -94,19 +94,33 @@ def cargo_cache_status(config: Config) -> CacheStatus:
|
|||||||
|
|
||||||
|
|
||||||
def purge_cargo_cache(
|
def purge_cargo_cache(
|
||||||
config: Config, *, retention_days: int | None = None, dry_run: bool = False
|
config: Config,
|
||||||
|
*,
|
||||||
|
retention_days: int | None = None,
|
||||||
|
force: bool = False,
|
||||||
|
dry_run: bool = False,
|
||||||
) -> PurgeResult:
|
) -> PurgeResult:
|
||||||
"""Remove cache entries older than ``retention_days`` (or config's).
|
"""Remove cache entries older than ``retention_days`` (or config's).
|
||||||
|
|
||||||
Sizes the victims before deleting (a real prune makes their bytes
|
Sizes the victims before deleting (a real prune makes their bytes
|
||||||
unreadable), so ``freed_bytes`` is accurate even for a real purge.
|
unreadable), so ``freed_bytes`` is accurate even for a real purge.
|
||||||
A disabled cache is left untouched and reports an all-zero result.
|
A disabled cache is left untouched and reports an all-zero result.
|
||||||
|
|
||||||
|
``force=True`` ignores the retention window entirely and empties the whole
|
||||||
|
cache (every entry, regardless of age) -- this backs the web UI's explicit
|
||||||
|
"Empty all" action, as opposed to the default age-based purge. A disabled
|
||||||
|
cache is still left untouched even under ``force``.
|
||||||
"""
|
"""
|
||||||
if not config.cargo_cache.enabled:
|
if not config.cargo_cache.enabled:
|
||||||
return PurgeResult(removed_entries=0, freed_bytes=0, dry_run=dry_run)
|
return PurgeResult(removed_entries=0, freed_bytes=0, dry_run=dry_run)
|
||||||
|
|
||||||
days = retention_days if retention_days is not None else config.cargo_cache.retention_days
|
if force:
|
||||||
cutoff = datetime.now(tz=UTC) - timedelta(days=days)
|
# A cutoff in the future makes every existing entry (mtime <= now)
|
||||||
|
# older than it, so the age-based prune removes the entire cache.
|
||||||
|
cutoff = datetime.now(tz=UTC) + timedelta(days=1)
|
||||||
|
else:
|
||||||
|
days = retention_days if retention_days is not None else config.cargo_cache.retention_days
|
||||||
|
cutoff = datetime.now(tz=UTC) - timedelta(days=days)
|
||||||
store = CargoCacheStore(config.cargo_cache.root)
|
store = CargoCacheStore(config.cargo_cache.root)
|
||||||
|
|
||||||
doomed = store.prune_older_than(cutoff, what_if=True)
|
doomed = store.prune_older_than(cutoff, what_if=True)
|
||||||
|
|||||||
@@ -178,6 +178,41 @@ def test_purge_cargo_cache_nothing_to_purge(tmp_path: Path) -> None:
|
|||||||
assert fresh.exists()
|
assert fresh.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_purge_cargo_cache_force_removes_everything_regardless_of_age(tmp_path: Path) -> None:
|
||||||
|
root = tmp_path / "cache" / "cargo"
|
||||||
|
fresh = _seed_entry(root, "new", age_days=0) # brand-new, well within retention
|
||||||
|
mid = _seed_entry(root, "mid", age_days=1)
|
||||||
|
config = _make_config(root, retention_days=30) # retention alone would spare both
|
||||||
|
|
||||||
|
result = purge_cargo_cache(config, force=True)
|
||||||
|
|
||||||
|
assert result.removed_entries == 2
|
||||||
|
assert not fresh.exists()
|
||||||
|
assert not mid.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_purge_cargo_cache_force_dry_run_counts_all_but_keeps_files(tmp_path: Path) -> None:
|
||||||
|
root = tmp_path / "cache" / "cargo"
|
||||||
|
fresh = _seed_entry(root, "new", age_days=0, size=2 * 1024 * 1024)
|
||||||
|
config = _make_config(root, retention_days=30)
|
||||||
|
|
||||||
|
result = purge_cargo_cache(config, force=True, dry_run=True)
|
||||||
|
|
||||||
|
assert result == PurgeResult(removed_entries=1, freed_bytes=2 * 1024 * 1024, dry_run=True)
|
||||||
|
assert fresh.exists(), "force dry_run must not delete anything"
|
||||||
|
|
||||||
|
|
||||||
|
def test_purge_cargo_cache_force_disabled_still_zero(tmp_path: Path) -> None:
|
||||||
|
root = tmp_path / "cache" / "cargo"
|
||||||
|
old = _seed_entry(root, "old", age_days=1)
|
||||||
|
config = _make_config(root, enabled=False, retention_days=30)
|
||||||
|
|
||||||
|
result = purge_cargo_cache(config, force=True)
|
||||||
|
|
||||||
|
assert result == PurgeResult(removed_entries=0, freed_bytes=0, dry_run=False)
|
||||||
|
assert old.exists()
|
||||||
|
|
||||||
|
|
||||||
def test_purge_cargo_cache_missing_root_is_not_an_error(tmp_path: Path) -> None:
|
def test_purge_cargo_cache_missing_root_is_not_an_error(tmp_path: Path) -> None:
|
||||||
root = tmp_path / "cache" / "cargo"
|
root = tmp_path / "cache" / "cargo"
|
||||||
config = _make_config(root, retention_days=30)
|
config = _make_config(root, retention_days=30)
|
||||||
|
|||||||
Reference in New Issue
Block a user