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