Skip to content

Instantly share code, notes, and snippets.

@aflansburg
Last active March 18, 2025 17:25
Show Gist options
  • Save aflansburg/307cad3b97008a7a76b8ac3a8f887991 to your computer and use it in GitHub Desktop.
Save aflansburg/307cad3b97008a7a76b8ac3a8f887991 to your computer and use it in GitHub Desktop.
Script to bump version w/ a release branch for `uv`
import re
import sys
import subprocess
import argparse
from pathlib import Path
from enum import Enum
SCRIPT_DIR = Path(__file__).parent
PROJECT_ROOT = SCRIPT_DIR.parent
PYPROJECT_FILE = PROJECT_ROOT / "pyproject.toml"
LOCK_FILE = PROJECT_ROOT / "uv.lock"
class VersionType(Enum):
MAJOR = "major"
MINOR = "minor"
PATCH = "patch"
def get_current_version():
"""
Extracts the current version from pyproject.toml
Returns:
tuple: The current version (major, minor, patch)
"""
with open(PYPROJECT_FILE, "r") as f:
content = f.read()
match = re.search(r'version = "(\d+)\.(\d+)\.(\d+)"', content)
if not match:
print("Error: Could not find version in pyproject.toml")
sys.exit(1)
return tuple(map(int, match.groups()))
def bump_version(version_type: VersionType):
"""
Bumps the version based on the specified type (major, minor, patch).
Args:
version_type (VersionType): The type of version bump to perform
Returns:
str: The new version
"""
major, minor, patch = get_current_version()
if version_type == VersionType.MAJOR:
major += 1
minor = 0
patch = 0
elif version_type == VersionType.MINOR:
minor += 1
patch = 0
elif version_type == VersionType.PATCH:
patch += 1
return f"{major}.{minor}.{patch}"
def update_pyproject_version(new_version, dry_run=False):
"""
Updates the version in pyproject.toml
Args:
new_version (str): The new version to set in pyproject.toml
dry_run (bool): If True, don't actually write changes
Returns:
str: The new version
"""
with open(PYPROJECT_FILE, "r") as f:
content = f.read()
new_content = re.sub(
r'version = "\d+\.\d+\.\d+"', f'version = "{new_version}"', content
)
if not dry_run:
with open(PYPROJECT_FILE, "w") as f:
f.write(new_content)
print(f"Updated pyproject.toml to version {new_version}")
else:
print(f"[DRY RUN] Would update pyproject.toml to version {new_version}")
def run_command(cmd, capture_output=False, dry_run=False):
"""Runs a shell command and exits if it fails
Args:
cmd (list): The command to run
capture_output (bool): Whether to capture the output of the command
dry_run (bool): If True, don't actually run the command
Returns:
str: The output of the command
"""
if dry_run:
print(f"[DRY RUN] Would run: {' '.join(cmd)}")
return None
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(
cmd, capture_output=capture_output, text=True, cwd=PROJECT_ROOT
)
if result.returncode != 0:
print(f"Error: {result.stderr}")
sys.exit(1)
return result.stdout.strip() if capture_output else None
def is_working_directory_clean():
"""
Checks if the git working directory is clean (no uncommitted changes)
Returns:
bool: True if working directory is clean, False otherwise
"""
result = subprocess.run(
["git", "status", "--porcelain"],
capture_output=True,
text=True,
cwd=PROJECT_ROOT,
)
if result.returncode != 0:
print(f"Error checking git status: {result.stderr}")
sys.exit(1)
return result.stdout.strip() == ""
def confirm_action(message):
"""
Asks the user to confirm an action
Args:
message (str): The message to display
Returns:
bool: True if user confirms, False otherwise
"""
response = input(f"{message} (y/n): ").lower().strip()
return response in ("y", "yes")
def main():
parser = argparse.ArgumentParser(
description="Bump version and create release branch"
)
parser.add_argument(
"version_type",
choices=["major", "minor", "patch"],
help="Type of version bump to perform",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would happen without making changes",
)
args = parser.parse_args()
version_type = VersionType(args.version_type)
dry_run = args.dry_run
if dry_run:
print("Running in dry-run mode. No changes will be made.")
# Check if working directory is clean
if not is_working_directory_clean():
print(
"Error: Working directory is not clean. Commit or stash your changes first."
)
sys.exit(1)
current_version = ".".join(map(str, get_current_version()))
new_version = bump_version(version_type)
release_branch = f"release/{new_version}"
print(f"Current version: {current_version}")
print(f"New version: {new_version}")
print(f"Release branch: {release_branch}")
if not dry_run and not confirm_action(
f"Proceed with bumping version to {new_version} and creating release branch {release_branch}?"
):
print("Operation cancelled.")
sys.exit(0)
update_pyproject_version(new_version, dry_run)
run_command(["uv", "lock"], dry_run=dry_run)
run_command(["uv", "sync"], dry_run=dry_run)
run_command(["git", "checkout", "-b", release_branch], dry_run=dry_run)
run_command(["git", "add", "pyproject.toml", "uv.lock"], dry_run=dry_run)
run_command(
["git", "commit", "-m", f"Bump version to {new_version}"], dry_run=dry_run
)
if not dry_run and not confirm_action(
f"Proceed with pushing the release branch {release_branch} to origin?"
):
print("Operation cancelled.")
sys.exit(0)
run_command(["git", "push", "origin", release_branch], dry_run=dry_run)
if dry_run:
print(f"[DRY RUN] Would create and push release branch '{release_branch}'")
else:
print(
f"Release branch '{release_branch}' created and pushed. Open a PR to merge."
)
if __name__ == "__main__":
main()

To bump the version and create a release branch, use the bump_version.py script:

# Bump patch version (1.0.0 -> 1.0.1)
uv run scripts/bump_version.py patch

# Bump minor version (1.0.0 -> 1.1.0)
uv run scripts/bump_version.py minor

# Bump major version (1.0.0 -> 2.0.0)
uv run scripts/bump_version.py major

# Dry run (show what would happen without making changes)
uv run scripts/bump_version.py patch --dry-run

The script will:

  1. Check if your working directory is clean (no uncommitted changes)
  2. Update the version in pyproject.toml
  3. Update dependencies with uv lock and uv sync
  4. Create a new branch named release/x.y.z
  5. Commit the changes and push the branch to origin

After running the script, you can create a PR to merge the release branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment