Add backport pipeline (#2268)
authorMarvin Schenkel <marvinschenkel@gmail.com>
Sun, 6 Jul 2025 06:04:17 +0000 (08:04 +0200)
committerGitHub <noreply@github.com>
Sun, 6 Jul 2025 06:04:17 +0000 (08:04 +0200)
* Vibe coded a backport pipeline

* Remove unused triggers

* Potential fix for code scanning alert no. 23: Workflow does not contain permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
.github/workflows/backport-to-stable.yml [new file with mode: 0644]

diff --git a/.github/workflows/backport-to-stable.yml b/.github/workflows/backport-to-stable.yml
new file mode 100644 (file)
index 0000000..def896e
--- /dev/null
@@ -0,0 +1,146 @@
+name: Backport to stable
+permissions:
+  contents: read
+  pull-requests: write
+
+on:
+  push:
+    branches:
+      - dev
+
+jobs:
+  backport:
+    name: Backport PRs with 'backport-to-stable' label to stable
+    runs-on: ubuntu-latest
+    if: github.event.commits[0].distinct == true
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0  # Needed for full git history
+
+      - name: Get merged PR info
+        id: prinfo
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const pr = await github.rest.pulls.list({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              state: 'closed',
+              base: 'dev',
+              sort: 'updated',
+              direction: 'desc',
+              per_page: 10
+            });
+            const merged = pr.data.find(p => p.merge_commit_sha === context.payload.head_commit.id);
+            if (!merged) return core.setFailed('No merged PR found for this commit.');
+            core.setOutput('pr_number', merged.number);
+            core.setOutput('pr_title', merged.title);
+            core.setOutput('pr_labels', merged.labels.map(l => l.name).join(','));
+            core.setOutput('merge_commit_sha', merged.merge_commit_sha);
+
+      - name: Check for backport-to-stable label
+        id: checklabel
+        run: |
+          echo "PR labels: ${{ steps.prinfo.outputs.pr_labels }}"
+          if [[ "${{ steps.prinfo.outputs.pr_labels }}" != *"backport-to-stable"* ]]; then
+            echo "No backport-to-stable label, skipping."
+            exit 0
+          fi
+
+      - name: Set up Git user
+        run: |
+          git config user.name "github-actions[bot]"
+          git config user.email "github-actions[bot]@users.noreply.github.com"
+
+      - name: Create or update backport branch
+        id: create_or_update_backport_branch
+        run: |
+          git fetch origin stable --tags
+          latest_tag=$(git tag --merged origin/stable --sort=-v:refname | head -1)
+          if [[ -z "$latest_tag" ]]; then
+            echo "No tags found on stable branch" >&2
+            exit 1
+          fi
+          version="$latest_tag"
+          IFS='.' read -r major minor patch <<< "$version"
+          next_patch=$((patch + 1))
+          next_version="$major.$minor.$next_patch"
+          branch_name="backport/$next_version"
+          git fetch origin $branch_name || true
+          if git show-ref --verify --quiet refs/remotes/origin/$branch_name; then
+            git checkout -B $branch_name origin/$branch_name
+          else
+            git checkout -b $branch_name origin/stable
+          fi
+          echo "branch_name=$branch_name" >> $GITHUB_OUTPUT
+
+      - name: Cherry-pick commit
+        run: |
+          git cherry-pick ${{ steps.prinfo.outputs.merge_commit_sha }} || {
+            echo 'Cherry-pick failed, please resolve conflicts manually.'
+            exit 1
+          }
+
+      - name: Push backport branch
+        run: |
+          git push origin ${{ steps.create_or_update_backport_branch.outputs.branch_name }}:${{ steps.create_or_update_backport_branch.outputs.branch_name }} --force
+
+      - name: Create or update backport PR with cherry-picked commits
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const { pr_number } = process.env;
+            const next_patch_version = process.env.next_patch_version;
+            const branch = process.env.branch_name;
+            const cherry_commit = process.env.cherry_commit;
+            const prs = await github.rest.pulls.list({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              state: 'open',
+              head: `${context.repo.owner}:${branch}`,
+              base: 'stable'
+            });
+            const commit_url = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${cherry_commit}`;
+            const commit_item = `- [${cherry_commit.substring(0,7)}](${commit_url})`;
+            if (prs.data.length === 0) {
+              // Create new PR with initial commit in body
+              await github.rest.pulls.create({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                title: `[Backport to stable] ${next_patch_version}`,
+                head: branch,
+                base: 'stable',
+                body: `Automated backport PR stable release ${next_patch_version} with cherry-picked commits:\n${commit_item}`
+              });
+            } else {
+              // Update PR body to append new commit if not already present
+              const pr = prs.data[0];
+              let body = pr.body || '';
+              if (!body.includes(commit_item)) {
+                // Try to find the start of the list
+                const listMatch = body.match(/(cherry-picked commits:\n)([\s\S]*)/);
+                if (listMatch) {
+                  // Append to existing list
+                  const before = listMatch[1];
+                  const list = listMatch[2].trim();
+                  const newList = list + '\n' + commit_item;
+                  body = body.replace(/(cherry-picked commits:\n)([\s\S]*)/, before + newList);
+                } else {
+                  // Add new list
+                  body = body.trim() + `\n\nCherry-picked commits:\n${commit_item}`;
+                }
+                await github.rest.pulls.update({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  pull_number: pr.number,
+                  body
+                });
+              }
+            }
+        env:
+          pr_number: ${{ steps.prinfo.outputs.pr_number }}
+          next_patch_version: ${{ steps.nextver.outputs.next_patch_version }}
+          branch_name: ${{ steps.create_or_update_backport_branch.outputs.branch_name }}
+          cherry_commit: ${{ steps.prinfo.outputs.merge_commit_sha }}