Auto-Push: Werkzeuge täglich nach claude-werkzeuge spiegeln
taeglich_push.py ruft nach dem Vault-Push werkzeuge_spiegel.py auf: committeter Stand von .vault-tools/ und .claude/ ohne Trading-Werkzeuge und watermarks-remover, README aus werkzeuge-README.md. Fallkatalog 14 von 14, drei Mutanten erkannt. veroeffentlichung.md und Stamm nachgezogen; Gitea seit heute anonym lesbar. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d06516097c
commit
57e2f64f98
7 files changed
+1243
-33
No files matched your search
@@ -0,0 +1,295 @@
|
||||
"""Fährt die Fälle gegen werkzeuge_spiegel.py, auf Wegwerf-Vaults mit echten bare-Remotes.
|
||||
|
||||
Ersteller: Pascal Michel
|
||||
Erstellt: 2026-10-14
|
||||
|
||||
Jeder Wegwerf-Vault hat als origin ein bare-Repo claude-regelwerk.git, daneben
|
||||
liegt claude-werkzeuge.git mit einem veralteten Stand. Der Spiegel leitet seine
|
||||
Adresse aus der des Vaults ab, wie im Betrieb. Geprüft wird, was nachher
|
||||
drüben liegt, nicht was das Skript meldet.
|
||||
|
||||
Aufruf:
|
||||
python .vault-tools/auto-push/faelle_werkzeuge_spiegel.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
HIER = Path(__file__).resolve().parent
|
||||
SPIEGEL = HIER / "werkzeuge_spiegel.py"
|
||||
README = HIER / "werkzeuge-README.md"
|
||||
|
||||
CRLF_INHALT = b"zeile eins\r\nzeile zwei\r\n"
|
||||
|
||||
ERWARTET = {
|
||||
"README.md",
|
||||
".vault-tools/auto-push/werkzeuge_spiegel.py",
|
||||
".vault-tools/auto-push/werkzeuge-README.md",
|
||||
".vault-tools/linkcheck/a.py",
|
||||
".vault-tools/linkcheck/crlf.txt",
|
||||
".claude/agents/x.md",
|
||||
}
|
||||
|
||||
|
||||
def raeume(pfad: Path) -> None:
|
||||
"""rmtree, das auch schreibgeschuetzte git-Objektdateien wegbekommt."""
|
||||
|
||||
def erzwinge(funktion, ziel, _fehler):
|
||||
try:
|
||||
os.chmod(ziel, stat.S_IWRITE)
|
||||
funktion(ziel)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
shutil.rmtree(pfad, onexc=erzwinge)
|
||||
|
||||
|
||||
def git(wo: Path, *argumente: str) -> subprocess.CompletedProcess[bytes]:
|
||||
return subprocess.run(
|
||||
["git", "-C", str(wo), *argumente],
|
||||
capture_output=True,
|
||||
stdin=subprocess.DEVNULL,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def lade_spiegel():
|
||||
spec = importlib.util.spec_from_file_location("werkzeuge_spiegel", SPIEGEL)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"nicht ladbar: {SPIEGEL}")
|
||||
modul = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(modul)
|
||||
return modul
|
||||
|
||||
|
||||
def schreibe(pfad: Path, inhalt: bytes) -> None:
|
||||
pfad.parent.mkdir(parents=True, exist_ok=True)
|
||||
pfad.write_bytes(inhalt)
|
||||
|
||||
|
||||
def richte_repo_ein(wo: Path, remote: Path) -> None:
|
||||
git(wo, "init", "-q")
|
||||
git(wo, "symbolic-ref", "HEAD", "refs/heads/main")
|
||||
git(wo, "config", "user.name", "Fall")
|
||||
git(wo, "config", "user.email", "[email protected]")
|
||||
git(wo, "config", "commit.gpgsign", "false")
|
||||
git(wo, "config", "core.autocrlf", "false")
|
||||
git(wo, "remote", "add", "origin", remote.as_uri())
|
||||
|
||||
|
||||
def committe(wo: Path, text: str) -> None:
|
||||
git(wo, "add", "-A")
|
||||
git(wo, "commit", "-q", "-m", text)
|
||||
|
||||
|
||||
def baue(basis: Path) -> tuple[Path, Path]:
|
||||
"""Legt Vault, beide bare-Remotes und den veralteten Spiegelstand an."""
|
||||
vault = basis / "vault"
|
||||
remotes = basis / "remotes"
|
||||
regelwerk = remotes / "claude-regelwerk.git"
|
||||
werkzeuge = remotes / "claude-werkzeuge.git"
|
||||
remotes.mkdir(parents=True)
|
||||
subprocess.run(["git", "init", "-q", "--bare", str(regelwerk)], check=True)
|
||||
subprocess.run(["git", "init", "-q", "--bare", "--initial-branch=main", str(werkzeuge)], check=True)
|
||||
|
||||
schreibe(vault / ".vault-tools" / "auto-push" / "werkzeuge_spiegel.py", SPIEGEL.read_bytes())
|
||||
schreibe(vault / ".vault-tools" / "auto-push" / "werkzeuge-README.md", README.read_bytes())
|
||||
schreibe(vault / ".vault-tools" / "linkcheck" / "a.py", b"print('a')\n")
|
||||
schreibe(vault / ".vault-tools" / "linkcheck" / "crlf.txt", CRLF_INHALT)
|
||||
schreibe(vault / ".vault-tools" / "trading-plattform-pruefung" / "t.py", b"trading\n")
|
||||
schreibe(vault / ".vault-tools" / "watermarks-remover" / "w.sh", b"aus\n")
|
||||
schreibe(vault / ".claude" / "agents" / "x.md", b"# x\n")
|
||||
schreibe(vault / "Inbox" / "geheim.md", b"Kundendaten\n")
|
||||
richte_repo_ein(vault, regelwerk)
|
||||
committe(vault, "Grundstand")
|
||||
# Ungesicherte Datei im Arbeitsbaum: darf nie hinueber.
|
||||
schreibe(vault / ".vault-tools" / "linkcheck" / "lokal.tmp", b"nicht committet\n")
|
||||
|
||||
alt = basis / "alt"
|
||||
schreibe(alt / "README.md", b"alt\n")
|
||||
schreibe(alt / "veraltet.txt", b"weg damit\n")
|
||||
richte_repo_ein(alt, werkzeuge)
|
||||
committe(alt, "Alter Stand")
|
||||
git(alt, "push", "-q", "origin", "main")
|
||||
return vault, werkzeuge
|
||||
|
||||
|
||||
def dateien(remote: Path) -> set[str]:
|
||||
lauf = git(remote, "ls-tree", "-r", "--name-only", "main")
|
||||
if lauf.returncode != 0:
|
||||
return set()
|
||||
return {z for z in lauf.stdout.decode("utf-8", errors="replace").splitlines() if z}
|
||||
|
||||
|
||||
def inhalt(remote: Path, pfad: str) -> bytes:
|
||||
return git(remote, "cat-file", "blob", f"main:{pfad}").stdout
|
||||
|
||||
|
||||
def commits(remote: Path) -> int:
|
||||
lauf = git(remote, "rev-list", "--count", "main")
|
||||
if lauf.returncode != 0:
|
||||
return -1
|
||||
return int(lauf.stdout.decode().strip() or "0")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
spiegel = lade_spiegel()
|
||||
basis = Path(tempfile.mkdtemp(prefix="werkzeuge-spiegel-faelle-"))
|
||||
gesamt = 0
|
||||
bestanden = 0
|
||||
|
||||
def pruefe(name: str, ok: bool, text: str) -> None:
|
||||
nonlocal gesamt, bestanden
|
||||
gesamt += 1
|
||||
bestanden += int(ok)
|
||||
print(f"{'BESTANDEN' if ok else 'FEHLER':10} {name:42} {text}")
|
||||
|
||||
def wirft(wurzel: Path, klon: Path) -> str | None:
|
||||
try:
|
||||
spiegel.spiegele(wurzel, klon)
|
||||
except spiegel.SpiegelFehler as fehler:
|
||||
return str(fehler)
|
||||
return None
|
||||
|
||||
try:
|
||||
# --- Grundlinie -------------------------------------------------
|
||||
vault, werkzeuge = baue(basis / "a")
|
||||
klon = basis / "a" / "klon"
|
||||
status, text = spiegel.spiegele(vault, klon)
|
||||
drueben = dateien(werkzeuge)
|
||||
pruefe(
|
||||
"grundlinie",
|
||||
status == "gespiegelt" and drueben == ERWARTET,
|
||||
f"{status}: {text}; zu viel {sorted(drueben - ERWARTET)}, fehlt {sorted(ERWARTET - drueben)}",
|
||||
)
|
||||
pruefe(
|
||||
"readme aus der quelle",
|
||||
inhalt(werkzeuge, "README.md") == README.read_bytes(),
|
||||
f"{len(inhalt(werkzeuge, 'README.md'))} Bytes drueben",
|
||||
)
|
||||
pruefe(
|
||||
"bytes bleiben (crlf)",
|
||||
inhalt(werkzeuge, ".vault-tools/linkcheck/crlf.txt") == CRLF_INHALT,
|
||||
repr(inhalt(werkzeuge, ".vault-tools/linkcheck/crlf.txt")),
|
||||
)
|
||||
|
||||
# --- Zweiter Lauf ohne Aenderung --------------------------------
|
||||
vorher = commits(werkzeuge)
|
||||
status, text = spiegel.spiegele(vault, klon)
|
||||
pruefe(
|
||||
"zweiter lauf -> unveraendert",
|
||||
status == "unveraendert" and commits(werkzeuge) == vorher,
|
||||
f"{status}: {text}; Commits {vorher}->{commits(werkzeuge)}",
|
||||
)
|
||||
|
||||
# --- Committet geht mit, ungesichert nicht ----------------------
|
||||
a_py = vault / ".vault-tools" / "linkcheck" / "a.py"
|
||||
schreibe(a_py, b"print('neu')\n")
|
||||
committe(vault, "a geaendert")
|
||||
schreibe(a_py, b"print('ungesichert')\n")
|
||||
status, text = spiegel.spiegele(vault, klon)
|
||||
pruefe(
|
||||
"committete aenderung geht mit",
|
||||
status == "gespiegelt" and inhalt(werkzeuge, ".vault-tools/linkcheck/a.py") == b"print('neu')\n",
|
||||
f"{status}: {text}; drueben {inhalt(werkzeuge, '.vault-tools/linkcheck/a.py')!r}",
|
||||
)
|
||||
|
||||
# --- Loeschung geht mit -----------------------------------------
|
||||
git(vault, "rm", "-q", ".claude/agents/x.md")
|
||||
committe(vault, "x geloescht")
|
||||
spiegel.spiegele(vault, klon)
|
||||
pruefe(
|
||||
"loeschung geht mit",
|
||||
".claude/agents/x.md" not in dateien(werkzeuge),
|
||||
f"drueben {sorted(dateien(werkzeuge))}",
|
||||
)
|
||||
|
||||
# --- Schutz: nichts leeren, was nicht unser Klon ist ------------
|
||||
fremd = basis / "fremd"
|
||||
fremd.mkdir()
|
||||
git(fremd, "init", "-q")
|
||||
# Erreichbares Remote in anderer Schreibweise: ohne Schutz liefe der
|
||||
# Abgleich durch und leerte den Ordner.
|
||||
git(fremd, "remote", "add", "origin", str(werkzeuge))
|
||||
schreibe(fremd / "bleibt.txt", b"x\n")
|
||||
meldung = wirft(vault, fremd)
|
||||
pruefe(
|
||||
"fremder klon -> befund, nichts geleert",
|
||||
meldung is not None and (fremd / "bleibt.txt").is_file(),
|
||||
str(meldung)[:100],
|
||||
)
|
||||
|
||||
innen = vault / "klon-im-vault"
|
||||
schreibe(innen / "bleibt.txt", b"x\n")
|
||||
meldung = wirft(vault, innen)
|
||||
pruefe(
|
||||
"klon im vault -> befund, nichts geleert",
|
||||
meldung is not None and (innen / "bleibt.txt").is_file(),
|
||||
str(meldung)[:100],
|
||||
)
|
||||
|
||||
ohne_git = basis / "ohne-git"
|
||||
schreibe(ohne_git / "bleibt.txt", b"x\n")
|
||||
meldung = wirft(vault, ohne_git)
|
||||
pruefe(
|
||||
"ordner ohne git -> befund, nichts geleert",
|
||||
meldung is not None and (ohne_git / "bleibt.txt").is_file(),
|
||||
str(meldung)[:100],
|
||||
)
|
||||
|
||||
# --- Fehlende Voraussetzungen sind Befunde ----------------------
|
||||
vault2, werkzeuge2 = baue(basis / "b")
|
||||
raeume(werkzeuge2)
|
||||
meldung = wirft(vault2, basis / "b" / "klon")
|
||||
pruefe("remote fehlt beim klonen -> befund", meldung is not None, str(meldung)[:100])
|
||||
|
||||
vault3, werkzeuge3 = baue(basis / "c")
|
||||
git(vault3, "rm", "-q", ".vault-tools/auto-push/werkzeuge-README.md")
|
||||
committe(vault3, "readme weg")
|
||||
meldung = wirft(vault3, basis / "c" / "klon")
|
||||
pruefe(
|
||||
"readme-quelle fehlt -> befund, nichts gepusht",
|
||||
meldung is not None and "README" in meldung and commits(werkzeuge3) == 1,
|
||||
f"Commits {commits(werkzeuge3)}; {str(meldung)[:80]}",
|
||||
)
|
||||
pruefe(
|
||||
"readme-quelle fehlt -> kein klon angelegt",
|
||||
not (basis / "c" / "klon").exists(),
|
||||
f"klon vorhanden: {(basis / 'c' / 'klon').exists()}",
|
||||
)
|
||||
|
||||
vault4, werkzeuge4 = baue(basis / "d")
|
||||
klon4 = basis / "d" / "klon"
|
||||
spiegel.spiegele(vault4, klon4)
|
||||
raeume(werkzeuge4)
|
||||
schreibe(vault4 / ".vault-tools" / "linkcheck" / "a.py", b"print('d')\n")
|
||||
committe(vault4, "a geaendert")
|
||||
meldung = wirft(vault4, klon4)
|
||||
pruefe("remote weg bei bestehendem klon -> befund", meldung is not None, str(meldung)[:100])
|
||||
|
||||
vault5, _ = baue(basis / "e")
|
||||
git(vault5, "remote", "remove", "origin")
|
||||
meldung = wirft(vault5, basis / "e" / "klon")
|
||||
pruefe("vault ohne origin -> befund", meldung is not None, str(meldung)[:100])
|
||||
|
||||
print()
|
||||
print(f"{bestanden} von {gesamt} Faellen bestanden.")
|
||||
return 0 if bestanden == gesamt else 1
|
||||
finally:
|
||||
raeume(basis)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
except AttributeError:
|
||||
pass
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user