71 lines
3.1 KiB
Python
71 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
# #region Tooling.SourceRepositoryIdentity [C:4] [TYPE Module] [SEMANTICS build,source,origin,immutable]
|
|
# @BRIEF Observe one unambiguous fetch/push origin; emit credential-free current metadata.
|
|
# @POST Dirty checkout is allowed; missing, ambiguous, or mismatched fetch/push origin refuses metadata.
|
|
# @RATIONALE Build-time origin observation replaces unavailable container Git, not runtime source discovery.
|
|
# @REJECTED Reusing stale files or guessing a repository from destination configuration cannot prove source identity.
|
|
import argparse
|
|
from dataclasses import asdict
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
|
|
OWNER = Path(__file__).resolve().parents[1] / "backend/src/core/source_repository_identity.py"
|
|
SPEC = importlib.util.spec_from_file_location("source_repository_identity_build", OWNER)
|
|
owner = importlib.util.module_from_spec(SPEC)
|
|
sys.modules[SPEC.name] = owner
|
|
SPEC.loader.exec_module(owner)
|
|
|
|
|
|
# #region Tooling.SourceRepositoryIdentity.Git [C:2] [TYPE Function]
|
|
def git(checkout, *args):
|
|
result = subprocess.run(["git", "-C", str(checkout), *args], capture_output=True, text=True, timeout=15)
|
|
if result.returncode:
|
|
raise owner.SourceRepositoryRefusal()
|
|
return result.stdout.strip()
|
|
# #endregion Tooling.SourceRepositoryIdentity.Git
|
|
|
|
|
|
# #region Tooling.SourceRepositoryIdentity.Observe [C:4] [TYPE Function]
|
|
def observe(checkout):
|
|
try:
|
|
root = Path(git(checkout, "rev-parse", "--show-toplevel")).resolve(strict=True)
|
|
if root != Path(checkout).resolve(strict=True):
|
|
raise owner.SourceRepositoryRefusal()
|
|
fetch = git(root, "config", "--get-all", "remote.origin.url").splitlines()
|
|
effective_fetch = git(root, "remote", "get-url", "--all", "origin").splitlines()
|
|
push = git(root, "remote", "get-url", "--push", "--all", "origin").splitlines()
|
|
if len(fetch) != 1 or len(effective_fetch) != 1 or len(push) != 1:
|
|
raise owner.SourceRepositoryRefusal()
|
|
origin = owner.normalize_repository_url(fetch[0])
|
|
if owner.normalize_repository_url(effective_fetch[0]) != origin or owner.normalize_repository_url(push[0]) != origin:
|
|
raise owner.SourceRepositoryRefusal()
|
|
value = {"origin": asdict(origin)}
|
|
owner.decode_source_identity(json.dumps(value))
|
|
return value
|
|
except (OSError, RuntimeError, subprocess.SubprocessError):
|
|
raise owner.SourceRepositoryRefusal() from None
|
|
# #endregion Tooling.SourceRepositoryIdentity.Observe
|
|
|
|
|
|
# #region Tooling.SourceRepositoryIdentity.Main [C:2] [TYPE Function]
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--checkout", required=True, type=Path)
|
|
args = parser.parse_args()
|
|
try:
|
|
value = observe(args.checkout)
|
|
except owner.SourceRepositoryRefusal as failure:
|
|
print(failure.code, file=sys.stderr)
|
|
return 2
|
|
print(json.dumps(value, sort_keys=True, separators=(",", ":")))
|
|
return 0
|
|
# #endregion Tooling.SourceRepositoryIdentity.Main
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|
|
# #endregion Tooling.SourceRepositoryIdentity
|