Skip to content

chore: migrate java repo to GitHub Release–based publish flow with strict version gating #1

chore: migrate java repo to GitHub Release–based publish flow with strict version gating

chore: migrate java repo to GitHub Release–based publish flow with strict version gating #1

name: Check Version Bump
on:
pull_request:
jobs:
check-version-bump:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate version and changelog updates
shell: bash
run: |
set -euo pipefail
VERSION_FILE="pom.xml"
CHANGELOG_FILE="CHANGELOG.md"
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
mapfile -t CHANGED_FILES < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
if [ "${#CHANGED_FILES[@]}" -eq 0 ]; then
echo "No changed files detected."
exit 0
fi
is_ignored_change() {
local f="$1"
[[ "$f" =~ ^docs/ ]] && return 0
[[ "$f" =~ ^\.github/ ]] && return 0
[[ "$f" =~ (^|/)tests?/ ]] && return 0
[[ "$f" =~ (^|/)src/test/ ]] && return 0
[[ "$f" =~ \.md$ ]] && [[ ! "$f" =~ (^|/)CHANGELOG\.md$ ]] && return 0
return 1
}
has_release_impact=false
for file in "${CHANGED_FILES[@]}"; do
if ! is_ignored_change "$file"; then
has_release_impact=true
break
fi
done
if [ "$has_release_impact" = false ]; then
echo "Skipping docs/test-only PR."
exit 0
fi
changed_file() {
local target="$1"
for file in "${CHANGED_FILES[@]}"; do
if [ "$file" = "$target" ]; then
return 0
fi
done
return 1
}
changed_file "$VERSION_FILE" || { echo "Version bump required in $VERSION_FILE."; exit 1; }
changed_file "$CHANGELOG_FILE" || { echo "Matching changelog update required in $CHANGELOG_FILE."; exit 1; }
extract_version() {
python3 -c 'import sys,xml.etree.ElementTree as ET;r=ET.fromstring(sys.stdin.read());ns={"m":r.tag.split("}")[0].strip("{")} if r.tag.startswith("{") else None;n=(r.find("m:version",ns) if ns else r.find("version"));print((n.text or "").strip() if n is not None else "")'
}
head_version=$(extract_version < "$VERSION_FILE")
CHANGELOG_HEAD=$(sed -nE 's/^## v?([^[:space:]]+).*/\1/p' "$CHANGELOG_FILE" | head -1)
[ -n "$CHANGELOG_HEAD" ] || { echo "::error::Could not find a top changelog heading like '## vX.Y.Z' in $CHANGELOG_FILE."; exit 1; }
[ "$CHANGELOG_HEAD" = "$head_version" ] || { echo "::error::$CHANGELOG_FILE top version ($CHANGELOG_HEAD) does not match project version ($head_version)."; exit 1; }
base_version=$(git show "$BASE_SHA:$VERSION_FILE" | extract_version)
latest_tag=$(git tag --list 'v*' --sort=-version:refname | sed -n '1p')
latest_version="${latest_tag#v}"
[ -n "$latest_version" ] || latest_version="0.0.0"
version_gt() {
python3 -c 'import sys;v=lambda s:[int(x) if x.isdigit() else 0 for x in (s.strip().lstrip("v").split("-",1)[0].split("+",1)[0].split(".")+["0","0","0"])[:3]];print("true" if v(sys.argv[1])>v(sys.argv[2]) else "false")' "$1" "$2"
}
[ "$(version_gt "$head_version" "$base_version")" = "true" ] || { echo "Version must be greater than base version ($base_version). Found $head_version."; exit 1; }
[ "$(version_gt "$head_version" "$latest_version")" = "true" ] || { echo "Version must be greater than latest tag version ($latest_version). Found $head_version."; exit 1; }