"""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}')