Skip to content

Commit 1f27349

Browse files
committed
add rebase command to undo patches
1 parent 9bfcf30 commit 1f27349

File tree

5 files changed

+312
-21
lines changed

5 files changed

+312
-21
lines changed

src/applyPatches.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,28 +283,32 @@ export function applyPatchesForApp({
283283
)
284284
}
285285

286-
savePatchApplicationState(
287-
patches[0],
288-
patches.slice(0, lastReversedPatchIndex).map((patch) => ({
286+
savePatchApplicationState({
287+
packageDetails: patches[0],
288+
patches: patches.slice(0, lastReversedPatchIndex).map((patch) => ({
289289
didApply: true,
290290
patchContentHash: hashFile(
291291
join(appPath, patchDir, patch.patchFilename),
292292
),
293293
patchFilename: patch.patchFilename,
294294
})),
295-
)
295+
isRebasing: false,
296+
})
296297
}
297298
} else {
298-
savePatchApplicationState(
299-
patches[0],
300-
appliedPatches.map((patch) => ({
299+
const allPatchesSucceeded =
300+
unappliedPatches.length === appliedPatches.length
301+
savePatchApplicationState({
302+
packageDetails: patches[0],
303+
patches: appliedPatches.map((patch) => ({
301304
didApply: true,
302305
patchContentHash: hashFile(
303306
join(appPath, patchDir, patch.patchFilename),
304307
),
305308
patchFilename: patch.patchFilename,
306309
})),
307-
)
310+
isRebasing: !allPatchesSucceeded,
311+
})
308312
}
309313
}
310314
}

src/index.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { join } from "./path"
1111
import { normalize, sep } from "path"
1212
import slash = require("slash")
1313
import { isCI } from "ci-info"
14+
import { rebase } from "./rebase"
1415

1516
const appPath = getAppRootPath()
1617
const argv = minimist(process.argv.slice(2), {
@@ -25,7 +26,7 @@ const argv = minimist(process.argv.slice(2), {
2526
"create-issue",
2627
"",
2728
],
28-
string: ["patch-dir", "append"],
29+
string: ["patch-dir", "append", "rebase"],
2930
})
3031
const packageNames = argv._
3132

@@ -44,7 +45,30 @@ if (argv.version || argv.v) {
4445
if (patchDir.startsWith("/")) {
4546
throw new Error("--patch-dir must be a relative path")
4647
}
47-
if (packageNames.length) {
48+
if ("rebase" in argv) {
49+
if (!argv.rebase) {
50+
console.error(
51+
chalk.red(
52+
"You must specify a patch file name or number when rebasing patches",
53+
),
54+
)
55+
process.exit(1)
56+
}
57+
if (packageNames.length !== 1) {
58+
console.error(
59+
chalk.red(
60+
"You must specify exactly one package name when rebasing patches",
61+
),
62+
)
63+
process.exit(1)
64+
}
65+
rebase({
66+
appPath,
67+
packagePathSpecifier: packageNames[0],
68+
patchDir,
69+
targetPatch: argv.rebase,
70+
})
71+
} else if (packageNames.length) {
4872
const includePaths = makeRegExp(
4973
argv.include,
5074
"include",

src/makePatch.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,11 @@ export function makePatch({
415415
},
416416
]
417417
if (nextState.length > 1) {
418-
savePatchApplicationState(packageDetails, nextState)
418+
savePatchApplicationState({
419+
packageDetails,
420+
patches: nextState,
421+
isRebasing: false,
422+
})
419423
} else {
420424
clearPatchApplicationState(packageDetails)
421425
}

src/rebase.ts

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import chalk from "chalk"
2+
import { existsSync } from "fs"
3+
import { join, resolve } from "path"
4+
import { applyPatch } from "./applyPatches"
5+
import { hashFile } from "./hash"
6+
import { PatchedPackageDetails } from "./PackageDetails"
7+
import { getGroupedPatches } from "./patchFs"
8+
import {
9+
getPatchApplicationState,
10+
savePatchApplicationState,
11+
} from "./stateFile"
12+
13+
export function rebase({
14+
appPath,
15+
patchDir,
16+
packagePathSpecifier,
17+
targetPatch,
18+
}: {
19+
appPath: string
20+
patchDir: string
21+
packagePathSpecifier: string
22+
targetPatch: string
23+
}): void {
24+
const patchesDirectory = join(appPath, patchDir)
25+
const groupedPatches = getGroupedPatches(patchesDirectory)
26+
27+
if (groupedPatches.numPatchFiles === 0) {
28+
console.error(chalk.blueBright("No patch files found"))
29+
process.exit(1)
30+
}
31+
32+
const packagePatches =
33+
groupedPatches.pathSpecifierToPatchFiles[packagePathSpecifier]
34+
if (!packagePatches) {
35+
console.error(
36+
chalk.blueBright("No patch files found for package"),
37+
packagePathSpecifier,
38+
)
39+
process.exit(1)
40+
}
41+
42+
const state = getPatchApplicationState(packagePatches[0])
43+
44+
if (!state) {
45+
console.error(
46+
chalk.blueBright("No patch state found"),
47+
"Did you forget to run",
48+
chalk.bold("patch-package"),
49+
"(without arguments) first?",
50+
)
51+
process.exit(1)
52+
}
53+
if (state.isRebasing) {
54+
console.error(
55+
chalk.blueBright("Already rebasing"),
56+
"Make changes to the files in",
57+
chalk.bold(packagePatches[0].path),
58+
"and then run `patch-package",
59+
packagePathSpecifier,
60+
"--continue` to",
61+
packagePatches.length === state.patches.length
62+
? "append a patch file"
63+
: `update the ${
64+
packagePatches[packagePatches.length - 1].patchFilename
65+
} file`,
66+
)
67+
console.error(
68+
`💡 To remove a broken patch file, delete it and reinstall node_modules`,
69+
)
70+
process.exit(1)
71+
}
72+
if (state.patches.length !== packagePatches.length) {
73+
console.error(
74+
chalk.blueBright("Some patches have not been applied."),
75+
"Reinstall node_modules and try again.",
76+
)
77+
}
78+
// check hashes
79+
for (let i = 0; i < state.patches.length; i++) {
80+
const patch = state.patches[i]
81+
const fullPatchPath = join(
82+
patchesDirectory,
83+
packagePatches[i].patchFilename,
84+
)
85+
if (!existsSync(fullPatchPath)) {
86+
console.error(
87+
chalk.blueBright("Expected patch file"),
88+
fullPatchPath,
89+
"to exist but it is missing. Try completely reinstalling node_modules first.",
90+
)
91+
process.exit(1)
92+
}
93+
if (patch.patchContentHash !== hashFile(fullPatchPath)) {
94+
console.error(
95+
chalk.blueBright("Patch file"),
96+
fullPatchPath,
97+
"has changed since it was applied. Try completely reinstalling node_modules first.",
98+
)
99+
}
100+
}
101+
102+
if (targetPatch === "0") {
103+
// unapply all
104+
unApplyPatches({
105+
patches: packagePatches,
106+
appPath,
107+
patchDir,
108+
})
109+
savePatchApplicationState({
110+
packageDetails: packagePatches[0],
111+
isRebasing: true,
112+
patches: [],
113+
})
114+
console.log(`
115+
Make any changes you need inside ${chalk.bold(packagePatches[0].path)}
116+
117+
When you are done, run
118+
119+
${chalk.bold(
120+
`patch-package ${packagePathSpecifier} --append 'MyChangeDescription'`,
121+
)}
122+
123+
to insert a new patch file.
124+
`)
125+
return
126+
}
127+
128+
// find target patch
129+
const target = packagePatches.find((p) => {
130+
if (p.patchFilename === targetPatch) {
131+
return true
132+
}
133+
if (
134+
resolve(process.cwd(), targetPatch) ===
135+
join(patchesDirectory, p.patchFilename)
136+
) {
137+
return true
138+
}
139+
140+
if (targetPatch === p.sequenceName) {
141+
return true
142+
}
143+
const n = Number(targetPatch.replace(/^0+/g, ""))
144+
if (!isNaN(n) && n === p.sequenceNumber) {
145+
return true
146+
}
147+
return false
148+
})
149+
150+
if (!target) {
151+
console.error(
152+
chalk.red("Could not find target patch file"),
153+
chalk.bold(targetPatch),
154+
)
155+
console.error()
156+
console.error("The list of available patch files is:")
157+
packagePatches.forEach((p) => {
158+
console.error(` - ${p.patchFilename}`)
159+
})
160+
161+
process.exit(1)
162+
}
163+
const currentHash = hashFile(join(patchesDirectory, target.patchFilename))
164+
165+
const prevApplication = state.patches.find(
166+
(p) => p.patchContentHash === currentHash,
167+
)
168+
if (!prevApplication) {
169+
console.error(
170+
chalk.red("Could not find previous application of patch file"),
171+
chalk.bold(target.patchFilename),
172+
)
173+
console.error()
174+
console.error("You should reinstall node_modules and try again.")
175+
process.exit(1)
176+
}
177+
178+
// ok, we are good to start undoing all the patches that were applied up to but not including the target patch
179+
const targetIdx = state.patches.indexOf(prevApplication)
180+
181+
unApplyPatches({
182+
patches: packagePatches.slice(targetIdx + 1),
183+
appPath,
184+
patchDir,
185+
})
186+
savePatchApplicationState({
187+
packageDetails: packagePatches[0],
188+
isRebasing: true,
189+
patches: packagePatches.slice(0, targetIdx + 1).map((p) => ({
190+
patchFilename: p.patchFilename,
191+
patchContentHash: hashFile(join(patchesDirectory, p.patchFilename)),
192+
didApply: true,
193+
})),
194+
})
195+
196+
console.log(`
197+
Make any changes you need inside ${chalk.bold(packagePatches[0].path)}
198+
199+
When you are done, do one of the following:
200+
201+
To update ${chalk.bold(packagePatches[targetIdx].patchFilename)} run
202+
203+
${chalk.bold(`patch-package ${packagePathSpecifier}`)}
204+
205+
To create a new patch file after ${chalk.bold(
206+
packagePatches[targetIdx].patchFilename,
207+
)} run
208+
209+
${chalk.bold(
210+
`patch-package ${packagePathSpecifier} --append 'MyChangeDescription'`,
211+
)}
212+
213+
`)
214+
}
215+
216+
function unApplyPatches({
217+
patches,
218+
appPath,
219+
patchDir,
220+
}: {
221+
patches: PatchedPackageDetails[]
222+
appPath: string
223+
patchDir: string
224+
}) {
225+
for (const patch of patches.slice().reverse()) {
226+
if (
227+
!applyPatch({
228+
patchFilePath: join(appPath, patchDir, patch.patchFilename) as string,
229+
reverse: true,
230+
patchDetails: patch,
231+
patchDir,
232+
cwd: process.cwd(),
233+
})
234+
) {
235+
console.error(
236+
chalk.red("Failed to un-apply patch file"),
237+
chalk.bold(patch.patchFilename),
238+
"Try completely reinstalling node_modules.",
239+
)
240+
process.exit(1)
241+
}
242+
console.log(chalk.green("Un-applied patch file"), patch.patchFilename)
243+
}
244+
}

0 commit comments

Comments
 (0)