Skip to content

Commit 7153143

Browse files
committed
chore: add release workflow
Use GitHub Actions to trigger the release workflow via issue.
1 parent d177739 commit 7153143

19 files changed

Lines changed: 1148 additions & 9 deletions
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Release branch
2+
description: Create a maintenance release branch.
3+
title: "Create release branch "
4+
labels: []
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
This release branch automation is for maintainers only. Submissions from non-maintainers will be rejected by the workflow.
10+
- type: input
11+
id: maintenanceBranch
12+
attributes:
13+
label: Maintenance branch
14+
description: Branch to create for maintenance releases.
15+
placeholder: "release/1.1"
16+
validations:
17+
required: true
18+
- type: input
19+
id: sourceBranch
20+
attributes:
21+
label: Source branch
22+
description: Branch to cut from.
23+
placeholder: "main"
24+
value: "main"
25+
validations:
26+
required: true
27+
- type: input
28+
id: sourceRef
29+
attributes:
30+
label: Source ref
31+
description: Optional exact ref or SHA. Leave blank to use the source branch tip.
32+
placeholder: "main"
33+
validations:
34+
required: false

.github/ISSUE_TEMPLATE/release.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Release
2+
description: Prepare and publish a library release.
3+
title: "Release "
4+
labels: []
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
This release automation is for maintainers only. Submissions from non-maintainers will be rejected by the workflow.
10+
- type: input
11+
id: releaseVersion
12+
attributes:
13+
label: Release version
14+
description: Version to publish, without the v tag prefix.
15+
placeholder: "1.0.4"
16+
validations:
17+
required: true
18+
- type: input
19+
id: nextVersion
20+
attributes:
21+
label: Next version
22+
description: Next development version after the release.
23+
placeholder: "1.0.5-SNAPSHOT"
24+
validations:
25+
required: true
26+
- type: input
27+
id: targetBranch
28+
attributes:
29+
label: Target branch
30+
description: Branch to release from and fast-forward on publish.
31+
placeholder: "main"
32+
value: "main"
33+
validations:
34+
required: true
35+
- type: textarea
36+
id: releaseNotes
37+
attributes:
38+
label: Release notes
39+
description: Optional text to include in the annotated tag message.
40+
validations:
41+
required: false
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
log_file="$1"
6+
workflow_name="$2"
7+
8+
body_file="$(mktemp)"
9+
redacted_log="$(mktemp)"
10+
if [[ -s "${log_file}" ]]; then
11+
cp "${log_file}" "${redacted_log}"
12+
if [[ -n "${PUSH_TOKEN:-}" ]]; then
13+
perl -0pi -e 'BEGIN { $s = $ENV{PUSH_TOKEN} // "" } if (length $s) { s/\Q$s\E/<redacted PUSH_TOKEN>/g }' "${redacted_log}"
14+
fi
15+
if [[ -n "${GH_TOKEN:-}" ]]; then
16+
perl -0pi -e 'BEGIN { $s = $ENV{GH_TOKEN} // "" } if (length $s) { s/\Q$s\E/<redacted GH_TOKEN>/g }' "${redacted_log}"
17+
fi
18+
fi
19+
20+
{
21+
echo "${workflow_name} failed."
22+
echo
23+
echo '```text'
24+
if [[ -s "${redacted_log}" ]]; then
25+
tail -n 80 "${redacted_log}"
26+
else
27+
echo "No script output was captured."
28+
fi
29+
echo '```'
30+
echo
31+
echo "Run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
32+
} >"${body_file}"
33+
34+
gh issue comment "${ISSUE_NUMBER}" --body-file "${body_file}"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
script_dir="$(cd "$(dirname "$0")" && pwd)"
6+
# shellcheck source=.github/scripts/release-common.sh
7+
source "${script_dir}/release-common.sh"
8+
9+
require_maintainer
10+
11+
maintenance_branch="$(extract_issue_field_line "Maintenance branch")"
12+
source_branch="$(extract_issue_field_line "Source branch")"
13+
source_ref="$(extract_issue_field_line "Source ref" || true)"
14+
15+
validate_maintenance_branch "${maintenance_branch}"
16+
validate_release_target_branch "${source_branch}"
17+
if [[ -z "${source_ref}" ]]; then
18+
source_ref="origin/${source_branch}"
19+
elif [[ "${source_ref}" == "${source_branch}" ]]; then
20+
source_ref="origin/${source_branch}"
21+
fi
22+
if [[ "${source_ref}" == -* ]]; then
23+
echo "Source ref must not start with '-'."
24+
exit 1
25+
fi
26+
27+
require_missing_remote_branch "${maintenance_branch}"
28+
require_remote_branch "${source_branch}"
29+
30+
configure_git_credentials
31+
git fetch origin "+refs/heads/${source_branch}:refs/remotes/origin/${source_branch}" --tags
32+
git rev-parse --verify --end-of-options "${source_ref}^{commit}" >/dev/null
33+
if ! git merge-base --is-ancestor -- "${source_ref}^{commit}" "origin/${source_branch}"; then
34+
echo "Source ref ${source_ref} is not reachable from origin/${source_branch}."
35+
exit 1
36+
fi
37+
git push origin "${source_ref}^{commit}:refs/heads/${maintenance_branch}"
38+
gh issue comment "${ISSUE_NUMBER}" --body "Created ${maintenance_branch} from ${source_ref}."

.github/scripts/prepare-release.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
script_dir="$(cd "$(dirname "$0")" && pwd)"
6+
# shellcheck source=.github/scripts/release-common.sh
7+
source "${script_dir}/release-common.sh"
8+
9+
require_maintainer
10+
11+
release_version="$(extract_issue_field_line "Release version")"
12+
next_version="$(extract_issue_field_line "Next version")"
13+
target_branch="$(extract_issue_field_line "Target branch")"
14+
15+
validate_release_version "${release_version}"
16+
validate_next_version "${next_version}"
17+
validate_release_target_branch "${target_branch}"
18+
19+
tag_name="v${release_version}"
20+
work_branch="release-work/${release_version}"
21+
22+
require_missing_remote_tag "${tag_name}"
23+
require_remote_branch "${target_branch}"
24+
require_missing_remote_branch "${work_branch}"
25+
26+
configure_git_author
27+
configure_git_credentials
28+
git fetch origin "+refs/heads/${target_branch}:refs/remotes/origin/${target_branch}" --tags
29+
git checkout -B "${work_branch}" "origin/${target_branch}"
30+
31+
./gradlew release \
32+
-Pcbssh.release.noPush=true \
33+
-Prelease.useAutomaticVersion=true \
34+
-Prelease.releaseVersion="${release_version}" \
35+
-Prelease.newVersion="${next_version}"
36+
37+
git push origin "HEAD:refs/heads/${work_branch}"
38+
39+
{
40+
echo "release_version=${release_version}"
41+
echo "next_version=${next_version}"
42+
echo "target_branch=${target_branch}"
43+
echo "work_branch=${work_branch}"
44+
echo "tag_name=${tag_name}"
45+
} >>"${GITHUB_OUTPUT}"

.github/scripts/publish-release.sh

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
script_dir="$(cd "$(dirname "$0")" && pwd)"
6+
# shellcheck source=.github/scripts/release-common.sh
7+
source "${script_dir}/release-common.sh"
8+
9+
require_maintainer
10+
11+
release_version="$(extract_issue_field_line "Release version")"
12+
next_version="$(extract_issue_field_line "Next version")"
13+
target_branch="$(extract_issue_field_line "Target branch")"
14+
release_notes="$(extract_issue_field "Release notes" || true)"
15+
16+
validate_release_version "${release_version}"
17+
validate_next_version "${next_version}"
18+
validate_release_target_branch "${target_branch}"
19+
20+
tag_name="v${release_version}"
21+
work_branch="release-work/${release_version}"
22+
23+
require_missing_remote_tag "${tag_name}"
24+
25+
pr_number="$(gh pr list \
26+
--head "${work_branch}" \
27+
--base "${target_branch}" \
28+
--state open \
29+
--json number \
30+
--jq '.[0].number // empty')"
31+
32+
if [[ -z "${pr_number}" ]]; then
33+
echo "No open release PR found for ${work_branch} into ${target_branch}."
34+
exit 1
35+
fi
36+
37+
pr_data="$(gh pr view "${pr_number}" --json baseRefName,headRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision)"
38+
actual_base="$(jq -r '.baseRefName' <<<"${pr_data}")"
39+
actual_head="$(jq -r '.headRefName' <<<"${pr_data}")"
40+
head_ref_oid="$(jq -r '.headRefOid' <<<"${pr_data}")"
41+
mergeable="$(jq -r '.mergeable' <<<"${pr_data}")"
42+
merge_state="$(jq -r '.mergeStateStatus' <<<"${pr_data}")"
43+
review_decision="$(jq -r '.reviewDecision' <<<"${pr_data}")"
44+
45+
if [[ "${actual_base}" != "${target_branch}" || "${actual_head}" != "${work_branch}" ]]; then
46+
echo "Release PR does not match the issue target branch and work branch."
47+
exit 1
48+
fi
49+
if [[ "${mergeable}" != "MERGEABLE" ]]; then
50+
echo "Release PR must be mergeable before publishing. Current mergeable state: ${mergeable}."
51+
exit 1
52+
fi
53+
if [[ "${merge_state}" != "CLEAN" ]]; then
54+
echo "Release PR must have a clean merge state before publishing. Current merge state: ${merge_state}."
55+
exit 1
56+
fi
57+
if [[ "${review_decision}" != "APPROVED" ]]; then
58+
echo "Release PR must be approved before publishing. Current review decision: ${review_decision}."
59+
exit 1
60+
fi
61+
62+
gh pr checks "${pr_number}" --required --fail-fast
63+
64+
configure_git_author
65+
configure_git_credentials
66+
git fetch origin \
67+
"+refs/heads/${target_branch}:refs/remotes/origin/${target_branch}" \
68+
"+refs/heads/${work_branch}:refs/remotes/origin/${work_branch}" \
69+
--tags
70+
71+
if [[ "$(git rev-parse "origin/${work_branch}")" != "${head_ref_oid}" ]]; then
72+
echo "Release branch changed after PR checks were inspected."
73+
exit 1
74+
fi
75+
76+
release_commit=""
77+
while read -r commit; do
78+
if git show "${commit}:gradle.properties" | grep -q "^version=${release_version}$"; then
79+
release_commit="${commit}"
80+
break
81+
fi
82+
done < <(git rev-list --reverse "origin/${target_branch}..origin/${work_branch}")
83+
84+
if [[ -z "${release_commit}" ]]; then
85+
echo "Could not find release commit with version=${release_version}."
86+
exit 1
87+
fi
88+
89+
if ! git show "origin/${work_branch}:gradle.properties" | grep -q "^version=${next_version}$"; then
90+
echo "Release branch tip does not contain version=${next_version}."
91+
exit 1
92+
fi
93+
94+
tag_message="$(mktemp)"
95+
{
96+
echo "Release ${tag_name}"
97+
if [[ -n "${release_notes}" ]]; then
98+
echo
99+
echo "${release_notes}"
100+
fi
101+
} >"${tag_message}"
102+
103+
git tag -a "${tag_name}" "${release_commit}" -F "${tag_message}"
104+
if [[ "$(git cat-file -t "${tag_name}")" != "tag" ]]; then
105+
echo "${tag_name} is not an annotated tag."
106+
exit 1
107+
fi
108+
109+
git push --atomic --follow-tags origin "refs/remotes/origin/${work_branch}:refs/heads/${target_branch}"
110+
gh issue comment "${ISSUE_NUMBER}" --body "Published ${tag_name} to ${target_branch}."

0 commit comments

Comments
 (0)