diff --git a/.github/scripts/publish_release.py b/.github/scripts/publish_release.py index 27522fe..d8fbe17 100644 --- a/.github/scripts/publish_release.py +++ b/.github/scripts/publish_release.py @@ -10,7 +10,7 @@ REPOSITORY = "krav01/usage-billing" TAG = "v0.3.0" -BRANCH = "release/v0.3.0-publisher-fix" +BRANCH = "release/v0.3.0-final" def github(method, path, data=None, optional=False): @@ -68,13 +68,19 @@ def publish(event, notes, api=github): return "Skipped: not the unique release branch merge" # List responses may omit merge metadata. Fetch the authoritative PR detail. pr = api("GET", f"pulls/{prs[0]['number']}") - if not pr["merged"] or pr["merge_commit_sha"] != sha: + if not pr["merged"]: return "Skipped: not the unique release branch merge" if ( pr["head"]["ref"] != BRANCH or pr["head"]["repo"]["full_name"] != REPOSITORY or pr["base"]["ref"] != "main" or pr["base"]["repo"]["full_name"] != REPOSITORY ): return "Skipped: unexpected release PR source" + # Bind the release to the actual Git merge object. PR responses can omit + # merge_commit_sha; the second parent must still be the exact reviewed head. + commit = api("GET", f"git/commits/{sha}") + parents = commit["parents"] + if commit["sha"] != sha or len(parents) != 2 or parents[1]["sha"] != pr["head"]["sha"]: + return "Skipped: commit does not merge the reviewed release head" query = urlencode({"head_sha": sha, "event": "push", "per_page": 100}) runs = api("GET", f"actions/runs?{query}")["workflow_runs"] verified = [] diff --git a/.github/scripts/test_publish_release.py b/.github/scripts/test_publish_release.py index 1855beb..5602825 100644 --- a/.github/scripts/test_publish_release.py +++ b/.github/scripts/test_publish_release.py @@ -28,9 +28,10 @@ def check(path, sha=SHA, event="push", number=1): class FakeAPI: def __init__(self): self.main = SHA + self.parents = ["c" * 40, HEAD] self.prs = [{ "number": 15, "merged": True, - "merged_at": "2026-09-03T00:00:00Z", "merge_commit_sha": SHA, + "merged_at": "2026-09-03T00:00:00Z", "head": {"sha": HEAD, "ref": BRANCH, "repo": {"full_name": REPOSITORY}}, "base": {"ref": "main", "repo": {"full_name": REPOSITORY}}, "html_url": "https://example.test/pr", @@ -62,6 +63,8 @@ def __call__(self, method, path, data=None, optional=False): return [{"number": pr["number"]} for pr in self.prs] if path == "pulls/15": return self.prs[0] + if path == f"git/commits/{SHA}": + return {"sha": SHA, "parents": [{"sha": parent} for parent in self.parents]} if path.startswith("actions/runs?"): query = parse_qs(urlsplit(path).query) return {"workflow_runs": self.runs if query["head_sha"] == [SHA] else self.dependencies} @@ -97,13 +100,15 @@ def forbidden(*args, **kwargs): publish(event, "", forbidden) def test_current_main_and_unique_release_merge_are_required(self): - for field in ("main", "merge", "unmerged", "multiple", "fork"): + for field in ("main", "merge", "nonmerge", "unmerged", "multiple", "fork"): with self.subTest(field=field): api = FakeAPI() if field == "main": api.main = "c" * 40 elif field == "merge": - api.prs[0]["merge_commit_sha"] = "c" * 40 + api.parents[1] = "c" * 40 + elif field == "nonmerge": + api.parents.pop() elif field == "unmerged": api.prs[0]["merged"] = False elif field == "multiple": diff --git a/.github/workflows/release-v0.3.0.yml b/.github/workflows/release-v0.3.0.yml index 78e15ec..867a116 100644 --- a/.github/workflows/release-v0.3.0.yml +++ b/.github/workflows/release-v0.3.0.yml @@ -20,7 +20,7 @@ jobs: github.event.workflow_run.event == 'push' && github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'main' && - contains(github.event.workflow_run.head_commit.message, 'from krav01/release/v0.3.0-publisher-fix') && + contains(github.event.workflow_run.head_commit.message, 'from krav01/release/v0.3.0-final') && github.event.workflow_run.head_sha == github.sha && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest