fix(cleanup): remove backup handling and references from apply-zip and related components

This commit is contained in:
akadeepesh
2026-09-22 02:32:37 +05:30
parent c21079d295
commit ad47ab87f1
7 changed files with 54 additions and 84 deletions
+1 -6
View File
@@ -354,8 +354,6 @@ def apply_zip(
classify every file as new, modified, unchanged, or one with no safe
baseline (locally edited since zipping, or never part of the original
manifest). Only adds and modifies files — nothing is ever deleted.
Every overwritten file is backed up first, under
``.contextzip/backups/<timestamp>/``.
Unlike the CLI, this applies immediately and does not prompt — check
``contextzip.applier.build_plan()`` yourself first if you want to
@@ -375,8 +373,7 @@ def apply_zip(
Returns
-------
ApplyResult
``written`` (list of relative paths written), ``backup_dir``
(``Path`` or ``None`` if nothing needed backing up), and
``written`` (list of relative paths written) and
``applied_zip_path`` (where the consumed zip ended up).
Raises
@@ -393,8 +390,6 @@ def apply_zip(
result = apply_zip() # picks up .contextzip/inbox/*.zip
print(f"Wrote {len(result.written)} files")
if result.backup_dir:
print(f"Backup at {result.backup_dir}")
"""
from contextzip.applier import (
ApplyError,
+9 -33
View File
@@ -48,7 +48,6 @@ from pathlib import Path
_MANIFEST_SUFFIX = ".manifest.json"
_INBOX_DIRNAME = "inbox"
_APPLIED_DIRNAME = "applied"
_BACKUPS_DIRNAME = "backups"
_OUTPUT_DIRNAME = "output" # must match packager._OUTPUT_DIRNAME
# Files contextzip itself writes into outgoing ZIPs that should never be
@@ -146,7 +145,6 @@ class ApplyPlan:
@dataclass
class ApplyResult:
written: list[str]
backup_dir: Path | None
applied_zip_path: Path
@@ -647,29 +645,6 @@ def discard_plan(plan: ApplyPlan) -> None:
# ---------------------------------------------------------------------------
def _backup_entries(
entries: list[ApplyEntry], project_dir: Path, workspace_root: Path
) -> Path | None:
"""
Copy the current, pre-apply version of every existing file about to be
touched into .contextzip/backups/<timestamp>/, preserving relative
paths. Files that don't exist yet locally (new files) have nothing to
back up. Returns the backup directory, or None if nothing needed one.
"""
to_backup = [e for e in entries if (project_dir / e.rel_path).is_file()]
if not to_backup:
return None
stamp = time.strftime("%Y%m%d-%H%M%S")
backup_dir = workspace_root / _BACKUPS_DIRNAME / stamp
for entry in to_backup:
src = project_dir / entry.rel_path
dst = backup_dir / entry.rel_path
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
return backup_dir
def _move_zip_to_applied(
zip_path: Path, project_dir: Path, workspace_root: Path, retain: int = 1
) -> Path:
@@ -716,9 +691,14 @@ def execute_plan(plan: ApplyPlan, project_dir: Path, retain: int = 1) -> ApplyRe
"""
Write every non-unchanged entry from *plan* into the project.
Backs up whatever's about to be overwritten first. Assumes the caller
has already decided to proceed — dry-run and confirmation prompts are
handled upstream (see cli.py's `apply-zip` command).
Assumes the caller has already decided to proceed — dry-run and
confirmation prompts are handled upstream (see cli.py's `apply-zip`
command). Files are overwritten directly, with no backup taken first:
`build_plan`'s manifest diff and the confirmation prompt it drives
(see `ApplyPlan.is_risky`) are the safety net here, and the zip you
just applied is itself always recoverable — it's archived to
`.contextzip/inbox/applied/` (or left wherever you pointed at it)
rather than deleted.
*retain* caps how many applied zips are kept in
.contextzip/inbox/applied/ — typically a project's
@@ -727,8 +707,6 @@ def execute_plan(plan: ApplyPlan, project_dir: Path, retain: int = 1) -> ApplyRe
workspace_root = _workspace_dir(project_dir)
to_write = plan.writable_entries
backup_dir = _backup_entries(to_write, project_dir, workspace_root)
written: list[str] = []
for entry in to_write:
dest = project_dir / entry.rel_path
@@ -741,6 +719,4 @@ def execute_plan(plan: ApplyPlan, project_dir: Path, retain: int = 1) -> ApplyRe
)
shutil.rmtree(plan.extraction_dir, ignore_errors=True)
return ApplyResult(
written=written, backup_dir=backup_dir, applied_zip_path=applied_zip_path
)
return ApplyResult(written=written, applied_zip_path=applied_zip_path)
+24 -24
View File
@@ -2,21 +2,16 @@
cleanup.py — Automatic workspace housekeeping for contextzip.
.contextzip/ accumulates disposable artifacts over time: a zip+manifest+report
set every run, a timestamped backup folder every risky `apply-zip`, and (until
pruned) old archived applied-zips. None of it is precious — every zip is
trivially reproducible by just re-running contextzip, and a backup only
matters until the apply that made it is confirmed good. So cleanup here is
deliberately brutal rather than cautious: it runs automatically after every
successful command (see `_auto_cleanup` in cli.py), with no prompt and no
separate command to remember, and keeps only the most recent
`cleanup.keep_recent` item(s) in each category — everything else is deleted
immediately, every time.
set every run, and (until pruned) old archived applied-zips and loose apply
reports. None of it is precious — every zip is trivially reproducible by just
re-running contextzip. So cleanup here is deliberately brutal rather than
cautious: it runs automatically after every successful command (see
`_auto_cleanup` in cli.py), with no prompt and no separate command to
remember, and keeps only the most recent `cleanup.keep_recent` item(s) in
each category — everything else is deleted immediately, every time.
The heavier caution used elsewhere in contextzip (backing up before overwrite
in apply-zip, never inferring deletions) is reserved for the project's own
files, where losing something can mean losing real work. Nothing here ever
touches the project — only disposable metadata under .contextzip/ that costs
nothing to regenerate.
Nothing here ever touches the project — only disposable metadata under
.contextzip/ that costs nothing to regenerate.
Never touched, under any settings:
- .contextzip/config.json (project preferences)
@@ -28,7 +23,6 @@ Never touched, under any settings:
Kept only-the-most-recent-N, everything older deleted immediately:
- zip/manifest/report sets under .contextzip/output/<mode>/, per mode
folder
- timestamped folders under .contextzip/backups/
- archived zips under .contextzip/inbox/applied/ (this is also governed
by `applied_zip_retention`, already enforced on every apply-zip run —
scanning it here too just catches the case where zips piled up before
@@ -40,6 +34,12 @@ Kept only-the-most-recent-N, everything older deleted immediately:
put it; its report always lands in inbox/ itself), so nothing else
here would ever catch them piling up run after run
Removed outright, no matter how recent:
- .contextzip/backups/ in its entirety. apply-zip no longer creates
per-run backups there at all — this only ever catches a leftover
folder from a project that used an older contextzip version, and it's
dead weight the moment that version is gone.
Scanning only ever touches .contextzip/ directory listings and file mtimes —
no hashing, no reading file contents — so this stays fast even called on
every command, on a large project.
@@ -53,7 +53,7 @@ from pathlib import Path
_OUTPUT_DIRNAME = "output"
_BACKUPS_DIRNAME = "backups"
_LEGACY_BACKUPS_DIRNAME = "backups"
_INBOX_DIRNAME = "inbox"
_APPLIED_DIRNAME = "applied"
@@ -66,7 +66,7 @@ _APPLIED_DIRNAME = "applied"
@dataclass
class CleanupItem:
path: Path
kind: str # "zip-set" | "backup" | "applied-zip"
kind: str # "zip-set" | "applied-zip" | "apply-report" | "legacy-backups"
size_bytes: int
@@ -166,14 +166,14 @@ def scan(
for f in _zip_set_files(zp):
items.append(CleanupItem(path=f, kind="zip-set", size_bytes=_size_of(f)))
# ── backups/<timestamp>/ ─────────────────────────────────────────────
backups_root = workspace / _BACKUPS_DIRNAME
# ── backups/ — the feature that created these is gone entirely; wipe
# the whole folder outright rather than pruning it, so upgrading
# from an older contextzip version doesn't leave it behind forever ──
backups_root = workspace / _LEGACY_BACKUPS_DIRNAME
if backups_root.is_dir():
backups = _newest_first([p for p in backups_root.iterdir() if p.is_dir()])
for backup_dir in backups[keep:]:
items.append(
CleanupItem(path=backup_dir, kind="backup", size_bytes=_size_of(backup_dir))
)
items.append(
CleanupItem(path=backups_root, kind="legacy-backups", size_bytes=_size_of(backups_root))
)
# ── inbox/applied/ ───────────────────────────────────────────────────
applied_root = workspace / _INBOX_DIRNAME / _APPLIED_DIRNAME
+6 -5
View File
@@ -382,8 +382,7 @@ def cmd_apply_zip(
every file as new, modified, unchanged, or one needing a closer look
(edited locally since zipping, or with no baseline at all) before
writing anything. Only adds and modifies files — nothing is ever
deleted. Every overwritten file is backed up first, under
.contextzip/backups/<timestamp>/.
deleted.
"""
project_dir = Path(os.getcwd()).resolve()
project_cfg = load_project_config(project_dir)
@@ -857,9 +856,11 @@ def _auto_cleanup(project_dir: Path, project_cfg) -> None:
to remember. Every zip is trivially reproducible by re-running
contextzip, so this is deliberately brutal rather than cautious: it
keeps only the `cleanup.keep_recent` most recent zip/manifest/report
set per mode folder, the most recent `cleanup.keep_recent` backup
folder(s), and the most recent `cleanup.keep_recent` archived
applied-zip(s) — everything else is deleted immediately, every run.
set per mode folder, the most recent `cleanup.keep_recent` archived
applied-zip(s), and the most recent `cleanup.keep_recent` loose
apply-report(s) in inbox/ — everything else is deleted immediately,
every run. Also wipes .contextzip/backups/ outright if a leftover one
still exists from an older contextzip version.
Gated on `cleanup.enabled` (default True) — set to False in
.contextzip/config.json to turn this off entirely.
+1 -3
View File
@@ -380,10 +380,8 @@ def print_apply_plan(
def print_apply_result(result, *, con: Console = console) -> None:
"""Two or three lines: what was written, backed up, and archived."""
"""Two lines: what was written and archived."""
ok(f"Applied {len(result.written)} files", con=con)
if result.backup_dir:
ok("Backed up to", str(result.backup_dir), con=con)
ok("Archived zip to", str(result.applied_zip_path), con=con)
+13 -12
View File
@@ -110,24 +110,25 @@ built-in default for the newer preference fields.
cleanup
Automatic housekeeping for `.contextzip/`, which otherwise only
grows over time (a zip+manifest+report set per run, a timestamped
folder under backups/ per risky apply-zip). See cleanup.py — after
every successful contextzip command, the workspace is pruned down
to only the most recent `keep_recent` item(s) per category, no
confirmation, no separate command needed. This is deliberately
brutal rather than cautious: every zip is trivially reproducible
by re-running contextzip, so there's little reason to let old ones
pile up.
grows over time (a zip+manifest+report set per run, archived zips
under inbox/applied/, loose apply-report.txt files in inbox/). See
cleanup.py — after every successful contextzip command, the
workspace is pruned down to only the most recent `keep_recent`
item(s) per category, no confirmation, no separate command needed.
This is deliberately brutal rather than cautious: every zip is
trivially reproducible by re-running contextzip, so there's little
reason to let old ones pile up.
enabled — if true (default), auto-cleanup runs after every
successful command. Set to false to keep
everything contextzip has ever generated.
keep_recent — how many most-recent item(s) are kept per
category before the rest are deleted: zip+manifest
+report sets per mode folder (output/codebase/,
output/git-changes/, output/prompt/), timestamped
folders under backups/, and archived zips under
inbox/applied/. Default 1 — keep only the latest
of each, delete everything older immediately.
output/git-changes/, output/prompt/), archived
zips under inbox/applied/, and loose
apply-report.txt files in inbox/. Default 1 —
keep only the latest of each, delete everything
older immediately.
The schema is intentionally a flat, easily-extended dict so future
preferences can be added without another migration.
-1
View File
@@ -175,7 +175,6 @@ def write_apply_report(
if result is not None:
w("Result")
w(f" written : {len(result.written)}")
w(f" backup : {result.backup_dir or 'none needed'}")
w(f" archived: {result.applied_zip_path}")
w("")