import shutil import tarfile from datetime import datetime from pathlib import Path from typer import Typer, Option from rich.console import Console from rich.table import Table import zstandard import uuid from repocat.config import load_config from repocat.models.catalog import RepoCatalogState app = Typer() console = Console() def archive_repo(source: Path, target_dir: Path, dry_run: bool) -> Path: today = datetime.today().strftime("%Y.%m.%d") unique = uuid.uuid4().hex[:8] archive_name = f"{today} - {source.name} - {unique}.tar.zst" archive_path = target_dir / archive_name if archive_path.exists(): raise FileExistsError(f"Archiv existiert bereits: {archive_path}") if dry_run: return archive_path with open(archive_path, "wb") as f: cctx = zstandard.ZstdCompressor(level=20, threads=-1) with cctx.stream_writer(f) as compressor: with tarfile.open(fileobj=compressor, mode="w|") as tar: tar.add(source, arcname=source.name) shutil.rmtree(source) return archive_path @app.command("run") def run( older_than: int = Option(30, "--older-than", help="Nur Repositories älter als X Tage archivieren."), dry_run: bool = Option(False, "--dry-run", help="Keine Änderungen vornehmen."), category: list[str] = Option(None, "--category", "-c", help="Nur bestimmte Kategorien prüfen."), ): config = load_config() catalog = RepoCatalogState.from_config(config) archive_cat = next((c for c in catalog.categories if c.config.is_archive), None) if not archive_cat: console.print("[red]Keine Archiv-Kategorie in der Konfiguration gefunden.[/]") raise SystemExit(1) archive_path = archive_cat.path archive_path.mkdir(parents=True, exist_ok=True) table = Table(title="Archivierung") table.add_column("Kategorie") table.add_column("Repository") table.add_column("Alter", justify="right") table.add_column("Größe (MB)", justify="right") table.add_column("Aktion") archived = 0 skipped = 0 for cat in catalog.categories: if cat.config.is_archive or cat.config.name == "review" or cat.config.volatile: continue if category and cat.config.name not in category: continue if cat.config.days == 0 or cat.config.days is None: continue limit_days = cat.config.days if cat.config.days is not None else older_than for repo in cat.repos: if not repo.is_expired(limit_days): table.add_row(cat.config.name, repo.name, str(repo.age_days), f"{repo.size_mb:.1f}", "[blue]Zu jung[/]") skipped += 1 continue if repo.git and repo.git.is_protected: table.add_row(cat.config.name, repo.name, str(repo.age_days), f"{repo.size_mb:.1f}", "[blue]Geschützt (git)[/]") skipped += 1 continue try: archive_file = archive_repo(repo.path, archive_path, dry_run) action = "[yellow]Würde archivieren[/]" if dry_run else f"[green]Archiviert →[/] {archive_file.name}" archived += 1 except Exception as e: action = f"[red]Fehler: {e}[/]" table.add_row(cat.config.name, repo.name, str(repo.age_days), f"{repo.size_mb:.1f}", action) console.print(table) console.print(f"\n[green]✓ Archivierung abgeschlossen[/] – {archived} archiviert, {skipped} übersprungen")