Skip to content

Commit ec0e69c

Browse files
committed
rebase continue behavior
1 parent d9d613d commit ec0e69c

File tree

3 files changed

+175
-30
lines changed

3 files changed

+175
-30
lines changed

src/applyPatches.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { existsSync } from "fs-extra"
33
import { posix } from "path"
44
import semver from "semver"
55
import { hashFile } from "./hash"
6+
import { logPatchSequenceError } from "./makePatch"
67
import { PackageDetails, PatchedPackageDetails } from "./PackageDetails"
78
import { packageIsDevDependency } from "./packageIsDevDependency"
89
import { executeEffects } from "./patch/apply"
@@ -13,6 +14,7 @@ import { join, relative } from "./path"
1314
import {
1415
clearPatchApplicationState,
1516
getPatchApplicationState,
17+
PatchState,
1618
savePatchApplicationState,
1719
} from "./stateFile"
1820

@@ -213,12 +215,13 @@ export function applyPatchesForPackage({
213215
appliedPatches.length = 0
214216
}
215217
if (appliedPatches.length) {
216-
// all patches have already been applied
218+
// some patches have already been applied
217219
appliedPatches.forEach(logPatchApplication)
218220
}
219221
if (!unappliedPatches.length) {
220222
return
221223
}
224+
let failedPatch: PatchedPackageDetails | null = null
222225
packageLoop: for (const patchDetails of unappliedPatches) {
223226
try {
224227
const { name, version, path, isDevOnly, patchFilename } = patchDetails
@@ -271,6 +274,12 @@ export function applyPatchesForPackage({
271274
)
272275
}
273276
logPatchApplication(patchDetails)
277+
} else if (patches.length > 1) {
278+
logPatchSequenceError({ patchDetails })
279+
// in case the package has multiple patches, we need to break out of this inner loop
280+
// because we don't want to apply more patches on top of the broken state
281+
failedPatch = patchDetails
282+
break packageLoop
274283
} else if (installedPackageVersion === version) {
275284
// completely failed to apply patch
276285
// TODO: propagate useful error messages from patch application
@@ -282,8 +291,6 @@ export function applyPatchesForPackage({
282291
path,
283292
}),
284293
)
285-
// in case the package has multiple patches, we need to break out of this inner loop
286-
// because we don't want to apply more patches on top of the broken state
287294
break packageLoop
288295
} else {
289296
errors.push(
@@ -353,18 +360,29 @@ export function applyPatchesForPackage({
353360
})
354361
}
355362
} else {
356-
const allPatchesSucceeded =
357-
unappliedPatches.length === appliedPatches.length
358-
savePatchApplicationState({
359-
packageDetails: patches[0],
360-
patches: appliedPatches.map((patch) => ({
363+
const nextState = appliedPatches.map(
364+
(patch): PatchState => ({
361365
didApply: true,
362366
patchContentHash: hashFile(
363367
join(appPath, patchDir, patch.patchFilename),
364368
),
365369
patchFilename: patch.patchFilename,
366-
})),
367-
isRebasing: !allPatchesSucceeded,
370+
}),
371+
)
372+
373+
if (failedPatch) {
374+
nextState.push({
375+
didApply: false,
376+
patchContentHash: hashFile(
377+
join(appPath, patchDir, failedPatch.patchFilename),
378+
),
379+
patchFilename: failedPatch.patchFilename,
380+
})
381+
}
382+
savePatchApplicationState({
383+
packageDetails: patches[0],
384+
patches: nextState,
385+
isRebasing: !!failedPatch,
368386
})
369387
}
370388
}
@@ -389,6 +407,10 @@ export function applyPatch({
389407
patchDir,
390408
})
391409
try {
410+
executeEffects(reverse ? reversePatch(patch) : patch, {
411+
dryRun: true,
412+
cwd,
413+
})
392414
executeEffects(reverse ? reversePatch(patch) : patch, {
393415
dryRun: false,
394416
cwd,

src/makePatch.ts

Lines changed: 142 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import chalk from "chalk"
2+
import console from "console"
23
import { renameSync } from "fs"
34
import {
45
copySync,
@@ -36,6 +37,7 @@ import { resolveRelativeFileDependencies } from "./resolveRelativeFileDependenci
3637
import { spawnSafeSync } from "./spawnSafe"
3738
import {
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+
}

src/stateFile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import stringify from "json-stable-stringify"
55
export interface PatchState {
66
patchFilename: string
77
patchContentHash: string
8-
didApply: true
8+
didApply: boolean
99
}
1010

1111
const version = 1

0 commit comments

Comments
 (0)