Initialize GovOPlaN meta repository
This commit is contained in:
42
tools/repo/bootstrap-repositories.py
Normal file
42
tools/repo/bootstrap-repositories.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Clone GovOPlaN repositories listed in repositories.json.")
|
||||
parser.add_argument("--check", action="store_true", help="Only report missing repositories.")
|
||||
parser.add_argument("--parent", type=Path, help="Override checkout parent directory.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
manifest = json.loads((ROOT / "repositories.json").read_text())
|
||||
parent = args.parent or Path(manifest["default_parent"])
|
||||
missing: list[dict[str, str]] = []
|
||||
|
||||
for entry in manifest["repositories"]:
|
||||
repo = parent / entry["path"]
|
||||
if repo.exists():
|
||||
continue
|
||||
missing.append(entry)
|
||||
print(f"missing: {entry['name']} -> {repo}")
|
||||
if not args.check:
|
||||
subprocess.run(["git", "clone", entry["remote"], str(repo)], check=True)
|
||||
|
||||
if args.check:
|
||||
return 1 if missing else 0
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
50
tools/repo/repo-status.py
Normal file
50
tools/repo/repo-status.py
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def run_git(repo: Path, *args: str) -> str:
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(repo), *args],
|
||||
check=False,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return f"git error: {result.stderr.strip()}"
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
manifest = json.loads((ROOT / "repositories.json").read_text())
|
||||
parent = Path(manifest["default_parent"])
|
||||
rows: list[tuple[str, str, str, str]] = []
|
||||
|
||||
for entry in manifest["repositories"]:
|
||||
repo = parent / entry["path"]
|
||||
if not repo.exists():
|
||||
rows.append((entry["name"], entry["category"], "missing", ""))
|
||||
continue
|
||||
branch = run_git(repo, "symbolic-ref", "--quiet", "--short", "HEAD") or "(detached)"
|
||||
status = run_git(repo, "status", "--short")
|
||||
state = "dirty" if status else "clean"
|
||||
rows.append((entry["name"], entry["category"], branch, state))
|
||||
|
||||
name_width = max(len(row[0]) for row in rows)
|
||||
category_width = max(len(row[1]) for row in rows)
|
||||
for name, category, branch, state in rows:
|
||||
print(f"{name:<{name_width}} {category:<{category_width}} {branch:<24} {state}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
7
tools/repo/repo-status.sh
Normal file
7
tools/repo/repo-status.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
PYTHON="${PYTHON:-python3}"
|
||||
|
||||
exec "$PYTHON" "$ROOT/tools/repo/repo-status.py" "$@"
|
||||
74
tools/repo/update-repository-type-notes.py
Normal file
74
tools/repo/update-repository-type-notes.py
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
START = "<!-- govoplan-repository-type:start -->"
|
||||
END = "<!-- govoplan-repository-type:end -->"
|
||||
BLOCK_RE = re.compile(rf"\n?{re.escape(START)}.*?{re.escape(END)}\n?", re.DOTALL)
|
||||
LEGACY_RE = re.compile(r"\n?\*\*Repository type:\*\* [^\n]+\.\n?", re.IGNORECASE)
|
||||
|
||||
|
||||
def type_block(category: str, subtype: str | None) -> str:
|
||||
if subtype:
|
||||
label = f"{category} ({subtype})"
|
||||
else:
|
||||
label = category
|
||||
return f"{START}\n**Repository type:** {label}.\n{END}\n"
|
||||
|
||||
|
||||
def update_readme(path: Path, repo_name: str, category: str, subtype: str | None) -> bool:
|
||||
block = type_block(category, subtype)
|
||||
if path.exists():
|
||||
original = path.read_text()
|
||||
else:
|
||||
original = f"# {repo_name}\n\n"
|
||||
|
||||
text = BLOCK_RE.sub("\n", original).strip() + "\n"
|
||||
text = LEGACY_RE.sub("\n", text).strip() + "\n"
|
||||
|
||||
lines = text.splitlines()
|
||||
insert_at = 0
|
||||
if lines and lines[0].startswith("# "):
|
||||
insert_at = 1
|
||||
while insert_at < len(lines) and lines[insert_at].strip() == "":
|
||||
insert_at += 1
|
||||
del lines[1:insert_at]
|
||||
insert_at = 1
|
||||
else:
|
||||
lines.insert(0, f"# {repo_name}")
|
||||
insert_at = 1
|
||||
|
||||
lines[insert_at:insert_at] = ["", *block.rstrip("\n").splitlines(), ""]
|
||||
updated = "\n".join(lines).rstrip() + "\n"
|
||||
if updated == original:
|
||||
return False
|
||||
path.write_text(updated)
|
||||
return True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
manifest = json.loads((ROOT / "repositories.json").read_text())
|
||||
parent = Path(manifest["default_parent"])
|
||||
changed = 0
|
||||
missing = 0
|
||||
for entry in manifest["repositories"]:
|
||||
repo = parent / entry["path"]
|
||||
if not repo.exists():
|
||||
print(f"missing repository: {repo}", file=sys.stderr)
|
||||
missing += 1
|
||||
continue
|
||||
if update_readme(repo / "README.md", entry["name"], entry["category"], entry.get("subtype")):
|
||||
print(f"updated: {entry['name']}/README.md")
|
||||
changed += 1
|
||||
print(f"repository type notes updated: {changed}, missing repositories: {missing}")
|
||||
return 1 if missing else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user