dp-zp-agent/tools/wiki_git.py

68 lines
2.9 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}')