-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path_remote_default.py
More file actions
118 lines (99 loc) · 3.83 KB
/
Copy path_remote_default.py
File metadata and controls
118 lines (99 loc) · 3.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/env python3
"""Resolve a default GitLab project / GitHub repo for preset payload ops.
Shared by ``gitlab/issue_create.py`` (``project``) and
``github/issue_create.py`` (``repo``) so the common case needs no per-payload
boilerplate.
Resolution order (most specific wins):
1. explicit value in the payload (handled by the caller)
2. ``defaults.<config_key>`` in ``.supertool.json`` (cwd or any parent)
3. the ``origin`` git remote, when its host matches the platform
Returns ``None`` when nothing resolves, so the caller raises its own
"missing required field" error unchanged.
"""
from __future__ import annotations
import json
import re
import subprocess
from pathlib import Path
def _run_git(args: list[str], timeout: int = 5) -> str | None:
"""Run a git command, returning trimmed stdout or None on any failure."""
try:
result = subprocess.run(
["git"] + args,
capture_output=True, text=True, timeout=timeout,
)
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
return None
if result.returncode != 0:
return None
return result.stdout.strip() or None
def config_default(key: str) -> str | None:
"""Return ``defaults[key]`` from the nearest .supertool.json, or None.
Walks from cwd up to the filesystem root. The first .supertool.json found
is authoritative — a malformed file or missing ``defaults`` block yields
None rather than continuing the search (mirrors core's single-file load).
"""
cwd = Path.cwd()
for directory in [cwd, *cwd.parents]:
candidate = directory / ".supertool.json"
if not candidate.is_file():
continue
try:
data = json.loads(candidate.read_text())
except (json.JSONDecodeError, OSError):
return None
if not isinstance(data, dict):
return None
defaults = data.get("defaults")
if isinstance(defaults, dict):
val = defaults.get(key)
if isinstance(val, str) and val.strip():
return val.strip()
return None
return None
def parse_remote(url: str) -> tuple[str, str] | None:
"""Parse a git remote URL into ``(host, 'namespace/repo')``.
Handles the three forms git emits:
scp-like git@host:namespace/repo.git
ssh:// ssh://git@host[:port]/namespace/repo.git
https:// https://host[:port]/namespace/repo.git
Returns None when the URL matches none of them.
"""
url = url.strip()
if not url:
return None
# scp-like: user@host:path (no scheme, single colon before the path)
scp = re.match(r"^[\w.+-]+@([^:/]+):(.+)$", url)
if scp:
host, path = scp.group(1), scp.group(2)
else:
# scheme://[user@]host[:port]/path
uri = re.match(r"^[a-zA-Z][\w+.-]*://(?:[^@/]+@)?([^/:]+)(?::\d+)?/(.+)$", url)
if not uri:
return None
host, path = uri.group(1), uri.group(2)
path = path.strip("/")
if path.endswith(".git"):
path = path[:-4]
path = path.strip("/")
if not host or not path:
return None
return host, path
def origin_slug(host_substr: str) -> str | None:
"""Return the ``origin`` remote's 'ns/repo' when its host matches.
``host_substr`` is matched as a substring of the remote host so a
self-hosted GitLab (``gitlab.dp.tools``) matches ``"gitlab"``.
"""
url = _run_git(["remote", "get-url", "origin"])
if not url:
return None
parsed = parse_remote(url)
if parsed is None:
return None
host, path = parsed
if host_substr in host:
return path
return None
def resolve(config_key: str, host_substr: str) -> str | None:
"""Config default wins over git-remote auto-detect. None if neither hits."""
return config_default(config_key) or origin_slug(host_substr)