@@ -29,31 +29,7 @@ const { data: pullRequest } = await github.rest.pulls.get({
2929
3030const prTitle = pullRequest . title ;
3131const prAuthor = pullRequest . user . login ;
32-
33- // Validate PR is merged
34- if ( ! pullRequest . merged ) {
35- // If triggered by a comment on an unmerged PR, acknowledge and exit gracefully
36- if ( context . eventName === 'issue_comment' ) {
37- core . info ( 'PR is not merged yet. Acknowledging /cherry-pick command and will backport after merge.' ) ;
38- // Add a reaction to the comment
39- await github . rest . reactions . createForIssueComment ( {
40- owner : context . repo . owner ,
41- repo : context . repo . repo ,
42- comment_id : context . payload . comment . id ,
43- content : 'eyes'
44- } ) ;
45- // Add a comment explaining what will happen
46- await github . rest . issues . createComment ( {
47- owner : context . repo . owner ,
48- repo : context . repo . repo ,
49- issue_number : prNumber ,
50- body : `👀 Backport request acknowledged for: ${ branches . map ( b => `\`${ b } \`` ) . join ( ', ' ) } \n\nThe backport PR(s) will be automatically created when this PR is merged.`
51- } ) ;
52- return [ ] ;
53- }
54- core . setFailed ( 'PR is not merged yet. Only merged PRs can be backported.' ) ;
55- return ;
56- }
32+ const isMerged = pullRequest . merged ;
5733
5834// Get all commits from the PR
5935const { data : commits } = await github . rest . pulls . listCommits ( {
@@ -63,8 +39,8 @@ const { data: commits } = await github.rest.pulls.listCommits({
6339} ) ;
6440
6541if ( commits . length === 0 ) {
66- core . setFailed ( 'No commits found in PR. This should not happen for merged PRs. ' ) ;
67- return ;
42+ core . warning ( 'No commits found in PR - skipping backport ' ) ;
43+ return [ ] ;
6844}
6945
7046core . info ( `Backporting PR #${ prNumber } : "${ prTitle } "` ) ;
@@ -83,10 +59,10 @@ for (const targetBranch of branches) {
8359 core . info ( `========================================` ) ;
8460 const backportBranch = `backport-${ prNumber } -to-${ targetBranch } ` ;
8561 try {
86- // Create backport branch from target release branch
87- core . info ( `Creating branch ${ backportBranch } from ${ targetBranch } ` ) ;
62+ // Create/reset backport branch from target release branch
63+ core . info ( `Creating/resetting branch ${ backportBranch } from ${ targetBranch } ` ) ;
8864 execSync ( `git fetch origin ${ targetBranch } :${ targetBranch } ` , { stdio : 'inherit' } ) ;
89- execSync ( `git checkout ${ backportBranch } || git checkout -b ${ backportBranch } ${ targetBranch } ` , { stdio : 'inherit' } ) ;
65+ execSync ( `git checkout -B ${ backportBranch } ${ targetBranch } ` , { stdio : 'inherit' } ) ;
9066 // Cherry-pick each commit from the PR
9167 let hasConflicts = false ;
9268 for ( let i = 0 ; i < commits . length ; i ++ ) {
@@ -113,24 +89,44 @@ for (const targetBranch of branches) {
11389 // If continue fails, make a simple commit
11490 execSync ( `git commit --no-edit --allow-empty-message || git commit -m "Cherry-pick ${ commitSha } (with conflicts)"` , { stdio : 'inherit' } ) ;
11591 }
92+ } else if ( error . message && error . message . includes ( 'previous cherry-pick is now empty' ) ) {
93+ // Handle empty commits (changes already exist in target branch)
94+ core . info ( `Commit ${ commitSha . substring ( 0 , 7 ) } is empty (changes already in target branch), skipping` ) ;
95+ execSync ( 'git cherry-pick --skip' , { stdio : 'inherit' } ) ;
11696 } else {
11797 throw error ;
11898 }
11999 }
120100 }
121- // Push the backport branch
101+ // Push the backport branch (force to handle updates)
122102 core . info ( `Pushing ${ backportBranch } to origin` ) ;
123- execSync ( `git push origin ${ backportBranch } ` , { stdio : 'inherit' } ) ;
103+ execSync ( `git push --force-with-lease origin ${ backportBranch } ` , { stdio : 'inherit' } ) ;
104+
105+ // Check if a PR already exists for this backport branch
106+ const { data : existingPRs } = await github . rest . pulls . list ( {
107+ owner : context . repo . owner ,
108+ repo : context . repo . repo ,
109+ head : `${ context . repo . owner } :${ backportBranch } ` ,
110+ base : targetBranch ,
111+ state : 'open'
112+ } ) ;
113+ const existingPR = existingPRs . length > 0 ? existingPRs [ 0 ] : null ;
114+
124115 // Create pull request
125116 const commitList = commits . map ( c => `- \`${ c . sha . substring ( 0 , 7 ) } \` ${ c . commit . message . split ( '\n' ) [ 0 ] } ` ) . join ( '\n' ) ;
126117
127- // Build PR body based on conflict status
118+ // Build PR body based on conflict status and merge status
128119 let prBody = `🤖 **Automated backport of #${ prNumber } to \`${ targetBranch } \`**\n\n` ;
129120
121+ // Add merge status indicator
122+ if ( ! isMerged ) {
123+ prBody += `⚠️ **Note:** The source PR #${ prNumber } is not yet merged. This backport was created from the current state of the PR and may need updates if more commits are added before merge.\n\n` ;
124+ }
125+
130126 if ( hasConflicts ) {
131127 prBody += `⚠️ **This PR has merge conflicts that need manual resolution.**
132128
133- Original PR: #${ prNumber }
129+ Original PR: #${ prNumber } ${ isMerged ? '(merged)' : '(not yet merged)' }
134130Original Author: @${ prAuthor }
135131
136132**Cherry-picked commits (${ commits . length } ):**
@@ -158,7 +154,7 @@ git push --force-with-lease origin ${backportBranch}
158154 } else {
159155 prBody += `✅ Cherry-pick completed successfully with no conflicts.
160156
161- Original PR: #${ prNumber }
157+ Original PR: #${ prNumber } ${ isMerged ? '(merged)' : '(not yet merged)' }
162158Original Author: @${ prAuthor }
163159
164160**Cherry-picked commits (${ commits . length } ):**
@@ -167,37 +163,98 @@ ${commitList}
167163This backport was automatically created by the backport bot.` ;
168164 }
169165
170- const newPR = await github . rest . pulls . create ( {
171- owner : context . repo . owner ,
172- repo : context . repo . repo ,
173- title : `[${ targetBranch } ] ${ prTitle } ` ,
174- head : backportBranch ,
175- base : targetBranch ,
176- body : prBody ,
177- draft : hasConflicts
178- } ) ;
179- // Add labels
180- await github . rest . issues . addLabels ( {
181- owner : context . repo . owner ,
182- repo : context . repo . repo ,
183- issue_number : newPR . data . number ,
184- labels : [ 'backport' , hasConflicts ? 'needs-manual-resolution' : 'auto-backport' ]
185- } ) ;
186- // Link to original PR
187- await github . rest . issues . createComment ( {
188- owner : context . repo . owner ,
189- repo : context . repo . repo ,
190- issue_number : prNumber ,
191- body : `🤖 Backport PR created for \`${ targetBranch } \`: #${ newPR . data . number } ${ hasConflicts ? '⚠️ (has conflicts)' : '✅' } `
192- } ) ;
193- results . push ( {
194- branch : targetBranch ,
195- success : true ,
196- prNumber : newPR . data . number ,
197- prUrl : newPR . data . html_url ,
198- hasConflicts
199- } ) ;
200- core . info ( `✅ Successfully created backport PR #${ newPR . data . number } ` ) ;
166+ if ( existingPR ) {
167+ // Update existing PR
168+ core . info ( `Found existing PR #${ existingPR . number } , updating it` ) ;
169+ await github . rest . pulls . update ( {
170+ owner : context . repo . owner ,
171+ repo : context . repo . repo ,
172+ pull_number : existingPR . number ,
173+ body : prBody ,
174+ draft : hasConflicts
175+ } ) ;
176+
177+ // Update labels
178+ const currentLabels = existingPR . labels . map ( l => l . name ) ;
179+ const desiredLabels = [ 'backport' , hasConflicts ? 'needs-manual-resolution' : 'auto-backport' ] ;
180+
181+ // Remove old labels if conflict status changed
182+ if ( hasConflicts && currentLabels . includes ( 'auto-backport' ) ) {
183+ await github . rest . issues . removeLabel ( {
184+ owner : context . repo . owner ,
185+ repo : context . repo . repo ,
186+ issue_number : existingPR . number ,
187+ name : 'auto-backport'
188+ } ) . catch ( ( ) => { } ) ; // Ignore if label doesn't exist
189+ } else if ( ! hasConflicts && currentLabels . includes ( 'needs-manual-resolution' ) ) {
190+ await github . rest . issues . removeLabel ( {
191+ owner : context . repo . owner ,
192+ repo : context . repo . repo ,
193+ issue_number : existingPR . number ,
194+ name : 'needs-manual-resolution'
195+ } ) . catch ( ( ) => { } ) ; // Ignore if label doesn't exist
196+ }
197+
198+ // Add current labels
199+ await github . rest . issues . addLabels ( {
200+ owner : context . repo . owner ,
201+ repo : context . repo . repo ,
202+ issue_number : existingPR . number ,
203+ labels : desiredLabels
204+ } ) ;
205+
206+ // Comment about the update
207+ await github . rest . issues . createComment ( {
208+ owner : context . repo . owner ,
209+ repo : context . repo . repo ,
210+ issue_number : prNumber ,
211+ body : `🤖 Updated existing backport PR for \`${ targetBranch } \`: #${ existingPR . number } ${ hasConflicts ? '⚠️ (has conflicts)' : '✅' } `
212+ } ) ;
213+
214+ results . push ( {
215+ branch : targetBranch ,
216+ success : true ,
217+ prNumber : existingPR . number ,
218+ prUrl : existingPR . html_url ,
219+ hasConflicts,
220+ updated : true
221+ } ) ;
222+ core . info ( `✅ Successfully updated backport PR #${ existingPR . number } ` ) ;
223+ } else {
224+ // Create new PR
225+ const newPR = await github . rest . pulls . create ( {
226+ owner : context . repo . owner ,
227+ repo : context . repo . repo ,
228+ title : `[${ targetBranch } ] ${ prTitle } ` ,
229+ head : backportBranch ,
230+ base : targetBranch ,
231+ body : prBody ,
232+ draft : hasConflicts
233+ } ) ;
234+ // Add labels
235+ await github . rest . issues . addLabels ( {
236+ owner : context . repo . owner ,
237+ repo : context . repo . repo ,
238+ issue_number : newPR . data . number ,
239+ labels : [ 'backport' , hasConflicts ? 'needs-manual-resolution' : 'auto-backport' ]
240+ } ) ;
241+ // Link to original PR
242+ await github . rest . issues . createComment ( {
243+ owner : context . repo . owner ,
244+ repo : context . repo . repo ,
245+ issue_number : prNumber ,
246+ body : `🤖 Backport PR created for \`${ targetBranch } \`: #${ newPR . data . number } ${ hasConflicts ? '⚠️ (has conflicts)' : '✅' } `
247+ } ) ;
248+ results . push ( {
249+ branch : targetBranch ,
250+ success : true ,
251+ prNumber : newPR . data . number ,
252+ prUrl : newPR . data . html_url ,
253+ hasConflicts,
254+ updated : false
255+ } ) ;
256+ core . info ( `✅ Successfully created backport PR #${ newPR . data . number } ` ) ;
257+ }
201258 } catch ( error ) {
202259 core . error ( `❌ Failed to backport to ${ targetBranch } : ${ error . message } ` ) ;
203260 // Comment on original PR about the failure
@@ -229,7 +286,8 @@ core.info('Backport Summary');
229286core . info ( '========================================' ) ;
230287for ( const result of results ) {
231288 if ( result . success ) {
232- core . info ( `✅ ${ result . branch } : PR #${ result . prNumber } ${ result . hasConflicts ? '(has conflicts)' : '' } ` ) ;
289+ const action = result . updated ? 'Updated' : 'Created' ;
290+ core . info ( `✅ ${ result . branch } : ${ action } PR #${ result . prNumber } ${ result . hasConflicts ? '(has conflicts)' : '' } ` ) ;
233291 } else {
234292 core . error ( `❌ ${ result . branch } : ${ result . error } ` ) ;
235293 }
0 commit comments