Skip to content

Commit 4ee3fda

Browse files
committed
feat(broccoli): add diffing MergeTrees plugin
Closes angular#1815 Closes angular#2064
1 parent 41ae8e7 commit 4ee3fda

File tree

5 files changed

+136
-3
lines changed

5 files changed

+136
-3
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/// <reference path="../typings/node/node.d.ts" />
2+
/// <reference path="../typings/jasmine/jasmine.d.ts" />
3+
4+
let mockfs = require('mock-fs');
5+
import fs = require('fs');
6+
import {TreeDiffer} from './tree-differ';
7+
import {MergeTrees} from './broccoli-merge-trees';
8+
9+
describe('MergeTrees', () => {
10+
afterEach(() => mockfs.restore());
11+
12+
function mergeTrees(inputPaths, cachePath, options) {
13+
return new MergeTrees(inputPaths, cachePath, options);
14+
}
15+
16+
function MakeTreeDiffers(rootDirs) {
17+
let treeDiffers = rootDirs.map((rootDir) => new TreeDiffer('MergeTrees', rootDir));
18+
treeDiffers.diffTrees = () => { return treeDiffers.map(tree => tree.diffTree()); };
19+
return treeDiffers;
20+
}
21+
22+
function read(path) { return fs.readFileSync(path, "utf-8"); }
23+
24+
it('should copy the file from the right-most inputTree', () => {
25+
let testDir: any = {
26+
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
27+
'tree2': {'foo.js': mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)})},
28+
'tree3': {'foo.js': mockfs.file({content: 'tree3/foo.js content', mtime: new Date(1000)})}
29+
};
30+
mockfs(testDir);
31+
let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']);
32+
let treeMerger = mergeTrees(['tree1', 'tree2', 'tree3'], 'dest', {});
33+
treeMerger.rebuild(treeDiffer.diffTrees());
34+
expect(read('dest/foo.js')).toBe('tree3/foo.js content');
35+
36+
delete testDir.tree2['foo.js'];
37+
delete testDir.tree3['foo.js'];
38+
mockfs(testDir);
39+
treeMerger.rebuild(treeDiffer.diffTrees());
40+
expect(read('dest/foo.js')).toBe('tree1/foo.js content');
41+
42+
testDir.tree2['foo.js'] = mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)});
43+
mockfs(testDir);
44+
treeMerger.rebuild(treeDiffer.diffTrees());
45+
expect(read('dest/foo.js')).toBe('tree2/foo.js content');
46+
});
47+
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import fs = require('fs');
2+
import fse = require('fs-extra');
3+
import path = require('path');
4+
var symlinkOrCopySync = require('symlink-or-copy').sync;
5+
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
6+
7+
function pathExists(filePath) {
8+
try {
9+
if (fs.statSync(filePath)) {
10+
return true;
11+
}
12+
} catch (e) {
13+
if (e.code !== "ENOENT") {
14+
throw e;
15+
}
16+
}
17+
return false;
18+
}
19+
20+
function outputFileSync(sourcePath, destPath) {
21+
let dirname = path.dirname(destPath);
22+
fse.mkdirsSync(dirname, {fs: fs});
23+
fse.removeSync(destPath);
24+
symlinkOrCopySync(sourcePath, destPath);
25+
}
26+
27+
export class MergeTrees implements DiffingBroccoliPlugin {
28+
private mergedPaths: {[key: string]: number} = Object.create(null);
29+
30+
constructor(public inputPaths: string[], public cachePath: string, public options) {}
31+
32+
rebuild(treeDiffs: DiffResult[]) {
33+
treeDiffs.forEach((treeDiff: DiffResult, index) => {
34+
let inputPath = this.inputPaths[index];
35+
let existsLater = (relativePath) => {
36+
for (let i = treeDiffs.length - 1; i > index; --i) {
37+
if (pathExists(path.join(this.inputPaths[i], relativePath))) {
38+
return true;
39+
}
40+
}
41+
return false;
42+
};
43+
let existsSooner = (relativePath) => {
44+
for (let i = index - 1; i >= 0; --i) {
45+
if (pathExists(path.join(this.inputPaths[i], relativePath))) {
46+
return i;
47+
}
48+
}
49+
return -1;
50+
};
51+
treeDiff.changedPaths.forEach((changedPath) => {
52+
let inputTreeIndex = this.mergedPaths[changedPath];
53+
if (inputTreeIndex !== index && !existsLater(changedPath)) {
54+
inputTreeIndex = this.mergedPaths[changedPath] = index;
55+
let sourcePath = path.join(inputPath, changedPath);
56+
let destPath = path.join(this.cachePath, changedPath);
57+
outputFileSync(sourcePath, destPath);
58+
}
59+
});
60+
61+
treeDiff.removedPaths.forEach((removedPath) => {
62+
let inputTreeIndex = this.mergedPaths[removedPath];
63+
64+
// if inputTreeIndex !== index, this same file was handled during
65+
// changedPaths handling
66+
if (inputTreeIndex !== index) return;
67+
68+
let destPath = path.join(this.cachePath, removedPath);
69+
fse.removeSync(destPath);
70+
let newInputTreeIndex = existsSooner(removedPath);
71+
72+
// Update cached value (to either newInputTreeIndex value or undefined)
73+
this.mergedPaths[removedPath] = newInputTreeIndex;
74+
75+
if (newInputTreeIndex >= 0) {
76+
// Copy the file from the newInputTreeIndex inputPath if necessary.
77+
let newInputPath = this.inputPaths[newInputTreeIndex];
78+
let sourcePath = path.join(newInputPath, removedPath);
79+
outputFileSync(sourcePath, destPath);
80+
}
81+
});
82+
});
83+
}
84+
}
85+
86+
export default wrapDiffingPlugin(MergeTrees);

tools/broccoli/trees/browser_tree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
var Funnel = require('broccoli-funnel');
44
var flatten = require('broccoli-flatten');
55
var htmlReplace = require('../html-replace');
6-
var mergeTrees = require('broccoli-merge-trees');
6+
import mergeTrees from '../broccoli-merge-trees';
77
var path = require('path');
88
var replace = require('broccoli-replace');
99
var stew = require('broccoli-stew');

tools/broccoli/trees/dart_tree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {MultiCopy} from './../multi_copy';
66
import destCopy from '../broccoli-dest-copy';
77
var Funnel = require('broccoli-funnel');
88
var glob = require('glob');
9-
var mergeTrees = require('broccoli-merge-trees');
9+
import mergeTrees from '../broccoli-merge-trees';
1010
var path = require('path');
1111
var renderLodashTemplate = require('broccoli-lodash');
1212
var replace = require('broccoli-replace');

tools/broccoli/trees/node_tree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import destCopy from '../broccoli-dest-copy';
44
import compileWithTypescript from '../broccoli-typescript';
55
import transpileWithTraceur from '../traceur/index';
66
var Funnel = require('broccoli-funnel');
7-
var mergeTrees = require('broccoli-merge-trees');
7+
import mergeTrees from '../broccoli-merge-trees';
88
var path = require('path');
99
var renderLodashTemplate = require('broccoli-lodash');
1010
var replace = require('broccoli-replace');

0 commit comments

Comments
 (0)