From bb47f2226f0a718cd1d5174984be0b7ef876648b Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 7 Nov 2025 12:05:22 -0800 Subject: [PATCH] feat: add --promote-alpha option to create_github_release script (#6370) Historically, running `create_github_release --publish-release` would always publish a new release from latest `main`, which isn't always the best idea. We should really publish an alpha, let it bake, and then promote it. This PR introduces a new flag, `--promote-alpha`, which does exactly that. It also works with `--dry-run`, so you can sanity check the commit it will use as the base commit for the new release before running it for real. ```shell $ ./codex-rs/scripts/create_github_release --dry-run --promote-alpha 0.56.0-alpha.2 Publishing version 0.56.0 Running gh api GET /repos/openai/codex/git/refs/tags/rust-v0.56.0-alpha.2 Running gh api GET /repos/openai/codex/git/tags/7d4ef77bc35b011aa0c76c5cbe6cd7d3e53f1dfe Running gh api GET /repos/openai/codex/compare/main...8b49211e67d3c863df5ecc13fc5f88516a20fa69 Would publish version 0.56.0 using base commit 62474a30e8b4a69a8639ae0889bcf7f576dc82f7 derived from rust-v0.56.0-alpha.2. ``` --- codex-rs/scripts/create_github_release | 72 ++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/codex-rs/scripts/create_github_release b/codex-rs/scripts/create_github_release index e121fc5c..47d6d3ce 100755 --- a/codex-rs/scripts/create_github_release +++ b/codex-rs/scripts/create_github_release @@ -21,6 +21,11 @@ def parse_args(argv: list[str]) -> argparse.Namespace: action="store_true", help="Print the version that would be used and exit before making changes.", ) + parser.add_argument( + "--promote-alpha", + metavar="VERSION", + help="Promote an existing alpha tag (e.g., 0.56.0-alpha.5) by using its merge-base with main as the base commit.", + ) group = parser.add_mutually_exclusive_group() group.add_argument( @@ -43,26 +48,43 @@ def parse_args(argv: list[str]) -> argparse.Namespace: args.publish_alpha or args.publish_release or args.emergency_version_override + or args.promote_alpha ): parser.error( - "Must specify --publish-alpha, --publish-release, or --emergency-version-override." + "Must specify --publish-alpha, --publish-release, --promote-alpha, or --emergency-version-override." ) return args def main(argv: list[str]) -> int: args = parse_args(argv) + + # Strip the leading "v" if present. + promote_alpha = args.promote_alpha + if promote_alpha and promote_alpha.startswith("v"): + promote_alpha = promote_alpha[1:] + try: - if args.emergency_version_override: + if promote_alpha: + version = derive_release_version_from_alpha(promote_alpha) + elif args.emergency_version_override: version = args.emergency_version_override else: version = determine_version(args) print(f"Publishing version {version}") - if args.dry_run: + if promote_alpha: + base_commit = get_promote_alpha_base_commit(promote_alpha) + if args.dry_run: + print( + f"Would publish version {version} using base commit {base_commit} derived from rust-v{promote_alpha}." + ) + return 0 + elif args.dry_run: return 0 - print("Fetching branch head...") - base_commit = get_branch_head() + if not promote_alpha: + print("Fetching branch head...") + base_commit = get_branch_head() print(f"Base commit: {base_commit}") print("Fetching commit tree...") base_tree = get_commit_tree(base_commit) @@ -130,6 +152,39 @@ def get_branch_head() -> str: raise ReleaseError("Unable to determine branch head.") from error +def get_promote_alpha_base_commit(alpha_version: str) -> str: + tag_name = f"rust-v{alpha_version}" + tag_commit_sha = get_tag_commit_sha(tag_name) + return get_merge_base_with_main(tag_commit_sha) + + +def get_tag_commit_sha(tag_name: str) -> str: + response = run_gh_api(f"/repos/{REPO}/git/refs/tags/{tag_name}") + try: + sha = response["object"]["sha"] + obj_type = response["object"]["type"] + except KeyError as error: + raise ReleaseError(f"Unable to resolve tag {tag_name}.") from error + while obj_type == "tag": + tag_response = run_gh_api(f"/repos/{REPO}/git/tags/{sha}") + try: + sha = tag_response["object"]["sha"] + obj_type = tag_response["object"]["type"] + except KeyError as error: + raise ReleaseError(f"Unable to resolve annotated tag {tag_name}.") from error + if obj_type != "commit": + raise ReleaseError(f"Tag {tag_name} does not reference a commit.") + return sha + + +def get_merge_base_with_main(commit_sha: str) -> str: + response = run_gh_api(f"/repos/{REPO}/compare/main...{commit_sha}") + try: + return response["merge_base_commit"]["sha"] + except KeyError as error: + raise ReleaseError("Unable to determine merge base with main.") from error + + def get_commit_tree(commit_sha: str) -> str: response = run_gh_api(f"/repos/{REPO}/git/commits/{commit_sha}") try: @@ -309,5 +364,12 @@ def format_version(major: int, minor: int, patch: int) -> str: return f"{major}.{minor}.{patch}" +def derive_release_version_from_alpha(alpha_version: str) -> str: + match = re.match(r"^(\d+)\.(\d+)\.(\d+)-alpha\.(\d+)$", alpha_version) + if match is None: + raise ReleaseError(f"Unexpected alpha version format: {alpha_version}") + return f"{match.group(1)}.{match.group(2)}.{match.group(3)}" + + if __name__ == "__main__": sys.exit(main(sys.argv))