Skip to content

Commit db7c8ca

Browse files
committed
feat: add setversion.sh script for managing project versioning with semver checks
1 parent e1d8e83 commit db7c8ca

1 file changed

Lines changed: 218 additions & 0 deletions

File tree

setversion.sh

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
#!/usr/bin/env bash
2+
3+
# setversion.sh
4+
# Safely set project version (Maven multi-module aware) with semver checks
5+
# Usage: ./setversion.sh <version> [--dry-run] [--force]
6+
7+
set -Eeuo pipefail
8+
9+
print_usage() {
10+
cat <<EOF
11+
This script sets the project version, commits the change, and optionally tags it.
12+
13+
Usage:
14+
./setversion.sh <version> [--dry-run] [--force]
15+
16+
Options:
17+
--dry-run Preview the changes without actually making them.
18+
--force Force the change even if it looks like a downgrade or tag already exists.
19+
20+
Examples:
21+
To create a new release:
22+
./setversion.sh 1.0.0
23+
To set a new SNAPSHOT version:
24+
./setversion.sh 1.0.1-SNAPSHOT
25+
To preview the changes:
26+
./setversion.sh 1.0.0 --dry-run
27+
After running the script:
28+
git push && git push --tags
29+
30+
EOF
31+
}
32+
33+
if [ "$#" -lt 1 ]; then
34+
print_usage
35+
exit 1
36+
fi
37+
38+
VERSION=""
39+
DRY_RUN=false
40+
FORCE=false
41+
42+
while [[ "$#" -gt 0 ]]; do
43+
case "$1" in
44+
--dry-run) DRY_RUN=true; shift ;;
45+
--force) FORCE=true; shift ;;
46+
-h|--help) print_usage; exit 0 ;;
47+
*)
48+
if [ -z "$VERSION" ]; then
49+
VERSION="$1"
50+
shift
51+
else
52+
echo "Unknown argument: $1" >&2
53+
print_usage
54+
exit 1
55+
fi
56+
;;
57+
esac
58+
done
59+
60+
# Basic semver validation (major.minor.patch) with optional -SNAPSHOT
61+
if [[ ! "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-SNAPSHOT)?$ ]]; then
62+
echo "Error: Invalid version format. Use 'major.minor.patch' or 'major.minor.patch-SNAPSHOT'." >&2
63+
exit 1
64+
fi
65+
66+
# helper: choose mvn wrapper if present
67+
MVN_CMD="mvn"
68+
if [ -x "$(pwd)/mvnw" ]; then
69+
MVN_CMD="$(pwd)/mvnw"
70+
fi
71+
72+
# get current project version via maven (robust)
73+
CURRENT_VERSION="$($MVN_CMD -q -DforceStdout help:evaluate -Dexpression=project.version 2>/dev/null || true)"
74+
if [ -z "$CURRENT_VERSION" ]; then
75+
echo "Error: Unable to determine current project version via Maven ($MVN_CMD)." >&2
76+
exit 1
77+
fi
78+
79+
echo "Current project version: $CURRENT_VERSION"
80+
81+
# parse semver parts
82+
parse_semver() {
83+
local v="$1"
84+
if [[ "$v" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-SNAPSHOT)?$ ]]; then
85+
echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]:-}" # major minor patch suffix
86+
else
87+
return 1
88+
fi
89+
}
90+
91+
compare_semver() {
92+
# returns: 1 if a>b, 0 if a==b, -1 if a<b
93+
local a=($1)
94+
local b=($2)
95+
for i in 0 1 2; do
96+
if (( ${a[i]} > ${b[i]} )); then
97+
echo 1; return 0
98+
elif (( ${a[i]} < ${b[i]} )); then
99+
echo -1; return 0
100+
fi
101+
done
102+
# numeric parts equal; treat SNAPSHOT as lower than release
103+
local sufA="${a[3]:-}"
104+
local sufB="${b[3]:-}"
105+
if [ "$sufA" = "$sufB" ]; then
106+
echo 0
107+
else
108+
# if a has -SNAPSHOT and b doesn't, a < b
109+
if [ "$sufA" = "-SNAPSHOT" ] && [ -z "$sufB" ]; then
110+
echo -1
111+
elif [ -z "$sufA" ] && [ "$sufB" = "-SNAPSHOT" ]; then
112+
echo 1
113+
else
114+
# fallback equal
115+
echo 0
116+
fi
117+
fi
118+
}
119+
120+
CUR_PARTS=($(parse_semver "$CURRENT_VERSION")) || { echo "Error: current version $CURRENT_VERSION is not semver-compatible" >&2; exit 1; }
121+
NEW_PARTS=($(parse_semver "$VERSION")) || { echo "Error: new version $VERSION is not semver-compatible" >&2; exit 1; }
122+
123+
cmp=$(compare_semver "${CUR_PARTS[*]}" "${NEW_PARTS[*]}") || true
124+
125+
if [ "$cmp" = "1" ]; then
126+
echo "Error: New version $VERSION is lower than current version $CURRENT_VERSION (potential downgrade)." >&2
127+
if [ "$FORCE" != "true" ]; then
128+
echo "Use --force to override." >&2
129+
exit 1
130+
else
131+
echo "--force provided: proceeding despite downgrade." >&2
132+
fi
133+
elif [ "$cmp" = "0" ]; then
134+
echo "Warning: New version $VERSION is equal to current version $CURRENT_VERSION." >&2
135+
if [ "$FORCE" != "true" ]; then
136+
echo "Use --force to proceed anyway." >&2
137+
exit 1
138+
else
139+
echo "--force provided: proceeding." >&2
140+
fi
141+
fi
142+
143+
# Branch check: require branch like '<major>.x' (e.g. 8.x) unless --force
144+
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)
145+
MAJOR="${NEW_PARTS[0]}"
146+
EXPECTED_BRANCH="${MAJOR}.x"
147+
if [ -n "$CURRENT_BRANCH" ] && [ "$CURRENT_BRANCH" != "$EXPECTED_BRANCH" ] && [ "$FORCE" != "true" ]; then
148+
echo "Error: Releasing version $VERSION requires being on branch '$EXPECTED_BRANCH' but current branch is '$CURRENT_BRANCH'." >&2
149+
echo "If you really want to proceed from this branch, pass --force." >&2
150+
exit 1
151+
fi
152+
153+
# Ensure git is configured
154+
if ! git config user.name &>/dev/null || ! git config user.email &>/dev/null; then
155+
echo "Error: Git user.name and user.email are not configured." >&2
156+
exit 1
157+
fi
158+
159+
# Ensure working tree is clean to avoid accidental overwrites (skip this check for --dry-run)
160+
if [ "$DRY_RUN" != "true" ] && [ -n "$(git status --porcelain)" ] && [ "$FORCE" != "true" ]; then
161+
echo "Error: Working tree is not clean. Commit or stash changes, or pass --force to override." >&2
162+
git status --porcelain
163+
exit 1
164+
fi
165+
166+
# Check if tag already exists (for release versions)
167+
if [[ "$VERSION" != *SNAPSHOT ]]; then
168+
if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
169+
echo "Error: Git tag '$VERSION' already exists." >&2
170+
if [ "$FORCE" != "true" ]; then
171+
echo "Use --force to overwrite or choose a different version." >&2
172+
exit 1
173+
else
174+
echo "--force provided: existing tag will be overwritten (old tag will be deleted and recreated)."
175+
fi
176+
fi
177+
fi
178+
179+
# Dry-run summary
180+
if [ "$DRY_RUN" = true ]; then
181+
echo "Dry-run: The following actions would be performed:"
182+
echo " - Run: $MVN_CMD versions:set -DnewVersion=$VERSION -DprocessAllModules=true -DgenerateBackupPoms=false"
183+
if [[ "$VERSION" == *SNAPSHOT ]]; then
184+
echo " - Commit with message: 'Bump version to $VERSION [skip ci]'"
185+
else
186+
echo " - Commit with message: 'Release version $VERSION'"
187+
echo " - Create git tag: $VERSION"
188+
fi
189+
if [ "$FORCE" = true ]; then
190+
echo " - (force mode enabled)"
191+
fi
192+
echo "No changes were made."
193+
exit 0
194+
fi
195+
196+
echo "Setting Maven version to $VERSION..."
197+
$MVN_CMD versions:set -DnewVersion="$VERSION" -DprocessAllModules=true -DgenerateBackupPoms=false
198+
199+
# Commit changes
200+
if [[ "$VERSION" == *SNAPSHOT ]]; then
201+
echo "This is a SNAPSHOT version. Committing bump..."
202+
git add -A
203+
git commit -m "Bump version to $VERSION [skip ci]"
204+
else
205+
echo "This is a Release version. Committing and tagging..."
206+
git add -A
207+
git commit -m "Release version $VERSION"
208+
# if tag exists and force, delete it first
209+
if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
210+
if [ "$FORCE" = "true" ]; then
211+
git tag -d "$VERSION" || true
212+
git push --delete origin "$VERSION" || true
213+
fi
214+
fi
215+
git tag "$VERSION"
216+
fi
217+
218+
echo "Version set to $VERSION successfully."

0 commit comments

Comments
 (0)