#!/usr/bin/env python3 """Confirm that a Git remote points at the configured release repository.""" from __future__ import annotations import argparse from pathlib import Path import re import sys from urllib.parse import unquote, urlsplit class RemoteValidationError(ValueError): pass def repository_identity(value: str) -> tuple[str, str]: if not value or any(character in value for character in "\r\n\0"): raise RemoteValidationError("repository URL is empty or malformed") scp = re.fullmatch(r"(?:[^@/:]+@)?([^/:]+):(.+)", value) if scp is not None and "://" not in value: return _remote_identity(scp.group(1), scp.group(2)) parts = urlsplit(value) if parts.scheme in {"https", "http", "ssh", "git"}: hostname = parts.hostname if hostname is None: raise RemoteValidationError("repository URL has no host") host = hostname.casefold() if parts.port is not None: host += f":{parts.port}" return _remote_identity(host, unquote(parts.path)) if parts.scheme == "file": return "file", str(Path(unquote(parts.path)).expanduser().resolve()) if parts.scheme: raise RemoteValidationError(f"unsupported repository URL scheme: {parts.scheme}") return "file", str(Path(value).expanduser().resolve()) def remote_matches(actual: str, expected: list[str]) -> bool: actual_identity = repository_identity(actual) return any(repository_identity(candidate) == actual_identity for candidate in expected) def _remote_identity(host: str, path: str) -> tuple[str, str]: normalized = path.strip("/") if normalized.endswith(".git"): normalized = normalized[:-4] if not host or not normalized or normalized.startswith("../"): raise RemoteValidationError("repository URL has an invalid host or path") return host.casefold(), normalized def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("actual") parser.add_argument("expected", nargs="+") arguments = parser.parse_args() try: if not remote_matches(arguments.actual, arguments.expected): raise RemoteValidationError( "configured Git remote does not point at the expected release repository" ) except RemoteValidationError as exc: print(f"Release remote validation failed: {exc}", file=sys.stderr) return 1 return 0 if __name__ == "__main__": raise SystemExit(main())