Skip to content

Commit 0797904

Browse files
j-bennetclaude
andauthored
Update release script (#1602)
* Add tag verification and PR commenting to release script Verify HEAD is on the expected tag before building distribution files, and comment on included PRs after uploading to PyPI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Make PR commenting a single step in release script Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Include PR list in release commit message Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix ruff formatting in release script Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix ruff 0.15.15 formatting --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b667e16 commit 0797904

1 file changed

Lines changed: 98 additions & 2 deletions

File tree

release.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,43 @@ def version(version_file):
5353
return ver
5454

5555

56+
def get_merged_prs_since_last_tag():
57+
"""Get list of PR numbers and titles merged since the last tag."""
58+
try:
59+
previous_tag = (
60+
subprocess
61+
.check_output(
62+
["git", "describe", "--abbrev=0", "--tags"],
63+
stderr=subprocess.DEVNULL,
64+
)
65+
.decode()
66+
.strip()
67+
)
68+
except subprocess.CalledProcessError:
69+
return []
70+
71+
log = subprocess.check_output(["git", "log", "--merges", "--oneline", "{}..HEAD".format(previous_tag)]).decode()
72+
73+
prs = re.findall(r"(.+\(#(\d+)\))", log)
74+
seen = set()
75+
result = []
76+
for line, num in prs:
77+
if num not in seen:
78+
seen.add(num)
79+
result.append(num)
80+
return result
81+
82+
5683
def commit_for_release(version_file, ver):
84+
pr_numbers = get_merged_prs_since_last_tag()
85+
pr_list = ""
86+
if pr_numbers:
87+
pr_list = "\n\n" + "\n".join("- #{}".format(n) for n in pr_numbers)
88+
89+
message = "Releasing version {}{}".format(ver, pr_list)
5790
run_step("git", "reset")
5891
run_step("git", "add", "-u")
59-
run_step("git", "commit", "--message", "Releasing version {}".format(ver))
92+
run_step("git", "commit", "--message", message)
6093

6194

6295
def create_git_tag(tag_name):
@@ -80,6 +113,67 @@ def push_tags_to_github():
80113
run_step("git", "push", "--tags", "origin")
81114

82115

116+
def check_tag(ver):
117+
"""Verify that HEAD is on the expected tag."""
118+
tag = "v{}".format(ver)
119+
try:
120+
current_tag = (
121+
subprocess
122+
.check_output(
123+
["git", "describe", "--exact-match", "--tags", "HEAD"],
124+
stderr=subprocess.DEVNULL,
125+
)
126+
.decode()
127+
.strip()
128+
)
129+
except subprocess.CalledProcessError:
130+
print("ERROR: HEAD is not on any tag. Expected tag '{}'.".format(tag))
131+
sys.exit(1)
132+
if current_tag != tag:
133+
print("ERROR: HEAD is on tag '{}', expected '{}'.".format(current_tag, tag))
134+
sys.exit(1)
135+
print("OK: on tag '{}'".format(tag))
136+
137+
138+
def comment_on_released_prs(ver):
139+
"""Post a comment on all PRs included in this release."""
140+
tag = "v{}".format(ver)
141+
try:
142+
previous_tag = (
143+
subprocess
144+
.check_output(
145+
["git", "describe", "--abbrev=0", "--tags", "{}^".format(tag)],
146+
stderr=subprocess.DEVNULL,
147+
)
148+
.decode()
149+
.strip()
150+
)
151+
except subprocess.CalledProcessError:
152+
print("WARNING: Could not find previous tag. Skipping PR comments.")
153+
return
154+
155+
log = subprocess.check_output(["git", "log", "--merges", "--oneline", "{}..{}".format(previous_tag, tag)]).decode()
156+
157+
pr_numbers = re.findall(r"#(\d+)", log)
158+
pr_numbers = list(set(pr_numbers))
159+
160+
if not pr_numbers:
161+
print("No PRs found between {} and {}.".format(previous_tag, tag))
162+
return
163+
164+
print("Found PRs: {}".format(", ".join("#" + n for n in pr_numbers)))
165+
message = "Released as part of {}.".format(ver)
166+
167+
print("gh pr comment --body '{}' {}".format(message, " ".join(pr_numbers)))
168+
if skip_step():
169+
print("--- Skipping...")
170+
elif DRY_RUN:
171+
print("--- Pretending to run...")
172+
else:
173+
for pr in pr_numbers:
174+
subprocess.check_output(["gh", "pr", "comment", pr, "--body", message])
175+
176+
83177
def checklist(questions):
84178
for question in questions:
85179
if not click.confirm("--- {}".format(question), default=False):
@@ -126,7 +220,9 @@ def checklist(questions):
126220

127221
commit_for_release("pgcli/__init__.py", ver)
128222
create_git_tag("v{}".format(ver))
129-
create_distribution_files()
130223
push_to_github()
131224
push_tags_to_github()
225+
check_tag(ver)
226+
create_distribution_files()
132227
upload_distribution_files()
228+
comment_on_released_prs(ver)

0 commit comments

Comments
 (0)