Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions .github/scripts/get_version_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import os
import re
import subprocess
from collections import defaultdict

# Regex patterns for Conventional Commits
feat_pattern = re.compile(r"^feat(\([^)]+\))?:", re.IGNORECASE)
feat_breaking_pattern = re.compile(r"^feat!|^feat!\([^)]*\):", re.IGNORECASE)
fix_pattern = re.compile(r"^fix(\([^)]+\))?:", re.IGNORECASE)


def get_version_bump_from_commits(last_tag: str | None) -> str | None:
"""
Determines the version bump type ('major', 'minor', 'patch') from commit messages.
"""
try:
if last_tag:
commits = subprocess.check_output(
["git", "log", f"{last_tag}..HEAD", "--merges", "--pretty=format:%B"],
text=True,
).strip()
else:
commits = subprocess.check_output(
["git", "log", "--merges", "--pretty=format:%B"], text=True
).strip()
except Exception as exc:
print(f"Error getting commits: {exc}")
commits = ""

if not commits:
return None

commit_lines = [c.strip() for c in commits.splitlines() if c.strip()]
bump_commits = defaultdict(list)

for c in commit_lines:
if feat_breaking_pattern.match(c):
bump_commits["major"].append(c)
elif feat_pattern.match(c):
bump_commits["minor"].append(c)
elif fix_pattern.match(c):
bump_commits["patch"].append(c)

if bump_commits["major"]:
bump_type = "major"
elif bump_commits["minor"]:
bump_type = "minor"
elif bump_commits["patch"]:
bump_type = "patch"
else:
bump_type = None

print(f"\nVersion bump type from commits: {bump_type}")
for bt in ["major", "minor", "patch"]:
if bump_commits[bt]:
print(f"\n{bt.upper()} commits ({len(bump_commits[bt])}):")
for commit in bump_commits[bt]:
print(f" {commit}")

return bump_type


def main():
"""
Main function to determine version bump part and current version.
"""
# Determine version part
part = os.environ.get("VERSION_PART_INPUT")
if not part:
try:
last_tag = (
subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"])
.decode()
.strip()
)
except subprocess.CalledProcessError as exc:
print(
f"Could not get last tag, probably because there are no tags yet. Error: {exc}"
)
last_tag = None
print(f"Last tag: {last_tag}")
part = get_version_bump_from_commits(last_tag)
else:
print(f"Got version bump part from input: {part}")

# Set outputs
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
if part:
f.write(f"part={part}\n")
f.write("bump_needed=true\n")
print(f"Version bump part: {part} and bump_needed=true")
else:
f.write("bump_needed=false\n")
print("No version bump needed.")


if __name__ == "__main__":
main()
69 changes: 69 additions & 0 deletions .github/scripts/increment_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import os
import re


def increment_version(version, part):
if "rc" in version:
if ".rc" in version:
base_version, rc_part = version.split(".rc")
else:
base_version, rc_part = version.split("rc")
major, minor, patch = map(int, base_version.split("."))
rc_num = int(rc_part)
else:
major, minor, patch = map(int, version.split("."))
rc_num = None

if part == "major":
major += 1
minor = 0
patch = 0
rc_num = None
elif part == "minor":
minor += 1
patch = 0
rc_num = None
elif part == "patch":
patch += 1
rc_num = None
elif part == "rc":
if rc_num is not None:
rc_num += 1
else:
rc_num = 0
elif part == "release":
rc_num = None
else:
raise ValueError(
"Part must be one of 'major', 'minor', 'patch', 'rc', or 'release'"
)

return (
f"{major}.{minor}.{patch}"
if rc_num is None
else f"{major}.{minor}.{patch}.rc{rc_num}"
)


def main():
part = os.environ["PART"]
# Get current version from pyproject.toml
with open("pyproject.toml", "r") as file:
content = file.read()
current_version_match = re.search(
r'version\s*=\s*"(\d+\.\d+\.\d+(?:\.?rc\d+)?)', content
)
if not current_version_match:
raise RuntimeError("Could not find version in pyproject.toml")
current_version = current_version_match.group(1)
new_version = increment_version(current_version, part)

with open(os.environ["GITHUB_OUTPUT"], "a") as f:
f.write(f"current_version={current_version}\n")
f.write(f"new_version={new_version}\n")

print(f"new_version: {new_version}")


if __name__ == "__main__":
main()
33 changes: 33 additions & 0 deletions .github/scripts/update_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os
import re


def update_file(file_path, old_version, new_version):
with open(file_path, "r") as file:
content = file.read()

# Use word boundaries to avoid replacing parts of other strings
old_version_pattern = r"\b" + re.escape(old_version) + r"\b"
content = re.sub(old_version_pattern, new_version, content)

with open(file_path, "w") as file:
file.write(content)


def main():
files_to_update = [
"pyproject.toml",
"src/fabric_cli/__init__.py",
"src/fabric_cli/core/fab_constant.py",
]

old_version = os.environ["OLD_VERSION"]
new_version = os.environ["NEW_VERSION"]

for file_path in files_to_update:
update_file(file_path, old_version, new_version)
print(f"Updated {file_path} from {old_version} to {new_version}")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions .github/workflows/semantic-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
- ready_for_review

permissions:
issues: write
pull-requests: write

jobs:
Expand Down
128 changes: 128 additions & 0 deletions .github/workflows/version-bump.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Bump Version test

on:
workflow_dispatch:
inputs:
version_part:
description: 'The part of the version to bump'
required: false
type: choice
default: 'patch'
options:
- major
- minor
- patch
- rc
- release

dry_run:
description: 'Run without creating a commit or PR to test version calculation.'
required: false
type: boolean
default: false

# NOTE: The 'push' trigger is for testing in a fork.
# Please remove it before creating a pull request to the main repository.
push:
branches:
- test-version-bump-workflow


jobs:
bump-version:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Get version info
id: version_info
run: python .github/scripts/get_version_info.py
env:
VERSION_PART_INPUT: ${{ github.event.inputs.version_part }}

- name: Prepare Inputs
id: prep_inputs
run: |
# For push events, github.event.inputs is empty. We need to provide default values.
# For workflow_dispatch, we'll use the provided inputs or the defaults.
VERSION_PART="${{ github.event.inputs.version_part || 'patch' }}"
DRY_RUN="${{ github.event.inputs.dry_run || 'false' }}"
echo "version_part=${VERSION_PART}" >> $GITHUB_OUTPUT
echo "dry_run=${DRY_RUN}" >> $GITHUB_OUTPUT

- name: Increment version
if: steps.version_info.outputs.bump_needed == 'true'
id: increment_version
run: python .github/scripts/increment_version.py
env:
PART: ${{ steps.version_info.outputs.part }}


- name: Update files
# if: github.event.inputs.dry_run == 'false' && steps.version_info.outputs.bump_needed == 'true'
if: steps.prep_inputs.outputs.dry_run == 'false' && steps.version_info.outputs.bump_needed == 'true'
id: update_files
run: python .github/scripts/update_files.py
env:
OLD_VERSION: ${{ steps.increment_version.outputs.current_version }}
NEW_VERSION: ${{ steps.increment_version.outputs.new_version }}

- name: Commit and Push Changes
if: steps.prep_inputs.outputs.dry_run == 'false' && steps.version_info.outputs.bump_needed == 'true'
id: commit_and_push
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git checkout -b chore/version-bump-${{ steps.increment_version.outputs.new_version }}
git add pyproject.toml src/fabric_cli/__init__.py src/fabric_cli/core/fab_constant.py
git commit -m "chore(version): bump '${{ steps.version_info.outputs.part }}' version to ${{ steps.increment_version.outputs.new_version }}"
git push --set-upstream origin chore/version-bump-${{ steps.increment_version.outputs.new_version }}

- name: Create Pull Request
# if: github.event.inputs.dry_run == 'false' && steps.version_info.outputs.bump_needed == 'true'
if: steps.prep_inputs.outputs.dry_run == 'false' && steps.version_info.outputs.bump_needed == 'true'
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const new_version = "${{ steps.increment_version.outputs.new_version }}";
const branch = `chore/version-bump-${new_version}`;

await github.rest.pulls.create({
owner,
repo,
title: `chore(version): bump version to ${new_version}`,
head: branch,
base: 'main',
body: `This PR bumps the version to ${new_version}`,
draft: true
});

- name: Tag new version
if: false || steps.prep_inputs.outputs.dry_run == 'false' && steps.version_info.outputs.bump_needed == 'true'
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const new_version = "v${{ steps.increment_version.outputs.new_version }}";
await github.rest.git.createRef({
owner,
repo,
ref: `refs/tags/${new_version}`,
sha: context.sha
});
Loading
Loading