| #!/bin/bash |
| |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| |
| # The following permissions are required to successfully execute this script: |
| # permissions: |
| # issues: write # to close milestone (script will silently fail if not set) |
| # contents: write # to commit |
| # pull-requests: write # to create merge PR |
| # |
| # This script expects the github action to have checked out the repository with fetch-depth: 0 |
| |
| set_value_or_error() { |
| local value="$1" |
| local defaultValue="$2" |
| local variableName="$3" |
| local validValues="${4:-}" # optional argument (if empty, skip validation) |
| |
| if [[ -z "$value" && -z "$defaultValue" ]]; then |
| echo "ERROR: A value for $variableName is required." >&2 |
| exit 1 |
| fi |
| |
| if [[ -n "$value" ]]; then |
| decidedValue="$value" |
| else |
| echo "${variableName}: Using default value: ${defaultValue}" |
| decidedValue="$defaultValue" |
| fi |
| |
| if [[ -n "$validValues" ]]; then |
| local match=false |
| local v |
| for v in $validValues; do |
| if [[ "$decidedValue" == "$v" ]]; then |
| match=true |
| break |
| fi |
| done |
| |
| if ! $match; then |
| echo "ERROR: Invalid value for '$variableName': '$decidedValue'." >&2 |
| echo " Must be one of: $validValues" >&2 |
| return 1 |
| fi |
| fi |
| |
| eval "export $variableName=\"\$decidedValue\"" |
| } |
| |
| set -e |
| |
| set_value_or_error "${GIT_USER_NAME}" "${GITHUB_ACTOR}" "GIT_USER_NAME" |
| set_value_or_error "${GITHUB_WORKSPACE}" "." "GIT_SAFE_DIR" |
| # for testing: git remote get-url origin | sed -E 's#(git@|https://)github.com[:/](.*).git#\2#' |
| set_value_or_error "${GITHUB_REPOSITORY}" "" "GITHUB_REPOSITORY" |
| set_value_or_error "${GITHUB_TOKEN}" "" "GITHUB_TOKEN" |
| set_value_or_error "${GITHUB_EVENT_PATH}" "" "GITHUB_EVENT_PATH" |
| set_value_or_error "${GITHUB_API_URL}" "" "GITHUB_API_URL" |
| set_value_or_error "${RELEASE_VERSION}" "${GITHUB_REF#refs/*/}" "RELEASE_VERSION" |
| set_value_or_error "${RELEASE_TAG_PREFIX}" "v" "RELEASE_TAG_PREFIX" |
| set_value_or_error "${PROPERTY_FILE_NAME}" "gradle.properties" "PROPERTY_FILE_NAME" |
| |
| label_failure_message="" |
| pr_failure_message="" |
| |
| echo "::group::Determine release version" |
| if [[ ! "${RELEASE_VERSION}" =~ ^(${RELEASE_TAG_PREFIX})?[^.]+\.[^.]+\.[^.]+$ ]]; then |
| echo "ERROR: RELEASE_VERSION must be in the format 'X.X.X' or '${RELEASE_TAG_PREFIX}X.X.X'. Got: '${RELEASE_VERSION}'" |
| exit 1 |
| fi |
| if [[ $RELEASE_VERSION == "${RELEASE_TAG_PREFIX}"* ]]; then |
| RELEASE_VERSION=${RELEASE_VERSION:${#RELEASE_TAG_PREFIX}} |
| else |
| RELEASE_VERSION="${RELEASE_VERSION}" |
| fi |
| echo "Release Version: ${RELEASE_VERSION}" |
| echo "::endgroup::" |
| |
| echo "::group::Close Milestone (if it exists)" |
| set +e |
| echo -n "Retrieving current milestone number: " |
| milestone_number=`curl -s ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/milestones | jq -c ".[] | select (.title == \"${RELEASE_VERSION}\") | .number" | sed -e 's/"//g'` |
| echo "Found Milestone Id: $milestone_number" |
| echo "Closing current milestone" |
| curl -s --request PATCH -H "Authorization: Bearer ${GITHUB_TOKEN}" -H "Content-Type: application/json" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/milestones/$milestone_number" --data '{"state":"closed"}' |
| set -e |
| echo "::endgroup::" |
| |
| echo "::group::Determine next version" |
| echo -n "Next Version: " |
| export NEXT_VERSION=`/increment_version.sh -p ${RELEASE_VERSION}` |
| echo "${NEXT_VERSION}" |
| echo "NEXT_VERSION=${NEXT_VERSION}" >> $GITHUB_OUTPUT |
| echo "::endgroup::" |
| |
| echo "::group::Configure Git" |
| git config --global --add safe.directory "${GIT_SAFE_DIR}" |
| git config --global user.email "${GIT_USER_NAME}@users.noreply.github.com" |
| git config --global user.name "${GIT_USER_NAME}" |
| git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" |
| git fetch |
| echo "::endgroup::" |
| |
| echo "::group::Determine target merge branch" |
| set_value_or_error "${TARGET_BRANCH}" "$(jq -r 'if has("release") and .release.target_commitish != null then .release.target_commitish else "" end' "$GITHUB_EVENT_PATH")" "TARGET_BRANCH" |
| echo "Target Branch is ${TARGET_BRANCH}" |
| TARGET_BRANCH="${TARGET_BRANCH#refs/*/}" |
| echo "Pruned Target Branch is ${TARGET_BRANCH}" |
| git checkout "${TARGET_BRANCH}" |
| echo "::endgroup::" |
| |
| echo "::group::Creating Merge Branch from ${RELEASE_TAG_PREFIX}${RELEASE_VERSION}" |
| MERGE_BRANCH_NAME="merge-back-${RELEASE_VERSION}" |
| git checkout -b "${MERGE_BRANCH_NAME}" "${RELEASE_TAG_PREFIX}${RELEASE_VERSION}" |
| echo "::endgroup::" |
| |
| echo "::group::Add commit to update to next version" |
| echo "Setting new snapshot version" |
| sed -i "s/^projectVersion\=.*$/projectVersion\=${NEXT_VERSION}-SNAPSHOT/" "${PROPERTY_FILE_NAME}" |
| sed -i "s/^version\=.*$/version\=${NEXT_VERSION}-SNAPSHOT/" "${PROPERTY_FILE_NAME}" |
| cat "${PROPERTY_FILE_NAME}" |
| printf "\n" |
| git add "${PROPERTY_FILE_NAME}" |
| |
| if [[ -n "${RELEASE_SCRIPT_PATH}" && -x "${GITHUB_WORKSPACE}/${RELEASE_SCRIPT_PATH}" ]]; then |
| echo "Executing additional release script at ${GITHUB_WORKSPACE}/${RELEASE_SCRIPT_PATH}" |
| "${GITHUB_WORKSPACE}/${RELEASE_SCRIPT_PATH}" |
| else |
| if [[ -n "${RELEASE_SCRIPT_PATH}" ]]; then |
| echo "ERROR: RELEASE_SCRIPT_PATH is set to '${RELEASE_SCRIPT_PATH}' but is not executable or does not exist." >&2 |
| exit 1 |
| fi |
| fi |
| |
| if git diff-index --quiet HEAD --; then |
| git checkout -- |
| echo "::notice:: No version change – nothing to commit, skipping commit." |
| else |
| echo "Committing next version ${NEXT_VERSION}-SNAPSHOT" |
| git commit -m "[skip ci] Bump version to ${NEXT_VERSION}-SNAPSHOT" |
| fi |
| echo "::endgroup::" |
| |
| echo "::group::Pushing Merge Branch ${MERGE_BRANCH_NAME}" |
| echo "Pushing changes to side branch: ${MERGE_BRANCH_NAME}" |
| git push --force origin "${MERGE_BRANCH_NAME}" |
| echo "::endgroup::" |
| |
| echo "::group::Open/Reuse pull request" |
| PR_TITLE="chore: merge ${RELEASE_VERSION}->${TARGET_BRANCH}; bump to ${NEXT_VERSION}-SNAPSHOT" |
| PR_BODY="Automated snapshot bump after completing release ${RELEASE_VERSION}" |
| PR_REFERENCE="${MERGE_BRANCH_NAME}" |
| if ! gh pr create \ |
| --title "${PR_TITLE}" \ |
| --body "${PR_BODY}" \ |
| --base "${TARGET_BRANCH}" \ |
| --head "${MERGE_BRANCH_NAME}" \ |
| --fill >/tmp/pr-url 2>/tmp/pr-err; then |
| echo "PR creation failed. Checking for an existing PR:" >&2 |
| cat /tmp/pr-err |
| if PR_REFERENCE="$(gh pr view "${MERGE_BRANCH_NAME}" --web 2>/tmp/pr-view-err)"; then |
| echo "${PR_REFERENCE}" |
| else |
| cat /tmp/pr-view-err >&2 |
| pr_failure_message="ERROR: Merge-back branch ${MERGE_BRANCH_NAME} was pushed, but pull request creation failed and no existing PR could be found. Create the PR manually." |
| echo "${pr_failure_message}" >&2 |
| PR_REFERENCE="" |
| fi |
| else |
| PR_REFERENCE="$(cat /tmp/pr-url)" |
| echo "${PR_REFERENCE}" |
| fi |
| |
| if [[ -n "${PR_REFERENCE}" && -n "${PR_LABELS}" ]]; then |
| IFS=',' read -r -a pr_labels <<< "${PR_LABELS}" |
| label_args=() |
| for pr_label in "${pr_labels[@]}"; do |
| trimmed_label="$(printf '%s' "${pr_label}" | xargs)" |
| if [[ -n "${trimmed_label}" ]]; then |
| label_args+=(--add-label "${trimmed_label}") |
| fi |
| done |
| |
| if [[ ${#label_args[@]} -gt 0 ]]; then |
| echo "Applying pull request labels: ${PR_LABELS}" |
| if ! gh pr edit "${PR_REFERENCE}" "${label_args[@]}"; then |
| label_failure_message="WARNING: Pull request ${PR_REFERENCE} was created or reused, but one or more labels could not be applied. Verify that all requested labels exist: ${PR_LABELS}" |
| echo "${label_failure_message}" >&2 |
| fi |
| fi |
| fi |
| echo "::endgroup::" |
| |
| echo "::group::Update Release Status" |
| release_update_payload=() |
| if [[ -n "${RELEASE_PRE_RELEASE}" ]]; then |
| if [[ "${RELEASE_PRE_RELEASE}" != "true" && "${RELEASE_PRE_RELEASE}" != "false" ]]; then |
| echo "ERROR: RELEASE_PRE_RELEASE must be 'true' or 'false' if set. Got: '${RELEASE_PRE_RELEASE}'" >&2 |
| exit 1 |
| fi |
| release_update_payload+=("\"prerelease\": ${RELEASE_PRE_RELEASE}") |
| fi |
| |
| if [[ -n "${RELEASE_LATEST}" ]]; then |
| if [[ "${RELEASE_LATEST}" != "true" && "${RELEASE_LATEST}" != "false" ]]; then |
| echo "ERROR: RELEASE_LATEST must be 'true' or 'false' if set. Got: '${RELEASE_LATEST}'" >&2 |
| exit 1 |
| fi |
| release_update_payload+=("\"make_latest\": \"${RELEASE_LATEST}\"") |
| fi |
| |
| if [[ ${#release_update_payload[@]} -eq 0 ]]; then |
| echo "No release flags set (RELEASE_PRE_RELEASE / RELEASE_LATEST). Skipping GitHub Release update." |
| else |
| json_payload_content=$(printf "%s, " "${release_update_payload[@]}") |
| json_payload_content="${json_payload_content%, }" # Remove trailing comma-space |
| json_payload="{${json_payload_content}}" |
| |
| echo "PATCH payload: ${json_payload}" |
| curl -fvs --request PATCH \ |
| -H "Authorization: Bearer ${GITHUB_TOKEN}" -H "Content-Type: application/json" \ |
| "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG_PREFIX}${RELEASE_VERSION}" \ |
| --data "${json_payload}" |
| fi |
| echo "::endgroup::" |
| |
| if [[ -n "${pr_failure_message}" ]]; then |
| echo "${pr_failure_message}" >&2 |
| exit 1 |
| fi |
| |
| if [[ -n "${label_failure_message}" ]]; then |
| echo "${label_failure_message}" >&2 |
| exit 1 |
| fi |