dp-zp-agent/tools/sync/wiki_git.py

93 lines
3.2 KiB
Python

"""Non-interactive, branch-specific Git updates of the configured wiki."""
from __future__ import annotations
import os
import re
import subprocess
from pathlib import Path
from urllib.parse import urlsplit
GIT_TIMEOUT_SECONDS = 120
def expected_branch() -> str:
branch = os.getenv("EXPECTED_GITEA_BRANCH", "master").strip()
if (
not branch
or branch.startswith(("-", "/"))
or branch.endswith(("/", "."))
or ".." in branch
or "@{" in branch
or branch == "@"
or re.search(r"[\s\x00-\x1f\x7f~^:?*\[\\]", branch)
or any(
not part or part.startswith(".") or part.endswith(".lock") for part in branch.split("/")
)
):
raise RuntimeError("EXPECTED_GITEA_BRANCH nie je platný názov Git vetvy")
return branch
def repository_from_url(url: str) -> str | None:
if "://" in url:
parsed = urlsplit(url)
if parsed.scheme not in {"ssh", "https", "http", "git"}:
return None
path = parsed.path
elif re.match(r"[^/\s]+:[^\s]+", url):
path = url.split(":", 1)[1]
else:
return None
path = path.strip("/").removesuffix(".git")
return path if len(path.split("/")) == 2 else None
def update_wiki(repo_path: Path) -> None:
if not repo_path.exists():
raise RuntimeError(f"ZPWIKI_ROOT neexistuje: {repo_path}")
if not (repo_path / ".git").exists():
raise RuntimeError(f"Nie je to git repozitár: {repo_path}")
environment = {**os.environ, "GIT_TERMINAL_PROMPT": "0"}
def git(*args):
try:
result = subprocess.run(
["git", *args],
cwd=repo_path,
env=environment,
text=True,
capture_output=True,
timeout=GIT_TIMEOUT_SECONDS,
check=False,
)
except subprocess.TimeoutExpired as error:
raise RuntimeError(
f"Git aktualizácia prekročila limit {GIT_TIMEOUT_SECONDS} sekúnd"
) from error
if result.returncode:
# Remote URLs and credentials can occur in Git stderr; do not place
# them in API responses or ordinary application logs.
raise RuntimeError(
f"Git operácia {args[0]} zlyhala s návratovým kódom {result.returncode}"
)
return result.stdout.strip()
expected = os.getenv("EXPECTED_GITEA_REPOSITORY", "").strip()
origin = repository_from_url(git("remote", "get-url", "origin"))
if not expected or origin is None or origin.casefold() != expected.casefold():
raise RuntimeError("Git origin nezodpovedá EXPECTED_GITEA_REPOSITORY")
branch = expected_branch()
if git("branch", "--show-current") != branch:
raise RuntimeError("Aktívna Git vetva nezodpovedá EXPECTED_GITEA_BRANCH")
if git("status", "--porcelain", "--untracked-files=normal"):
raise RuntimeError("Wiki obsahuje lokálne zmeny; automatická aktualizácia ich neprepíše")
git(
"fetch",
"--no-tags",
"--no-recurse-submodules",
"origin",
f"refs/heads/{branch}:refs/remotes/origin/{branch}",
)
git("merge", "--ff-only", f"refs/remotes/origin/{branch}")