Skip to content

Instantly share code, notes, and snippets.

@Boomatang
Created October 20, 2025 10:38
Show Gist options
  • Select an option

  • Save Boomatang/2af4e9ed4f2e0e3e1f9a6b6994aa9588 to your computer and use it in GitHub Desktop.

Select an option

Save Boomatang/2af4e9ed4f2e0e3e1f9a6b6994aa9588 to your computer and use it in GitHub Desktop.
Bump the version in a cargo.toml file and commit the changes
#!/usr/bin/env python3
"""
Bump version to next development version after a release.
This script:
1. Reads the current version from Cargo.toml
2. Bumps to the next minor version with -dev suffix
3. Updates Cargo.toml
4. Runs cargo check to update Cargo.lock
5. Commits the changes with proper message format
Requirements:
pip install semver
"""
import re
import subprocess
import sys
import tomllib
from pathlib import Path
try:
import semver
except ImportError:
print("❌ Error: semver package not found")
print(" Install with: pip install semver")
sys.exit(1)
def run_command(cmd, check=True, capture_output=True):
"""Run a shell command and return the result."""
result = subprocess.run(
cmd,
shell=True,
check=check,
capture_output=capture_output,
text=True
)
return result
def get_git_user():
"""Get git user name and email for Signed-off-by."""
try:
name = run_command("git config user.name").stdout.strip()
email = run_command("git config user.email").stdout.strip()
return f"{name} <{email}>"
except subprocess.CalledProcessError:
return None
def read_cargo_toml():
"""Read Cargo.toml and return both the parsed data and raw content."""
cargo_path = Path("Cargo.toml")
if not cargo_path.exists():
print("❌ Error: Cargo.toml not found in current directory")
sys.exit(1)
raw_content = cargo_path.read_text()
try:
with open(cargo_path, "rb") as f:
parsed_data = tomllib.load(f)
except tomllib.TOMLDecodeError as e:
print(f"❌ Error: Could not parse Cargo.toml: {e}")
sys.exit(1)
return parsed_data, raw_content
def get_version(cargo_data):
"""Extract the current version from parsed Cargo.toml data."""
if "package" not in cargo_data or "version" not in cargo_data["package"]:
print("❌ Error: Could not find version in Cargo.toml [package] section")
sys.exit(1)
return cargo_data["package"]["version"]
def bump_version(current_version):
"""
Bump the minor version and add -dev suffix.
Examples:
0.11.0-dev -> 0.12.0-dev
0.11.0 -> 0.12.0-dev
"""
# Remove -dev suffix if present for parsing
version_str = current_version.replace("-dev", "")
try:
# Parse the version
ver = semver.Version.parse(version_str)
# Bump minor version
new_ver = ver.bump_minor()
# Add -dev suffix
new_version = f"{new_ver}-dev"
return new_version
except ValueError as e:
print(f"❌ Error: Invalid version format: {current_version}")
print(f" {e}")
sys.exit(1)
def update_cargo_toml(cargo_content, old_version, new_version):
"""Update the version in Cargo.toml content."""
# Replace the version line in the [package] section
updated = re.sub(
rf'^version\s*=\s*"{re.escape(old_version)}"',
f'version = "{new_version}"',
cargo_content,
count=1,
flags=re.MULTILINE
)
if updated == cargo_content:
print(f"❌ Error: Could not update version in Cargo.toml")
sys.exit(1)
return updated
def write_cargo_toml(content):
"""Write updated content back to Cargo.toml."""
Path("Cargo.toml").write_text(content)
def main():
print("🚀 Version Bump Script")
print("=" * 50)
# Step 1: Read Cargo.toml
print("\n📖 Reading Cargo.toml...")
cargo_data, cargo_content = read_cargo_toml()
# Step 2: Parse current version
current_version = get_version(cargo_data)
print(f" Current version: {current_version}")
# Step 3: Calculate next version
new_version = bump_version(current_version)
print(f" Next version: {new_version}")
# Confirm with user
print(f"\n⚠️ About to bump version: {current_version} → {new_version}")
response = input(" Continue? [y/N]: ").strip().lower()
if response != 'y':
print("❌ Aborted by user")
sys.exit(0)
# Step 4: Update Cargo.toml
print("\n✏️ Updating Cargo.toml...")
updated_content = update_cargo_toml(cargo_content, current_version, new_version)
write_cargo_toml(updated_content)
print(" ✅ Cargo.toml updated")
# Step 5: Run cargo check to update Cargo.lock
print("\n🔧 Running cargo check to update Cargo.lock...")
try:
result = run_command("cargo check", capture_output=True)
print(" ✅ Cargo.lock updated")
except subprocess.CalledProcessError as e:
print(f" ❌ cargo check failed:")
print(e.stderr)
sys.exit(1)
# Step 6: Git add files
print("\n📝 Staging changes...")
try:
run_command("git add Cargo.toml Cargo.lock")
print(" ✅ Staged Cargo.toml and Cargo.lock")
except subprocess.CalledProcessError as e:
print(f" ❌ git add failed: {e}")
sys.exit(1)
# Step 7: Create commit message
# Remove -dev suffix for commit message
version_for_commit = new_version.replace("-dev", "")
commit_message = f"On to the next release: {version_for_commit}"
# Add Signed-off-by if git user is configured
git_user = get_git_user()
if git_user:
commit_message += f"\n\nSigned-off-by: {git_user}"
# Commit changes
print("\n💾 Committing changes...")
try:
run_command(f'git commit -m "{commit_message}"')
print(f' ✅ Committed with message: "{commit_message.split(chr(10))[0]}"')
except subprocess.CalledProcessError as e:
print(f" ❌ git commit failed: {e}")
sys.exit(1)
# Step 8: Summary
print("\n" + "=" * 50)
print("✅ SUCCESS!")
print("=" * 50)
print(f"\n📦 Version bumped: {current_version} → {new_version}")
print(f"💬 Commit message: {commit_message.split(chr(10))[0]}")
print("\n📋 Next steps:")
print(" 1. Review the commit: git show")
print(" 2. Push the commit: git push")
print("\n⚠️ Remember: This script does NOT push automatically!")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment