Skip to content

Instantly share code, notes, and snippets.

@dmos62
Created September 20, 2017 18:42
Show Gist options
  • Save dmos62/5ac03373328a9d9cdc3288185f531547 to your computer and use it in GitHub Desktop.
Save dmos62/5ac03373328a9d9cdc3288185f531547 to your computer and use it in GitHub Desktop.
Patcher for Node.js that uses hashes to not patch twice
/*
Usage looks like this:
```
$ node auto-patch.js get \
--original node_modules/epub.js/build/epub.js \
--changed epubjs.changed > patches/epubjs.patch
$ node auto-patch.js apply patches/epubjs.patch
```
The patch knows the hash of the resulting file, so, before applying,
it checks if the hashes are different. If they are the same,
it silently skips patching, so the second command above can be rerun
multiple times without corrupting the file.
Example automatic patching with npm:
{
...,
"scripts": {
"patch-epubjs": "node auto-patch.js apply patches/epubjs.patch",
"prepare": "npm run patch-epubjs",
...
}
}
Requires: "node": ">=8.5.0"
*/
const Crypto = require('crypto')
const DiffMatchPatch = new (require('diff-match-patch'))
const Fs = require('fs')
const debug = require('debug')('auto-patch')
const getHash = (s) =>
{
const hash = Crypto.createHash('sha256')
hash.update(s)
return hash.digest('hex')
}
const getPatch = (originalString, changedString) =>
{
const patches = DiffMatchPatch.patch_make(originalString, changedString)
return DiffMatchPatch.patch_toText(patches)
}
const applyPatch = (string, patch) =>
{
const patches = DiffMatchPatch.patch_fromText(patch)
return DiffMatchPatch.patch_apply(patches, string)[0]
}
const getStringContents = (path) => String(Fs.readFileSync(path))
const getAutoPatch = (originalFile, changedFile, optPath = undefined) =>
{
const originalString = getStringContents(originalFile)
const changedString = getStringContents(changedFile)
const path = optPath || originalFile
const hash = getHash(changedString)
const patch = getPatch(originalString, changedString)
const json =
{
path,
hash,
patch
}
return json
}
const overwriteFile = (path, stringContents) =>
Fs.writeFileSync(path, stringContents)
const backupOriginal = (path) => Fs.copyFileSync(path, path + '.unpatched')
const applyAutoPatch = (autoPatch) =>
{
const path = autoPatch.path
const currentString = getStringContents(path)
const currentHash = getHash(currentString)
if (currentHash == autoPatch.hash) {
debug(`${path} is already patched. Skipping.`)
return
}
const patchedString = applyPatch(currentString, autoPatch.patch)
if (autoPatch.hash != getHash(patchedString)) {
console.error(
`Patch for ${path} is corrupted,` +
` specified hash does not match the resulting hash.`
)
return
}
debug(`Backing up and patching ${path}.`)
backupOriginal(path)
overwriteFile(path, patchedString)
}
const argv = require('minimist')(process.argv.slice(2));
switch (true) {
case argv._[0] == 'get':
// usage:
// `node auto-patch.js get --original original.js --changed changed.js`
// optionally, --path, that the resulting patch will patch,
// can be specified separately. otherwise will be same as --original.
{
const patch = getAutoPatch(argv.original, argv.changed, argv.path)
const json = JSON.stringify(patch)
process.stdout.write(json)
}
break
case argv._[0] == 'apply':
// usage: node auto-patch.js apply path/to/the.patch
{
const json = getStringContents(argv._[1])
const patch = JSON.parse(json)
applyAutoPatch(patch)
}
break
default:
console.error('Wrong arguments.')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment