Skip to content

Commit e3525a5

Browse files
Merge pull request #6 from akshatnerella/akshatnerella/versioning-fix
changed CI to commit package version during PR
2 parents 895fd62 + a8d582d commit e3525a5

File tree

7 files changed

+187
-163
lines changed

7 files changed

+187
-163
lines changed

.github/scripts/bump_version.py

Lines changed: 0 additions & 99 deletions
This file was deleted.

.github/scripts/pr_bump_version.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import argparse
2+
import os
3+
import re
4+
import subprocess
5+
from pathlib import Path
6+
7+
8+
def run(*args: str) -> str:
9+
return subprocess.check_output(args, text=True).strip()
10+
11+
12+
def write_output(key: str, value: str) -> None:
13+
output_path = os.getenv("GITHUB_OUTPUT")
14+
if not output_path:
15+
return
16+
with open(output_path, "a", encoding="utf-8") as fh:
17+
fh.write(f"{key}={value}\n")
18+
19+
20+
def parse_setup_version(text: str) -> str:
21+
match = re.search(r"version\s*=\s*['\"](\d+\.\d+\.\d+)['\"]", text)
22+
if not match:
23+
raise RuntimeError("Could not parse version from setup.py")
24+
return match.group(1)
25+
26+
27+
def latest_tag_version() -> str | None:
28+
tags = run("git", "tag", "--list", "v*", "--sort=-v:refname").splitlines()
29+
for tag in tags:
30+
if re.fullmatch(r"v\d+\.\d+\.\d+", tag):
31+
return tag[1:]
32+
return None
33+
34+
35+
def base_branch_version(base_ref: str) -> str:
36+
setup_on_base = run("git", "show", f"origin/{base_ref}:setup.py")
37+
return parse_setup_version(setup_on_base)
38+
39+
40+
def bump_version(version: str, bump: str) -> str:
41+
major, minor, patch = [int(part) for part in version.split(".")]
42+
if bump == "major":
43+
return f"{major + 1}.0.0"
44+
if bump == "minor":
45+
return f"{major}.{minor + 1}.0"
46+
return f"{major}.{minor}.{patch + 1}"
47+
48+
49+
def update_setup(new_version: str) -> bool:
50+
setup_path = Path("setup.py")
51+
original = setup_path.read_text(encoding="utf-8")
52+
updated, replacements = re.subn(
53+
r"version\s*=\s*['\"]\d+\.\d+\.\d+['\"]",
54+
f"version='{new_version}'",
55+
original,
56+
count=1,
57+
)
58+
if replacements == 0:
59+
raise RuntimeError("Could not find version assignment in setup.py")
60+
if updated == original:
61+
return False
62+
setup_path.write_text(updated, encoding="utf-8")
63+
return True
64+
65+
66+
def main() -> None:
67+
parser = argparse.ArgumentParser()
68+
parser.add_argument("--bump", choices=["major", "minor", "patch"], required=True)
69+
parser.add_argument("--base-ref", required=True)
70+
args = parser.parse_args()
71+
72+
setup_text = Path("setup.py").read_text(encoding="utf-8")
73+
current_version = parse_setup_version(setup_text)
74+
base_version = latest_tag_version() or base_branch_version(args.base_ref)
75+
target_version = bump_version(base_version, args.bump)
76+
77+
changed = update_setup(target_version)
78+
write_output("version", target_version)
79+
write_output("changed", "true" if changed else "false")
80+
print(f"current={current_version} base={base_version} target={target_version} changed={changed}")
81+
82+
83+
if __name__ == "__main__":
84+
main()
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: PR Version Bump
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- reopened
8+
- synchronize
9+
- labeled
10+
- unlabeled
11+
12+
permissions:
13+
contents: write
14+
pull-requests: write
15+
16+
jobs:
17+
bump-version:
18+
if: github.event.pull_request.head.repo.full_name == github.repository
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Determine bump label
23+
id: label
24+
uses: actions/github-script@v7
25+
with:
26+
script: |
27+
const labels = context.payload.pull_request.labels.map(l => l.name);
28+
let bump = "patch";
29+
if (labels.includes("release:major")) {
30+
bump = "major";
31+
} else if (labels.includes("release:minor")) {
32+
bump = "minor";
33+
} else if (labels.includes("release:patch")) {
34+
bump = "patch";
35+
} else {
36+
try {
37+
await github.rest.issues.addLabels({
38+
owner: context.repo.owner,
39+
repo: context.repo.repo,
40+
issue_number: context.payload.pull_request.number,
41+
labels: ["release:patch"]
42+
});
43+
} catch (error) {
44+
if (error.status !== 422) {
45+
throw error;
46+
}
47+
await github.rest.issues.createLabel({
48+
owner: context.repo.owner,
49+
repo: context.repo.repo,
50+
name: "release:patch",
51+
color: "0e8a16",
52+
description: "Patch version bump"
53+
});
54+
await github.rest.issues.addLabels({
55+
owner: context.repo.owner,
56+
repo: context.repo.repo,
57+
issue_number: context.payload.pull_request.number,
58+
labels: ["release:patch"]
59+
});
60+
}
61+
}
62+
core.setOutput("bump", bump);
63+
64+
- name: Checkout PR branch
65+
uses: actions/checkout@v4
66+
with:
67+
ref: ${{ github.event.pull_request.head.ref }}
68+
repository: ${{ github.event.pull_request.head.repo.full_name }}
69+
fetch-depth: 0
70+
71+
- name: Fetch base branch and tags
72+
run: |
73+
git fetch origin "${{ github.event.pull_request.base.ref }}"
74+
git fetch --tags
75+
76+
- name: Bump setup.py version
77+
id: bump
78+
run: |
79+
python .github/scripts/pr_bump_version.py \
80+
--bump "${{ steps.label.outputs.bump }}" \
81+
--base-ref "${{ github.event.pull_request.base.ref }}"
82+
83+
- name: Commit and push version update
84+
if: steps.bump.outputs.changed == 'true'
85+
env:
86+
NEW_VERSION: ${{ steps.bump.outputs.version }}
87+
run: |
88+
git config user.name "github-actions[bot]"
89+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
90+
git add setup.py
91+
git commit -m "chore: bump version to ${NEW_VERSION}"
92+
git push origin "HEAD:${{ github.event.pull_request.head.ref }}"

.github/workflows/publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ name: Publish to PyPI
22

33
on:
44
push:
5-
tags:
6-
- "v*.*.*"
5+
branches:
6+
- main
77
workflow_dispatch:
88

99
jobs:
1010
publish:
11-
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
11+
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
1212
runs-on: ubuntu-latest
1313
permissions:
1414
contents: read

.github/workflows/release.yml

Lines changed: 0 additions & 54 deletions
This file was deleted.

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,17 @@ We welcome contributions to add new features, fix bugs, and improve documentatio
106106

107107
This repository is configured with GitHub Actions:
108108
- `CI`: runs tests on pull requests and pushes to `main`.
109-
- `Auto Version Release`: on each merge/push to `main`, it auto-bumps version in `setup.py`, creates a git tag (for example `v0.1.1`), and pushes it.
110-
- `Publish to PyPI`: builds and uploads the package when a release tag is pushed.
109+
- `PR Version Bump`: when a pull request is opened/updated, it checks release labels and commits a version bump to `setup.py` directly in the PR branch.
110+
- `Publish to PyPI`: builds and uploads the package when code is merged/pushed to `main`.
111111

112112
One-time setup required:
113113
1. Create a PyPI API token (`__token__`) for your PyPI project.
114114
2. In GitHub: `Settings -> Secrets and variables -> Actions`, add a repository secret named `PYPI_API_TOKEN`.
115-
3. Use conventional commit messages in merged commits/PR titles for semantic bumps:
116-
- `feat:` -> minor bump
117-
- `fix:` (or anything else) -> patch bump
118-
- `BREAKING CHANGE` or `feat!:` / `fix!:` -> major bump
115+
3. Use one release label on PRs:
116+
- `release:major`
117+
- `release:minor`
118+
- `release:patch`
119+
4. If no release label is present, the workflow automatically adds `release:patch` and bumps patch version.
119120

120121
If the version already exists on PyPI, publish is skipped safely.
121122

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name='pyladdersim',
5-
version='0.1.0',
5+
version='0.1.1',
66
packages=find_packages(exclude=["tests", "tests.*"]),
77
install_requires=[
88
'matplotlib',

0 commit comments

Comments
 (0)