-
-
Notifications
You must be signed in to change notification settings - Fork 458
266 lines (234 loc) · 10.6 KB
/
Copy pathbackport-to-stable.yml
File metadata and controls
266 lines (234 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
name: Backport to stable
permissions:
contents: write
pull-requests: write
on:
push:
branches:
- dev
pull_request_target:
types: [labeled]
branches:
- dev
jobs:
backport:
name: Backport PRs with 'backport-to-stable' label to stable
runs-on: ubuntu-latest
if: |
(github.event_name == 'push' && github.event.commits[0].distinct == true) ||
(github.event_name == 'pull_request_target')
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0 # Needed for full git history
- name: Determine workflow trigger and context
id: trigger
uses: actions/github-script@v9
with:
script: |
// Determine if this is a push or label event
const isPushEvent = context.eventName === 'push';
const isLabelEvent = context.eventName === 'pull_request_target';
core.setOutput('is_push_event', isPushEvent);
core.setOutput('is_label_event', isLabelEvent);
// Early exit for wrong label in label events
if (isLabelEvent) {
const labelName = context.payload.label?.name;
if (labelName !== 'backport-to-stable') {
console.log(`Label '${labelName}' is not backport-to-stable, skipping`);
process.exit(0);
}
}
- name: Get merged PR info
id: prinfo
uses: actions/github-script@v9
with:
script: |
const isPushEvent = '${{ steps.trigger.outputs.is_push_event }}' === 'true';
const isLabelEvent = '${{ steps.trigger.outputs.is_label_event }}' === 'true';
let merged;
if (isPushEvent) {
// Existing logic: Find PR from commit
const pr = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'closed',
base: 'dev',
sort: 'updated',
direction: 'desc',
per_page: 10
});
merged = pr.data.find(p => p.merge_commit_sha === context.payload.head_commit.id);
if (!merged) return core.setFailed('No merged PR found for this commit.');
} else if (isLabelEvent) {
// New logic: Get PR from label event context
const pr = context.payload.pull_request;
// Verify PR is merged (exit gracefully if not)
if (!pr.merged_at) {
console.log('PR is not merged yet, skipping backport');
process.exit(0);
}
// Fetch full PR details to get merge commit SHA
const fullPR = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number
});
merged = fullPR.data;
}
core.setOutput('pr_number', merged.number);
core.setOutput('pr_title', merged.title);
core.setOutput('pr_labels', merged.labels.map(l => l.name).join(','));
core.setOutput('merge_commit_sha', merged.merge_commit_sha);
- name: Check for backport-to-stable label
id: checklabel
run: |
echo "PR labels: ${{ steps.prinfo.outputs.pr_labels }}"
if [[ "${{ steps.prinfo.outputs.pr_labels }}" == *"backport-to-stable"* ]]; then
echo "backport-to-stable label found, proceeding with backport."
echo "should_backport=true" >> $GITHUB_OUTPUT
else
echo "No backport-to-stable label found, skipping backport."
echo "should_backport=false" >> $GITHUB_OUTPUT
fi
- name: Set up Git user
if: steps.checklabel.outputs.should_backport == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Calculate next patch version
if: steps.checklabel.outputs.should_backport == 'true'
id: nextver
run: |
git fetch origin stable --tags
# Filter out beta/rc tags and get only stable versions (e.g., 2.5.5, v2.5.5)
latest_tag=$(git tag --merged origin/stable --sort=-v:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
if [[ -z "$latest_tag" ]]; then
echo "No stable tags found on stable branch" >&2
exit 1
fi
echo "Latest stable tag: $latest_tag"
# Remove 'v' prefix if present
version="$latest_tag"
if [[ "$version" =~ ^v ]]; then
version="${version#v}"
fi
# Parse version components
if [[ "$version" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
major="${BASH_REMATCH[1]}"
minor="${BASH_REMATCH[2]}"
patch="${BASH_REMATCH[3]}"
else
echo "Invalid version format: $version" >&2
exit 1
fi
next_patch=$((patch + 1))
next_version="$major.$minor.$next_patch"
echo "Current version: $version"
echo "Next version: $next_version"
echo "next_patch_version=$next_version" >> $GITHUB_OUTPUT
- name: Create or update backport branch
if: steps.checklabel.outputs.should_backport == 'true'
id: create_or_update_backport_branch
run: |
next_version="${{ steps.nextver.outputs.next_patch_version }}"
branch_name="backport/$next_version"
echo "Creating/updating branch: $branch_name"
# Check if branch already exists on remote
git fetch origin $branch_name || true
if git show-ref --verify --quiet refs/remotes/origin/$branch_name; then
echo "Branch $branch_name already exists, checking out"
git checkout -B $branch_name origin/$branch_name
else
echo "Branch $branch_name does not exist, creating from stable"
git checkout -b $branch_name origin/stable
fi
echo "branch_name=$branch_name" >> $GITHUB_OUTPUT
- name: Cherry-pick commit
if: steps.checklabel.outputs.should_backport == 'true'
run: |
# Check if commit is already in the branch to avoid redundant cherry-picks
if git log --format=%H | grep -q "^${{ steps.prinfo.outputs.merge_commit_sha }}$"; then
echo "Commit ${{ steps.prinfo.outputs.merge_commit_sha }} already exists in backport branch, skipping cherry-pick"
exit 0
fi
# Try cherry-pick with --empty=drop to handle redundant commits gracefully
if git cherry-pick --empty=drop ${{ steps.prinfo.outputs.merge_commit_sha }}; then
echo "Cherry-pick successful"
else
echo 'Cherry-pick failed, please resolve conflicts manually.'
exit 1
fi
- name: Push backport branch
if: steps.checklabel.outputs.should_backport == 'true'
run: |
git push origin ${{ steps.create_or_update_backport_branch.outputs.branch_name }}:${{ steps.create_or_update_backport_branch.outputs.branch_name }} --force
- name: Create or update backport PR with cherry-picked commits
if: steps.checklabel.outputs.should_backport == 'true'
uses: actions/github-script@v9
with:
script: |
const pr_number = process.env.pr_number;
const pr_title = process.env.pr_title;
const next_patch_version = process.env.next_patch_version;
const branch = process.env.branch_name;
const cherry_commit = process.env.cherry_commit;
console.log(`Processing backport for PR #${pr_number}: ${pr_title}`);
console.log(`Next patch version: ${next_patch_version}`);
console.log(`Branch: ${branch}`);
console.log(`Cherry-pick commit: ${cherry_commit}`);
const prs = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:${branch}`,
base: 'stable'
});
const commit_url = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${cherry_commit}`;
const commit_item = `- [${cherry_commit.substring(0,7)}](${commit_url}) - ${pr_title} (#${pr_number})`;
if (prs.data.length === 0) {
// Create new PR with initial commit in body
console.log('Creating new backport PR');
await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[Backport to stable] ${next_patch_version}`,
head: branch,
base: 'stable',
body: `Automated backport PR for stable release ${next_patch_version} with cherry-picked commits:\n\n${commit_item}`
});
} else {
// Update PR body to append new commit if not already present
console.log('Updating existing backport PR');
const pr = prs.data[0];
let body = pr.body || '';
if (!body.includes(cherry_commit.substring(0,7))) {
// Try to find the start of the list (case-insensitive)
const listMatch = body.match(/(cherry-picked commits:\n\n)([\s\S]*)/i);
if (listMatch) {
// Append to existing list
const before = listMatch[1];
const list = listMatch[2].trim();
const newList = list + '\n' + commit_item;
body = body.replace(/(cherry-picked commits:\n\n)([\s\S]*)/i, before + newList);
} else {
// Add new list
body = body.trim() + `\n\nCherry-picked commits:\n\n${commit_item}`;
}
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
body
});
} else {
console.log('Commit already exists in PR body, skipping update');
}
}
env:
pr_number: ${{ steps.prinfo.outputs.pr_number }}
pr_title: ${{ steps.prinfo.outputs.pr_title }}
next_patch_version: ${{ steps.nextver.outputs.next_patch_version }}
branch_name: ${{ steps.create_or_update_backport_branch.outputs.branch_name }}
cherry_commit: ${{ steps.prinfo.outputs.merge_commit_sha }}