1
- import os from 'node:os' ;
2
1
import path from 'node:path' ;
3
2
import { getMetadata } from '../components/metadata.js' ;
4
3
5
4
import {
6
- runAsync , runSync , forceRunAsync
5
+ runAsync , runSync
7
6
} from './run.js' ;
8
- import { writeFile } from './file.js' ;
9
- import {
10
- shortSha , getEditor
11
- } from './utils.js' ;
12
7
import { getNcuDir } from './config.js' ;
8
+ import LandingSession , { LINT_RESULTS } from './landing_session.js' ;
13
9
14
- const LINT_RESULTS = {
15
- SKIPPED : 'skipped' ,
16
- FAILED : 'failed' ,
17
- SUCCESS : 'success'
18
- } ;
19
-
20
- export default class CheckPick {
10
+ export default class CherryPick {
21
11
constructor ( prid , dir , cli , {
22
12
owner,
23
13
repo,
@@ -46,11 +36,6 @@ export default class CheckPick {
46
36
return this . options . lint ;
47
37
}
48
38
49
- getUpstreamHead ( ) {
50
- const { upstream, branch } = this ;
51
- return runSync ( 'git' , [ 'rev-parse' , `${ upstream } /${ branch } ` ] ) . trim ( ) ;
52
- }
53
-
54
39
getCurrentRev ( ) {
55
40
return runSync ( 'git' , [ 'rev-parse' , 'HEAD' ] ) . trim ( ) ;
56
41
}
@@ -73,16 +58,6 @@ export default class CheckPick {
73
58
return path . resolve ( this . ncuDir , `${ this . prid } ` ) ;
74
59
}
75
60
76
- getMessagePath ( rev ) {
77
- return path . resolve ( this . pullDir , `${ shortSha ( rev ) } .COMMIT_EDITMSG` ) ;
78
- }
79
-
80
- saveMessage ( rev , message ) {
81
- const file = this . getMessagePath ( rev ) ;
82
- writeFile ( file , message ) ;
83
- return file ;
84
- }
85
-
86
61
async start ( ) {
87
62
const { cli } = this ;
88
63
@@ -91,7 +66,7 @@ export default class CheckPick {
91
66
owner : this . owner ,
92
67
repo : this . repo
93
68
} , false , cli ) ;
94
- const expectedCommitShas =
69
+ this . expectedCommitShas =
95
70
metadata . data . commits . map ( ( { commit } ) => commit . oid ) ;
96
71
97
72
const amend = await cli . prompt (
@@ -104,7 +79,7 @@ export default class CheckPick {
104
79
}
105
80
106
81
try {
107
- const commitInfo = await this . downloadAndPatch ( expectedCommitShas ) ;
82
+ const commitInfo = await this . downloadAndPatch ( ) ;
108
83
const cleanLint = await this . validateLint ( ) ;
109
84
if ( cleanLint === LINT_RESULTS . FAILED ) {
110
85
cli . error ( 'Patch still contains lint errors. ' +
@@ -120,68 +95,6 @@ export default class CheckPick {
120
95
}
121
96
}
122
97
123
- async downloadAndPatch ( expectedCommitShas ) {
124
- const { cli, repo, owner, prid } = this ;
125
-
126
- cli . startSpinner ( `Downloading patch for ${ prid } ` ) ;
127
- // fetch via ssh to handle private repo
128
- await runAsync ( 'git' , [
129
- 'fetch' , `[email protected] :${ owner } /${ repo } .git` ,
130
- `refs/pull/${ prid } /merge` ] ) ;
131
- // We fetched the commit that would result if we used `git merge`.
132
- // ^1 and ^2 refer to the PR base and the PR head, respectively.
133
- const [ base , head ] = await runAsync ( 'git' ,
134
- [ 'rev-parse' , 'FETCH_HEAD^1' , 'FETCH_HEAD^2' ] ,
135
- { captureStdout : 'lines' } ) ;
136
- const commitShas = await runAsync ( 'git' ,
137
- [ 'rev-list' , `${ base } ..${ head } ` ] ,
138
- { captureStdout : 'lines' } ) ;
139
- cli . stopSpinner ( `Fetched commits as ${ shortSha ( base ) } ..${ shortSha ( head ) } ` ) ;
140
- cli . separator ( ) ;
141
-
142
- const mismatchedCommits = [
143
- ...commitShas . filter ( ( sha ) => ! expectedCommitShas . includes ( sha ) )
144
- . map ( ( sha ) => `Unexpected commit ${ sha } ` ) ,
145
- ...expectedCommitShas . filter ( ( sha ) => ! commitShas . includes ( sha ) )
146
- . map ( ( sha ) => `Missing commit ${ sha } ` )
147
- ] . join ( '\n' ) ;
148
- if ( mismatchedCommits . length > 0 ) {
149
- throw new Error ( `Mismatched commits:\n${ mismatchedCommits } ` ) ;
150
- }
151
-
152
- const commitInfo = { base, head, shas : commitShas } ;
153
-
154
- try {
155
- await forceRunAsync ( 'git' , [ 'cherry-pick' , `${ base } ..${ head } ` ] , {
156
- ignoreFailure : false
157
- } ) ;
158
- } catch ( ex ) {
159
- await forceRunAsync ( 'git' , [ 'cherry-pick' , '--abort' ] ) ;
160
- throw new Error ( 'Failed to apply patches' ) ;
161
- }
162
-
163
- cli . ok ( 'Patches applied' ) ;
164
- return commitInfo ;
165
- }
166
-
167
- async validateLint ( ) {
168
- // The linter is currently only run on non-Windows platforms.
169
- if ( os . platform ( ) === 'win32' ) {
170
- return LINT_RESULTS . SKIPPED ;
171
- }
172
-
173
- if ( ! this . lint ) {
174
- return LINT_RESULTS . SKIPPED ;
175
- }
176
-
177
- try {
178
- await runAsync ( 'make' , [ 'lint' ] ) ;
179
- return LINT_RESULTS . SUCCESS ;
180
- } catch {
181
- return LINT_RESULTS . FAILED ;
182
- }
183
- }
184
-
185
98
async amend ( metadata , commitInfo ) {
186
99
const { cli } = this ;
187
100
const subjects = await runAsync ( 'git' ,
@@ -203,102 +116,23 @@ export default class CheckPick {
203
116
await runAsync ( 'git' , [ 'commit' , '--amend' , '--no-edit' ] ) ;
204
117
}
205
118
206
- return this . _amend ( metadata ) ;
119
+ return LandingSession . prototype . amend . call ( this , metadata ) ;
207
120
}
208
121
209
- async _amend ( metadataStr ) {
210
- const { cli } = this ;
211
-
212
- const rev = this . getCurrentRev ( ) ;
213
- const original = runSync ( 'git' , [
214
- 'show' , 'HEAD' , '-s' , '--format=%B'
215
- ] ) . trim ( ) ;
216
- // git has very specific rules about what is a trailer and what is not.
217
- // Instead of trying to implement those ourselves, let git parse the
218
- // original commit message and see if it outputs any trailers.
219
- const originalHasTrailers = runSync ( 'git' , [
220
- 'interpret-trailers' , '--parse' , '--no-divider'
221
- ] , {
222
- input : `${ original } \n`
223
- } ) . trim ( ) . length !== 0 ;
224
- const metadata = metadataStr . trim ( ) . split ( '\n' ) ;
225
- const amended = original . split ( '\n' ) ;
226
-
227
- // If the original commit message already contains trailers (such as
228
- // "Co-authored-by"), we simply add our own metadata after those. Otherwise,
229
- // we have to add an empty line so that git recognizes our own metadata as
230
- // trailers in the amended commit message.
231
- if ( ! originalHasTrailers ) {
232
- amended . push ( '' ) ;
233
- }
234
-
235
- const BACKPORT_RE = / B A C K P O R T - P R - U R L \s * : \s * ( \S + ) / i;
236
- const PR_RE = / P R - U R L \s * : \s * ( \S + ) / i;
237
- const REVIEW_RE = / R e v i e w e d - B y \s * : \s * ( \S + ) / i;
238
- const CVE_RE = / C V E - I D \s * : \s * ( \S + ) / i;
239
-
240
- let containCVETrailer = false ;
241
- for ( const line of metadata ) {
242
- if ( line . length !== 0 && original . includes ( line ) ) {
243
- if ( line . match ( CVE_RE ) ) {
244
- containCVETrailer = true ;
245
- }
246
- if ( originalHasTrailers ) {
247
- cli . warn ( `Found ${ line } , skipping..` ) ;
248
- } else {
249
- throw new Error (
250
- 'Git found no trailers in the original commit message, ' +
251
- `but '${ line } ' is present and should be a trailer.` ) ;
252
- }
253
- } else {
254
- if ( line . match ( BACKPORT_RE ) ) {
255
- let prIndex = amended . findIndex ( datum => datum . match ( PR_RE ) ) ;
256
- if ( prIndex === - 1 ) {
257
- prIndex = amended . findIndex ( datum => datum . match ( REVIEW_RE ) ) - 1 ;
258
- }
259
- amended . splice ( prIndex + 1 , 0 , line ) ;
260
- } else {
261
- amended . push ( line ) ;
262
- }
263
- }
264
- }
265
-
266
- if ( ! containCVETrailer && this . includeCVE ) {
267
- const cveID = await cli . prompt (
268
- 'Git found no CVE-ID trailer in the original commit message. ' +
269
- 'Please, provide the CVE-ID' ,
270
- { questionType : 'input' , defaultAnswer : 'CVE-2023-XXXXX' }
271
- ) ;
272
- amended . push ( 'CVE-ID: ' + cveID ) ;
273
- }
122
+ readyToAmend ( ) {
123
+ return true ;
124
+ }
274
125
275
- const message = amended . join ( '\n' ) ;
276
- const messageFile = this . saveMessage ( rev , message ) ;
277
- cli . separator ( 'New Message' ) ;
278
- cli . log ( message . trim ( ) ) ;
279
- const takeMessage = await cli . prompt ( 'Use this message?' ) ;
280
- if ( takeMessage ) {
281
- await runAsync ( 'git' , [ 'commit' , '--amend' , '-F' , messageFile ] ) ;
282
- return true ;
283
- }
126
+ startAmending ( ) {
127
+ // No-op
128
+ }
284
129
285
- const editor = await getEditor ( { git : true } ) ;
286
- if ( editor ) {
287
- try {
288
- await forceRunAsync (
289
- editor ,
290
- [ `"${ messageFile } "` ] ,
291
- { ignoreFailure : false , spawnArgs : { shell : true } }
292
- ) ;
293
- await runAsync ( 'git' , [ 'commit' , '--amend' , '-F' , messageFile ] ) ;
294
- return true ;
295
- } catch {
296
- cli . warn ( `Please manually edit ${ messageFile } , then run\n` +
297
- `\`git commit --amend -F ${ messageFile } \` ` +
298
- 'to finish amending the message' ) ;
299
- throw new Error (
300
- 'Failed to edit the message using the configured editor' ) ;
301
- }
302
- }
130
+ saveCommitInfo ( ) {
131
+ // No-op
303
132
}
304
133
}
134
+
135
+ CherryPick . prototype . downloadAndPatch = LandingSession . prototype . downloadAndPatch ;
136
+ CherryPick . prototype . validateLint = LandingSession . prototype . validateLint ;
137
+ CherryPick . prototype . getMessagePath = LandingSession . prototype . getMessagePath ;
138
+ CherryPick . prototype . saveMessage = LandingSession . prototype . saveMessage ;
0 commit comments