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
This commit is contained in:
130
codex-rs/scripts/create_github_release
Executable file
130
codex-rs/scripts/create_github_release
Executable file
@@ -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))
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user