Skip to content

Commit

Permalink
Merge pull request #474 from ds300/multiple-patches
Browse files Browse the repository at this point in the history
Support loading multiple patches.
  • Loading branch information
ds300 committed Jul 27, 2023
2 parents 3d407bd + 3ba21d8 commit efd88cc
Show file tree
Hide file tree
Showing 133 changed files with 5,002 additions and 671 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/main.yml
@@ -1,4 +1,7 @@
on: [push, pull_request]
on:
pull_request:
push:
branches: [master]
name: Test
jobs:
test:
Expand Down
131 changes: 121 additions & 10 deletions README.md
Expand Up @@ -61,13 +61,15 @@ files.

### yarn v2+

yarn 2+ have native support for patching dependencies via [`yarn patch`](https://yarnpkg.com/cli/patch).
You do not need to use patch-package on these projects.
yarn 2+ have native support for patching dependencies via
[`yarn patch`](https://yarnpkg.com/cli/patch). You do not need to use
patch-package on these projects.

### pnpm

pnpm has native support for patching dependencies via [`pnpm patch`](https://pnpm.io/cli/patch).
You do not need to use patch-package on these projects.
pnpm has native support for patching dependencies via
[`pnpm patch`](https://pnpm.io/cli/patch). You do not need to use patch-package
on these projects.

### Heroku

Expand All @@ -88,30 +90,38 @@ details.
Otherwise if you update a patch then the change may not be reflected on
subsequent CI runs.


### CircleCI
Create a hash of your patches before loading/saving your cache. If using a Linux machine, run `md5sum patches/* > patches.hash`. If running on a macOS machine, use `md5 patches/* > patches.hash`

Create a hash of your patches before loading/saving your cache. If using a Linux
machine, run `md5sum patches/* > patches.hash`. If running on a macOS machine,
use `md5 patches/* > patches.hash`

```yaml
- run:
name: patch-package hash
command: md5sum patches/* > patches.hash
```

Then, update your hash key to include a checksum of that file:

```yaml
- restore_cache:
key: app-node_modules-v1-{{ checksum "yarn.lock" }}-{{ checksum "patches.hash" }}
```
key:
app-node_modules-v1-{{ checksum "yarn.lock" }}-{{ checksum "patches.hash"
}}
```

As well as the save_cache

```yaml
- save_cache:
key: app-node_modules-v1-{{ checksum "yarn.lock" }}-{{ checksum "patches.hash" }}
key:
app-node_modules-v1-{{ checksum "yarn.lock" }}-{{ checksum "patches.hash"
}}
paths:
- ./node_modules
```


## Usage

### Making patches
Expand Down Expand Up @@ -248,6 +258,107 @@ to
This will allow those patch files to be safely ignored when
`NODE_ENV=production`.

### Creating multiple patches for the same package

_💡 This is an advanced feature and is not recommended unless you really, really
need it._

Let's say you have a patch for react-native called

- `patches/react-native+0.72.0.patch`

If you want to add another patch file to `react-native`, you can use the
`--append` flag while supplying a name for the patch.

Just make you changes inside `node_modules/react-native` then run e.g.

npx patch-package react-native --append 'fix-touchable-opacity'

This will create a new patch file while renaming the old patch file so that you
now have:

- `patches/react-native+0.72.0+001+initial.patch`
- `patches/react-native+0.72.0+002+fix-touchable-opacity.patch`

The patches are ordered in a sequence, so that they can build on each other if
necessary. **Think of these as commits in a git history**.

#### Updating a sequenced patch file

If the patch file is the last one in the sequence, you can just make your
changes inside e.g. `node_modules/react-native` and then run

npx patch-package react-native

This will update the last patch file in the sequence.

If the patch file is not the last one in the sequence **you need to use the
`--rebase` feature** to un-apply the succeeding patch files first.

Using the example above, let's say you want to update the `001+initial` patch
but leave the other patch alone. You can run

npx patch-package react-native --rebase patches/react-native+0.72.0+001+initial.patch

This will undo the `002-fix-touchable-opacity` patch file. You can then make
your changes and run

npx patch-package react-native

to finish the rebase by updating the `001+initial` patch file and re-apply the
`002-fix-touchable-opacity` patch file, leaving you with all patches applied and
up-to-date.

#### Inserting a new patch file in the middle of an existing sequence

Using the above example, let's say you want to insert a new patch file between
the `001+initial` and `002+fix-touchable-opacity` patch files. You can run

npx patch-package react-native --rebase patches/react-native+0.72.0+001+initial.patch

This will undo the `002-fix-touchable-opacity` patch file. You can then make any
changes you want to insert in a new patch file and run

npx patch-package react-native --append 'fix-console-warnings'

This will create a new patch file while renaming any successive patches to
maintain the sequence order, leaving you with

- `patches/react-native+0.72.0+001+initial.patch`
- `patches/react-native+0.72.0+002+fix-console-warnings.patch`
- `patches/react-native+0.72.0+003+fix-touchable-opacity.patch`

To insert a new patch file at the start of the sequence, you can run

npx patch-package react-native --rebase 0

Which will un-apply all patch files in the sequence. Then follow the process
above to create a new patch file numbered `001`.

#### Deleting a sequenced patch file

To delete a sequenced patch file, just delete it, then remove and reinstall your
`node_modules` folder.

If you deleted one of the patch files other than the last one, you don't need to
update the sequence numbers in the successive patch file names, but you might
want to do so to keep things tidy.

#### Partially applying a broken patch file

Normally patch application is atomic per patch file. i.e. if a patch file
contains an error anywhere then none of the changes in the patch file will be
applied and saved to disk.

This can be problematic if you have a patch with many changes and you want to
keep some of them and update others.

In this case you can use the `--partial` option. Patch-package will apply as
many of the changes as it can and then leave it to you to fix the rest.

Any errors encountered will be written to a file `./patch-package-errors.log` to
help you keep track of what needs fixing.

## Benefits of patching over forking

- Sometimes forks need extra build steps, e.g. with react-native for Android.
Expand Down
@@ -0,0 +1,98 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Test append-patches: 00: basic patch file 1`] = `
"SNAPSHOT: basic patch file
left-pad+1.3.0.patch
END SNAPSHOT"
`;

exports[`Test append-patches: 01: after appending a patch file 1`] = `
"SNAPSHOT: after appending a patch file
left-pad+1.3.0+001+initial.patch
left-pad+1.3.0+002+MillionDollars.patch
END SNAPSHOT"
`;

exports[`Test append-patches: 02: the second patch file should go from patch-package to a million dollars 1`] = `
"SNAPSHOT: the second patch file should go from patch-package to a million dollars
diff --git a/node_modules/left-pad/index.js b/node_modules/left-pad/index.js
index a409e14..73d2a7c 100644
--- a/node_modules/left-pad/index.js
+++ b/node_modules/left-pad/index.js
@@ -3,7 +3,7 @@
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details. */
-'use patch-package';
+'use a million dollars';
module.exports = leftPad;
var cache = [
END SNAPSHOT"
`;

exports[`Test append-patches: 03: creating a first patch file with --append 1`] = `
"SNAPSHOT: creating a first patch file with --append
left-pad+1.3.0+001+FirstPatch.patch
END SNAPSHOT"
`;

exports[`Test append-patches: 04: the squashed patch file should go from use strict to a million dollars 1`] = `
"SNAPSHOT: the squashed patch file should go from use strict to a million dollars
diff --git a/node_modules/left-pad/index.js b/node_modules/left-pad/index.js
index e90aec3..73d2a7c 100644
--- a/node_modules/left-pad/index.js
+++ b/node_modules/left-pad/index.js
@@ -3,7 +3,7 @@
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details. */
-'use strict';
+'use a million dollars';
module.exports = leftPad;
var cache = [
END SNAPSHOT"
`;

exports[`Test append-patches: 05: after appending a billion dollars 1`] = `
"SNAPSHOT: after appending a billion dollars
left-pad+1.3.0+001+FirstPatch.patch
left-pad+1.3.0+002+BillionDollars.patch
END SNAPSHOT"
`;

exports[`Test append-patches: 06: after updating the appended patch file to a TRILLION dollars 1`] = `
"SNAPSHOT: after updating the appended patch file to a TRILLION dollars
diff --git a/node_modules/left-pad/index.js b/node_modules/left-pad/index.js
index 73d2a7c..f53ea10 100644
--- a/node_modules/left-pad/index.js
+++ b/node_modules/left-pad/index.js
@@ -3,7 +3,7 @@
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details. */
-'use a million dollars';
+'use a trillion dollars';
module.exports = leftPad;
var cache = [
END SNAPSHOT"
`;

exports[`Test append-patches: 07: patch-package fails when a patch in the sequence is invalid 1`] = `
"SNAPSHOT: patch-package fails when a patch in the sequence is invalid
patch-package 0.0.0
• Creating temporary folder
• Installing left-pad@1.3.0 with npm
• Diffing your files with clean files
Failed to apply patch left-pad+1.3.0+001+FirstPatch.patch to left-pad
END SNAPSHOT"
`;

exports[`Test append-patches: 08: --append is not compatible with --create-issue 1`] = `
"SNAPSHOT: --append is not compatible with --create-issue
patch-package 0.0.0
--create-issue is not compatible with --append.
END SNAPSHOT"
`;
84 changes: 84 additions & 0 deletions integration-tests/append-patches/append-patches.sh
@@ -0,0 +1,84 @@
#!/bin/bash
# make sure errors stop the script
set -e

npm install

echo "add patch-package"
npm add $1

function patch-package {
./node_modules/.bin/patch-package "$@"
}

function replace {
npx replace "$1" "$2" node_modules/left-pad/index.js
}

echo "making an initial patch file does not add a sequence number to the file by default"
replace 'use strict' 'use patch-package'

patch-package left-pad

echo "SNAPSHOT: basic patch file"
ls patches
echo "END SNAPSHOT"

echo "using --apend creates a patch file with a sequence number and updates the original patch file"

replace 'use patch-package' 'use a million dollars'

patch-package left-pad --append 'MillionDollars'

echo "SNAPSHOT: after appending a patch file"
ls patches
echo "END SNAPSHOT"

echo "SNAPSHOT: the second patch file should go from patch-package to a million dollars"
cat patches/left-pad*MillionDollars.patch
echo "END SNAPSHOT"

echo "we can squash the patches together by deleting the patch files"
rm patches/left-pad*patch

patch-package left-pad --append 'FirstPatch'

echo "SNAPSHOT: creating a first patch file with --append"
ls patches
echo "END SNAPSHOT"

echo "SNAPSHOT: the squashed patch file should go from use strict to a million dollars"
cat patches/left-pad*FirstPatch.patch
echo "END SNAPSHOT"

echo "i can update an appended patch file"

replace 'use a million dollars' 'use a billion dollars'

patch-package left-pad --append 'BillionDollars'

echo "SNAPSHOT: after appending a billion dollars"
ls patches
echo "END SNAPSHOT"

replace 'use a billion dollars' 'use a trillion dollars'
patch-package left-pad

echo "SNAPSHOT: after updating the appended patch file to a TRILLION dollars"
cat patches/left-pad*BillionDollars.patch
echo "END SNAPSHOT"

echo "if one of the patches in the sequence is invalid, the sequence is not applied"
npx replace 'use strict' 'use bananas' patches/*FirstPatch.patch

echo "SNAPSHOT: patch-package fails when a patch in the sequence is invalid"
if patch-package left-pad --append 'Bananas' ; then
exit 1
fi
echo "END SNAPSHOT"

echo "SNAPSHOT: --append is not compatible with --create-issue"
if patch-package left-pad --append 'Bananas' --create-issue ; then
exit 1
fi
echo "END SNAPSHOT"
@@ -1,5 +1,5 @@
import { runIntegrationTest } from "../runIntegrationTest"
runIntegrationTest({
projectName: "delete-old-patch-files",
shouldProduceSnapshots: false,
projectName: "append-patches",
shouldProduceSnapshots: true,
})

0 comments on commit efd88cc

Please sign in to comment.