Skip to content

Commit 77126b8

Browse files
thomasturrellCopilotCopilot
authored
Add Automated Release workflow: prepare/perform Maven releases and publish draft releases (#427)
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 061a97c commit 77126b8

File tree

1 file changed

+138
-54
lines changed

1 file changed

+138
-54
lines changed

.github/workflows/release.yml

Lines changed: 138 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,96 @@
1-
# This workflow automates the release process when a release is created on GitHub
2-
# It uses Maven release plugin to manage versions and deploys to Maven Central
1+
# This workflow automates the release process when a draft release is created
2+
# on GitHub. It uses Maven release plugin to manage versions and deploys to
3+
# Maven Central
34
#
45
# Usage:
5-
# 1. Create a new release in GitHub UI with tag format: vX.Y.Z (e.g., v1.2.0)
6+
# 1. Create a new draft release in GitHub UI with tag format: vX.Y.Z
7+
# (e.g., v1.2.0)
68
# - Select the target branch (typically main)
79
# 2. This workflow will automatically:
8-
# - Use Maven release:prepare to update versions and create release commits
10+
# - Use Maven release:prepare to update versions and create release commit
911
# - Use Maven release:perform to build and deploy artifacts to Maven Central
10-
# - Move the tag to point to the actual release commit
12+
# - Create the tag to point to release commit
1113
# - Push commits back to the originating branch
14+
# - Publish the GitHub release (mark draft as non-draft)
1215

1316
name: Automated Release
1417

1518
on:
1619
release:
17-
types: [created] # Trigger when release is created
20+
types: [created] # Only fire when a release is created
1821

1922
permissions:
20-
contents: write # Required to push commits and update tags
23+
contents: write # Required to push commits, update tags and edit releases
2124

2225
jobs:
2326
release:
27+
# Only run for draft releases; ignore non-draft created events
28+
if: github.event.release.draft
29+
2430
runs-on: ubuntu-latest
25-
31+
2632
steps:
2733
- name: Validate release tag format
2834
id: validate_tag
2935
run: |
3036
TAG_NAME="${{ github.event.release.tag_name }}"
3137
echo "Release tag: $TAG_NAME"
32-
38+
3339
# Validate tag format (should be vX.Y.Z)
3440
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
3541
echo "::error::Invalid tag format. Expected format: vX.Y.Z (e.g., v1.2.0)"
3642
exit 1
3743
fi
38-
44+
3945
# Remove 'v' prefix to get the version number
4046
VERSION="${TAG_NAME#v}"
41-
echo "version=${VERSION}" >> $GITHUB_OUTPUT
47+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
4248
echo "Extracted version: $VERSION"
43-
49+
4450
- name: Determine target branch
4551
id: target_branch
4652
run: |
4753
# Use target_commitish to determine the originating branch
4854
TARGET="${{ github.event.release.target_commitish }}"
49-
55+
5056
# If target_commitish is empty or a SHA, default to main
5157
if [[ -z "$TARGET" ]] || [[ "$TARGET" =~ ^[0-9a-f]{40}$ ]]; then
5258
TARGET="main"
5359
fi
54-
55-
echo "target_branch=${TARGET}" >> $GITHUB_OUTPUT
60+
61+
echo "target_branch=${TARGET}" >> "$GITHUB_OUTPUT"
5662
echo "Target branch: $TARGET"
57-
63+
5864
- name: Generate GitHub App Token
5965
id: generate_token
6066
uses: actions/create-github-app-token@v2
6167
with:
6268
app-id: ${{ secrets.APP_ID }}
6369
private-key: ${{ secrets.APP_PRIVATE_KEY }}
64-
70+
6571
- name: Checkout repository
66-
uses: actions/checkout@v6
72+
uses: actions/checkout@v4
6773
with:
6874
ref: ${{ steps.target_branch.outputs.target_branch }}
6975
fetch-depth: 0
7076
token: ${{ steps.generate_token.outputs.token }}
71-
72-
- name: Delete user-created tag
77+
78+
- name: Pre-build condition checks
7379
run: |
7480
TAG_NAME="${{ github.event.release.tag_name }}"
75-
76-
# Delete the tag created by the user (will be recreated by release:prepare)
77-
git tag -d "$TAG_NAME" || true
78-
git push origin ":refs/tags/$TAG_NAME" || true
79-
80-
echo "Deleted user-created tag $TAG_NAME"
81+
82+
echo "Performing pre-build condition checks..."
83+
84+
# Fetch latest state from remote
85+
git fetch origin --tags
86+
87+
# Check if a tag exists
88+
if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME$"; then
89+
echo "::error::Tag $TAG_NAME already exists on remote."
90+
exit 1
91+
fi
92+
93+
echo "Pre-build checks passed. No issues detected."
8194
8295
- name: Set up JDK 25
8396
uses: actions/setup-java@v5
@@ -95,24 +108,20 @@ jobs:
95108
server-password: MAVEN_PASSWORD
96109
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
97110
gpg-passphrase: MAVEN_GPG_PASSPHRASE
98-
111+
99112
- name: Configure Git
100113
run: |
101114
git config user.name "github-actions[bot]"
102115
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
103-
116+
104117
- name: Run Maven release:prepare
105118
run: |
106119
VERSION="${{ steps.validate_tag.outputs.version }}"
107120
TAG_NAME="${{ github.event.release.tag_name }}"
108-
121+
109122
echo "Preparing release version: $VERSION"
110123
echo "Tag name: $TAG_NAME"
111-
112-
# Run release:prepare with explicit release version
113-
# Maven will automatically calculate the next development version
114-
# Only prepare production modules, exclude all sample modules
115-
# Pass -pl/-am to forked Maven invocations via -Darguments
124+
116125
./mvnw -B release:prepare \
117126
-DreleaseVersion="${VERSION}" \
118127
-Dtag="${TAG_NAME}" \
@@ -122,40 +131,115 @@ jobs:
122131
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
123132
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
124133
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
125-
134+
126135
- name: Run Maven release:perform
127136
run: |
128137
echo "Performing release and deploying to Maven Central"
129-
130-
# Run release:perform to build and deploy
131-
# Only release production modules, exclude all sample modules
132-
# Pass -pl/-am to forked Maven invocations via -Darguments
138+
133139
./mvnw -B release:perform \
134140
-DlocalCheckout=true \
135-
-DeployAtEnd=true \
141+
-DdeployAtEnd=true \
136142
-Darguments="-pl xapi-model,xapi-client,xapi-model-spring-boot-starter -am"
137143
env:
138144
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
139145
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
140146
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
141-
142-
- name: Push changes to originating branch
147+
148+
- name: Push changes to originating branch and tag (with merge fallback)
143149
run: |
144150
TARGET_BRANCH="${{ steps.target_branch.outputs.target_branch }}"
145151
TAG_NAME="${{ github.event.release.tag_name }}"
146-
152+
147153
echo "Pushing changes to branch: $TARGET_BRANCH"
148-
149-
# Push the commits created by release:prepare
150-
if ! git push --force-with-lease origin "HEAD:${TARGET_BRANCH}"; then
151-
echo "::error::Failed to push release commits to ${TARGET_BRANCH} due to branch divergence."
152-
echo "The remote branch may have new commits. Please resolve the conflict manually:"
153-
echo " 1. Fetch the latest changes: git fetch origin"
154-
echo " 2. Rebase or merge as needed, then push again with --force-with-lease."
154+
155+
# First, try a normal fast-forward push
156+
if git push origin "HEAD:${TARGET_BRANCH}"; then
157+
echo "Fast-forward push to ${TARGET_BRANCH} succeeded."
158+
else
159+
echo "::warning::Fast-forward push to ${TARGET_BRANCH} failed. Trying merge fallback."
160+
161+
# Fetch latest state of the branch
162+
git fetch origin "${TARGET_BRANCH}"
163+
164+
# Merge origin/TARGET_BRANCH into our release HEAD.
165+
# If this conflicts, we bail out rather than trying to auto-resolve.
166+
if ! git merge --no-edit "origin/${TARGET_BRANCH}"; then
167+
echo "::error::Merge conflict detected - likely due to a race condition."
168+
echo ""
169+
echo "This typically happens when changes to POM files were merged to ${TARGET_BRANCH}"
170+
echo "while this release workflow was running."
171+
echo ""
172+
echo "Recommended resolution:"
173+
echo " 1. Delete this draft release in GitHub"
174+
echo " 2. Create a new release via the GitHub Release UI"
175+
echo ""
176+
echo "Note: Artifacts have been deployed to Maven Central."
177+
exit 1
178+
fi
179+
180+
# Now push the merge commit
181+
if git push origin "HEAD:${TARGET_BRANCH}"; then
182+
echo "Pushed merge commit to ${TARGET_BRANCH}."
183+
else
184+
echo "::error::Failed to push merge commit to ${TARGET_BRANCH} after merge."
185+
echo ""
186+
echo "This may be due to branch protection rules or another race condition."
187+
echo ""
188+
echo "Recommended resolution:"
189+
echo " 1. Delete this draft release in GitHub"
190+
echo " 2. Create a new release via the GitHub Release UI"
191+
exit 1
192+
fi
193+
fi
194+
195+
echo "Pushing tag $TAG_NAME"
196+
if ! git push origin "$TAG_NAME"; then
197+
echo "::error::Failed to push tag $TAG_NAME."
198+
echo ""
199+
echo "This may be due to concurrent tag creation (tag collision), where someone created a tag with"
200+
echo "the same name while this workflow was running."
201+
echo ""
202+
echo "Recommended resolution:"
203+
echo " 1. Check if the tag $TAG_NAME already exists on the remote"
204+
echo " 2. If the tag exists but points to wrong commit, delete it"
205+
echo " 3. Create a new draft release via the GitHub Release UI"
155206
exit 1
156207
fi
157-
158-
# Push the tag created by release:prepare
159-
git push origin "$TAG_NAME"
160-
161208
echo "Pushed release commits and tag to $TARGET_BRANCH"
209+
210+
- name: Collect release assets
211+
run: |
212+
set -e
213+
VERSION="${{ steps.validate_tag.outputs.version }}"
214+
mkdir -p artifacts
215+
# Define artifact paths
216+
CLIENT_JAR="xapi-client/target/xapi-client-${VERSION}.jar"
217+
MODEL_JAR="xapi-model/target/xapi-model-${VERSION}.jar"
218+
STARTER_JAR="xapi-model-spring-boot-starter/target/xapi-model-spring-boot-starter-${VERSION}.jar"
219+
# Check existence and copy
220+
for JAR in "$CLIENT_JAR" "$MODEL_JAR" "$STARTER_JAR"; do
221+
if [ ! -f "$JAR" ]; then
222+
echo "::error::Artifact not found: $JAR"
223+
exit 1
224+
fi
225+
cp "$JAR" artifacts/
226+
done
227+
228+
- name: Upload release assets
229+
env:
230+
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
231+
run: |
232+
TAG_NAME="${{ github.event.release.tag_name }}"
233+
for FILE in artifacts/*; do
234+
echo "Uploading $(basename "$FILE")"
235+
gh release upload "$TAG_NAME" "$FILE" --clobber
236+
done
237+
238+
- name: Publish GitHub release
239+
env:
240+
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
241+
run: |
242+
TAG_NAME="${{ github.event.release.tag_name }}"
243+
echo "Publishing draft release for $TAG_NAME"
244+
gh release edit "$TAG_NAME" --draft=false
245+

0 commit comments

Comments
 (0)