Update version_branch.yml
This commit is contained in:
304
.github/workflows/version_branch.yml
vendored
304
.github/workflows/version_branch.yml
vendored
@@ -4,224 +4,154 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
new_version:
|
new_version:
|
||||||
description: "New version (for example: 01.02.00). Leave blank to auto-increment from highest version/* branch."
|
description: "New version in format NN.NN.NN (example 01.03.00)"
|
||||||
required: false
|
required: true
|
||||||
default: ""
|
|
||||||
base_branch:
|
base_branch:
|
||||||
description: "Base branch to create version branch from"
|
description: "Base branch to branch from"
|
||||||
required: false
|
required: false
|
||||||
default: "main"
|
default: "main"
|
||||||
|
branch_prefix:
|
||||||
|
description: "Prefix for the new version branch"
|
||||||
|
required: false
|
||||||
|
default: "version/"
|
||||||
|
commit_changes:
|
||||||
|
description: "Commit and push changes"
|
||||||
|
required: false
|
||||||
|
default: "true"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- "true"
|
||||||
|
- "false"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-version-branch:
|
version-bump:
|
||||||
name: Create version branch and update versions
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
NEW_VERSION: ${{ github.event.inputs.new_version }}
|
||||||
BASE_BRANCH: ${{ github.event.inputs.base_branch }}
|
BASE_BRANCH: ${{ github.event.inputs.base_branch }}
|
||||||
|
BRANCH_PREFIX: ${{ github.event.inputs.branch_prefix }}
|
||||||
|
COMMIT_CHANGES: ${{ github.event.inputs.commit_changes }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ env.BASE_BRANCH }}
|
ref: ${{ env.BASE_BRANCH }}
|
||||||
|
|
||||||
- name: Determine new version (input or auto-increment)
|
- name: Validate inputs
|
||||||
id: version
|
shell: bash
|
||||||
env:
|
|
||||||
INPUT_VERSION: ${{ github.event.inputs.new_version }}
|
|
||||||
run: |
|
run: |
|
||||||
python << 'PY'
|
set -Eeuo pipefail
|
||||||
import os
|
trap 'echo "[FATAL] Validation error at line $LINENO" >&2' ERR
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
input_version = os.environ.get("INPUT_VERSION", "").strip()
|
echo "[INFO] Inputs received:"
|
||||||
|
echo " NEW_VERSION=${NEW_VERSION}"
|
||||||
|
echo " BASE_BRANCH=${BASE_BRANCH}"
|
||||||
|
echo " BRANCH_PREFIX=${BRANCH_PREFIX}"
|
||||||
|
|
||||||
if input_version:
|
[[ -n "${NEW_VERSION}" ]] || { echo "[ERROR] new_version missing"; exit 2; }
|
||||||
new_version = input_version
|
[[ "${NEW_VERSION}" =~ ^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$ ]] || {
|
||||||
else:
|
echo "[ERROR] Invalid version format: ${NEW_VERSION}"
|
||||||
completed = subprocess.run(
|
exit 2
|
||||||
["git", "ls-remote", "--heads", "origin"],
|
}
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
pattern = re.compile(r"refs/heads/version/([0-9]+\.[0-9]+\.[0-9]+)$")
|
git show-ref --verify --quiet "refs/remotes/origin/${BASE_BRANCH}" || {
|
||||||
versions = []
|
echo "[ERROR] Base branch does not exist on origin: ${BASE_BRANCH}"
|
||||||
|
git branch -a
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
for line in completed.stdout.splitlines():
|
echo "[INFO] Input validation passed"
|
||||||
parts = line.split()
|
|
||||||
if len(parts) != 2:
|
|
||||||
continue
|
|
||||||
ref = parts[1]
|
|
||||||
m = pattern.search(ref)
|
|
||||||
if not m:
|
|
||||||
continue
|
|
||||||
|
|
||||||
v_str = m.group(1)
|
- name: Configure git identity
|
||||||
try:
|
shell: bash
|
||||||
major, minor, patch = map(int, v_str.split("."))
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
versions.append((major, minor, patch))
|
|
||||||
|
|
||||||
if versions:
|
|
||||||
major, minor, patch = max(versions)
|
|
||||||
patch += 1
|
|
||||||
else:
|
|
||||||
major, minor, patch = 1, 0, 0
|
|
||||||
|
|
||||||
new_version = f"{major:02d}.{minor:02d}.{patch:02d}"
|
|
||||||
|
|
||||||
print(f"Using version {new_version}")
|
|
||||||
|
|
||||||
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
|
|
||||||
fh.write(f"new_version={new_version}\n")
|
|
||||||
PY
|
|
||||||
|
|
||||||
- name: Compute branch name
|
|
||||||
id: branch
|
|
||||||
env:
|
|
||||||
NEW_VERSION: ${{ steps.version.outputs.new_version }}
|
|
||||||
run: |
|
|
||||||
SAFE_VERSION="${NEW_VERSION// /-}"
|
|
||||||
BRANCH_NAME="version/${SAFE_VERSION}"
|
|
||||||
echo "Using branch name: $BRANCH_NAME"
|
|
||||||
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Create version branch from base
|
|
||||||
run: |
|
|
||||||
git checkout -b "${{ steps.branch.outputs.branch_name }}"
|
|
||||||
|
|
||||||
- name: Bump version strings across repo
|
|
||||||
env:
|
|
||||||
NEW_VERSION: ${{ steps.version.outputs.new_version }}
|
|
||||||
run: |
|
|
||||||
echo "Updating version strings to ${NEW_VERSION} across repository"
|
|
||||||
|
|
||||||
python << 'PY'
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
new_version = os.environ["NEW_VERSION"]
|
|
||||||
|
|
||||||
targets = [
|
|
||||||
Path("src"),
|
|
||||||
Path("docs"),
|
|
||||||
]
|
|
||||||
|
|
||||||
patterns = [
|
|
||||||
(re.compile(r"\(VERSION\s+[0-9]+\.[0-9]+\.[0-9]+\)"), lambda v: f"(VERSION {v})"),
|
|
||||||
(re.compile(r"(VERSION[:\s]+)([0-9]+\.[0-9]+\.[0-9]+)"), lambda v: r"\1" + v),
|
|
||||||
(re.compile(r"(<version>)([0-9]+\.[0-9]+\.[0-9]+)(</version>)"), lambda v: r"\1" + v + r"\3"),
|
|
||||||
(re.compile(r'(<extension\\b[^>]*\\bversion=")([0-9]+\.[0-9]+\.[0-9]+)(")'), lambda v: r"\1" + v + r"\3"),
|
|
||||||
(re.compile(r'(\"version\"\s*:\s*\")(\d+\.\d+\.\d+)(\")'), lambda v: r"\1" + v + r"\3"),
|
|
||||||
]
|
|
||||||
|
|
||||||
def update_file(path: Path) -> bool:
|
|
||||||
try:
|
|
||||||
text = path.read_text(encoding="utf-8")
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
original = text
|
|
||||||
for regex, repl in patterns:
|
|
||||||
text = regex.sub(lambda m, r=repl: r(m), text)
|
|
||||||
|
|
||||||
if text != original:
|
|
||||||
path.write_text(text, encoding="utf-8")
|
|
||||||
print(f"Updated version in {path}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
for root in targets:
|
|
||||||
if not root.exists():
|
|
||||||
continue
|
|
||||||
|
|
||||||
for path in root.rglob("*"):
|
|
||||||
if not path.is_file():
|
|
||||||
continue
|
|
||||||
|
|
||||||
if path.suffix.lower() in {
|
|
||||||
".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico",
|
|
||||||
".zip", ".pdf", ".tar", ".gz",
|
|
||||||
}:
|
|
||||||
continue
|
|
||||||
|
|
||||||
update_file(path)
|
|
||||||
|
|
||||||
repo_root = Path(".").resolve()
|
|
||||||
|
|
||||||
def is_under_any_target(p: Path) -> bool:
|
|
||||||
for t in targets:
|
|
||||||
if not t.exists():
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
p.resolve().relative_to(t.resolve())
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Explicitly update README.md and CHANGELOG.md
|
|
||||||
for fname in ["README.md", "CHANGELOG.md"]:
|
|
||||||
p = Path(fname)
|
|
||||||
if p.exists() and p.is_file():
|
|
||||||
update_file(p)
|
|
||||||
|
|
||||||
# Update remaining markdown files outside src/docs
|
|
||||||
for path in repo_root.rglob("*.md"):
|
|
||||||
if not path.is_file():
|
|
||||||
continue
|
|
||||||
|
|
||||||
if path.name.lower() in {"readme.md", "changelog.md"}:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if is_under_any_target(path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
update_file(path)
|
|
||||||
PY
|
|
||||||
|
|
||||||
- name: Update CHANGELOG using script
|
|
||||||
env:
|
|
||||||
NEW_VERSION: ${{ steps.version.outputs.new_version }}
|
|
||||||
run: |
|
|
||||||
if [ ! -f "scripts/update_changelog.sh" ]; then
|
|
||||||
echo "scripts/update_changelog.sh not found. Failing version branch creation."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod +x scripts/update_changelog.sh
|
|
||||||
./scripts/update_changelog.sh "${NEW_VERSION}"
|
|
||||||
|
|
||||||
- name: Configure git user
|
|
||||||
run: |
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
echo "[INFO] Git identity configured"
|
||||||
|
|
||||||
- name: Commit version bump
|
- name: Create version branch
|
||||||
env:
|
shell: bash
|
||||||
NEW_VERSION: ${{ steps.version.outputs.new_version }}
|
|
||||||
run: |
|
run: |
|
||||||
git status
|
set -Eeuo pipefail
|
||||||
if git diff --quiet; then
|
trap 'echo "[FATAL] Branch creation failed at line $LINENO" >&2' ERR
|
||||||
echo "No changes detected after version bump. Skipping commit."
|
|
||||||
exit 0
|
BRANCH_NAME="${BRANCH_PREFIX}${NEW_VERSION}"
|
||||||
|
echo "[INFO] Creating branch ${BRANCH_NAME}"
|
||||||
|
|
||||||
|
git fetch --all --tags --prune
|
||||||
|
|
||||||
|
if git ls-remote --exit-code --heads origin "${BRANCH_NAME}" >/dev/null 2>&1; then
|
||||||
|
echo "[ERROR] Branch already exists on origin: ${BRANCH_NAME}"
|
||||||
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git add -A
|
git checkout -B "${BRANCH_NAME}" "origin/${BASE_BRANCH}"
|
||||||
|
|
||||||
git commit -m "chore: bump version to ${NEW_VERSION}"
|
- name: Bump versions in headers and XML
|
||||||
|
shell: bash
|
||||||
- name: Push version branch
|
|
||||||
run: |
|
run: |
|
||||||
git push -u origin "${{ steps.branch.outputs.branch_name }}"
|
set -Eeuo pipefail
|
||||||
|
trap 'echo "[FATAL] Version bump failed at line $LINENO" >&2' ERR
|
||||||
|
|
||||||
|
python3 - <<'PY'
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
new_version = os.environ["NEW_VERSION"]
|
||||||
|
root = Path(".").resolve()
|
||||||
|
|
||||||
|
header_re = re.compile(r"(?m)^(\\s*VERSION\\s*:\\s*)(\\d{2}\\.\\d{2}\\.\\d{2})(\\s*)$")
|
||||||
|
xml_re = re.compile(r"(?is)(<version\\s*>)(\\s*\\d{2}\\.\\d{2}\\.\\d{2}\\s*)(</version\\s*>)")
|
||||||
|
|
||||||
|
updated = []
|
||||||
|
|
||||||
|
for p in root.rglob("*"):
|
||||||
|
if not p.is_file():
|
||||||
|
continue
|
||||||
|
if p.suffix.lower() == ".json":
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
text = p.read_text(encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
original = text
|
||||||
|
text = header_re.sub(r"\\1" + new_version + r"\\3", text)
|
||||||
|
if p.suffix.lower() == ".xml":
|
||||||
|
text = xml_re.sub(r"\\1" + new_version + r"\\3", text)
|
||||||
|
|
||||||
|
if text != original:
|
||||||
|
p.write_text(text, encoding="utf-8")
|
||||||
|
updated.append(str(p))
|
||||||
|
|
||||||
|
if not updated:
|
||||||
|
raise SystemExit("[ERROR] No files updated. Check headers and XML manifests.")
|
||||||
|
|
||||||
|
print(f"[INFO] Updated {len(updated)} files")
|
||||||
|
for f in updated:
|
||||||
|
print(f" - {f}")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
if: ${{ env.COMMIT_CHANGES == 'true' }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
git status --porcelain
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore(release): bump version to ${NEW_VERSION}"
|
||||||
|
|
||||||
|
- name: Push branch
|
||||||
|
if: ${{ env.COMMIT_CHANGES == 'true' }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
git push --set-upstream origin "${BRANCH_PREFIX}${NEW_VERSION}"
|
||||||
|
|||||||
Reference in New Issue
Block a user