161 lines
4.2 KiB
Python
Executable File
161 lines
4.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
COMMIT_RE = re.compile(r"^[0-9a-f]{40}$")
|
|
|
|
|
|
def command(args, cwd=None, capture=False):
|
|
result = subprocess.run(
|
|
args,
|
|
cwd=cwd,
|
|
check=True,
|
|
text=True,
|
|
stdout=subprocess.PIPE if capture else None,
|
|
)
|
|
return result.stdout.strip() if capture else ""
|
|
|
|
|
|
def load_lock(path):
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
if not isinstance(data, dict):
|
|
raise SystemExit("Le verrou doit être un objet JSON.")
|
|
|
|
if "frappe" not in data:
|
|
raise SystemExit("Entrée frappe absente du verrou.")
|
|
|
|
entries = []
|
|
|
|
for name, metadata in data.items():
|
|
if not isinstance(metadata, dict):
|
|
raise SystemExit(f"Métadonnées invalides pour {name}.")
|
|
|
|
resolution = metadata.get("resolution") or {}
|
|
url = metadata.get("url")
|
|
branch = resolution.get("branch")
|
|
commit = resolution.get("commit_hash")
|
|
idx = metadata.get("idx")
|
|
|
|
if not isinstance(url, str) or not url.startswith("https://"):
|
|
raise SystemExit(f"URL publique invalide pour {name}: {url!r}")
|
|
|
|
if not isinstance(branch, str) or not branch:
|
|
raise SystemExit(f"Branche absente pour {name}.")
|
|
|
|
if not isinstance(commit, str) or not COMMIT_RE.fullmatch(commit):
|
|
raise SystemExit(f"Commit invalide pour {name}: {commit!r}")
|
|
|
|
if not isinstance(idx, int):
|
|
raise SystemExit(f"Index invalide pour {name}: {idx!r}")
|
|
|
|
entries.append((name, metadata))
|
|
|
|
entries.sort(key=lambda item: item[1]["idx"])
|
|
|
|
expected = list(range(1, len(entries) + 1))
|
|
actual = [metadata["idx"] for _, metadata in entries]
|
|
|
|
if actual != expected:
|
|
raise SystemExit(f"Indices non continus dans le verrou: {actual!r}")
|
|
|
|
return entries
|
|
|
|
|
|
def verify_head(repository, expected_commit, app_name):
|
|
actual = command(
|
|
["git", "rev-parse", "HEAD"],
|
|
cwd=repository,
|
|
capture=True,
|
|
)
|
|
|
|
if actual != expected_commit:
|
|
raise SystemExit(
|
|
f"Commit incorrect pour {app_name}: "
|
|
f"attendu={expected_commit} obtenu={actual}"
|
|
)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Clone les applications Frappe aux commits du verrou."
|
|
)
|
|
parser.add_argument("lock_file", type=Path)
|
|
parser.add_argument("bench_path", type=Path)
|
|
parser.add_argument("--validate-only", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
entries = load_lock(args.lock_file)
|
|
app_entries = [
|
|
(name, metadata)
|
|
for name, metadata in entries
|
|
if name != "frappe"
|
|
]
|
|
|
|
if args.validate_only:
|
|
print(
|
|
f"Verrou valide: {len(entries)} entrées, "
|
|
f"{len(app_entries)} applications."
|
|
)
|
|
return
|
|
|
|
bench = args.bench_path.resolve()
|
|
apps_dir = bench / "apps"
|
|
sites_dir = bench / "sites"
|
|
|
|
frappe_metadata = dict(entries)["frappe"]
|
|
verify_head(
|
|
apps_dir / "frappe",
|
|
frappe_metadata["resolution"]["commit_hash"],
|
|
"frappe",
|
|
)
|
|
|
|
for name, metadata in app_entries:
|
|
target = apps_dir / name
|
|
|
|
if target.exists():
|
|
raise SystemExit(f"Répertoire déjà présent: {target}")
|
|
|
|
resolution = metadata["resolution"]
|
|
branch = resolution["branch"]
|
|
commit = resolution["commit_hash"]
|
|
url = metadata["url"]
|
|
|
|
print(f"Clonage de {name} @ {commit}")
|
|
|
|
command(
|
|
[
|
|
"git",
|
|
"clone",
|
|
"--filter=blob:none",
|
|
"--no-checkout",
|
|
"--single-branch",
|
|
"--branch",
|
|
branch,
|
|
url,
|
|
str(target),
|
|
]
|
|
)
|
|
command(
|
|
["git", "checkout", "--detach", commit],
|
|
cwd=target,
|
|
)
|
|
verify_head(target, commit, name)
|
|
|
|
app_names = [name for name, _ in entries]
|
|
(sites_dir / "apps.txt").write_text(
|
|
"\n".join(app_names) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
print(f"Applications verrouillées clonées: {len(app_entries)}")
|
|
print("Frappe et applications vérifiés: OK")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|