From 5712ca5a336d9862ec9cbc8276c0ab87ed4aba83 Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Tue, 2 Jun 2026 17:33:17 -0700 Subject: [PATCH 1/5] 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 --- release.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/release.py b/release.py index 929393796..b8c9bffd1 100644 --- a/release.py +++ b/release.py @@ -80,6 +80,53 @@ def push_tags_to_github(): run_step("git", "push", "--tags", "origin") +def check_tag(ver): + """Verify that HEAD is on the expected tag.""" + tag = "v{}".format(ver) + try: + current_tag = subprocess.check_output( + ["git", "describe", "--exact-match", "--tags", "HEAD"], + stderr=subprocess.DEVNULL, + ).decode().strip() + except subprocess.CalledProcessError: + print("ERROR: HEAD is not on any tag. Expected tag '{}'.".format(tag)) + sys.exit(1) + if current_tag != tag: + print("ERROR: HEAD is on tag '{}', expected '{}'.".format(current_tag, tag)) + sys.exit(1) + print("OK: on tag '{}'".format(tag)) + + +def comment_on_released_prs(ver): + """Post a comment on all PRs included in this release.""" + tag = "v{}".format(ver) + try: + previous_tag = subprocess.check_output( + ["git", "describe", "--abbrev=0", "--tags", "{}^".format(tag)], + stderr=subprocess.DEVNULL, + ).decode().strip() + except subprocess.CalledProcessError: + print("WARNING: Could not find previous tag. Skipping PR comments.") + return + + log = subprocess.check_output( + ["git", "log", "--merges", "--oneline", "{}..{}".format(previous_tag, tag)] + ).decode() + + pr_numbers = re.findall(r"#(\d+)", log) + pr_numbers = list(set(pr_numbers)) + + if not pr_numbers: + print("No PRs found between {} and {}.".format(previous_tag, tag)) + return + + print("Found PRs: {}".format(", ".join("#" + n for n in pr_numbers))) + message = "Released as part of {}.".format(ver) + + for pr in pr_numbers: + run_step("gh", "pr", "comment", pr, "--body", message) + + def checklist(questions): for question in questions: if not click.confirm("--- {}".format(question), default=False): @@ -126,7 +173,9 @@ def checklist(questions): commit_for_release("pgcli/__init__.py", ver) create_git_tag("v{}".format(ver)) - create_distribution_files() push_to_github() push_tags_to_github() + check_tag(ver) + create_distribution_files() upload_distribution_files() + comment_on_released_prs(ver) From 79054961337d7a76803c260b63ad8a4ba3a861b5 Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Tue, 2 Jun 2026 17:36:26 -0700 Subject: [PATCH 2/5] Make PR commenting a single step in release script Co-Authored-By: Claude Opus 4.6 --- release.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/release.py b/release.py index b8c9bffd1..dd7b8ccc8 100644 --- a/release.py +++ b/release.py @@ -123,8 +123,14 @@ def comment_on_released_prs(ver): print("Found PRs: {}".format(", ".join("#" + n for n in pr_numbers))) message = "Released as part of {}.".format(ver) - for pr in pr_numbers: - run_step("gh", "pr", "comment", pr, "--body", message) + print("gh pr comment --body '{}' {}".format(message, " ".join(pr_numbers))) + if skip_step(): + print("--- Skipping...") + elif DRY_RUN: + print("--- Pretending to run...") + else: + for pr in pr_numbers: + subprocess.check_output(["gh", "pr", "comment", pr, "--body", message]) def checklist(questions): From 21f09c39f0a6bd5c179604d6b4c8438bd2c81472 Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Tue, 2 Jun 2026 17:39:56 -0700 Subject: [PATCH 3/5] Include PR list in release commit message Co-Authored-By: Claude Opus 4.6 --- release.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/release.py b/release.py index dd7b8ccc8..b23b0e9ca 100644 --- a/release.py +++ b/release.py @@ -53,10 +53,40 @@ def version(version_file): return ver +def get_merged_prs_since_last_tag(): + """Get list of PR numbers and titles merged since the last tag.""" + try: + previous_tag = subprocess.check_output( + ["git", "describe", "--abbrev=0", "--tags"], + stderr=subprocess.DEVNULL, + ).decode().strip() + except subprocess.CalledProcessError: + return [] + + log = subprocess.check_output( + ["git", "log", "--merges", "--oneline", "{}..HEAD".format(previous_tag)] + ).decode() + + prs = re.findall(r"(.+\(#(\d+)\))", log) + seen = set() + result = [] + for line, num in prs: + if num not in seen: + seen.add(num) + result.append(num) + return result + + def commit_for_release(version_file, ver): + pr_numbers = get_merged_prs_since_last_tag() + pr_list = "" + if pr_numbers: + pr_list = "\n\n" + "\n".join("- #{}".format(n) for n in pr_numbers) + + message = "Releasing version {}{}".format(ver, pr_list) run_step("git", "reset") run_step("git", "add", "-u") - run_step("git", "commit", "--message", "Releasing version {}".format(ver)) + run_step("git", "commit", "--message", message) def create_git_tag(tag_name): From d3321c14f3c6a216859fe9a72e789d15a995ee2f Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Tue, 2 Jun 2026 17:50:16 -0700 Subject: [PATCH 4/5] Fix ruff formatting in release script Co-Authored-By: Claude Opus 4.6 --- release.py | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/release.py b/release.py index b23b0e9ca..5a13af1f5 100644 --- a/release.py +++ b/release.py @@ -56,16 +56,18 @@ def version(version_file): def get_merged_prs_since_last_tag(): """Get list of PR numbers and titles merged since the last tag.""" try: - previous_tag = subprocess.check_output( - ["git", "describe", "--abbrev=0", "--tags"], - stderr=subprocess.DEVNULL, - ).decode().strip() + previous_tag = ( + subprocess.check_output( + ["git", "describe", "--abbrev=0", "--tags"], + stderr=subprocess.DEVNULL, + ) + .decode() + .strip() + ) except subprocess.CalledProcessError: return [] - log = subprocess.check_output( - ["git", "log", "--merges", "--oneline", "{}..HEAD".format(previous_tag)] - ).decode() + log = subprocess.check_output(["git", "log", "--merges", "--oneline", "{}..HEAD".format(previous_tag)]).decode() prs = re.findall(r"(.+\(#(\d+)\))", log) seen = set() @@ -114,10 +116,14 @@ def check_tag(ver): """Verify that HEAD is on the expected tag.""" tag = "v{}".format(ver) try: - current_tag = subprocess.check_output( - ["git", "describe", "--exact-match", "--tags", "HEAD"], - stderr=subprocess.DEVNULL, - ).decode().strip() + current_tag = ( + subprocess.check_output( + ["git", "describe", "--exact-match", "--tags", "HEAD"], + stderr=subprocess.DEVNULL, + ) + .decode() + .strip() + ) except subprocess.CalledProcessError: print("ERROR: HEAD is not on any tag. Expected tag '{}'.".format(tag)) sys.exit(1) @@ -131,17 +137,19 @@ def comment_on_released_prs(ver): """Post a comment on all PRs included in this release.""" tag = "v{}".format(ver) try: - previous_tag = subprocess.check_output( - ["git", "describe", "--abbrev=0", "--tags", "{}^".format(tag)], - stderr=subprocess.DEVNULL, - ).decode().strip() + previous_tag = ( + subprocess.check_output( + ["git", "describe", "--abbrev=0", "--tags", "{}^".format(tag)], + stderr=subprocess.DEVNULL, + ) + .decode() + .strip() + ) except subprocess.CalledProcessError: print("WARNING: Could not find previous tag. Skipping PR comments.") return - log = subprocess.check_output( - ["git", "log", "--merges", "--oneline", "{}..{}".format(previous_tag, tag)] - ).decode() + log = subprocess.check_output(["git", "log", "--merges", "--oneline", "{}..{}".format(previous_tag, tag)]).decode() pr_numbers = re.findall(r"#(\d+)", log) pr_numbers = list(set(pr_numbers)) From af37cddf14905a8bf6ffb3e7dce17d6dd109b3df Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Tue, 2 Jun 2026 17:56:38 -0700 Subject: [PATCH 5/5] Fix ruff 0.15.15 formatting --- release.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/release.py b/release.py index 5a13af1f5..46065d0d5 100644 --- a/release.py +++ b/release.py @@ -57,7 +57,8 @@ def get_merged_prs_since_last_tag(): """Get list of PR numbers and titles merged since the last tag.""" try: previous_tag = ( - subprocess.check_output( + subprocess + .check_output( ["git", "describe", "--abbrev=0", "--tags"], stderr=subprocess.DEVNULL, ) @@ -117,7 +118,8 @@ def check_tag(ver): tag = "v{}".format(ver) try: current_tag = ( - subprocess.check_output( + subprocess + .check_output( ["git", "describe", "--exact-match", "--tags", "HEAD"], stderr=subprocess.DEVNULL, ) @@ -138,7 +140,8 @@ def comment_on_released_prs(ver): tag = "v{}".format(ver) try: previous_tag = ( - subprocess.check_output( + subprocess + .check_output( ["git", "describe", "--abbrev=0", "--tags", "{}^".format(tag)], stderr=subprocess.DEVNULL, )