Skip to content

Commit 14b1ff5

Browse files
committed
✨ feat: add cherry-pick workflow for pull requests
1 parent d1957ee commit 14b1ff5

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

.github/workflows/cherry-pick.yml

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
name: Cherry Pick
2+
3+
on:
4+
pull_request_target:
5+
types: [closed, labeled]
6+
7+
permissions:
8+
contents: write
9+
pull-requests: write
10+
11+
jobs:
12+
cherry-pick:
13+
if: github.event.pull_request.merged == true
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Cherry Pick
22+
env:
23+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24+
PR_NUMBER: ${{ github.event.pull_request.number }}
25+
PR_TITLE: ${{ github.event.pull_request.title }}
26+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
27+
MERGE_COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }}
28+
run: |
29+
# Function to post comment
30+
post_comment() {
31+
gh pr comment "$PR_NUMBER" --body "$1"
32+
}
33+
34+
# Configure git for the original author
35+
echo "Fetching author info for $PR_AUTHOR..."
36+
AUTHOR_INFO=$(gh api "/users/$PR_AUTHOR" --jq '{email: .email, name: .name}')
37+
AUTHOR_EMAIL=$(echo "$AUTHOR_INFO" | jq -r '.email')
38+
AUTHOR_NAME=$(echo "$AUTHOR_INFO" | jq -r '.name')
39+
40+
if [ "$AUTHOR_EMAIL" = "null" ] || [ -z "$AUTHOR_EMAIL" ]; then
41+
AUTHOR_EMAIL="${PR_AUTHOR}@users.noreply.github.com"
42+
echo "Author email not found, using default: $AUTHOR_EMAIL"
43+
fi
44+
if [ "$AUTHOR_NAME" = "null" ] || [ -z "$AUTHOR_NAME" ]; then
45+
AUTHOR_NAME="${PR_AUTHOR}"
46+
echo "Author name not found, using username: $AUTHOR_NAME"
47+
fi
48+
49+
git config user.name "$AUTHOR_NAME"
50+
git config user.email "$AUTHOR_EMAIL"
51+
52+
# Capture current SHA to return to later
53+
ORIGINAL_HEAD_SHA=$(git rev-parse HEAD)
54+
55+
# Get labels
56+
LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name')
57+
58+
if [ -z "$LABELS" ]; then
59+
echo "No labels found."
60+
exit 0
61+
fi
62+
63+
# Loop through labels
64+
echo "$LABELS" | while read -r label; do
65+
if [[ "$label" == cherry-pick:* ]]; then
66+
TARGET_BRANCH=$(echo "${label#cherry-pick:}" | xargs)
67+
68+
if [ -z "$TARGET_BRANCH" ]; then
69+
echo "Empty target branch for label '$label', skipping."
70+
continue
71+
fi
72+
73+
echo "Processing cherry-pick to $TARGET_BRANCH"
74+
75+
# Check if target branch exists on remote
76+
if ! git ls-remote --exit-code --heads origin "$TARGET_BRANCH"; then
77+
echo "Target branch $TARGET_BRANCH does not exist."
78+
post_comment "❌ Cherry-pick failed: Target branch \`$TARGET_BRANCH\` does not exist."
79+
continue
80+
fi
81+
82+
# Create a new branch for the cherry-pick
83+
NEW_BRANCH="cherry-pick/$PR_NUMBER/$TARGET_BRANCH"
84+
85+
# Clean up local branch if it exists (from previous run)
86+
if git show-ref --verify --quiet "refs/heads/$NEW_BRANCH"; then
87+
git branch -D "$NEW_BRANCH"
88+
fi
89+
90+
# Fetch the target branch and checkout a new branch from it
91+
git fetch origin "$TARGET_BRANCH"
92+
git checkout -b "$NEW_BRANCH" "origin/$TARGET_BRANCH"
93+
94+
# Cherry pick
95+
# Try standard cherry-pick first (for squash merges or single commits)
96+
if git cherry-pick "$MERGE_COMMIT_SHA"; then
97+
echo "Cherry-pick successful."
98+
else
99+
echo "Standard cherry-pick failed, trying with -m 1 (for merge commits)..."
100+
git cherry-pick --abort
101+
if git cherry-pick -m 1 "$MERGE_COMMIT_SHA"; then
102+
echo "Cherry-pick with -m 1 successful."
103+
else
104+
echo "Cherry-pick failed."
105+
git cherry-pick --abort
106+
post_comment "❌ Cherry-pick failed: Conflicts detected when cherry-picking to \`$TARGET_BRANCH\`. Please resolve manually."
107+
108+
# Cleanup
109+
git checkout "$ORIGINAL_HEAD_SHA"
110+
git branch -D "$NEW_BRANCH"
111+
continue
112+
fi
113+
fi
114+
115+
# Push
116+
# Check if remote branch already exists, if so, force push? Or maybe fail?
117+
# Force push is better to update the PR if we are re-running.
118+
git push origin "$NEW_BRANCH" --force
119+
120+
# Create PR
121+
NEW_TITLE="[$TARGET_BRANCH] $PR_TITLE"
122+
123+
# Check if PR already exists
124+
EXISTING_PR=$(gh pr list --base "$TARGET_BRANCH" --head "$NEW_BRANCH" --json url --jq '.[0].url')
125+
126+
if [ -n "$EXISTING_PR" ]; then
127+
echo "PR already exists: $EXISTING_PR"
128+
post_comment "ℹ️ Cherry-pick PR already exists: $EXISTING_PR"
129+
else
130+
CREATED_PR_URL=$(gh pr create --base "$TARGET_BRANCH" --head "$NEW_BRANCH" --title "$NEW_TITLE" --body "Cherry-pick of #$PR_NUMBER" --reviewer "$PR_AUTHOR")
131+
echo "Created PR: $CREATED_PR_URL"
132+
post_comment "✅ Cherry-pick successful! Created PR: $CREATED_PR_URL"
133+
fi
134+
135+
# Cleanup for next loop
136+
git checkout "$ORIGINAL_HEAD_SHA"
137+
git branch -D "$NEW_BRANCH"
138+
fi
139+
done

0 commit comments

Comments
 (0)