Skip to content

Commit

Permalink
add rebase command to undo patches
Browse files Browse the repository at this point in the history
  • Loading branch information
ds300 committed Jul 13, 2023
1 parent 9bfcf30 commit 1f27349
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 21 deletions.
20 changes: 12 additions & 8 deletions src/applyPatches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,28 +283,32 @@ export function applyPatchesForApp({
)
}

savePatchApplicationState(
patches[0],
patches.slice(0, lastReversedPatchIndex).map((patch) => ({
savePatchApplicationState({
packageDetails: patches[0],
patches: patches.slice(0, lastReversedPatchIndex).map((patch) => ({
didApply: true,
patchContentHash: hashFile(
join(appPath, patchDir, patch.patchFilename),
),
patchFilename: patch.patchFilename,
})),
)
isRebasing: false,
})
}
} else {
savePatchApplicationState(
patches[0],
appliedPatches.map((patch) => ({
const allPatchesSucceeded =
unappliedPatches.length === appliedPatches.length
savePatchApplicationState({
packageDetails: patches[0],
patches: appliedPatches.map((patch) => ({
didApply: true,
patchContentHash: hashFile(
join(appPath, patchDir, patch.patchFilename),
),
patchFilename: patch.patchFilename,
})),
)
isRebasing: !allPatchesSucceeded,
})
}
}
}
Expand Down
28 changes: 26 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { join } from "./path"
import { normalize, sep } from "path"
import slash = require("slash")
import { isCI } from "ci-info"
import { rebase } from "./rebase"

const appPath = getAppRootPath()
const argv = minimist(process.argv.slice(2), {
Expand All @@ -25,7 +26,7 @@ const argv = minimist(process.argv.slice(2), {
"create-issue",
"",
],
string: ["patch-dir", "append"],
string: ["patch-dir", "append", "rebase"],
})
const packageNames = argv._

Expand All @@ -44,7 +45,30 @@ if (argv.version || argv.v) {
if (patchDir.startsWith("/")) {
throw new Error("--patch-dir must be a relative path")
}
if (packageNames.length) {
if ("rebase" in argv) {
if (!argv.rebase) {
console.error(
chalk.red(
"You must specify a patch file name or number when rebasing patches",
),
)
process.exit(1)
}
if (packageNames.length !== 1) {
console.error(
chalk.red(
"You must specify exactly one package name when rebasing patches",
),
)
process.exit(1)
}
rebase({
appPath,
packagePathSpecifier: packageNames[0],
patchDir,
targetPatch: argv.rebase,
})
} else if (packageNames.length) {
const includePaths = makeRegExp(
argv.include,
"include",
Expand Down
6 changes: 5 additions & 1 deletion src/makePatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,11 @@ export function makePatch({
},
]
if (nextState.length > 1) {
savePatchApplicationState(packageDetails, nextState)
savePatchApplicationState({
packageDetails,
patches: nextState,
isRebasing: false,
})
} else {
clearPatchApplicationState(packageDetails)
}
Expand Down
244 changes: 244 additions & 0 deletions src/rebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import chalk from "chalk"
import { existsSync } from "fs"
import { join, resolve } from "path"
import { applyPatch } from "./applyPatches"
import { hashFile } from "./hash"
import { PatchedPackageDetails } from "./PackageDetails"
import { getGroupedPatches } from "./patchFs"
import {
getPatchApplicationState,
savePatchApplicationState,
} from "./stateFile"

export function rebase({
appPath,
patchDir,
packagePathSpecifier,
targetPatch,
}: {
appPath: string
patchDir: string
packagePathSpecifier: string
targetPatch: string
}): void {
const patchesDirectory = join(appPath, patchDir)
const groupedPatches = getGroupedPatches(patchesDirectory)

if (groupedPatches.numPatchFiles === 0) {
console.error(chalk.blueBright("No patch files found"))
process.exit(1)
}

const packagePatches =
groupedPatches.pathSpecifierToPatchFiles[packagePathSpecifier]
if (!packagePatches) {
console.error(
chalk.blueBright("No patch files found for package"),
packagePathSpecifier,
)
process.exit(1)
}

const state = getPatchApplicationState(packagePatches[0])

if (!state) {
console.error(
chalk.blueBright("No patch state found"),
"Did you forget to run",
chalk.bold("patch-package"),
"(without arguments) first?",
)
process.exit(1)
}
if (state.isRebasing) {
console.error(
chalk.blueBright("Already rebasing"),
"Make changes to the files in",
chalk.bold(packagePatches[0].path),
"and then run `patch-package",
packagePathSpecifier,
"--continue` to",
packagePatches.length === state.patches.length
? "append a patch file"
: `update the ${
packagePatches[packagePatches.length - 1].patchFilename
} file`,
)
console.error(
`💡 To remove a broken patch file, delete it and reinstall node_modules`,
)
process.exit(1)
}
if (state.patches.length !== packagePatches.length) {
console.error(
chalk.blueBright("Some patches have not been applied."),
"Reinstall node_modules and try again.",
)
}
// check hashes
for (let i = 0; i < state.patches.length; i++) {
const patch = state.patches[i]
const fullPatchPath = join(
patchesDirectory,
packagePatches[i].patchFilename,
)
if (!existsSync(fullPatchPath)) {
console.error(
chalk.blueBright("Expected patch file"),
fullPatchPath,
"to exist but it is missing. Try completely reinstalling node_modules first.",
)
process.exit(1)
}
if (patch.patchContentHash !== hashFile(fullPatchPath)) {
console.error(
chalk.blueBright("Patch file"),
fullPatchPath,
"has changed since it was applied. Try completely reinstalling node_modules first.",
)
}
}

if (targetPatch === "0") {
// unapply all
unApplyPatches({
patches: packagePatches,
appPath,
patchDir,
})
savePatchApplicationState({
packageDetails: packagePatches[0],
isRebasing: true,
patches: [],
})
console.log(`
Make any changes you need inside ${chalk.bold(packagePatches[0].path)}
When you are done, run
${chalk.bold(
`patch-package ${packagePathSpecifier} --append 'MyChangeDescription'`,
)}
to insert a new patch file.
`)
return
}

// find target patch
const target = packagePatches.find((p) => {
if (p.patchFilename === targetPatch) {
return true
}
if (
resolve(process.cwd(), targetPatch) ===
join(patchesDirectory, p.patchFilename)
) {
return true
}

if (targetPatch === p.sequenceName) {
return true
}
const n = Number(targetPatch.replace(/^0+/g, ""))
if (!isNaN(n) && n === p.sequenceNumber) {
return true
}
return false
})

if (!target) {
console.error(
chalk.red("Could not find target patch file"),
chalk.bold(targetPatch),
)
console.error()
console.error("The list of available patch files is:")
packagePatches.forEach((p) => {
console.error(` - ${p.patchFilename}`)
})

process.exit(1)
}
const currentHash = hashFile(join(patchesDirectory, target.patchFilename))

const prevApplication = state.patches.find(
(p) => p.patchContentHash === currentHash,
)
if (!prevApplication) {
console.error(
chalk.red("Could not find previous application of patch file"),
chalk.bold(target.patchFilename),
)
console.error()
console.error("You should reinstall node_modules and try again.")
process.exit(1)
}

// ok, we are good to start undoing all the patches that were applied up to but not including the target patch
const targetIdx = state.patches.indexOf(prevApplication)

unApplyPatches({
patches: packagePatches.slice(targetIdx + 1),
appPath,
patchDir,
})
savePatchApplicationState({
packageDetails: packagePatches[0],
isRebasing: true,
patches: packagePatches.slice(0, targetIdx + 1).map((p) => ({
patchFilename: p.patchFilename,
patchContentHash: hashFile(join(patchesDirectory, p.patchFilename)),
didApply: true,
})),
})

console.log(`
Make any changes you need inside ${chalk.bold(packagePatches[0].path)}
When you are done, do one of the following:
To update ${chalk.bold(packagePatches[targetIdx].patchFilename)} run
${chalk.bold(`patch-package ${packagePathSpecifier}`)}
To create a new patch file after ${chalk.bold(
packagePatches[targetIdx].patchFilename,
)} run
${chalk.bold(
`patch-package ${packagePathSpecifier} --append 'MyChangeDescription'`,
)}
`)
}

function unApplyPatches({
patches,
appPath,
patchDir,
}: {
patches: PatchedPackageDetails[]
appPath: string
patchDir: string
}) {
for (const patch of patches.slice().reverse()) {
if (
!applyPatch({
patchFilePath: join(appPath, patchDir, patch.patchFilename) as string,
reverse: true,
patchDetails: patch,
patchDir,
cwd: process.cwd(),
})
) {
console.error(
chalk.red("Failed to un-apply patch file"),
chalk.bold(patch.patchFilename),
"Try completely reinstalling node_modules.",
)
process.exit(1)
}
console.log(chalk.green("Un-applied patch file"), patch.patchFilename)
}
}
Loading

0 comments on commit 1f27349

Please sign in to comment.