54 lines
2.1 KiB
Python
54 lines
2.1 KiB
Python
"""git-Helfer (nackte CLI, keine Bibliothek): Anker-Stände und Diff fürs Re-Scannen."""
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def _git(wurzel: Path, *args: str) -> str:
|
|
res = subprocess.run(["git", "-C", str(wurzel), *args],
|
|
capture_output=True, text=True, timeout=30)
|
|
if res.returncode != 0:
|
|
raise RuntimeError(f"git {' '.join(args)}: {res.stderr.strip() or res.stdout.strip()}")
|
|
return res.stdout
|
|
|
|
|
|
def ist_repo(wurzel: Path) -> bool:
|
|
try:
|
|
_git(wurzel, "rev-parse", "--git-dir")
|
|
return True
|
|
except (RuntimeError, FileNotFoundError):
|
|
return False
|
|
|
|
|
|
def head_hash(wurzel: Path) -> str:
|
|
return _git(wurzel, "rev-parse", "--short=12", "HEAD").strip()
|
|
|
|
|
|
def working_tree_sauber(wurzel: Path) -> bool:
|
|
"""False bei uncommitteten Änderungen (inkl. untracked) — Scan/Board arbeiten nur
|
|
auf sauberem Tree, sonst vermischen sich Stände. AUSNAHME `.planer/`: die eigenen
|
|
Artefakte (z. B. aus einem Teillauf) dürfen den Folgelauf nicht blockieren."""
|
|
for zeile in _git(wurzel, "status", "--porcelain").splitlines():
|
|
pfad = zeile[3:].split(" -> ")[-1].strip().strip('"')
|
|
if not pfad.startswith(".planer/"):
|
|
return False
|
|
return True
|
|
|
|
|
|
def geaenderte_dateien(wurzel: Path, alt: str, neu: str = "HEAD") -> tuple[set[str], set[str]]:
|
|
"""(geändert_oder_neu, gelöscht) zwischen `alt` und `neu`, `.planer/` ausgefiltert.
|
|
Renames (R) = alt gelöscht + neu geändert (v1: kein Anker-Matching über Renames)."""
|
|
geaendert: set[str] = set()
|
|
geloescht: set[str] = set()
|
|
for zeile in _git(wurzel, "diff", "--name-status", f"{alt}..{neu}").splitlines():
|
|
teile = zeile.split("\t")
|
|
status = teile[0]
|
|
if status.startswith("R") and len(teile) == 3:
|
|
geloescht.add(teile[1])
|
|
geaendert.add(teile[2])
|
|
elif status == "D":
|
|
geloescht.add(teile[1])
|
|
elif len(teile) >= 2:
|
|
geaendert.add(teile[1])
|
|
kein_planer = lambda s: not s.startswith(".planer/") # noqa: E731
|
|
return set(filter(kein_planer, geaendert)), set(filter(kein_planer, geloescht))
|