Skip to content

Commit c3122f5

Browse files
committed
[feature] Add reusable backport workflow #501
Automates cherry-picking fixes to stable branches via [backport X.Y] in commit messages or /backport X.Y comments, with conflict notification. Closes #501
1 parent 24e9775 commit c3122f5

2 files changed

Lines changed: 133 additions & 0 deletions

File tree

.github/workflows/backport.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Backport fixes to stable branch
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
issue_comment:
8+
types: [created]
9+
10+
permissions:
11+
contents: write
12+
pull-requests: write
13+
14+
jobs:
15+
backport-on-push:
16+
if: github.event_name == 'push'
17+
uses: ./.github/workflows/reusable-backport.yml
18+
with:
19+
commit_sha: ${{ github.sha }}
20+
21+
backport-on-comment:
22+
if: >
23+
github.event_name == 'issue_comment' &&
24+
github.event.issue.pull_request &&
25+
github.event.issue.state == 'closed' &&
26+
contains(fromJSON('["MEMBER", "OWNER"]'), github.event.comment.author_association) &&
27+
startsWith(github.event.comment.body, '/backport')
28+
uses: ./.github/workflows/reusable-backport.yml
29+
with:
30+
pr_number: ${{ github.event.issue.number }}
31+
comment_body: ${{ github.event.comment.body }}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: Backport fixes to stable branch
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
commit_sha:
7+
required: false
8+
type: string
9+
default: ""
10+
pr_number:
11+
required: false
12+
type: number
13+
default: 0
14+
comment_body:
15+
required: false
16+
type: string
17+
default: ""
18+
19+
jobs:
20+
parse:
21+
runs-on: ubuntu-latest
22+
outputs:
23+
branches: ${{ steps.extract.outputs.branches }}
24+
sha: ${{ steps.extract.outputs.sha }}
25+
steps:
26+
- name: Extract backport targets
27+
id: extract
28+
env:
29+
GH_TOKEN: ${{ github.token }}
30+
run: |
31+
if [ -n "${{ inputs.commit_sha }}" ]; then
32+
SHA="${{ inputs.commit_sha }}"
33+
COMMIT_MSG=$(gh api repos/${{ github.repository }}/git/commits/$SHA --jq '.message')
34+
BRANCHES=$(echo "$COMMIT_MSG" | grep -oP '\[backport\s+\K[^\]]+' | tr '\n' ',' | sed 's/,$//')
35+
else
36+
SHA=$(gh pr view ${{ inputs.pr_number }} --repo ${{ github.repository }} --json mergeCommit --jq '.mergeCommit.oid')
37+
BRANCHES=$(echo "${{ inputs.comment_body }}" | grep -oP '/backport\s+\K\S+' | tr '\n' ',' | sed 's/,$//')
38+
fi
39+
40+
if [ -z "$BRANCHES" ]; then
41+
echo "branches=[]" >> $GITHUB_OUTPUT
42+
else
43+
echo "branches=$(echo "$BRANCHES" | tr ',' '\n' | jq -R . | jq -sc .)" >> $GITHUB_OUTPUT
44+
fi
45+
echo "sha=$SHA" >> $GITHUB_OUTPUT
46+
47+
backport:
48+
needs: parse
49+
if: needs.parse.outputs.branches != '[]'
50+
runs-on: ubuntu-latest
51+
strategy:
52+
fail-fast: false
53+
matrix:
54+
branch: ${{ fromJSON(needs.parse.outputs.branches) }}
55+
steps:
56+
- uses: actions/checkout@v6
57+
with:
58+
fetch-depth: 0
59+
60+
- name: Configure Git
61+
run: |
62+
git config user.name 'github-actions[bot]'
63+
git config user.email 'github-actions[bot]@users.noreply.github.com'
64+
65+
- name: Cherry-pick and create PR
66+
env:
67+
GH_TOKEN: ${{ github.token }}
68+
run: |
69+
SHA="${{ needs.parse.outputs.sha }}"
70+
BRANCH="${{ matrix.branch }}"
71+
PR_DATA=$(gh api repos/${{ github.repository }}/commits/$SHA/pulls --jq '.[0] | {number, title}')
72+
PR_NUMBER=$(echo "$PR_DATA" | jq -r '.number')
73+
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
74+
BACKPORT_BRANCH="backport/${PR_NUMBER}-to-${BRANCH}"
75+
76+
git checkout -b "$BACKPORT_BRANCH" "origin/$BRANCH"
77+
78+
if git cherry-pick -x "$SHA"; then
79+
git push origin "$BACKPORT_BRANCH"
80+
gh pr create \
81+
--base "$BRANCH" \
82+
--head "$BACKPORT_BRANCH" \
83+
--title "[backport] $PR_TITLE (to $BRANCH)" \
84+
--body "Backport of #$PR_NUMBER to \`$BRANCH\`."
85+
else
86+
git cherry-pick --abort || true
87+
{
88+
echo "❌ Cherry-pick to \`$BRANCH\` failed due to conflicts. Please backport manually:"
89+
echo ""
90+
echo "\`\`\`bash"
91+
echo "git fetch origin $BRANCH master"
92+
echo "git checkout -b backport/${PR_NUMBER}-to-${BRANCH} origin/$BRANCH"
93+
echo "git cherry-pick -x $SHA"
94+
echo "# resolve conflicts"
95+
echo "git cherry-pick --continue"
96+
echo "git push origin backport/${PR_NUMBER}-to-${BRANCH}"
97+
echo "\`\`\`"
98+
echo ""
99+
echo "Then open a PR targeting \`$BRANCH\`."
100+
} > /tmp/backport-comment.md
101+
gh pr comment "$PR_NUMBER" --body-file /tmp/backport-comment.md
102+
fi

0 commit comments

Comments
 (0)