11import chalk from "chalk"
2+ import console from "console"
23import { renameSync } from "fs"
34import {
45 copySync ,
@@ -36,6 +37,7 @@ import { resolveRelativeFileDependencies } from "./resolveRelativeFileDependenci
3637import { spawnSafeSync } from "./spawnSafe"
3738import {
3839 clearPatchApplicationState ,
40+ getPatchApplicationState ,
3941 PatchState ,
4042 savePatchApplicationState ,
4143 STATE_FILE_NAME ,
@@ -78,22 +80,54 @@ export function makePatch({
7880 return
7981 }
8082
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+
8196 const existingPatches =
8297 getGroupedPatches ( patchDir ) . pathSpecifierToPatchFiles [
8398 packageDetails . pathSpecifier
8499 ] || [ ]
85100
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+
86114 if ( createIssue && mode . type === "append" ) {
87115 console . error ( "--create-issue is not compatible with --append." )
88116 process . exit ( 1 )
89117 }
90118
119+ if ( createIssue && isRebasing ) {
120+ console . error ( "--create-issue is not compatible with rebasing." )
121+ process . exit ( 1 )
122+ }
123+
91124 const numPatchesAfterCreate =
92125 mode . type === "append" || existingPatches . length === 0
93126 ? existingPatches . length + 1
94127 : existingPatches . length
95128 const vcs = getPackageVCSDetails ( packageDetails )
96129 const canCreateIssue =
130+ ! isRebasing &&
97131 shouldRecommendIssue ( vcs ) &&
98132 numPatchesAfterCreate === 1 &&
99133 mode . type !== "append"
@@ -224,11 +258,7 @@ export function makePatch({
224258 // remove ignored files first
225259 removeIgnoredFiles ( tmpRepoPackagePath , includePaths , excludePaths )
226260
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 ) {
232262 if (
233263 ! applyPatch ( {
234264 patchDetails,
@@ -339,10 +369,10 @@ export function makePatch({
339369 }
340370
341371 // 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
346376 if ( prevPatch ) {
347377 const patchFilePath = join ( appPath , patchDir , prevPatch . patchFilename )
348378 try {
@@ -351,7 +381,7 @@ export function makePatch({
351381 // noop
352382 }
353383 }
354- } else if ( existingPatches . length === 1 ) {
384+ } else if ( ! isRebasing && existingPatches . length === 1 ) {
355385 // if we are appending to an existing patch that doesn't have a sequence number let's rename it
356386 const prevPatch = existingPatches [ 0 ]
357387 if ( prevPatch . sequenceNumber === undefined ) {
@@ -370,9 +400,9 @@ export function makePatch({
370400 }
371401 }
372402
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
376406 const sequenceName =
377407 mode . type === "append" ? mode . name : lastPatch ?. sequenceName
378408 const sequenceNumber =
@@ -396,10 +426,33 @@ export function makePatch({
396426 console . log (
397427 `${ chalk . green ( "✔" ) } Created file ${ join ( patchDir , patchFileName ) } \n` ,
398428 )
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 (
403456 ( p ) : PatchState => ( {
404457 patchFilename : p . patchFilename ,
405458 didApply : true ,
@@ -414,15 +467,61 @@ export function makePatch({
414467 patchContentHash : hashFile ( patchPath ) ,
415468 } ,
416469 ]
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 ) {
418516 savePatchApplicationState ( {
419517 packageDetails,
420518 patches : nextState ,
421- isRebasing : false ,
519+ isRebasing : didFailWhileFinishingRebase ,
422520 } )
423521 } else {
424522 clearPatchApplicationState ( packageDetails )
425523 }
524+
426525 if ( canCreateIssue ) {
427526 if ( createIssue ) {
428527 openIssueCreationLink ( {
@@ -466,3 +565,27 @@ function createPatchFileName({
466565
467566 return `${ nameAndVersion } ${ num } ${ name } .patch`
468567}
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