From bd65f81e54168262d75f83b28665f465de5901cd Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 5 Sep 2025 21:54:18 -0700 Subject: [PATCH] chore: rewrite codex-rs/scripts/create_github_release.sh in Python (#3226) Migrating to Python to make this script easier to iterate on. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/3226). * #3231 * #3230 * #3228 * __->__ #3226 --- codex-rs/scripts/create_github_release | 130 ++++++++++++++++++++++ codex-rs/scripts/create_github_release.sh | 64 ----------- 2 files changed, 130 insertions(+), 64 deletions(-) create mode 100755 codex-rs/scripts/create_github_release delete mode 100755 codex-rs/scripts/create_github_release.sh diff --git a/codex-rs/scripts/create_github_release b/codex-rs/scripts/create_github_release new file mode 100755 index 00000000..4bbfcde7 --- /dev/null +++ b/codex-rs/scripts/create_github_release @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 + +import argparse +import os +import re +import subprocess +import sys +from pathlib import Path + + +ROOT_DIR = Path(__file__).resolve().parent.parent +CARGO_TOML = ROOT_DIR / "Cargo.toml" + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Create a tagged Codex release.") + parser.add_argument( + "version", + help="Version string used for Cargo.toml and the Git tag (e.g. 0.1.0-alpha.4).", + ) + return parser.parse_args(argv[1:]) + + +def main(argv: list[str]) -> int: + os.chdir(ROOT_DIR) + args = parse_args(argv) + try: + ensure_clean_worktree() + branch = current_branch() + ensure_on_main(branch) + ensure_on_origin_main() + create_release(args.version, branch) + except ReleaseError as error: + print(f"ERROR: {error}", file=sys.stderr) + return 1 + return 0 + + +def ensure_clean_worktree() -> None: + commands = [ + ["diff", "--quiet"], + ["diff", "--cached", "--quiet"], + ] + for command in commands: + result = run_git(command, check=False) + if result.returncode != 0: + raise ReleaseError("You have uncommitted changes.") + + untracked = run_git(["ls-files", "--others", "--exclude-standard"], capture_output=True) + if untracked.stdout.strip(): + raise ReleaseError("You have untracked files.") + + +def ensure_on_main(branch: str) -> None: + if branch != "main": + raise ReleaseError( + f"Releases must be created from the 'main' branch (current: '{branch}')." + ) + + +def ensure_on_origin_main() -> None: + try: + run_git(["fetch", "--quiet", "origin", "main"]) + except ReleaseError as error: + raise ReleaseError( + "Failed to fetch 'origin/main'. Ensure the 'origin' remote is configured and reachable." + ) from error + + result = run_git(["merge-base", "--is-ancestor", "HEAD", "origin/main"], check=False) + if result.returncode != 0: + raise ReleaseError( + "Your local 'main' HEAD commit is not present on 'origin/main'. " + "Please push first (git push origin main) or check out a commit on 'origin/main'." + ) + + +def current_branch() -> str: + result = run_git(["symbolic-ref", "--short", "-q", "HEAD"], capture_output=True, check=False) + branch = result.stdout.strip() + if result.returncode != 0 or not branch: + raise ReleaseError("Could not determine the current branch (detached HEAD?).") + return branch + + +def update_version(version: str) -> None: + content = CARGO_TOML.read_text(encoding="utf-8") + new_content, matches = re.subn( + r'^version = "[^"]+"', f'version = "{version}"', content, count=1, flags=re.MULTILINE + ) + if matches != 1: + raise ReleaseError("Unable to update version in Cargo.toml.") + CARGO_TOML.write_text(new_content, encoding="utf-8") + + +def create_release(version: str, branch: str) -> None: + tag = f"rust-v{version}" + run_git(["checkout", "-b", tag]) + try: + update_version(version) + run_git(["add", "Cargo.toml"]) + run_git(["commit", "-m", f"Release {version}"]) + run_git(["tag", "-a", tag, "-m", f"Release {version}"]) + run_git(["push", "origin", f"refs/tags/{tag}"]) + finally: + run_git(["checkout", branch]) + + +class ReleaseError(RuntimeError): + pass + + +def run_git( + args: list[str], *, capture_output: bool = False, check: bool = True +) -> subprocess.CompletedProcess: + result = subprocess.run( + ["git", *args], + cwd=ROOT_DIR, + text=True, + capture_output=capture_output, + ) + if check and result.returncode != 0: + stderr = result.stderr.strip() if result.stderr else "" + stdout = result.stdout.strip() if result.stdout else "" + message = stderr if stderr else stdout + raise ReleaseError(message or f"git {' '.join(args)} failed") + return result + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/codex-rs/scripts/create_github_release.sh b/codex-rs/scripts/create_github_release.sh deleted file mode 100755 index 4bc4e251..00000000 --- a/codex-rs/scripts/create_github_release.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -# By default, this script uses a version based on the current date and time. -# If you want to specify a version, pass it as the first argument. Example: -# -# ./scripts/create_github_release.sh 0.1.0-alpha.4 -# -# The value will be used to update the `version` field in `Cargo.toml`. - -# Change to the root of the Cargo workspace. -cd "$(dirname "${BASH_SOURCE[0]}")/.." - -# Cancel if there are uncommitted changes. -if ! git diff --quiet || ! git diff --cached --quiet || [ -n "$(git ls-files --others --exclude-standard)" ]; then - echo "ERROR: You have uncommitted or untracked changes." >&2 - exit 1 -fi - -# Fail if in a detached HEAD state. -CURRENT_BRANCH=$(git symbolic-ref --short -q HEAD 2>/dev/null || true) -if [ -z "${CURRENT_BRANCH:-}" ]; then - echo "ERROR: Could not determine the current branch (detached HEAD?)." >&2 - echo " Please run this script from a checked-out branch." >&2 - exit 1 -fi - -# Ensure we are on the 'main' branch before proceeding. -if [ "${CURRENT_BRANCH}" != "main" ]; then - echo "ERROR: Releases must be created from the 'main' branch (current: '${CURRENT_BRANCH}')." >&2 - echo " Please switch to 'main' and try again." >&2 - exit 1 -fi - -# Ensure the current local commit on 'main' is present on 'origin/main'. -# This guarantees we only create releases from commits that are already on -# the canonical repository (https://github.com/openai/codex). -if ! git fetch --quiet origin main; then - echo "ERROR: Failed to fetch 'origin/main'. Ensure the 'origin' remote is configured and reachable." >&2 - exit 1 -fi - -if ! git merge-base --is-ancestor HEAD origin/main; then - echo "ERROR: Your local 'main' HEAD commit is not present on 'origin/main'." >&2 - echo " Please push your commits first (git push origin main) or check out a commit on 'origin/main'." >&2 - exit 1 -fi - -# Create a new branch for the release and make a commit with the new version. -if [ $# -ge 1 ]; then - VERSION="$1" -else - VERSION=$(printf '0.0.%d' "$(date +%y%m%d%H%M)") -fi -TAG="rust-v$VERSION" -git checkout -b "$TAG" -perl -i -pe "s/^version = \".*\"/version = \"$VERSION\"/" Cargo.toml -git add Cargo.toml -git commit -m "Release $VERSION" -git tag -a "$TAG" -m "Release $VERSION" -git push origin "refs/tags/$TAG" - -git checkout "$CURRENT_BRANCH"