Commit 85e89e583d for woocommerce

commit 85e89e583d28b4c88463ebb28db8e02578870744
Author: Alba Rincón <albarin@users.noreply.github.com>
Date:   Mon Jan 26 09:14:36 2026 +0100

    Prevent the cherry-pick to trunk workflow to fail due to duplicate branches (#62578)

    * Prevent the cherry-pick workflow to create duplicate branches

    * Delete branch before creating PR

    * Revert changes

    * Skip if branch exists

    * Skip cherry-pick without failing when branch already exists

    * Handle skipped

    * Detect PR status and update the message accordingly

    * Update messages

    * Fix error

    * Fix interpolation

    * Removed unused output

    * Remove unused output

    * set pr to empty

    * Reopen closed pr, fix order and skip cherry pick if branch exists

    * Fix permission

    * Fix core.info error

    * Refactor verify branches

    * Simplify the logic for already existing PRs

    * Improve comment

diff --git a/.github/workflows/cherry-pick-to-trunk.yml b/.github/workflows/cherry-pick-to-trunk.yml
index 2ded09b280..605eba14a4 100644
--- a/.github/workflows/cherry-pick-to-trunk.yml
+++ b/.github/workflows/cherry-pick-to-trunk.yml
@@ -187,6 +187,28 @@ jobs:
             :info: *Cherry pick <${{ env.CHERRY_PICK_PR_URL }}|created> (${{ env.BASE_BRANCH }} → `trunk`)* Please ensure <${{ env.CHERRY_PICK_PR_URL }}|this cherry pick> is merged before releasing. Source PR: _<${{ env.SOURCE_PR_URL }}|#${{ env.SOURCE_PR_NUMBER }} - ${{ env.SOURCE_PR_TITLE }}>_.
             ${{ env.ADD_CP_TO_FROZEN_NAG == 'true' && format('\n\nNOTE: This PR might need to be cherry-picked to the frozen release ({0}).', env.NEXT_BRANCH) || '' }}

+  handle-skipped:
+    needs: [prepare, cherry-pick, frozen-release-check]
+    runs-on: ubuntu-latest
+    if: ${{ always() && needs.cherry-pick.outputs.status == 'skipped' }}
+    steps:
+      - name: Comment on original PR about skip
+        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1
+        env:
+          ERROR_MESSAGE: ${{ needs.cherry-pick.outputs.error_message }}
+        with:
+          github-token: ${{ secrets.WC_BOT_PR_CREATE_TOKEN || secrets.GITHUB_TOKEN }}
+          script: |
+            let body = '⚠️ **Cherry-pick to `trunk` was skipped.**\n\n' +
+              '**Reason:** ' + process.env.ERROR_MESSAGE + '\n\n';
+
+            await github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: parseInt('${{ needs.prepare.outputs.pr_number }}'),
+              body
+            });
+
   handle-failures:
     needs: [prepare, cherry-pick, frozen-release-check]
     runs-on: ubuntu-latest
diff --git a/.github/workflows/shared-cherry-pick.yml b/.github/workflows/shared-cherry-pick.yml
index 33b43553e1..9802b570b6 100644
--- a/.github/workflows/shared-cherry-pick.yml
+++ b/.github/workflows/shared-cherry-pick.yml
@@ -47,8 +47,8 @@ jobs:
     name: Verify requirements
     runs-on: ubuntu-latest
     permissions:
-      contents: read
-      pull-requests: read
+      contents: write
+      pull-requests: write
     outputs:
       status: ${{ steps.aggregate-status.outputs.status }}
       error_message: ${{ steps.aggregate-status.outputs.error_message }}
@@ -110,7 +110,7 @@ jobs:
             const cherryPickBranch = `cherry-pick-PR${prNumber}-to-${targetBranch}`;

             try {
-              // Check target branch exists
+              // 1. Verify target branch exists
               await github.rest.repos.getBranch({
                 owner: context.repo.owner,
                 repo: context.repo.repo,
@@ -118,31 +118,72 @@ jobs:
               });
               core.info(`Target branch '${targetBranch}' exists`);

-              // Check cherry-pick branch doesn't exist
+              // 2. Check if cherry-pick branch exists
+              let branchExists = false;
               try {
                 await github.rest.repos.getBranch({
                   owner: context.repo.owner,
                   repo: context.repo.repo,
                   branch: cherryPickBranch
                 });
-                core.setOutput('error_message', `Branch '${cherryPickBranch}' already exists. Please delete it first.`);
-                core.setFailed(`Branch '${cherryPickBranch}' already exists.`);
-                return;
+                branchExists = true;
+                core.info(`Branch '${cherryPickBranch}' exists`);
               } catch (branchError) {
-                if (branchError.status !== 404) {
-                  core.setOutput('error_message', `Error checking cherry-pick branch: ${branchError.message}`);
-                  core.setFailed(`Error checking cherry-pick branch: ${branchError.message}`);
-                  return;
-                }
+                if (branchError.status !== 404) throw branchError;
+                core.info(`Branch '${cherryPickBranch}' does not exist (will be created)`);
+              }
+
+              // 3. Check if a PR already exists for this cherry-pick
+              const { data: prs } = await github.rest.pulls.list({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                head: `${context.repo.owner}:${cherryPickBranch}`,
+                state: 'all'
+              });
+
+              if (prs.length === 0) {
+                // No existing PR - proceed with cherry-pick
+                core.info(`No existing PR found - ready to create cherry-pick`);
+                core.setOutput('cherry_pick_branch', cherryPickBranch);
+                return;
+              }
+
+              // 4. Handle existing PR scenarios
+              const openPr = prs.find(pr => pr.state === 'open');
+              const mergedPr = prs.find(pr => pr.merged_at);
+
+              // Case 1: There's an open PR
+              if (openPr) {
+                core.info(`Found open PR #${openPr.number}`);
+                core.setOutput('status', 'skipped');
+                core.setOutput('error_message', `An open cherry-pick PR already exists: #${openPr.number}`);
+                return;
               }

+              // Case 2: A PR was already merged
+              if (mergedPr) {
+                core.info(`Found merged PR #${mergedPr.number}`);
+                core.setOutput('status', 'skipped');
+                core.setOutput('error_message', `A cherry-pick PR was already merged: #${mergedPr.number}`);
+                return;
+              }
+
+              // Case 3: Only closed (not merged) PRs exist, we delete branch and create fresh PR
+              if (branchExists) {
+                await github.rest.git.deleteRef({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  ref: `heads/${cherryPickBranch}`
+                  });
+                core.info(`Found only closed PRs. Deleted branch '${cherryPickBranch}'.`);
+              }
+
               core.setOutput('cherry_pick_branch', cherryPickBranch);
-              core.info(`Cherry-pick branch '${cherryPickBranch}' is available`);

             } catch (error) {
               const errorMsg = error.status === 404
                 ? `Target branch '${targetBranch}' does not exist`
-                : `Error checking target branch: ${error.message}`;
+                : `Error during verification: ${error.message}`;

               core.setOutput('error_message', errorMsg);
               core.setFailed(errorMsg);
@@ -152,7 +193,10 @@ jobs:
         id: aggregate-status
         if: always()
         run: |
-          if [[ "${{ steps.check-pr.outcome }}" == "failure" ]]; then
+          if [[ "${{ steps.verify-branches.outputs.status }}" == "skipped" ]]; then
+            echo "status=skipped" >> $GITHUB_OUTPUT
+            echo "error_message=${{ steps.verify-branches.outputs.error_message }}" >> $GITHUB_OUTPUT
+          elif [[ "${{ steps.check-pr.outcome }}" == "failure" ]]; then
             echo "status=failed" >> $GITHUB_OUTPUT
             echo "error_message=${{ steps.check-pr.outputs.error_message }}" >> $GITHUB_OUTPUT
           elif [[ "${{ steps.verify-branches.outcome }}" == "failure" ]]; then
@@ -192,6 +236,12 @@ jobs:
           PR_NUMBER="${{ inputs.pr_number }}"
           TARGET_BRANCH="${{ inputs.target_branch }}"

+          # Check if branch already exists
+          if git ls-remote --heads origin "$CHERRY_PICK_BRANCH" | grep -q "$CHERRY_PICK_BRANCH"; then
+            echo "Branch $CHERRY_PICK_BRANCH already exists remotely. Skipping cherry-pick."
+            exit 0
+          fi
+
           # Create cherry-pick branch
           git checkout -b "$CHERRY_PICK_BRANCH"
           git fetch origin "$MERGE_COMMIT_SHA"
@@ -401,7 +451,6 @@ jobs:
             echo "cherry_pick_pr_number=${{ steps.create-pr.outputs.pr_number }}" >> $GITHUB_OUTPUT
           fi

-
   aggregate-overall-status:
     name: Aggregate final status
     runs-on: ubuntu-latest
@@ -418,7 +467,11 @@ jobs:
           verify_status="${{ needs.verify.outputs.status }}"
           cherry_status="${{ needs.cherry-pick.outputs.status }}"

-          if [[ "$verify_status" != "success" ]]; then
+          if [[ "$verify_status" == "skipped" ]]; then
+            echo "status=skipped" >> $GITHUB_OUTPUT
+            echo "error_message=${{ needs.verify.outputs.error_message }}" >> $GITHUB_OUTPUT
+            echo "cherry_pick_pr_number=" >> $GITHUB_OUTPUT
+          elif [[ "$verify_status" != "success" ]]; then
             echo "status=failed" >> $GITHUB_OUTPUT
             echo "error_message=${{ needs.verify.outputs.error_message }}" >> $GITHUB_OUTPUT
             echo "cherry_pick_pr_number=" >> $GITHUB_OUTPUT