2015-05-21 12:07:52 -04:00
|
|
|
import fs = require('fs');
|
|
|
|
import fse = require('fs-extra');
|
|
|
|
import path = require('path');
|
|
|
|
var symlinkOrCopySync = require('symlink-or-copy').sync;
|
|
|
|
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
|
|
|
|
2015-05-31 20:24:21 -04:00
|
|
|
var isWindows = process.platform === 'win32';
|
|
|
|
|
2016-03-24 13:00:19 -04:00
|
|
|
export interface MergeTreesOptions { overwrite?: boolean; }
|
2015-05-21 12:07:52 -04:00
|
|
|
|
2016-01-22 13:51:16 -05:00
|
|
|
function outputFileSync(sourcePath: string, destPath: string) {
|
2015-05-21 12:07:52 -04:00
|
|
|
let dirname = path.dirname(destPath);
|
|
|
|
fse.mkdirsSync(dirname, {fs: fs});
|
|
|
|
symlinkOrCopySync(sourcePath, destPath);
|
|
|
|
}
|
|
|
|
|
2016-01-22 13:51:16 -05:00
|
|
|
function pathOverwrittenError(path: string) {
|
2015-06-16 12:24:29 -04:00
|
|
|
const msg = 'Either remove the duplicate or enable the "overwrite" option for this merge.';
|
|
|
|
return new Error(`Duplicate path found while merging trees. Path: "${path}".\n${msg}`);
|
|
|
|
}
|
|
|
|
|
2015-05-21 12:07:52 -04:00
|
|
|
export class MergeTrees implements DiffingBroccoliPlugin {
|
2015-06-04 17:39:53 -04:00
|
|
|
private pathCache: {[key: string]: number[]} = Object.create(null);
|
|
|
|
public options: MergeTreesOptions;
|
|
|
|
private firstBuild: boolean = true;
|
2015-05-21 12:07:52 -04:00
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
constructor(public inputPaths: string[], public cachePath: string,
|
|
|
|
options: MergeTreesOptions = {}) {
|
2015-06-04 17:39:53 -04:00
|
|
|
this.options = options || {};
|
|
|
|
}
|
2015-05-21 12:07:52 -04:00
|
|
|
|
|
|
|
rebuild(treeDiffs: DiffResult[]) {
|
2015-06-04 17:39:53 -04:00
|
|
|
let overwrite = this.options.overwrite;
|
|
|
|
let pathsToEmit: string[] = [];
|
|
|
|
let pathsToRemove: string[] = [];
|
|
|
|
let emitted: {[key: string]: boolean} = Object.create(null);
|
2016-01-22 13:51:16 -05:00
|
|
|
let contains = (cache: number[], val: number) => {
|
2015-06-04 17:39:53 -04:00
|
|
|
for (let i = 0, ii = cache.length; i < ii; ++i) {
|
|
|
|
if (cache[i] === val) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2016-01-22 13:51:16 -05:00
|
|
|
let emit = (relativePath: string) => {
|
2015-06-04 17:39:53 -04:00
|
|
|
// ASSERT(!emitted[relativePath]);
|
|
|
|
pathsToEmit.push(relativePath);
|
|
|
|
emitted[relativePath] = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (this.firstBuild) {
|
2015-05-31 20:24:21 -04:00
|
|
|
this.firstBuild = false;
|
|
|
|
|
2015-06-04 17:39:53 -04:00
|
|
|
// Build initial cache
|
2016-01-22 13:51:16 -05:00
|
|
|
treeDiffs.reverse().forEach((treeDiff: DiffResult, index: number) => {
|
2015-06-04 17:39:53 -04:00
|
|
|
index = treeDiffs.length - 1 - index;
|
2015-05-31 20:24:21 -04:00
|
|
|
treeDiff.addedPaths.forEach((changedPath) => {
|
2015-06-04 17:39:53 -04:00
|
|
|
let cache = this.pathCache[changedPath];
|
|
|
|
if (cache === undefined) {
|
|
|
|
this.pathCache[changedPath] = [index];
|
|
|
|
pathsToEmit.push(changedPath);
|
|
|
|
} else if (overwrite) {
|
|
|
|
// ASSERT(contains(pathsToEmit, changedPath));
|
|
|
|
cache.unshift(index);
|
|
|
|
} else {
|
2015-06-16 12:24:29 -04:00
|
|
|
throw pathOverwrittenError(changedPath);
|
2015-06-04 17:39:53 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2015-05-31 20:24:21 -04:00
|
|
|
|
2015-06-04 17:39:53 -04:00
|
|
|
} else {
|
|
|
|
// Update cache
|
2016-01-22 13:51:16 -05:00
|
|
|
treeDiffs.reverse().forEach((treeDiff: DiffResult, index: number) => {
|
2015-06-04 17:39:53 -04:00
|
|
|
index = treeDiffs.length - 1 - index;
|
2015-06-18 18:44:44 -04:00
|
|
|
if (treeDiff.removedPaths) {
|
|
|
|
treeDiff.removedPaths.forEach((removedPath) => {
|
|
|
|
let cache = this.pathCache[removedPath];
|
|
|
|
// ASSERT(cache !== undefined);
|
|
|
|
// ASSERT(contains(cache, index));
|
|
|
|
if (cache[cache.length - 1] === index) {
|
|
|
|
pathsToRemove.push(path.join(this.cachePath, removedPath));
|
|
|
|
cache.pop();
|
|
|
|
if (cache.length === 0) {
|
|
|
|
this.pathCache[removedPath] = undefined;
|
|
|
|
} else if (!emitted[removedPath]) {
|
|
|
|
if (cache.length === 1 && !overwrite) {
|
|
|
|
throw pathOverwrittenError(removedPath);
|
|
|
|
}
|
|
|
|
emit(removedPath);
|
2015-06-04 17:39:53 -04:00
|
|
|
}
|
|
|
|
}
|
2015-06-18 18:44:44 -04:00
|
|
|
});
|
|
|
|
}
|
2015-05-31 20:24:21 -04:00
|
|
|
|
|
|
|
let pathsToUpdate = treeDiff.addedPaths;
|
|
|
|
|
|
|
|
if (isWindows) {
|
|
|
|
pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths);
|
|
|
|
}
|
|
|
|
|
|
|
|
pathsToUpdate.forEach((changedPath) => {
|
2015-06-04 17:39:53 -04:00
|
|
|
let cache = this.pathCache[changedPath];
|
|
|
|
if (cache === undefined) {
|
|
|
|
// File was added
|
|
|
|
this.pathCache[changedPath] = [index];
|
|
|
|
emit(changedPath);
|
|
|
|
} else if (!contains(cache, index)) {
|
|
|
|
cache.push(index);
|
|
|
|
cache.sort((a, b) => a - b);
|
|
|
|
if (cache.length > 1 && !overwrite) {
|
2015-06-16 12:24:29 -04:00
|
|
|
throw pathOverwrittenError(changedPath);
|
2015-06-04 17:39:53 -04:00
|
|
|
}
|
|
|
|
if (cache[cache.length - 1] === index && !emitted[changedPath]) {
|
|
|
|
emit(changedPath);
|
|
|
|
}
|
2015-05-21 12:07:52 -04:00
|
|
|
}
|
2015-06-04 17:39:53 -04:00
|
|
|
});
|
2015-05-21 12:07:52 -04:00
|
|
|
});
|
2015-06-04 17:39:53 -04:00
|
|
|
}
|
2015-05-21 12:07:52 -04:00
|
|
|
|
2015-06-04 17:39:53 -04:00
|
|
|
pathsToRemove.forEach((destPath) => fse.removeSync(destPath));
|
|
|
|
pathsToEmit.forEach((emittedPath) => {
|
|
|
|
let cache = this.pathCache[emittedPath];
|
|
|
|
let destPath = path.join(this.cachePath, emittedPath);
|
|
|
|
let sourceIndex = cache[cache.length - 1];
|
|
|
|
let sourceInputPath = this.inputPaths[sourceIndex];
|
|
|
|
let sourcePath = path.join(sourceInputPath, emittedPath);
|
|
|
|
if (cache.length > 1) {
|
2015-05-21 12:07:52 -04:00
|
|
|
fse.removeSync(destPath);
|
2015-06-04 17:39:53 -04:00
|
|
|
}
|
|
|
|
outputFileSync(sourcePath, destPath);
|
2015-05-21 12:07:52 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default wrapDiffingPlugin(MergeTrees);
|