1
1
import chalk from "chalk"
2
+ import console from "console"
2
3
import { renameSync } from "fs"
3
4
import {
4
5
copySync ,
@@ -36,6 +37,7 @@ import { resolveRelativeFileDependencies } from "./resolveRelativeFileDependenci
36
37
import { spawnSafeSync } from "./spawnSafe"
37
38
import {
38
39
clearPatchApplicationState ,
40
+ getPatchApplicationState ,
39
41
PatchState ,
40
42
savePatchApplicationState ,
41
43
STATE_FILE_NAME ,
@@ -78,22 +80,54 @@ export function makePatch({
78
80
return
79
81
}
80
82
83
+ const state = getPatchApplicationState ( packageDetails )
84
+ const isRebasing = state ?. isRebasing ?? false
85
+ // TODO: verify applied patch hashes
86
+ // TODO: handle case for --rebase 0
87
+ // TODO: handle empty diffs while rebasing
88
+ if (
89
+ mode . type === "overwrite_last" &&
90
+ isRebasing &&
91
+ state ?. patches . length === 0
92
+ ) {
93
+ mode = { type : "append" , name : "initial" }
94
+ }
95
+
81
96
const existingPatches =
82
97
getGroupedPatches ( patchDir ) . pathSpecifierToPatchFiles [
83
98
packageDetails . pathSpecifier
84
99
] || [ ]
85
100
101
+ // apply all existing patches if appending
102
+ // otherwise apply all but the last
103
+ const previouslyAppliedPatches = state ?. patches . filter ( ( p ) => p . didApply )
104
+ const patchesToApplyBeforeDiffing : PatchedPackageDetails [ ] = isRebasing
105
+ ? mode . type === "append"
106
+ ? existingPatches . slice ( 0 , previouslyAppliedPatches ! . length )
107
+ : state ! . patches [ state ! . patches . length - 1 ] . didApply
108
+ ? existingPatches . slice ( 0 , previouslyAppliedPatches ! . length - 1 )
109
+ : existingPatches . slice ( 0 , previouslyAppliedPatches ! . length )
110
+ : mode . type === "append"
111
+ ? existingPatches
112
+ : existingPatches . slice ( 0 , - 1 )
113
+
86
114
if ( createIssue && mode . type === "append" ) {
87
115
console . error ( "--create-issue is not compatible with --append." )
88
116
process . exit ( 1 )
89
117
}
90
118
119
+ if ( createIssue && isRebasing ) {
120
+ console . error ( "--create-issue is not compatible with rebasing." )
121
+ process . exit ( 1 )
122
+ }
123
+
91
124
const numPatchesAfterCreate =
92
125
mode . type === "append" || existingPatches . length === 0
93
126
? existingPatches . length + 1
94
127
: existingPatches . length
95
128
const vcs = getPackageVCSDetails ( packageDetails )
96
129
const canCreateIssue =
130
+ ! isRebasing &&
97
131
shouldRecommendIssue ( vcs ) &&
98
132
numPatchesAfterCreate === 1 &&
99
133
mode . type !== "append"
@@ -224,11 +258,7 @@ export function makePatch({
224
258
// remove ignored files first
225
259
removeIgnoredFiles ( tmpRepoPackagePath , includePaths , excludePaths )
226
260
227
- // apply all existing patches if appending
228
- // otherwise apply all but the last
229
- const patchesToApplyBeforeCommit =
230
- mode . type === "append" ? existingPatches : existingPatches . slice ( 0 , - 1 )
231
- for ( const patchDetails of patchesToApplyBeforeCommit ) {
261
+ for ( const patchDetails of patchesToApplyBeforeDiffing ) {
232
262
if (
233
263
! applyPatch ( {
234
264
patchDetails,
@@ -339,10 +369,10 @@ export function makePatch({
339
369
}
340
370
341
371
// maybe delete existing
342
- if ( mode . type === "overwrite_last" ) {
343
- const prevPatch = existingPatches [ existingPatches . length - 1 ] as
344
- | PatchedPackageDetails
345
- | undefined
372
+ if ( ! isRebasing && mode . type === "overwrite_last" ) {
373
+ const prevPatch = patchesToApplyBeforeDiffing [
374
+ patchesToApplyBeforeDiffing . length - 1
375
+ ] as PatchedPackageDetails | undefined
346
376
if ( prevPatch ) {
347
377
const patchFilePath = join ( appPath , patchDir , prevPatch . patchFilename )
348
378
try {
@@ -351,7 +381,7 @@ export function makePatch({
351
381
// noop
352
382
}
353
383
}
354
- } else if ( existingPatches . length === 1 ) {
384
+ } else if ( ! isRebasing && existingPatches . length === 1 ) {
355
385
// if we are appending to an existing patch that doesn't have a sequence number let's rename it
356
386
const prevPatch = existingPatches [ 0 ]
357
387
if ( prevPatch . sequenceNumber === undefined ) {
@@ -370,9 +400,9 @@ export function makePatch({
370
400
}
371
401
}
372
402
373
- const lastPatch = existingPatches [ existingPatches . length - 1 ] as
374
- | PatchedPackageDetails
375
- | undefined
403
+ const lastPatch = existingPatches [
404
+ state ? state . patches . length - 1 : existingPatches . length - 1
405
+ ] as PatchedPackageDetails | undefined
376
406
const sequenceName =
377
407
mode . type === "append" ? mode . name : lastPatch ?. sequenceName
378
408
const sequenceNumber =
@@ -396,10 +426,33 @@ export function makePatch({
396
426
console . log (
397
427
`${ chalk . green ( "✔" ) } Created file ${ join ( patchDir , patchFileName ) } \n` ,
398
428
)
399
- const prevState : PatchState [ ] = ( mode . type === "append"
400
- ? existingPatches
401
- : existingPatches . slice ( 0 , - 1 )
402
- ) . map (
429
+
430
+ // if we inserted a new patch into a sequence we may need to update the sequence numbers
431
+ if ( isRebasing && mode . type === "append" ) {
432
+ const patchesToNudge = existingPatches . slice ( state ! . patches . length )
433
+ if ( sequenceNumber === undefined ) {
434
+ throw new Error ( "sequenceNumber is undefined while rebasing" )
435
+ }
436
+ if (
437
+ patchesToNudge [ 0 ] ?. sequenceNumber !== undefined &&
438
+ patchesToNudge [ 0 ] . sequenceNumber <= sequenceNumber
439
+ ) {
440
+ let next = sequenceNumber + 1
441
+ for ( const p of patchesToNudge ) {
442
+ const newName = createPatchFileName ( {
443
+ packageDetails,
444
+ packageVersion,
445
+ sequenceName : p . sequenceName ,
446
+ sequenceNumber : next ++ ,
447
+ } )
448
+ const oldPath = join ( appPath , patchDir , p . patchFilename )
449
+ const newPath = join ( appPath , patchDir , newName )
450
+ renameSync ( oldPath , newPath )
451
+ }
452
+ }
453
+ }
454
+
455
+ const prevState : PatchState [ ] = patchesToApplyBeforeDiffing . map (
403
456
( p ) : PatchState => ( {
404
457
patchFilename : p . patchFilename ,
405
458
didApply : true ,
@@ -414,15 +467,61 @@ export function makePatch({
414
467
patchContentHash : hashFile ( patchPath ) ,
415
468
} ,
416
469
]
417
- if ( nextState . length > 1 ) {
470
+
471
+ // if any patches come after this one we just made, we should reapply them
472
+ let didFailWhileFinishingRebase = false
473
+ if ( isRebasing ) {
474
+ const previouslyUnappliedPatches = existingPatches . slice (
475
+ // if we overwrote a previously failing patch we should not include that in here
476
+ previouslyAppliedPatches ! . length +
477
+ ( mode . type === "overwrite_last" &&
478
+ ! state ?. patches [ state . patches . length - 1 ] . didApply
479
+ ? 1
480
+ : 0 ) ,
481
+ )
482
+ if ( previouslyUnappliedPatches . length ) {
483
+ console . log ( `Fast forwarding...` )
484
+ for ( const patch of previouslyUnappliedPatches ) {
485
+ const patchFilePath = join ( appPath , patchDir , patch . patchFilename )
486
+ if (
487
+ ! applyPatch ( {
488
+ patchDetails : patch ,
489
+ patchDir,
490
+ patchFilePath,
491
+ reverse : false ,
492
+ cwd : tmpRepo . name ,
493
+ } )
494
+ ) {
495
+ didFailWhileFinishingRebase = true
496
+ logPatchSequenceError ( { patchDetails : patch } )
497
+ nextState . push ( {
498
+ patchFilename : patch . patchFilename ,
499
+ didApply : false ,
500
+ patchContentHash : hashFile ( patchFilePath ) ,
501
+ } )
502
+ break
503
+ } else {
504
+ console . log ( ` ${ chalk . green ( "✔" ) } ${ patch . patchFilename } ` )
505
+ nextState . push ( {
506
+ patchFilename : patch . patchFilename ,
507
+ didApply : true ,
508
+ patchContentHash : hashFile ( patchFilePath ) ,
509
+ } )
510
+ }
511
+ }
512
+ }
513
+ }
514
+
515
+ if ( isRebasing || numPatchesAfterCreate > 1 ) {
418
516
savePatchApplicationState ( {
419
517
packageDetails,
420
518
patches : nextState ,
421
- isRebasing : false ,
519
+ isRebasing : didFailWhileFinishingRebase ,
422
520
} )
423
521
} else {
424
522
clearPatchApplicationState ( packageDetails )
425
523
}
524
+
426
525
if ( canCreateIssue ) {
427
526
if ( createIssue ) {
428
527
openIssueCreationLink ( {
@@ -466,3 +565,27 @@ function createPatchFileName({
466
565
467
566
return `${ nameAndVersion } ${ num } ${ name } .patch`
468
567
}
568
+
569
+ export function logPatchSequenceError ( {
570
+ patchDetails,
571
+ } : {
572
+ patchDetails : PatchedPackageDetails
573
+ } ) {
574
+ console . log ( `
575
+ ${ chalk . red . bold ( "⛔ ERROR" ) }
576
+
577
+ Failed to apply patch file ${ chalk . bold ( patchDetails . patchFilename ) } .
578
+
579
+ If this patch file is no longer useful, delete it and run
580
+
581
+ ${ chalk . bold ( `patch-package` ) }
582
+
583
+ Otherwise you should open ${
584
+ patchDetails . path
585
+ } , manually apply the changes from the patch file, and run
586
+
587
+ ${ chalk . bold ( `patch-package ${ patchDetails . pathSpecifier } ` ) }
588
+
589
+ to update the patch file.
590
+ ` )
591
+ }
0 commit comments