diff --git a/.gitea/workflows/backport-commoncore.yml b/.gitea/workflows/backport-commoncore.yml new file mode 100644 index 00000000..360278d9 --- /dev/null +++ b/.gitea/workflows/backport-commoncore.yml @@ -0,0 +1,163 @@ +name: Backport CommonCore + +on: + pull_request: + types: + - closed + branches: + - main + +permissions: + contents: write + pull-requests: write + +env: + BACKPORT_PATH: CommonCore + BACKPORT_BRANCH_PREFIX: backport/commoncore + DISABLE_BACKPORT_LABEL: no-backport + +jobs: + backport: + name: Create CommonCore backport PRs + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Check backport eligibility + id: eligibility + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + api_url="${GITHUB_API_URL:-${GITHUB_SERVER_URL}/api/v1}" + + merged="$(jq -r '.pull_request.merged // (.pull_request.merged_at != null)' "$GITHUB_EVENT_PATH")" + base_branch="$(jq -r '.pull_request.base.ref' "$GITHUB_EVENT_PATH")" + has_disable_label="$(jq -r --arg label "$DISABLE_BACKPORT_LABEL" 'any(.pull_request.labels[]?; .name == $label)' "$GITHUB_EVENT_PATH")" + + { + echo "should_backport=$([[ "$merged" == "true" && "$base_branch" == "main" && "$has_disable_label" != "true" ]] && echo true || echo false)" + echo "pr_number=$(jq -r '.pull_request.number' "$GITHUB_EVENT_PATH")" + echo "base_sha=$(jq -r '.pull_request.base.sha' "$GITHUB_EVENT_PATH")" + echo "head_sha=$(jq -r '.pull_request.head.sha' "$GITHUB_EVENT_PATH")" + echo "pr_title<> "$GITHUB_OUTPUT" + + labels="$(curl -fsS \ + -H "Accept: application/json" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + "${api_url}/repos/${GITHUB_REPOSITORY}/labels")" + + if ! jq -e --arg label "$DISABLE_BACKPORT_LABEL" 'any(.[]; .name == $label)' <<< "$labels" >/dev/null; then + curl -fsS -X POST \ + -H "Accept: application/json" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$(jq -n --arg name "$DISABLE_BACKPORT_LABEL" --arg color "ededed" --arg description "Disable automatic CommonCore backporting for this pull request." '{name: $name, color: $color, description: $description}')" \ + "${api_url}/repos/${GITHUB_REPOSITORY}/labels" + fi + + - name: Create backport pull requests + if: steps.eligibility.outputs.should_backport == 'true' + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.eligibility.outputs.pr_number }} + PR_TITLE: ${{ steps.eligibility.outputs.pr_title }} + BASE_SHA: ${{ steps.eligibility.outputs.base_sha }} + HEAD_SHA: ${{ steps.eligibility.outputs.head_sha }} + run: | + set -euo pipefail + + api_url="${GITHUB_API_URL:-${GITHUB_SERVER_URL}/api/v1}" + + git config user.name "SteamWar Backport Bot" + git config user.email "actions@steamwar.de" + + if [[ "${GITHUB_SERVER_URL}" == https://* ]]; then + auth_host="${GITHUB_SERVER_URL#https://}" + git remote set-url origin "https://oauth2:${GITHUB_TOKEN}@${auth_host}/${GITHUB_REPOSITORY}.git" + fi + + git fetch --prune origin '+refs/heads/version/*:refs/remotes/origin/version/*' + git fetch origin "refs/pull/${PR_NUMBER}/head:refs/remotes/origin/pr/${PR_NUMBER}/head" || true + + diff_head="${HEAD_SHA}" + if ! git cat-file -e "${diff_head}^{commit}" 2>/dev/null; then + diff_head="refs/remotes/origin/pr/${PR_NUMBER}/head" + fi + + if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then + echo "Base commit ${BASE_SHA} is not available." + exit 1 + fi + if ! git cat-file -e "${diff_head}^{commit}" 2>/dev/null; then + echo "Head commit ${HEAD_SHA} is not available." + exit 1 + fi + + if git diff --quiet "${BASE_SHA}...${diff_head}" -- "${BACKPORT_PATH}"; then + echo "Pull request #${PR_NUMBER} has no ${BACKPORT_PATH} changes to backport." + exit 0 + fi + + git diff --binary "${BASE_SHA}...${diff_head}" -- "${BACKPORT_PATH}" > commoncore-backport.patch + + mapfile -t target_branches < <(git for-each-ref --format='%(refname:strip=3)' refs/remotes/origin/version) + if [[ "${#target_branches[@]}" -eq 0 ]]; then + echo "No version/* branches found." + exit 0 + fi + + for target_branch in "${target_branches[@]}"; do + safe_target="${target_branch//\//-}" + backport_branch="${BACKPORT_BRANCH_PREFIX}/pr-${PR_NUMBER}-to-${safe_target}" + + git checkout -B "${backport_branch}" "origin/${target_branch}" + git reset --hard "origin/${target_branch}" + + if ! git apply --3way --index commoncore-backport.patch; then + echo "Failed to apply CommonCore backport for ${target_branch}." + exit 1 + fi + + if git diff --cached --quiet; then + echo "CommonCore changes from #${PR_NUMBER} are already present in ${target_branch}." + continue + fi + + git commit -m "Backport CommonCore changes from #${PR_NUMBER}" -m "${PR_TITLE}" + git push --force-with-lease origin "${backport_branch}" + + open_pr_number="$(curl -fsS \ + -H "Accept: application/json" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + "${api_url}/repos/${GITHUB_REPOSITORY}/pulls?state=open" \ + | jq -r --arg base "$target_branch" --arg head "$backport_branch" '[.[] | select(.base.ref == $base and .head.ref == $head) | (.number // .index)][0] // empty')" + + if [[ -n "$open_pr_number" ]]; then + echo "Backport PR #${open_pr_number} already exists for ${target_branch}." + continue + fi + + pr_body="$(printf 'Automatic CommonCore backport of #%s.\n\nOriginal PR title: %s\n\nOnly files below `CommonCore/` are included.' "$PR_NUMBER" "$PR_TITLE")" + + curl -fsS -X POST \ + -H "Accept: application/json" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg base "$target_branch" \ + --arg head "$backport_branch" \ + --arg title "Backport CommonCore changes from #${PR_NUMBER} to ${target_branch}" \ + --arg body "$pr_body" \ + '{base: $base, head: $head, title: $title, body: $body}')" \ + "${api_url}/repos/${GITHUB_REPOSITORY}/pulls" + done diff --git a/.gitea/workflows/pull-request-build.yml b/.gitea/workflows/pull-request-build.yml index e2902e04..7aa8d812 100644 --- a/.gitea/workflows/pull-request-build.yml +++ b/.gitea/workflows/pull-request-build.yml @@ -3,6 +3,10 @@ name: Pull Request Build on: pull_request: +permissions: + contents: write + pull-requests: write + jobs: build: name: Build @@ -39,3 +43,34 @@ jobs: echo "$SW_MAVEN_CREDENTIALS" > steamwar.properties - name: Build with Gradle run: ./gradlew build --no-daemon + + - name: Merge successful backport PR + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BACKPORT_BRANCH_PREFIX: backport/commoncore + run: | + set -euo pipefail + + head_branch="$(jq -r '.pull_request.head.ref // ""' "$GITHUB_EVENT_PATH")" + base_branch="$(jq -r '.pull_request.base.ref // ""' "$GITHUB_EVENT_PATH")" + pr_number="$(jq -r '.pull_request.number' "$GITHUB_EVENT_PATH")" + + if [[ "${head_branch}" != "${BACKPORT_BRANCH_PREFIX}/"* ]]; then + echo "Not a CommonCore backport PR." + exit 0 + fi + + if [[ "${base_branch}" != version/* ]]; then + echo "Backport PR target is not a version/* branch." + exit 0 + fi + + api_url="${GITHUB_API_URL:-${GITHUB_SERVER_URL}/api/v1}" + + curl -fsS -X PUT \ + -H "Accept: application/json" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"Do":"merge","delete_branch_after_merge":true}' \ + "${api_url}/repos/${GITHUB_REPOSITORY}/pulls/${pr_number}/merge"