Add channel-aware release tooling and updater recovery
This commit is contained in:
70
scripts/validate_release_remote.py
Normal file
70
scripts/validate_release_remote.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user