feat(clean): add snapshot support for volatile repo cleanup
Some checks failed
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Failing after 6s
Build and Publish nightly package / build-and-publish (push) Successful in 33s

This commit is contained in:
2025-11-27 18:21:20 +01:00
parent 801c8e24a1
commit 7f629fd752
2 changed files with 80 additions and 11 deletions

View File

@@ -2,6 +2,8 @@ from typer import Typer, Option
from rich.console import Console from rich.console import Console
from rich.table import Table from rich.table import Table
import shutil import shutil
import os
from datetime import datetime
from repocat.config import load_config from repocat.config import load_config
from repocat.models.catalog import RepoCatalogState from repocat.models.catalog import RepoCatalogState
@@ -23,7 +25,16 @@ def clean_volatile(
""" """
Löscht **bedingungslos** alle Repositories aus `volatile`-Kategorien (z.B. `scratches`). Löscht **bedingungslos** alle Repositories aus `volatile`-Kategorien (z.B. `scratches`).
""" """
catalog = RepoCatalogState.from_config(load_config()) config = load_config()
catalog = RepoCatalogState.from_config(config)
# Snapshot configuration
use_snapshots = config.defaults.snapshots if config.defaults else False
snapshot_count = config.defaults.snapshot_count if config.defaults else 5
snapshot_root = config.base_dir / ".snapshots"
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
current_snapshot_dir = snapshot_root / timestamp
table = Table(title="Clean – Volatile (Zerstörung)") table = Table(title="Clean – Volatile (Zerstörung)")
table.add_column("Kategorie") table.add_column("Kategorie")
@@ -33,6 +44,7 @@ def clean_volatile(
deleted = 0 deleted = 0
skipped = 0 skipped = 0
snapshotted_categories = set()
for cat in catalog.categories: for cat in catalog.categories:
if not cat.config.volatile: if not cat.config.volatile:
@@ -40,23 +52,78 @@ def clean_volatile(
if category and cat.config.name not in category: if category and cat.config.name not in category:
continue continue
for repo in cat.repos: # If snapshots are enabled, we handle the whole category at once
size_mb = repo.size_mb if use_snapshots:
if not cat.path.exists():
continue
if dry_run: if dry_run:
action = "[yellow]Würde löschen[/]" action = f"[yellow]Würde verschieben nach .snapshots/{timestamp}[/]"
else: else:
try: try:
shutil.rmtree(repo.path) # Create snapshot dir only if we actually have something to move
action = "[red]Gelöscht[/]" if not current_snapshot_dir.exists():
deleted += 1 current_snapshot_dir.mkdir(parents=True, exist_ok=True)
target_dir = current_snapshot_dir / cat.config.subdir
# Ensure parent of target exists (if subdir is nested)
target_dir.parent.mkdir(parents=True, exist_ok=True)
# Move the whole category folder
# Note: shutil.move(src, dst) where dst does not exist moves src to dst.
# If dst exists, it moves src inside dst.
# We want to rename cat.path to target_dir.
shutil.move(str(cat.path), str(target_dir))
# Recreate the empty category folder
cat.path.mkdir(parents=True, exist_ok=True)
action = f"[green]Verschoben nach .snapshots/{timestamp}[/]"
snapshotted_categories.add(cat.config.name)
except Exception as e: except Exception as e:
action = f"[red]Fehler: {e}[/]" action = f"[red]Fehler beim Verschieben: {e}[/]"
table.add_row(cat.config.name, repo.name, f"{size_mb:.1f}", action) # Add rows for all repos in this category to show what happened to them
if cat.repos:
for repo in cat.repos:
table.add_row(cat.config.name, repo.name, f"{repo.size_mb:.1f}", action)
deleted += 1 # Count as "processed/removed from volatile"
else:
# If no repos but folder existed (and maybe had other files), show a generic row
table.add_row(cat.config.name, "(Alle Dateien)", "-", action)
else:
# Old behavior: Delete individual repos
for repo in cat.repos:
size_mb = repo.size_mb
if dry_run:
action = "[yellow]Würde löschen[/]"
else:
try:
shutil.rmtree(repo.path)
action = "[red]Gelöscht[/]"
deleted += 1
except Exception as e:
action = f"[red]Fehler: {e}[/]"
table.add_row(cat.config.name, repo.name, f"{size_mb:.1f}", action)
console.print(table) console.print(table)
console.print(f"\n[green]✓ Volatile-Clean abgeschlossen[/] – {deleted} gelöscht, {skipped} übersprungen")
# Prune old snapshots
if use_snapshots and not dry_run and snapshot_root.exists():
try:
snapshots = sorted([p for p in snapshot_root.iterdir() if p.is_dir()], key=lambda p: p.name)
if len(snapshots) > snapshot_count:
to_delete = snapshots[:-snapshot_count]
for snap in to_delete:
shutil.rmtree(snap)
console.print(f"[dim]Alte Snapshots bereinigt: {len(to_delete)} gelöscht[/]")
except Exception as e:
console.print(f"[red]Fehler beim Bereinigen der Snapshots: {e}[/]")
console.print(f"\n[green]✓ Volatile-Clean abgeschlossen[/] – {deleted} Repositories verarbeitet")
@app.command() @app.command()

View File

@@ -13,6 +13,8 @@ class RepoCategory(BaseModel):
class RepoCatDefaults(BaseModel): class RepoCatDefaults(BaseModel):
dry_run: bool = True dry_run: bool = True
auto_create_directories: bool = True auto_create_directories: bool = True
snapshots: bool = False
snapshot_count: int = 5
class RepoCatConfig(BaseModel): class RepoCatConfig(BaseModel):
base_dir: Path = Field(default=Path("~/Repositories").expanduser()) base_dir: Path = Field(default=Path("~/Repositories").expanduser())