Skip to content

fix: sync lockfile to match bumped package.json (#68) #32

fix: sync lockfile to match bumped package.json (#68)

fix: sync lockfile to match bumped package.json (#68) #32

name: Cherry-pick to main
on:
pull_request:
types: [closed]
permissions:
contents: write
pull-requests: write
issues: write
concurrency:
group: enclave-cherry-pick-${{ github.event.pull_request.number }}
cancel-in-progress: false
jobs:
cherry-pick:
# Only run when:
# - PR was merged (not just closed)
# - PR is not from a fork (forked PRs have read-only GITHUB_TOKEN)
# - Target branch is a release branch (not main)
if: >
github.event.pull_request.merged == true &&
github.event.pull_request.head.repo.fork == false &&
github.event.pull_request.base.ref != github.event.repository.default_branch &&
(startsWith(github.event.pull_request.base.ref, 'release/') ||
startsWith(github.event.pull_request.base.ref, 'next/'))
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure git user
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Prepare cherry-pick branch
id: prepare
shell: bash
env:
ORIGINAL_BRANCH: ${{ github.event.pull_request.head.ref }}
RELEASE_BRANCH: ${{ github.event.pull_request.base.ref }}
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
set -euo pipefail
# Create cherry-pick branch name
# Sanitize branch name (remove invalid characters, truncate if needed)
CHERRY_BRANCH="${ORIGINAL_BRANCH}-cherry-pick-main"
CHERRY_BRANCH=$(echo "$CHERRY_BRANCH" | sed 's/[^a-zA-Z0-9._/-]/-/g' | head -c 100)
# Validate branch name is valid for git
if ! git check-ref-format --branch "$CHERRY_BRANCH" >/dev/null 2>&1; then
echo "::error::Invalid branch name after sanitization: $CHERRY_BRANCH"
exit 1
fi
echo "cherry_branch=$CHERRY_BRANCH" >> "$GITHUB_OUTPUT"
echo "Cherry-pick branch: $CHERRY_BRANCH"
# Get default branch
DEFAULT_BRANCH="${{ github.event.repository.default_branch }}"
# Checkout default branch (main)
git fetch origin "$DEFAULT_BRANCH"
git checkout "$DEFAULT_BRANCH"
git pull origin "$DEFAULT_BRANCH"
# Create the cherry-pick branch (delete if already exists)
if git show-ref --verify --quiet "refs/heads/$CHERRY_BRANCH"; then
echo "Branch $CHERRY_BRANCH already exists locally, deleting..."
git branch -D "$CHERRY_BRANCH"
fi
if git ls-remote --heads origin "$CHERRY_BRANCH" | grep -q .; then
echo "Branch $CHERRY_BRANCH already exists on remote, deleting..."
git push origin --delete "$CHERRY_BRANCH" || true
fi
git checkout -b "$CHERRY_BRANCH"
# Attempt cherry-pick
echo "Attempting cherry-pick of $MERGE_SHA..."
# Detect if merge commit (has multiple parents)
PARENT_COUNT=$(git rev-list --parents -n1 "$MERGE_SHA" | wc -w)
PARENT_COUNT=$((PARENT_COUNT - 1)) # Subtract the commit itself
CHERRY_PICK_ARGS="--no-commit"
if [ "$PARENT_COUNT" -gt 1 ]; then
echo "Detected merge commit with $PARENT_COUNT parents, using -m 1"
CHERRY_PICK_ARGS="-m 1 --no-commit"
fi
if git cherry-pick $CHERRY_PICK_ARGS "$MERGE_SHA"; then
echo "conflict=false" >> "$GITHUB_OUTPUT"
# Create commit with proper attribution
git commit -m "$(cat <<EOF
Cherry-pick: $PR_TITLE
Cherry-picked from #$PR_NUMBER (merged to $RELEASE_BRANCH)
Original commit: $MERGE_SHA
Co-Authored-By: ${{ github.event.pull_request.user.login }} <${{ github.event.pull_request.user.id }}+${{ github.event.pull_request.user.login }}@users.noreply.github.com>
EOF
)"
echo "Cherry-pick successful"
else
echo "conflict=true" >> "$GITHUB_OUTPUT"
git cherry-pick --abort || true
echo "Cherry-pick failed due to conflicts"
fi
- name: Ensure labels exist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh label create "cherry-pick" --color "0E8A16" --description "Cherry-picked changes" 2>/dev/null || true
gh label create "auto-cherry-pick" --color "1D76DB" --description "Auto cherry-pick" 2>/dev/null || true
gh label create "conflict" --color "D93F0B" --description "Has conflicts" 2>/dev/null || true
gh label create "needs-attention" --color "FBCA04" --description "Needs attention" 2>/dev/null || true
- name: Push branch and create PR
if: steps.prepare.outputs.conflict == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CHERRY_BRANCH: ${{ steps.prepare.outputs.cherry_branch }}
RELEASE_BRANCH: ${{ github.event.pull_request.base.ref }}
ORIGINAL_PR: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
# Push the cherry-pick branch
git push origin "$CHERRY_BRANCH"
# Create the PR
PR_URL=$(gh pr create \
--base "$DEFAULT_BRANCH" \
--head "$CHERRY_BRANCH" \
--title "Cherry-pick: $PR_TITLE" \
--label "cherry-pick" \
--label "auto-cherry-pick" \
--body "$(cat <<EOF
## Cherry-pick to main
This PR cherry-picks the changes from #$ORIGINAL_PR which was merged to \`$RELEASE_BRANCH\`.
### Original PR
- **PR:** #$ORIGINAL_PR
- **Title:** $PR_TITLE
- **Author:** @$PR_AUTHOR
- **Merged to:** \`$RELEASE_BRANCH\`
### Action Required
Please review this cherry-pick to ensure it applies correctly to \`$DEFAULT_BRANCH\`.
If the changes are NOT needed in \`$DEFAULT_BRANCH\`, close this PR.
---
_This PR was automatically created by the cherry-pick workflow._
EOF
)")
echo "Created cherry-pick PR: $PR_URL"
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
- name: Comment on original PR (success)
if: steps.prepare.outputs.conflict == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORIGINAL_PR: ${{ github.event.pull_request.number }}
CHERRY_BRANCH: ${{ steps.prepare.outputs.cherry_branch }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
gh pr comment "$ORIGINAL_PR" --body "$(cat <<EOF
## Cherry-pick Created
A cherry-pick PR to \`$DEFAULT_BRANCH\` has been automatically created.
**Please review and merge if this change should also be in \`$DEFAULT_BRANCH\`.**
If the cherry-pick is not needed, close the PR.
EOF
)"
- name: Create issue for conflict
id: create_issue
if: steps.prepare.outputs.conflict == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_BRANCH: ${{ github.event.pull_request.base.ref }}
ORIGINAL_PR: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
# Create issue without assignee (assignee may fail if user can't be assigned)
ISSUE_URL=$(gh issue create \
--title "Manual cherry-pick needed: $PR_TITLE" \
--label "cherry-pick" \
--label "conflict" \
--label "needs-attention" \
--body "$(cat <<EOF
## Manual Cherry-pick Required
The automatic cherry-pick of #$ORIGINAL_PR to \`$DEFAULT_BRANCH\` failed due to merge conflicts.
### Original PR
- **PR:** #$ORIGINAL_PR
- **Title:** $PR_TITLE
- **Author:** @$PR_AUTHOR
- **Merged to:** \`$RELEASE_BRANCH\`
- **Commit:** \`$MERGE_SHA\`
### Manual Cherry-pick Instructions
\`\`\`bash
# Checkout main branch
git checkout $DEFAULT_BRANCH
git pull origin $DEFAULT_BRANCH
# Create a new branch for the cherry-pick
git checkout -b cherry-pick-pr-$ORIGINAL_PR
# Cherry-pick the commit (use -m 1 for merge commits)
git cherry-pick $MERGE_SHA
# OR for merge commits:
# git cherry-pick -m 1 $MERGE_SHA
# Resolve conflicts manually, then:
git add .
git cherry-pick --continue
# Push and create PR
git push origin cherry-pick-pr-$ORIGINAL_PR
gh pr create --base $DEFAULT_BRANCH --title "Cherry-pick: $PR_TITLE"
\`\`\`
### Decision Required
- [ ] Cherry-pick is needed - resolve conflicts and apply
- [ ] Cherry-pick is NOT needed - close this issue
---
_This issue was automatically created by the cherry-pick workflow._
EOF
)")
echo "Created issue for manual cherry-pick: $ISSUE_URL"
echo "issue_url=$ISSUE_URL" >> "$GITHUB_OUTPUT"
- name: Attempt to assign issue
if: steps.prepare.outputs.conflict == 'true' && steps.create_issue.outputs.issue_url != ''
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
ISSUE_URL: ${{ steps.create_issue.outputs.issue_url }}
run: |
# Extract issue number from URL
ISSUE_NUMBER=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$')
gh issue edit "$ISSUE_NUMBER" --add-assignee "$PR_AUTHOR" || echo "Could not assign to $PR_AUTHOR, skipping assignment"
- name: Comment on original PR (conflict)
if: steps.prepare.outputs.conflict == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORIGINAL_PR: ${{ github.event.pull_request.number }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
gh pr comment "$ORIGINAL_PR" --body "$(cat <<EOF
## Cherry-pick Conflict
Automatic cherry-pick to \`$DEFAULT_BRANCH\` failed due to merge conflicts.
An issue has been created with manual instructions. Please resolve if this change should also be in \`$DEFAULT_BRANCH\`.
EOF
)"