From d5aeeaf5e330f8adfd938e866c8fc0836d51db6e Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Fri, 15 May 2026 12:04:02 +0200 Subject: [PATCH] Add backport workflow for CommonCore changes and update build.yml Signed-off-by: Chaoscaot --- .gitea/workflows/backport-commoncore.yml | 213 +++++++++++++++++++++++ .gitea/workflows/build.yml | 30 ++++ 2 files changed, 243 insertions(+) create mode 100644 .gitea/workflows/backport-commoncore.yml diff --git a/.gitea/workflows/backport-commoncore.yml b/.gitea/workflows/backport-commoncore.yml new file mode 100644 index 00000000..8cb081f5 --- /dev/null +++ b/.gitea/workflows/backport-commoncore.yml @@ -0,0 +1,213 @@ +name: Backport CommonCore + +on: + pull_request: + types: [closed] + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + backport: + name: Backport CommonCore changes + runs-on: ubuntu-latest + if: ${{ github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main' }} + steps: + - name: Checkout sources + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 0 + token: ${{ secrets.GITEA_TOKEN }} + + - name: Create version branch backports + shell: bash + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -euo pipefail + + api() { + local method="$1" + local path="$2" + local body="${3:-}" + + if [[ -n "$body" ]]; then + curl --fail --silent --show-error \ + -X "$method" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + --data "$body" \ + "${GITHUB_API_URL}${path}" + else + curl --fail --silent --show-error \ + -X "$method" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Accept: application/json" \ + "${GITHUB_API_URL}${path}" + fi + } + + repo_path="/repos/${GITHUB_REPOSITORY}" + pr_number="$(jq -r '.number' "$GITHUB_EVENT_PATH")" + pr_title="$(jq -r '.pull_request.title // ""' "$GITHUB_EVENT_PATH")" + event_commit="$(jq -r '.pull_request.merge_commit_sha // .pull_request.merged_commit_id // .commit_id // env.GITHUB_SHA' "$GITHUB_EVENT_PATH")" + + commoncore_files=() + page=1 + while true; do + response="$(api GET "${repo_path}/pulls/${pr_number}/files?page=${page}&limit=50")" + count="$(jq 'length' <<< "$response")" + while IFS= read -r file; do + [[ -n "$file" ]] && commoncore_files+=("$file") + done < <(jq -r '.[] | (.filename // .path // .name // "") | select(startswith("CommonCore/"))' <<< "$response") + + [[ "$count" -lt 50 ]] && break + page=$((page + 1)) + done + + if [[ "${#commoncore_files[@]}" -eq 0 ]]; then + echo "PR #${pr_number} did not change CommonCore/; nothing to backport." + exit 0 + fi + + echo "PR #${pr_number} changed CommonCore files:" + printf ' - %s\n' "${commoncore_files[@]}" + + pr_commits=() + page=1 + while true; do + response="$(api GET "${repo_path}/pulls/${pr_number}/commits?page=${page}&limit=50")" + count="$(jq 'length' <<< "$response")" + while IFS= read -r commit; do + [[ -n "$commit" ]] && pr_commits+=("$commit") + done < <(jq -r '.[] | .sha // .id // empty' <<< "$response") + + [[ "$count" -lt 50 ]] && break + page=$((page + 1)) + done + + source_commits=("$event_commit") + if [[ "${#pr_commits[@]}" -gt 1 ]]; then + for commit in "${pr_commits[@]}"; do + if [[ "$commit" == "$event_commit" ]]; then + source_commits=("${pr_commits[@]}") + break + fi + done + fi + + echo "Using source commit plan:" + printf ' - %s\n' "${source_commits[@]}" + + git config --global user.name "SteamWar Backport Bot" + git config --global user.email "backport-bot@steamwar.de" + git config --global http.extraHeader "Authorization: token ${GITEA_TOKEN}" + git fetch origin '+refs/heads/*:refs/remotes/origin/*' + + for commit in "${source_commits[@]}"; do + git cat-file -e "${commit}^{commit}" + done + + mapfile -t version_branches < <( + git for-each-ref --format='%(refname:short)' refs/remotes/origin/version | + sed 's#^origin/##' | + sort -u + ) + + if [[ "${#version_branches[@]}" -eq 0 ]]; then + echo "No origin/version/* branches found; nothing to backport." + exit 0 + fi + + failures=() + for target_branch in "${version_branches[@]}"; do + safe_target="$(sed -E 's#[^A-Za-z0-9._-]+#-#g; s#^-+##; s#-+$##' <<< "$target_branch")" + head_branch="backport/pr-${pr_number}-to-${safe_target}" + + existing_pr="$(api GET "${repo_path}/pulls?state=open&base_branch=$(jq -rn --arg value "$target_branch" '$value|@uri')&limit=50" | + jq -r --arg head "$head_branch" '.[] | select(.head.ref == $head) | .number' | + head -n 1)" + + if [[ -n "$existing_pr" ]]; then + echo "Backport PR #${existing_pr} already exists for ${target_branch}; leaving it unchanged." + continue + fi + + echo "Creating ${head_branch} from ${target_branch}" + git checkout -B "$head_branch" "origin/${target_branch}" + + for commit in "${source_commits[@]}"; do + cherry_pick_args=(-x) + if [[ "$(git rev-list --parents -n 1 "$commit" | wc -w)" -gt 2 ]]; then + cherry_pick_args=(-x -m 1) + fi + + if git cherry-pick "${cherry_pick_args[@]}" "$commit"; then + continue + fi + + if [[ -z "$(git status --porcelain)" ]]; then + echo "Cherry-pick of ${commit} produced no changes; skipping it." + git cherry-pick --skip || true + continue + fi + + git cherry-pick --abort || true + failures+=("${target_branch}: cherry-pick of ${commit} failed") + break + done + + if [[ "${failures[*]:-}" == *"${target_branch}:"* ]]; then + git checkout main + continue + fi + + if git diff --quiet "origin/${target_branch}" HEAD; then + echo "${target_branch} already contains the change; no PR needed." + git checkout main + continue + fi + + git push origin "HEAD:refs/heads/${head_branch}" --force-with-lease + + if [[ "${#source_commits[@]}" -eq 1 ]]; then + source_text="Source commit: \`${source_commits[0]}\`" + else + source_text="Source commits:" + for commit in "${source_commits[@]}"; do + source_text="${source_text}"$'\n'"- \`${commit}\`" + done + fi + + body="$(cat </dev/null + echo "Created backport PR from ${head_branch} to ${target_branch}." + git checkout main + done + + if [[ "${#failures[@]}" -gt 0 ]]; then + echo "Backport failures:" + printf ' - %s\n' "${failures[@]}" + exit 1 + fi diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index d287c937..2a42cbb9 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -131,3 +131,33 @@ jobs: ssh -i ~/.ssh/deploy_key -p "$port" "${DEPLOY_USER}@${DEPLOY_HOST}" "mkdir -p '$DEPLOY_PATH'" scp -i ~/.ssh/deploy_key -P "$port" deploy/* "${DEPLOY_USER}@${DEPLOY_HOST}:$DEPLOY_PATH/" + + merge-backport: + name: Merge backport + runs-on: ubuntu-latest + needs: build + if: ${{ github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'version/') && startsWith(github.event.pull_request.head.ref, 'backport/pr-') }} + permissions: + contents: write + pull-requests: write + steps: + - name: Merge successful backport PR + shell: bash + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -euo pipefail + + pr_number="$(jq -r '.number' "$GITHUB_EVENT_PATH")" + target_branch="$(jq -r '.pull_request.base.ref' "$GITHUB_EVENT_PATH")" + payload="$(jq -n \ + --arg title "Merge backport #${pr_number} into ${target_branch}" \ + '{Do: "merge", MergeTitleField: $title, MergeMessageField: "Automatic CommonCore backport after successful build.", delete_branch_after_merge: true}')" + + curl --fail --silent --show-error \ + -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + --data "$payload" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${pr_number}/merge"