71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
#!/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())
|