Adds cp upgrade #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Cherry Pick | ||
| on: | ||
| issue_comment: | ||
| types: [created] | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| jobs: | ||
| cherry-pick: | ||
| runs-on: ubuntu-latest | ||
| if: | | ||
| github.event_name == 'issue_comment' && | ||
| github.event.issue.pull_request != null && | ||
| (startsWith(github.event.comment.body, '/cherry-pick') || contains(github.event.comment.body, '/cherry-pick')) | ||
| steps: | ||
| - name: Check job condition | ||
| run: | | ||
| echo "Event name: ${{ github.event_name }}" | ||
| echo "Comment body: ${{ github.event.comment.body }}" | ||
| echo "Is PR: ${{ github.event.issue.pull_request != null }}" | ||
| echo "Comment starts with /cherry-pick: ${{ startsWith(github.event.comment.body, '/cherry-pick') }}" | ||
| - name: Check out repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Set up Git | ||
| run: | | ||
| git config --global user.name "github-actions[bot]" | ||
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | ||
| - name: Debug event context | ||
| if: github.event_name == 'issue_comment' | ||
| run: | | ||
| echo "Event type: ${{ github.event_name }}" | ||
| echo "Comment body: ${{ github.event.comment.body }}" | ||
| echo "Issue number: ${{ github.event.issue.number }}" | ||
| echo "Is PR: ${{ github.event.issue.pull_request != null }}" | ||
| echo "PR URL: ${{ github.event.issue.pull_request.url }}" | ||
| - name: Get PR information | ||
| id: pr_info | ||
| continue-on-error: true | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
| script: | | ||
| const issueNumber = context.payload.issue.number; | ||
| try { | ||
| const { data: pr } = await github.rest.pulls.get({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: issueNumber, | ||
| }); | ||
| core.setOutput('pr_number', issueNumber.toString()); | ||
| core.setOutput('base_branch', pr.base.ref); | ||
| core.setOutput('head_branch', pr.head.ref); | ||
| core.setOutput('head_sha', pr.head.sha); | ||
| core.setOutput('title', pr.title || ''); | ||
| core.setOutput('body', pr.body || ''); | ||
| } catch (error) { | ||
| core.setOutput('pr_number', issueNumber.toString()); | ||
| core.setFailed(`Failed to get PR information: ${error.message}`); | ||
| throw error; | ||
| } | ||
| - name: Extract target branch | ||
| id: extract | ||
| run: | | ||
| COMMENT_BODY="${{ github.event.comment.body }}" | ||
| TARGET_BRANCH=$(echo "$COMMENT_BODY" | sed -n 's|.*/cherry-pick[[:space:]]*\([^[:space:]]*\).*|\1|p') | ||
| if [ -z "$TARGET_BRANCH" ]; then | ||
| echo "❌ Error: No target branch specified. Usage: /cherry-pick <branch-name>" | ||
| exit 1 | ||
| fi | ||
| echo "branch=$TARGET_BRANCH" >> $GITHUB_OUTPUT | ||
| echo "Target branch: $TARGET_BRANCH" | ||
| - name: Validate target branch exists | ||
| id: validate_branch | ||
| run: | | ||
| TARGET_BRANCH="${{ steps.extract.outputs.branch }}" | ||
| # Remove any remote prefix if present | ||
| BRANCH_NAME=$(echo "$TARGET_BRANCH" | sed 's|^[^/]*/||') | ||
| # Check if branch exists in origin | ||
| if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then | ||
| echo "✅ Found branch: origin/$BRANCH_NAME" | ||
| echo "remote=origin" >> $GITHUB_OUTPUT | ||
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | ||
| # Check if downstream remote exists and has the branch | ||
| elif git remote | grep -q "^downstream$"; then | ||
| if git ls-remote --heads downstream "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then | ||
| echo "✅ Found branch: downstream/$BRANCH_NAME" | ||
| echo "remote=downstream" >> $GITHUB_OUTPUT | ||
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "❌ Error: Branch '$BRANCH_NAME' does not exist in downstream remote" | ||
| exit 1 | ||
| fi | ||
| else | ||
| echo "❌ Error: Branch '$BRANCH_NAME' does not exist in origin" | ||
| exit 1 | ||
| fi | ||
| - name: Create cherry-pick branch | ||
| id: create_branch | ||
| run: | | ||
| TARGET_BRANCH="${{ steps.validate_branch.outputs.branch_name }}" | ||
| REMOTE="${{ steps.validate_branch.outputs.remote }}" | ||
| PR_NUMBER="${{ steps.pr_info.outputs.pr_number }}" | ||
| # Create branch name: cherry-pick-<pr-number>-to-<target-branch> | ||
| # Replace any slashes with dashes for branch name | ||
| SAFE_BRANCH_NAME=$(echo "$TARGET_BRANCH" | tr '/' '-') | ||
| CHERRY_PICK_BRANCH="cherry-pick-${PR_NUMBER}-to-${SAFE_BRANCH_NAME}" | ||
| echo "branch_name=$CHERRY_PICK_BRANCH" >> $GITHUB_OUTPUT | ||
| # Fetch and checkout target branch | ||
| git fetch "$REMOTE" "$TARGET_BRANCH" | ||
| git checkout -b "$CHERRY_PICK_BRANCH" "${REMOTE}/${TARGET_BRANCH}" | ||
| echo "✅ Created branch: $CHERRY_PICK_BRANCH from ${REMOTE}/${TARGET_BRANCH}" | ||
| - name: Cherry-pick commits | ||
| id: cherry_pick | ||
| run: | | ||
| PR_NUMBER="${{ steps.pr_info.outputs.pr_number }}" | ||
| BASE_BRANCH="${{ steps.pr_info.outputs.base_branch }}" | ||
| # Fetch the PR branch | ||
| git fetch origin "pull/${PR_NUMBER}/head:pr-${PR_NUMBER}" | ||
| # Get all commits from the PR (compare base branch to PR branch) | ||
| COMMITS=$(git log --oneline --reverse origin/${BASE_BRANCH}..pr-${PR_NUMBER} | awk '{print $1}') | ||
| if [ -z "$COMMITS" ]; then | ||
| echo "❌ Error: No commits found to cherry-pick" | ||
| exit 1 | ||
| fi | ||
| echo "Found commits to cherry-pick:" | ||
| echo "$COMMITS" | ||
| # Cherry-pick each commit | ||
| CHERRY_PICKED_COMMITS="" | ||
| for COMMIT in $COMMITS; do | ||
| echo "Cherry-picking commit: $COMMIT" | ||
| if git cherry-pick "$COMMIT"; then | ||
| CHERRY_PICKED_COMMITS="$CHERRY_PICKED_COMMITS $COMMIT" | ||
| echo "✅ Successfully cherry-picked $COMMIT" | ||
| else | ||
| echo "❌ Error: Failed to cherry-pick commit $COMMIT" | ||
| git cherry-pick --abort | ||
| exit 1 | ||
| fi | ||
| done | ||
| echo "✅ Successfully cherry-picked all commits" | ||
| <<<<<<< Updated upstream | ||
| ======= | ||
| echo "CHERRY_PICK_SUCCESS=true" >> $GITHUB_ENV | ||
| - name: Post error comment on failure | ||
| if: always() && (failure() || env.CHERRY_PICK_ERROR != '' || env.EXTRACT_ERROR != '' || env.VALIDATE_ERROR != '' || steps.pr_info.outcome == 'failure') | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
| script: | | ||
| const prNumber = parseInt('${{ steps.pr_info.outputs.pr_number }}') || context.payload.issue.number; | ||
| const prInfoFailed = '${{ steps.pr_info.outcome }}' === 'failure'; | ||
| const targetBranch = '${{ steps.validate_branch.outputs.branch_name }}' || '${{ steps.extract.outputs.branch }}' || 'unknown'; | ||
| const extractError = '${{ env.EXTRACT_ERROR }}'; | ||
| const validateError = '${{ env.VALIDATE_ERROR }}'; | ||
| const cherryPickError = '${{ env.CHERRY_PICK_ERROR }}'; | ||
| const failedCommit = '${{ env.FAILED_COMMIT }}' || 'unknown'; | ||
| const failedCommitMsg = '${{ env.FAILED_COMMIT_MSG }}' || ''; | ||
| const conflictFiles = '${{ env.CONFLICT_FILES }}'; | ||
| const modifyDelete = '${{ env.MODIFY_DELETE }}'; | ||
| const contentConflicts = '${{ env.CONTENT_CONFLICTS }}'; | ||
| const errorDetails = `${{ env.CHERRY_PICK_ERROR_DETAILS }}` || cherryPickError || validateError || extractError; | ||
| let errorMessage = `❌ **Cherry-pick failed**\n\n`; | ||
| // Handle PR info failure | ||
| if (prInfoFailed) { | ||
| errorMessage += `**Error:** Failed to retrieve PR information.\n\n`; | ||
| errorMessage += `This might happen if:\n`; | ||
| errorMessage += `- The comment was made on an issue instead of a pull request\n`; | ||
| errorMessage += `- The PR number could not be determined\n`; | ||
| errorMessage += `- There was an API error\n\n`; | ||
| errorMessage += `Please ensure you're commenting on a pull request, not an issue.\n`; | ||
| } | ||
| // Handle different error types | ||
| else if (extractError) { | ||
| errorMessage += `**Error:** ${extractError}\n\n`; | ||
| errorMessage += `Please specify a target branch: \`/cherry-pick <branch-name>\`\n`; | ||
| } else if (validateError) { | ||
| errorMessage += `**Error:** ${validateError}\n\n`; | ||
| errorMessage += `Please verify that the branch exists and try again.\n`; | ||
| } else if (cherryPickError) { | ||
| errorMessage += `**Target branch:** \`${targetBranch}\`\n`; | ||
| errorMessage += `**Failed commit:** \`${failedCommit}\`\n`; | ||
| if (failedCommitMsg) { | ||
| errorMessage += `**Commit message:** ${failedCommitMsg}\n\n`; | ||
| } | ||
| // Format conflicts | ||
| if (modifyDelete || contentConflicts || conflictFiles) { | ||
| errorMessage += `## Merge Conflicts Detected\n\n`; | ||
| if (contentConflicts) { | ||
| const files = contentConflicts.split(',').filter(f => f); | ||
| if (files.length > 0) { | ||
| errorMessage += `### Content conflicts (files modified in both branches):\n`; | ||
| files.forEach(file => { | ||
| errorMessage += `- \`${file}\`\n`; | ||
| }); | ||
| errorMessage += `\n`; | ||
| } | ||
| } | ||
| if (modifyDelete) { | ||
| const files = modifyDelete.split(',').filter(f => f); | ||
| if (files.length > 0) { | ||
| errorMessage += `### Modify/delete conflicts (files deleted in target branch but modified in PR):\n`; | ||
| files.forEach(file => { | ||
| errorMessage += `- \`${file}\`\n`; | ||
| }); | ||
| errorMessage += `\n`; | ||
| } | ||
| } | ||
| errorMessage += `## Next Steps\n\n`; | ||
| errorMessage += `This cherry-pick requires manual resolution. You can:\n\n`; | ||
| errorMessage += `1. Create a manual cherry-pick branch from \`${targetBranch}\`\n`; | ||
| errorMessage += `2. Resolve the conflicts manually\n`; | ||
| errorMessage += `3. Create a PR with the resolved changes\n\n`; | ||
| errorMessage += `Or use the workflow run logs for detailed conflict information.\n`; | ||
| } else if (errorDetails) { | ||
| errorMessage += `\n**Error details:**\n\`\`\`\n${errorDetails.substring(0, 2000)}\n\`\`\`\n`; | ||
| } | ||
| } else { | ||
| errorMessage += `**Error:** An unexpected error occurred during cherry-pick.\n\n`; | ||
| if (errorDetails) { | ||
| errorMessage += `**Error details:**\n\`\`\`\n${errorDetails.substring(0, 2000)}\n\`\`\`\n`; | ||
| } | ||
| } | ||
| // Add link to workflow run | ||
| const workflowRunUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | ||
| errorMessage += `\n---\n`; | ||
| errorMessage += `📋 [View workflow run](${workflowRunUrl}) for full details`; | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: prNumber, | ||
| body: errorMessage, | ||
| }); | ||
| // Fail the workflow | ||
| if (prInfoFailed || cherryPickError || validateError || extractError) { | ||
| core.setFailed('Cherry-pick failed'); | ||
| } | ||
| >>>>>>> Stashed changes | ||
| - name: Push cherry-pick branch | ||
| run: | | ||
| CHERRY_PICK_BRANCH="${{ steps.create_branch.outputs.branch_name }}" | ||
| git push origin "$CHERRY_PICK_BRANCH" | ||
| - name: Create pull request | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const targetBranch = '${{ steps.validate_branch.outputs.branch_name }}'; | ||
| const cherryPickBranch = '${{ steps.create_branch.outputs.branch_name }}'; | ||
| const prNumber = parseInt('${{ steps.pr_info.outputs.pr_number }}'); | ||
| // Get original PR details | ||
| const { data: originalPR } = await github.rest.pulls.get({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: prNumber, | ||
| }); | ||
| const title = `Cherry-pick #${prNumber} to ${targetBranch}: ${originalPR.title}`; | ||
| const body = `This PR cherry-picks #${prNumber} to \`${targetBranch}\`.\n\n` + | ||
| `**Original PR:** #${prNumber}\n` + | ||
| `**Target branch:** \`${targetBranch}\`\n\n` + | ||
| `---\n` + | ||
| `_This PR was created automatically by the cherry-pick workflow._`; | ||
| const { data: pr } = await github.rest.pulls.create({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| title: title, | ||
| body: body, | ||
| head: cherryPickBranch, | ||
| base: targetBranch, | ||
| }); | ||
| // Add a comment to the original PR | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: prNumber, | ||
| body: `✅ Cherry-pick PR created: #${pr.number}\n\n` + | ||
| `**Target branch:** \`${targetBranch}\`\n` + | ||
| `**Cherry-pick PR:** #${pr.number}`, | ||
| }); | ||
| console.log(`Created PR #${pr.number}`); | ||