feat(broccoli): add diffing MergeTrees plugin

Closes #1815
Closes #2064
This commit is contained in:
Caitlin Potter 2015-05-21 12:07:52 -04:00
parent 41ae8e76f0
commit 4ee3fdaf7f
5 changed files with 136 additions and 3 deletions

View File

@ -0,0 +1,47 @@
/// <reference path="../typings/node/node.d.ts" />
/// <reference path="../typings/jasmine/jasmine.d.ts" />
let mockfs = require('mock-fs');
import fs = require('fs');
import {TreeDiffer} from './tree-differ';
import {MergeTrees} from './broccoli-merge-trees';
describe('MergeTrees', () => {
afterEach(() => mockfs.restore());
function mergeTrees(inputPaths, cachePath, options) {
return new MergeTrees(inputPaths, cachePath, options);
}
function MakeTreeDiffers(rootDirs) {
let treeDiffers = rootDirs.map((rootDir) => new TreeDiffer('MergeTrees', rootDir));
treeDiffers.diffTrees = () => { return treeDiffers.map(tree => tree.diffTree()); };
return treeDiffers;
}
function read(path) { return fs.readFileSync(path, "utf-8"); }
it('should copy the file from the right-most inputTree', () => {
let testDir: any = {
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
'tree2': {'foo.js': mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)})},
'tree3': {'foo.js': mockfs.file({content: 'tree3/foo.js content', mtime: new Date(1000)})}
};
mockfs(testDir);
let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']);
let treeMerger = mergeTrees(['tree1', 'tree2', 'tree3'], 'dest', {});
treeMerger.rebuild(treeDiffer.diffTrees());
expect(read('dest/foo.js')).toBe('tree3/foo.js content');
delete testDir.tree2['foo.js'];
delete testDir.tree3['foo.js'];
mockfs(testDir);
treeMerger.rebuild(treeDiffer.diffTrees());
expect(read('dest/foo.js')).toBe('tree1/foo.js content');
testDir.tree2['foo.js'] = mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)});
mockfs(testDir);
treeMerger.rebuild(treeDiffer.diffTrees());
expect(read('dest/foo.js')).toBe('tree2/foo.js content');
});
});

View File

@ -0,0 +1,86 @@
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';
function pathExists(filePath) {
try {
if (fs.statSync(filePath)) {
return true;
}
} catch (e) {
if (e.code !== "ENOENT") {
throw e;
}
}
return false;
}
function outputFileSync(sourcePath, destPath) {
let dirname = path.dirname(destPath);
fse.mkdirsSync(dirname, {fs: fs});
fse.removeSync(destPath);
symlinkOrCopySync(sourcePath, destPath);
}
export class MergeTrees implements DiffingBroccoliPlugin {
private mergedPaths: {[key: string]: number} = Object.create(null);
constructor(public inputPaths: string[], public cachePath: string, public options) {}
rebuild(treeDiffs: DiffResult[]) {
treeDiffs.forEach((treeDiff: DiffResult, index) => {
let inputPath = this.inputPaths[index];
let existsLater = (relativePath) => {
for (let i = treeDiffs.length - 1; i > index; --i) {
if (pathExists(path.join(this.inputPaths[i], relativePath))) {
return true;
}
}
return false;
};
let existsSooner = (relativePath) => {
for (let i = index - 1; i >= 0; --i) {
if (pathExists(path.join(this.inputPaths[i], relativePath))) {
return i;
}
}
return -1;
};
treeDiff.changedPaths.forEach((changedPath) => {
let inputTreeIndex = this.mergedPaths[changedPath];
if (inputTreeIndex !== index && !existsLater(changedPath)) {
inputTreeIndex = this.mergedPaths[changedPath] = index;
let sourcePath = path.join(inputPath, changedPath);
let destPath = path.join(this.cachePath, changedPath);
outputFileSync(sourcePath, destPath);
}
});
treeDiff.removedPaths.forEach((removedPath) => {
let inputTreeIndex = this.mergedPaths[removedPath];
// if inputTreeIndex !== index, this same file was handled during
// changedPaths handling
if (inputTreeIndex !== index) return;
let destPath = path.join(this.cachePath, removedPath);
fse.removeSync(destPath);
let newInputTreeIndex = existsSooner(removedPath);
// Update cached value (to either newInputTreeIndex value or undefined)
this.mergedPaths[removedPath] = newInputTreeIndex;
if (newInputTreeIndex >= 0) {
// Copy the file from the newInputTreeIndex inputPath if necessary.
let newInputPath = this.inputPaths[newInputTreeIndex];
let sourcePath = path.join(newInputPath, removedPath);
outputFileSync(sourcePath, destPath);
}
});
});
}
}
export default wrapDiffingPlugin(MergeTrees);

View File

@ -3,7 +3,7 @@
var Funnel = require('broccoli-funnel');
var flatten = require('broccoli-flatten');
var htmlReplace = require('../html-replace');
var mergeTrees = require('broccoli-merge-trees');
import mergeTrees from '../broccoli-merge-trees';
var path = require('path');
var replace = require('broccoli-replace');
var stew = require('broccoli-stew');

View File

@ -6,7 +6,7 @@ import {MultiCopy} from './../multi_copy';
import destCopy from '../broccoli-dest-copy';
var Funnel = require('broccoli-funnel');
var glob = require('glob');
var mergeTrees = require('broccoli-merge-trees');
import mergeTrees from '../broccoli-merge-trees';
var path = require('path');
var renderLodashTemplate = require('broccoli-lodash');
var replace = require('broccoli-replace');

View File

@ -4,7 +4,7 @@ import destCopy from '../broccoli-dest-copy';
import compileWithTypescript from '../broccoli-typescript';
import transpileWithTraceur from '../traceur/index';
var Funnel = require('broccoli-funnel');
var mergeTrees = require('broccoli-merge-trees');
import mergeTrees from '../broccoli-merge-trees';
var path = require('path');
var renderLodashTemplate = require('broccoli-lodash');
var replace = require('broccoli-replace');