Skip to content

Commit

Permalink
Merged in lettertwo/merge-driver (pull request #64)
Browse files Browse the repository at this point in the history
Add custom merge driver

Approved-by: Brian Do
  • Loading branch information
lettertwo committed Mar 7, 2022
2 parents 4dc3087 + 1081acf commit 7d5a921
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Expand Up @@ -5,3 +5,5 @@
*.png binary
*.wasm binary
*.woff2 binary
package.json merge=atlassian-package-json
test/**/package.json !merge
6 changes: 6 additions & 0 deletions .husky/post-checkout
@@ -0,0 +1,6 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# ATLASSIAN: Configure a custom merge driver to make merges from upstream easier
git config merge.atlassian-package-json.name "Merge driver for @atlassian/parcel package.json files"
git config merge.atlassian-package-json.driver "./scripts/atlassian-merge-package-json.js %A %O %B %P"
142 changes: 142 additions & 0 deletions scripts/atlassian-merge-package-json.js
@@ -0,0 +1,142 @@
#!/usr/bin/env node

/* eslint-disable no-console */

// Custom git merge driver for @atlassian/parcel package.json files

let fs = require('fs');
let spawnSync = require('child_process').spawnSync;

function merge(ours, base, theirs) {
return spawnSync(
'git',
[
'merge-file',
'-p',
'-L',
'current',
'-L',
'base',
'-L',
'incoming',
ours,
base,
theirs,
],
{stdio: [0, 'pipe', 2]},
);
}

function loadPackageJson(filepath) {
return JSON.parse(fs.readFileSync(filepath, 'utf-8'));
}

function writePackageJson(filepath, data) {
return fs.writeFileSync(filepath, JSON.stringify(data, null, 2));
}

function has(obj, key) {
if (obj == null) return false;
return Object.prototype.hasOwnProperty.call(obj, key);
}

function copyValue(path, ours, base, theirs) {
let name = path;
if (Array.isArray(path)) {
for (let i = 0; i < path.length - 1; i++) {
ours = ours?.[path[i]];
base = base?.[path[i]];
theirs = theirs?.[path[i]];
}
name = path[path.length - 1];
}
if (has(ours, name) && has(base, name) && has(theirs, name)) {
base[name] = ours[name];
theirs[name] = ours[name];
return true;
}
return false;
}

let cfg = {
// @parcel packages that are versioned separately from parcel
// (e.g., not in the monorepo) should be explicitly excluded.
'@parcel/css': false,
'@parcel/source-map': false,
'@parcel/watcher': false,

// Packages that should be patched but don't match @parcel/*
fuzzer: true,
parcel: true,
};

function shouldPatch(name) {
return name && (cfg[name] ?? name.startsWith('@parcel/'));
}

function patchVersions(ours, base, theirs) {
let patched = copyValue('version', ours, base, theirs);
patched = copyValue(['engines', 'parcel'], ours, base, theirs) || patched;
for (let type of [
'dependencies',
'devDependencies',
'peerDependencies',
'parcelDependencies',
]) {
if (has(ours, type)) {
for (let name of Object.keys(ours[type])) {
if (shouldPatch(name)) {
patched = copyValue([type, name], ours, base, theirs) || patched;
}
}
}
}
return patched;
}

if (require.main === module) {
let args = process.argv.slice(2);
if (args.length < 4) {
console.log(
`To use this merge driver, do the following:
git config merge.atlassian-package-json.name "Merge driver for @atlassian/parcel package.json files"
git config merge.atlassian-package-json.driver "./scripts/atlassian-merge-package-json.js %A %O %B %P"
`,
);
process.exit(1);
}

let [ours, base, theirs, src] = args;

// Try a merge first. Maybe it will be fine?
let {stdout, status} = merge(ours, base, theirs);

// Non-zero status means the merge failed.
if (status) {
try {
let ourJson = loadPackageJson(ours);
if (shouldPatch(ourJson.name)) {
let baseJson = loadPackageJson(base);
let theirJson = loadPackageJson(theirs);
if (patchVersions(ourJson, baseJson, theirJson)) {
console.error(`Patching Parcel versions in ${src}`);
writePackageJson(base, baseJson);
writePackageJson(theirs, theirJson);
// Try to merge again after patching version numbers.
({stdout, status} = merge(ours, base, theirs));
}
}
} catch (e) {
console.error(e);
}
}

// from `man gitattributes`:
// > The merge driver is expected to leave the result of the merge
// > in the file named with %A by overwriting it, and exit
// > with zero status if it managed to merge them cleanly,
// > or non-zero if there were conflicts.
fs.writeFileSync(ours, stdout);
process.exit(status);
}

0 comments on commit 7d5a921

Please sign in to comment.