Skip to content

Commit 5067ce5

Browse files
authored
Generate release note additions (#1609)
1 parent b51be68 commit 5067ce5

2 files changed

Lines changed: 106 additions & 1 deletion

File tree

.github/scripts/release_verify.py

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import ast
77
import pathlib
88
import re
9+
import subprocess
910
from collections.abc import Sequence
1011

1112
try:
@@ -108,6 +109,98 @@ def verify_dist(args: argparse.Namespace) -> None:
108109
print(f" {name}")
109110

110111

112+
def _git(args: Sequence[str], *, cwd: pathlib.Path | None = None) -> str:
113+
return subprocess.check_output(
114+
["git", *args],
115+
cwd=cwd,
116+
encoding="utf-8",
117+
stderr=subprocess.STDOUT,
118+
).strip()
119+
120+
121+
def _version_tuple(version: str) -> tuple[int, ...] | None:
122+
match = re.fullmatch(r"([0-9]+(?:\.[0-9]+)+)(?:[a-zA-Z0-9_.+-]+)?", version)
123+
if not match:
124+
return None
125+
return tuple(int(part) for part in match.group(1).split("."))
126+
127+
128+
def _previous_release_tag(version: str) -> str:
129+
current = _version_tuple(version)
130+
if current is None:
131+
raise RuntimeError(f"Cannot determine previous release for {version!r}")
132+
133+
candidates: list[tuple[int, ...]] = []
134+
for tag in _git(["tag"]).splitlines():
135+
tag_version = _version_tuple(tag)
136+
if tag_version is not None and tag_version < current:
137+
candidates.append(tag_version)
138+
if not candidates:
139+
raise RuntimeError(f"Could not find a previous release tag before {version!r}")
140+
return ".".join(str(part) for part in max(candidates))
141+
142+
143+
def _gitlink(rev: str, path: str) -> str:
144+
output = _git(["ls-tree", rev, path])
145+
parts = output.split()
146+
if len(parts) < 3 or parts[0] != "160000":
147+
raise RuntimeError(f"Could not find submodule gitlink {path!r} at {rev!r}")
148+
return parts[2]
149+
150+
151+
def _clean_commit_subject(subject: str) -> str:
152+
subject = subject.encode("ascii", "ignore").decode("ascii")
153+
subject = re.sub(r"\s+", " ", subject).strip()
154+
subject = re.sub(r"^:[a-z0-9_+-]+:\s*", "", subject)
155+
return subject.replace(" : ", ": ")
156+
157+
158+
def _link_sdk_core_prs(subject: str) -> str:
159+
return re.sub(
160+
r"\(#([0-9]+)\)",
161+
r"([#\1](https://github.com/temporalio/sdk-rust/pull/\1))",
162+
subject,
163+
)
164+
165+
166+
def _sdk_core_release_notes(version: str, path: str) -> list[str]:
167+
previous_tag = _previous_release_tag(version)
168+
previous_commit = _gitlink(previous_tag, path)
169+
current_commit = _gitlink("HEAD", path)
170+
if previous_commit == current_commit:
171+
return []
172+
173+
submodule_path = pathlib.Path(path)
174+
if not (submodule_path / ".git").exists():
175+
raise RuntimeError(
176+
f"Submodule {path!r} is not initialized; checkout with submodules"
177+
)
178+
179+
log_args = [
180+
"log",
181+
"--format=%H%x00%h%x00%s",
182+
"--reverse",
183+
f"{previous_commit}..{current_commit}",
184+
]
185+
try:
186+
log_output = _git(log_args, cwd=submodule_path)
187+
except subprocess.CalledProcessError:
188+
_git(["fetch", "--quiet", "origin", "main"], cwd=submodule_path)
189+
log_output = _git(log_args, cwd=submodule_path)
190+
if not log_output:
191+
return []
192+
193+
lines = ["### SDK Core", ""]
194+
for line in log_output.splitlines():
195+
full_hash, short_hash, subject = line.split("\0", 2)
196+
subject = _link_sdk_core_prs(_clean_commit_subject(subject))
197+
lines.append(
198+
f"- [`{short_hash}`](https://github.com/temporalio/sdk-rust/commit/"
199+
f"{full_hash}) {subject}"
200+
)
201+
return lines
202+
203+
111204
def changelog_notes(args: argparse.Namespace) -> None:
112205
changelog_path = pathlib.Path(args.changelog)
113206
lines = changelog_path.read_text(encoding="utf-8").splitlines()
@@ -140,7 +233,12 @@ def changelog_notes(args: argparse.Namespace) -> None:
140233
if not section_lines:
141234
raise RuntimeError(f"Changelog section for {args.version!r} is empty")
142235

143-
notes = "## Changelog\n\n" + "\n".join(section_lines) + "\n"
236+
note_lines = ["## Notable Changes", "", *section_lines]
237+
sdk_core_notes = _sdk_core_release_notes(args.version, args.sdk_core_path)
238+
if sdk_core_notes:
239+
note_lines.extend(["", *sdk_core_notes])
240+
241+
notes = "\n".join(note_lines) + "\n"
144242
pathlib.Path(args.output).write_text(notes, encoding="utf-8")
145243

146244

@@ -162,6 +260,9 @@ def main(argv: Sequence[str] | None = None) -> None:
162260
changelog_parser.add_argument("--version", required=True)
163261
changelog_parser.add_argument("--changelog", default="CHANGELOG.md")
164262
changelog_parser.add_argument("--output", required=True)
263+
changelog_parser.add_argument(
264+
"--sdk-core-path", default="temporalio/bridge/sdk-core"
265+
)
165266
changelog_parser.set_defaults(func=changelog_notes)
166267

167268
args = parser.parse_args(argv)

.github/workflows/release-publish.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ jobs:
101101
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
102102
with:
103103
ref: ${{ github.sha }}
104+
fetch-depth: 0
105+
submodules: recursive
104106
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
105107
with:
106108
python-version: "3.11"
@@ -258,6 +260,8 @@ jobs:
258260
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
259261
with:
260262
ref: ${{ needs.verify_artifacts.outputs.release_sha }}
263+
fetch-depth: 0
264+
submodules: recursive
261265
- name: Build changelog release notes
262266
env:
263267
VERSION: ${{ needs.verify_artifacts.outputs.version }}

0 commit comments

Comments
 (0)