Skip to content

Commit b910a97

Browse files
committed
(ELI-466) refactoring
1 parent 75d1e2f commit b910a97

File tree

4 files changed

+200
-121
lines changed

4 files changed

+200
-121
lines changed

.act/auto_preprod_trigger_latest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"status": "completed",
1313
"conclusion": "success",
1414
"head_branch": "main",
15-
"head_sha": "166abc418e62d986484c8336f06223fc9135e864"
15+
"head_sha": "758a8d751ca1885695e9ac0766fcc0007dfb2995"
1616
},
1717
"repository": {
1818
"full_name": "NHSDigital/eligibility-signposting-api",

scripts/workflow/ci_utils.py

Lines changed: 94 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
BRANCH = os.getenv("BRANCH", "main")
1616
REPO_FALLBACK = "NHSDigital/eligibility-signposting-api"
1717

18+
19+
def _repo() -> str:
20+
return os.environ.get("GITHUB_REPOSITORY") or REPO_FALLBACK
21+
1822
def fail(msg: str) -> NoReturn:
1923
print(f"::error::{msg}", file=sys.stderr)
2024
sys.exit(1)
@@ -41,34 +45,6 @@ def git_ok(args: List[str]) -> bool:
4145
def is_ancestor(older: str, newer: str) -> bool:
4246
return subprocess.run(["git", "merge-base", "--is-ancestor", older, newer]).returncode == 0
4347

44-
def gh_json(args: List[str]) -> Any:
45-
# Map GITHUB_TOKEN -> GH_TOKEN (gh prefers GH_TOKEN)
46-
if "GH_TOKEN" not in os.environ and os.environ.get("GITHUB_TOKEN"):
47-
os.environ["GH_TOKEN"] = os.environ["GITHUB_TOKEN"]
48-
49-
# Ensure repo is explicit (act containers often need this)
50-
repo = os.environ.get("GITHUB_REPOSITORY") or REPO_FALLBACK
51-
base = ["gh", *args]
52-
if "--repo" not in args and "-R" not in args:
53-
base.extend(["--repo", repo])
54-
55-
cp = run(base, check=True)
56-
try:
57-
return json.loads(cp.stdout or "null")
58-
except json.JSONDecodeError as e:
59-
raise RuntimeError(f"Failed to parse gh JSON: {e}\nSTDOUT:\n{cp.stdout}\nSTDERR:\n{cp.stderr}")
60-
61-
def gh_api(path: str, jq: Optional[str] = None) -> List[str]:
62-
"""
63-
A simple python wrapper around the GitHub API
64-
to make it a callable function.
65-
"""
66-
args = ["gh", "api", path]
67-
if jq:
68-
args += ["--jq", jq]
69-
cp = run(args, check=True)
70-
return [x for x in cp.stdout.splitlines() if x]
71-
7248
def dev_tag_for_sha(sha: str) -> Optional[str]:
7349
cp = run(["git", "tag", "--points-at", sha], check=False)
7450
for t in cp.stdout.splitlines():
@@ -80,7 +56,7 @@ def sha_for_tag(tag: str) -> Optional[str]:
8056
cp = run(["git", "rev-list", "-n1", tag], check=False)
8157
return cp.stdout.strip() or None
8258

83-
def latest_final_tag() -> Optional[str]:
59+
def latest_release_tag() -> Optional[str]:
8460
cp = run(["git", "tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"], check=True)
8561
tags = cp.stdout.splitlines()
8662
return tags[0] if tags else None
@@ -91,24 +67,100 @@ def first_commit() -> str:
9167
9268
We will never use this for our project since we
9369
already have a release but can be used as a
94-
fallback for new projects.
70+
fallback.
9571
"""
9672
return run(["git", "rev-list", "--max-parents=0", "HEAD"], check=True).stdout.strip()
9773

98-
def list_merged_pr_commits(base: str, head: str) -> List[str]:
74+
def labels_for_pr(pr: int) -> List[str]:
75+
"""Return all labels on a PR (or issue)."""
76+
args = [
77+
"gh", "api",
78+
f"/repos/{_repo()}/issues/{pr}/labels",
79+
"-H", "X-GitHub-Api-Version: 2022-11-28",
80+
"--jq", ".[].name",
81+
]
82+
cp = run(args, check=False)
83+
if cp.returncode not in (0, 1):
84+
print(f"Warning: gh api exit {cp.returncode} for PR #{pr}", file=sys.stderr)
85+
if cp.stdout.strip():
86+
return [x.strip() for x in cp.stdout.splitlines() if x.strip()]
87+
return []
88+
89+
90+
def commit_subject(sha: str) -> str:
91+
"""Return the one-line subject for a commit SHA."""
92+
cp = run(["git", "log", "-1", "--pretty=%s", sha], check=False)
93+
return (cp.stdout or "").strip()
94+
95+
def parse_merge_subject_for_pr_numbers(subject: str) -> List[int]:
96+
"""
97+
Extract PR numbers from common merge subjects, e.g.:
98+
- 'Merge pull request #123 from ...'
99+
- 'Some feature (#456)'
100+
"""
101+
import re
102+
nums = set()
103+
for m in re.finditer(r"(?:#|\bPR\s*#)(\d+)", subject, flags=re.IGNORECASE):
104+
try:
105+
nums.add(int(m.group(1)))
106+
except ValueError:
107+
pass
108+
# Also match explicit 'Merge pull request #123'
109+
m2 = re.search(r"Merge pull request #(\d+)", subject, flags=re.IGNORECASE)
110+
if m2:
111+
try:
112+
nums.add(int(m2.group(1)))
113+
except ValueError:
114+
pass
115+
return sorted(nums)
116+
117+
def list_merge_commits(base: str, head: str) -> List[str]:
118+
"""
119+
Merge commits on the first-parent path from base..head.
120+
"""
99121
rng = f"{base}..{head}"
100122
cp = run(["git", "rev-list", "--merges", "--first-parent", rng], check=False)
101123
return [x for x in cp.stdout.splitlines() if x]
102124

103-
def prs_for_commit(sha: str) -> List[int]:
104-
nums = gh_api(
105-
f"/repos/{os.getenv('GITHUB_REPOSITORY')}/commits/{sha}/pulls",
106-
jq=".[].number",
107-
)
108-
return [int(n) for n in nums]
125+
def list_all_commits(base: str, head: str) -> List[str]:
126+
"""
127+
All commits on the first-parent path base..head (includes squash merges as single commits).
128+
"""
129+
rng = f"{base}..{head}"
130+
# We choose first-parent so we're scanning the mainline history only.
131+
cp = run(["git", "rev-list", "--first-parent", rng], check=False)
132+
return [x for x in cp.stdout.splitlines() if x]
109133

110-
def labels_for_pr(pr: int) -> List[str]:
111-
return gh_api(
112-
f"/repos/{os.getenv('GITHUB_REPOSITORY')}/issues/{pr}/labels",
113-
jq=".[].name",
114-
)
134+
def prs_for_commit_via_api(sha: str) -> List[int]:
135+
"""
136+
Use the official endpoint linking any commit to associated PRs.
137+
Works for merge commits and for commits that landed via rebase merges.
138+
"""
139+
args = [
140+
"gh", "api",
141+
f"/repos/{_repo()}/commits/{sha}/pulls",
142+
"-H", "Accept: application/vnd.github.groot-preview+json",
143+
"-H", "X-GitHub-Api-Version: 2022-11-28",
144+
"--jq", ".[].number"
145+
]
146+
try:
147+
cp = run(args, check=True)
148+
return [int(x) for x in cp.stdout.splitlines() if x]
149+
except subprocess.CalledProcessError as e:
150+
print(f"Warning: commit→PR lookup failed for {sha[:7]} ({e.returncode})", file=sys.stderr)
151+
return []
152+
153+
def title_for_pr(pr: int) -> str:
154+
"""
155+
Return the title of a pull request.
156+
"""
157+
args = [
158+
"gh", "pr", "view", str(pr),
159+
"--repo", _repo(),
160+
"--json", "title",
161+
"--jq", ".title",
162+
]
163+
cp = run(args, check=False)
164+
if cp.returncode not in (0, 1):
165+
print(f"Warning: gh pr view exit {cp.returncode} for PR #{pr}", file=sys.stderr)
166+
return cp.stdout.strip()

scripts/workflow/pre-release_resolver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ class RefInfo:
4444
ref: str
4545

4646
def get_event_name() -> str:
47-
"""Determine the effective event name, correcting for act quirks."""
47+
"""Determine the effective event name,
48+
correcting for act quirks."""
4849
evt_env = os.getenv("GITHUB_EVENT_NAME", "")
4950
evt_payload = None
5051
path = os.getenv("GITHUB_EVENT_PATH")
@@ -81,7 +82,6 @@ def _run_gh(args: list[str]) -> str:
8182
raise RuntimeError(f"Command failed: {' '.join(cmd)}")
8283
return cp.stdout
8384

84-
8585
def list_successful_test_shas() -> List[str]:
8686
"""
8787
Return SHAs for successful runs of the test deploy workflow on BRANCH.

0 commit comments

Comments
 (0)