gc.py
python
| 1 | """``muse gc`` — garbage-collect unreachable objects. |
| 2 | |
| 3 | Content-addressed storage accumulates blobs that no live commit can reach. |
| 4 | These orphaned objects are safe to delete. ``muse gc`` walks the full commit |
| 5 | graph from every live branch and tag, marks every referenced object as |
| 6 | reachable, then removes the rest. |
| 7 | |
| 8 | Usage:: |
| 9 | |
| 10 | muse gc # remove unreachable objects |
| 11 | muse gc --dry-run # show what would be removed, touch nothing |
| 12 | muse gc --verbose # print each removed object ID |
| 13 | |
| 14 | Exit codes:: |
| 15 | |
| 16 | 0 — success (even if nothing was collected) |
| 17 | 1 — internal error (e.g. corrupt store) |
| 18 | """ |
| 19 | |
| 20 | from __future__ import annotations |
| 21 | |
| 22 | import json |
| 23 | import logging |
| 24 | from typing import Annotated |
| 25 | |
| 26 | import typer |
| 27 | |
| 28 | from muse.core.gc import run_gc |
| 29 | from muse.core.repo import require_repo |
| 30 | |
| 31 | logger = logging.getLogger(__name__) |
| 32 | app = typer.Typer(help="Remove unreachable objects from the object store.") |
| 33 | |
| 34 | |
| 35 | def _fmt_bytes(n: int) -> str: |
| 36 | """Human-readable byte count.""" |
| 37 | if n < 1024: |
| 38 | return f"{n} B" |
| 39 | if n < 1024 * 1024: |
| 40 | return f"{n / 1024:.1f} KiB" |
| 41 | return f"{n / (1024 * 1024):.1f} MiB" |
| 42 | |
| 43 | |
| 44 | @app.callback(invoke_without_command=True) |
| 45 | def gc( |
| 46 | dry_run: Annotated[ |
| 47 | bool, |
| 48 | typer.Option("--dry-run", "-n", help="Show what would be removed without removing anything."), |
| 49 | ] = False, |
| 50 | verbose: Annotated[ |
| 51 | bool, |
| 52 | typer.Option("--verbose", "-v", help="Print each collected object ID."), |
| 53 | ] = False, |
| 54 | fmt: Annotated[ |
| 55 | str, |
| 56 | typer.Option("--format", "-f", help="Output format: text or json."), |
| 57 | ] = "text", |
| 58 | ) -> None: |
| 59 | """Remove unreachable objects from the Muse object store. |
| 60 | |
| 61 | Muse stores every tracked file as a content-addressed blob. Blobs that are |
| 62 | no longer referenced by any commit, snapshot, branch, or tag are *garbage*. |
| 63 | This command identifies and removes them, reclaiming disk space. |
| 64 | |
| 65 | Safety: the reachability walk always runs before any deletion. Use |
| 66 | ``--dry-run`` to preview the impact before committing to a sweep. |
| 67 | Agents should pass ``--format json`` to receive a machine-readable result |
| 68 | with ``collected_count``, ``collected_bytes``, ``reachable_count``, |
| 69 | ``elapsed_seconds``, ``dry_run``, and ``collected_ids``. |
| 70 | |
| 71 | Examples:: |
| 72 | |
| 73 | muse gc # safe cleanup |
| 74 | muse gc --dry-run # preview only |
| 75 | muse gc --verbose # show every removed object |
| 76 | muse gc --format json # machine-readable |
| 77 | """ |
| 78 | if fmt not in ("text", "json"): |
| 79 | typer.echo(f"❌ Unknown --format '{fmt}'. Choose text or json.", err=True) |
| 80 | raise typer.Exit(code=1) |
| 81 | |
| 82 | repo_root = require_repo() |
| 83 | result = run_gc(repo_root, dry_run=dry_run) |
| 84 | |
| 85 | if fmt == "json": |
| 86 | typer.echo(json.dumps({ |
| 87 | "collected_count": result.collected_count, |
| 88 | "collected_bytes": result.collected_bytes, |
| 89 | "reachable_count": result.reachable_count, |
| 90 | "elapsed_seconds": result.elapsed_seconds, |
| 91 | "dry_run": result.dry_run, |
| 92 | "collected_ids": sorted(result.collected_ids), |
| 93 | })) |
| 94 | return |
| 95 | |
| 96 | prefix = "[dry-run] " if dry_run else "" |
| 97 | |
| 98 | if verbose and result.collected_ids: |
| 99 | typer.echo(f"{prefix}Unreachable objects:") |
| 100 | for oid in sorted(result.collected_ids): |
| 101 | typer.echo(f" {oid}") |
| 102 | |
| 103 | action = "Would remove" if dry_run else "Removed" |
| 104 | typer.echo( |
| 105 | f"{prefix}{action} {result.collected_count} object(s) " |
| 106 | f"({_fmt_bytes(result.collected_bytes)}) " |
| 107 | f"in {result.elapsed_seconds:.3f}s " |
| 108 | f"[{result.reachable_count} reachable]" |
| 109 | ) |