build(broccoli): add support for DiffResult#addedPaths

Some plugins want to explicitly know of new paths, so we need to distinguish them from changed paths.
This commit is contained in:
Igor Minar 2015-05-31 17:24:21 -07:00
parent efab03274f
commit dc45559c17
13 changed files with 204 additions and 106 deletions

View File

@ -30,12 +30,13 @@ class DartFormatter implements DiffingBroccoliPlugin {
rebuild(treeDiff: DiffResult): Promise<any> { rebuild(treeDiff: DiffResult): Promise<any> {
let args = ['-w']; let args = ['-w'];
treeDiff.changedPaths.forEach((changedFile) => { treeDiff.addedPaths.concat(treeDiff.changedPaths)
let sourcePath = path.join(this.inputPath, changedFile); .forEach((changedFile) => {
let destPath = path.join(this.cachePath, changedFile); let sourcePath = path.join(this.inputPath, changedFile);
if (/\.dart$/.test(changedFile)) args.push(destPath); let destPath = path.join(this.cachePath, changedFile);
fse.copySync(sourcePath, destPath); if (/\.dart$/.test(changedFile)) args.push(destPath);
}); fse.copySync(sourcePath, destPath);
});
treeDiff.removedPaths.forEach((removedFile) => { treeDiff.removedPaths.forEach((removedFile) => {
let destPath = path.join(this.cachePath, removedFile); let destPath = path.join(this.cachePath, removedFile);
fse.removeSync(destPath); fse.removeSync(destPath);

View File

@ -15,13 +15,14 @@ class DestCopy implements DiffingBroccoliPlugin {
rebuild(treeDiff: DiffResult) { rebuild(treeDiff: DiffResult) {
treeDiff.changedPaths.forEach((changedFilePath) => { treeDiff.addedPaths.concat(treeDiff.changedPaths)
var destFilePath = path.join(this.outputRoot, changedFilePath); .forEach((changedFilePath) => {
var destFilePath = path.join(this.outputRoot, changedFilePath);
var destDirPath = path.dirname(destFilePath); var destDirPath = path.dirname(destFilePath);
fse.mkdirsSync(destDirPath); fse.mkdirsSync(destDirPath);
fse.copySync(path.join(this.inputPath, changedFilePath), destFilePath); fse.copySync(path.join(this.inputPath, changedFilePath), destFilePath);
}); });
treeDiff.removedPaths.forEach((removedFilePath) => { treeDiff.removedPaths.forEach((removedFilePath) => {
var destFilePath = path.join(this.outputRoot, removedFilePath); var destFilePath = path.join(this.outputRoot, removedFilePath);

View File

@ -52,4 +52,26 @@ describe('Flatten', () => {
expect(fs.readdirSync('output')).toEqual(['file-1.1.txt', 'file-2.txt', 'file-3.txt']); expect(fs.readdirSync('output')).toEqual(['file-1.1.txt', 'file-2.txt', 'file-3.txt']);
}); });
it('should throw an exception if duplicates are found', () => {
let testDir = {
'input': {
'dir1': {
'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}),
'subdir-1': {
'file-1.txt': mockfs.file({content: 'file-1.1.txt content', mtime: new Date(1000)})
},
'empty-dir': {}
},
},
'output': {}
};
mockfs(testDir);
let differ = new TreeDiffer('testLabel', 'input');
let flattenedTree = flatten('input');
expect(() => flattenedTree.rebuild(differ.diffTree())).
toThrowError("Duplicate file 'file-1.txt' found in path 'dir1/subdir-1/file-1.txt'");
});
}); });

View File

@ -2,6 +2,9 @@ import fs = require('fs');
import fse = require('fs-extra'); import fse = require('fs-extra');
import path = require('path'); import path = require('path');
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin'; import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
var symlinkOrCopy = require('symlink-or-copy').sync;
var isWindows = process.platform === 'win32';
/** /**
@ -11,8 +14,17 @@ import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-br
export class DiffingFlatten implements DiffingBroccoliPlugin { export class DiffingFlatten implements DiffingBroccoliPlugin {
constructor(private inputPath, private cachePath, private options) {} constructor(private inputPath, private cachePath, private options) {}
rebuild(treeDiff: DiffResult) { rebuild(treeDiff: DiffResult) {
treeDiff.changedPaths.forEach((changedFilePath) => { let pathsToUpdate = treeDiff.addedPaths;
// since we need to run on Windows as well we can't rely on symlinks being available,
// which means that we need to respond to both added and changed paths
if (isWindows) {
pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths);
}
pathsToUpdate.forEach((changedFilePath) => {
var sourceFilePath = path.join(this.inputPath, changedFilePath); var sourceFilePath = path.join(this.inputPath, changedFilePath);
var destFilePath = path.join(this.cachePath, path.basename(changedFilePath)); var destFilePath = path.join(this.cachePath, path.basename(changedFilePath));
var destDirPath = path.dirname(destFilePath); var destDirPath = path.dirname(destFilePath);
@ -21,9 +33,11 @@ export class DiffingFlatten implements DiffingBroccoliPlugin {
fse.mkdirpSync(destDirPath); fse.mkdirpSync(destDirPath);
} }
// TODO: once we have addedPaths support, we should throw dupes are found
if (!fs.existsSync(destFilePath)) { if (!fs.existsSync(destFilePath)) {
fs.symlinkSync(sourceFilePath, destFilePath); symlinkOrCopy(sourceFilePath, destFilePath);
} else {
throw new Error(`Duplicate file '${path.basename(changedFilePath)}' ` +
`found in path '${changedFilePath}'`);
} }
}); });

View File

@ -37,7 +37,7 @@ export class LodashRenderer implements DiffingBroccoliPlugin {
fs.unlinkSync(destFilePath); fs.unlinkSync(destFilePath);
}; };
treeDiff.changedPaths.forEach(processFile); treeDiff.addedPaths.concat(treeDiff.changedPaths).forEach(processFile);
treeDiff.removedPaths.forEach(removeFile); treeDiff.removedPaths.forEach(removeFile);
} }
} }

View File

@ -45,7 +45,7 @@ describe('MergeTrees', () => {
expect(read('dest/foo.js')).toBe('tree2/foo.js content'); expect(read('dest/foo.js')).toBe('tree2/foo.js content');
}); });
it('should throw if duplicates are used by default', () => { it('should throw if duplicates are found during the initial build', () => {
let testDir: any = { let testDir: any = {
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})}, '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)})}, 'tree2': {'foo.js': mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)})},
@ -54,15 +54,34 @@ describe('MergeTrees', () => {
mockfs(testDir); mockfs(testDir);
let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']); let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']);
let treeMerger = mergeTrees(['tree1', 'tree2', 'tree3'], 'dest', {}); let treeMerger = mergeTrees(['tree1', 'tree2', 'tree3'], 'dest', {});
expect(() => treeMerger.rebuild(treeDiffer.diffTrees())).toThrow(); expect(() => treeMerger.rebuild(treeDiffer.diffTrees())).
toThrowError("`overwrite` option is required for handling duplicates.");
delete testDir.tree2['foo.js']; testDir = {
delete testDir.tree3['foo.js']; 'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
'tree2': {},
'tree3': {}
};
mockfs(testDir); mockfs(testDir);
});
it('should throw if duplicates are found during rebuild', () => {
let testDir = {
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
'tree2': {},
'tree3': {}
};
mockfs(testDir);
let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']);
let treeMerger = mergeTrees(['tree1', 'tree2', 'tree3'], 'dest', {});
expect(() => treeMerger.rebuild(treeDiffer.diffTrees())).not.toThrow(); expect(() => treeMerger.rebuild(treeDiffer.diffTrees())).not.toThrow();
testDir.tree2['foo.js'] = mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)}); testDir.tree2['foo.js'] = mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)});
mockfs(testDir); mockfs(testDir);
expect(() => treeMerger.rebuild(treeDiffer.diffTrees())).toThrow(); expect(() => treeMerger.rebuild(treeDiffer.diffTrees())).
toThrowError("`overwrite` option is required for handling duplicates.");
}); });
}); });

View File

@ -4,6 +4,8 @@ import path = require('path');
var symlinkOrCopySync = require('symlink-or-copy').sync; var symlinkOrCopySync = require('symlink-or-copy').sync;
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin'; import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
var isWindows = process.platform === 'win32';
interface MergeTreesOptions { interface MergeTreesOptions {
overwrite?: boolean; overwrite?: boolean;
} }
@ -43,10 +45,12 @@ export class MergeTrees implements DiffingBroccoliPlugin {
}; };
if (this.firstBuild) { if (this.firstBuild) {
this.firstBuild = false;
// Build initial cache // Build initial cache
treeDiffs.reverse().forEach((treeDiff: DiffResult, index) => { treeDiffs.reverse().forEach((treeDiff: DiffResult, index) => {
index = treeDiffs.length - 1 - index; index = treeDiffs.length - 1 - index;
treeDiff.changedPaths.forEach((changedPath) => { treeDiff.addedPaths.forEach((changedPath) => {
let cache = this.pathCache[changedPath]; let cache = this.pathCache[changedPath];
if (cache === undefined) { if (cache === undefined) {
this.pathCache[changedPath] = [index]; this.pathCache[changedPath] = [index];
@ -59,7 +63,7 @@ export class MergeTrees implements DiffingBroccoliPlugin {
} }
}); });
}); });
this.firstBuild = false;
} else { } else {
// Update cache // Update cache
treeDiffs.reverse().forEach((treeDiff: DiffResult, index) => { treeDiffs.reverse().forEach((treeDiff: DiffResult, index) => {
@ -81,7 +85,14 @@ export class MergeTrees implements DiffingBroccoliPlugin {
} }
} }
}); });
treeDiff.changedPaths.forEach((changedPath) => {
let pathsToUpdate = treeDiff.addedPaths;
if (isWindows) {
pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths);
}
pathsToUpdate.forEach((changedPath) => {
let cache = this.pathCache[changedPath]; let cache = this.pathCache[changedPath];
if (cache === undefined) { if (cache === undefined) {
// File was added // File was added

View File

@ -17,32 +17,33 @@ class DiffingReplace implements DiffingBroccoliPlugin {
var patterns = this.options.patterns; var patterns = this.options.patterns;
var files = this.options.files; var files = this.options.files;
treeDiff.changedPaths.forEach((changedFilePath) => { treeDiff.addedPaths.concat(treeDiff.changedPaths)
var sourceFilePath = path.join(this.inputPath, changedFilePath); .forEach((changedFilePath) => {
var destFilePath = path.join(this.cachePath, changedFilePath); var sourceFilePath = path.join(this.inputPath, changedFilePath);
var destDirPath = path.dirname(destFilePath); var destFilePath = path.join(this.cachePath, changedFilePath);
var destDirPath = path.dirname(destFilePath);
if (!fs.existsSync(destDirPath)) { if (!fs.existsSync(destDirPath)) {
fse.mkdirpSync(destDirPath); fse.mkdirpSync(destDirPath);
} }
var fileMatches = files.some((filePath) => minimatch(changedFilePath, filePath)); var fileMatches = files.some((filePath) => minimatch(changedFilePath, filePath));
if (fileMatches) { if (fileMatches) {
var content = fs.readFileSync(sourceFilePath, FILE_ENCODING); var content = fs.readFileSync(sourceFilePath, FILE_ENCODING);
patterns.forEach((pattern) => { patterns.forEach((pattern) => {
var replacement = pattern.replacement; var replacement = pattern.replacement;
if (typeof replacement === 'function') { if (typeof replacement === 'function') {
replacement = function(content) { replacement = function(content) {
return pattern.replacement(content, changedFilePath); return pattern.replacement(content, changedFilePath);
}; };
}
content = content.replace(pattern.match, replacement);
});
fs.writeFileSync(destFilePath, content, FILE_ENCODING);
} else if (!fs.existsSync(destFilePath)) {
fs.symlinkSync(sourceFilePath, destFilePath);
} }
content = content.replace(pattern.match, replacement);
}); });
fs.writeFileSync(destFilePath, content, FILE_ENCODING);
} else if (!fs.existsSync(destFilePath)) {
fs.symlinkSync(sourceFilePath, destFilePath);
}
});
treeDiff.removedPaths.forEach((removedFilePath) => { treeDiff.removedPaths.forEach((removedFilePath) => {
var destFilePath = path.join(this.cachePath, removedFilePath); var destFilePath = path.join(this.cachePath, removedFilePath);

View File

@ -23,16 +23,17 @@ class TSToDartTranspiler implements DiffingBroccoliPlugin {
rebuild(treeDiff: DiffResult) { rebuild(treeDiff: DiffResult) {
let toEmit = []; let toEmit = [];
let getDartFilePath = (path: string) => path.replace(/((\.js)|(\.ts))$/i, '.dart'); let getDartFilePath = (path: string) => path.replace(/((\.js)|(\.ts))$/i, '.dart');
treeDiff.changedPaths.forEach((changedPath) => { treeDiff.addedPaths.concat(treeDiff.changedPaths)
let inputFilePath = path.resolve(this.inputPath, changedPath); .forEach((changedPath) => {
let inputFilePath = path.resolve(this.inputPath, changedPath);
// Ignore files which don't need to be transpiled to Dart // Ignore files which don't need to be transpiled to Dart
let dartInputFilePath = getDartFilePath(inputFilePath); let dartInputFilePath = getDartFilePath(inputFilePath);
if (fs.existsSync(dartInputFilePath)) return; if (fs.existsSync(dartInputFilePath)) return;
// Prepare to rebuild // Prepare to rebuild
toEmit.push(path.resolve(this.inputPath, changedPath)); toEmit.push(path.resolve(this.inputPath, changedPath));
}); });
treeDiff.removedPaths.forEach((removedPath) => { treeDiff.removedPaths.forEach((removedPath) => {
let absolutePath = path.resolve(this.inputPath, removedPath); let absolutePath = path.resolve(this.inputPath, removedPath);

View File

@ -50,16 +50,17 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin {
let pathsToEmit = []; let pathsToEmit = [];
let pathsWithErrors = []; let pathsWithErrors = [];
treeDiff.changedPaths.forEach((tsFilePath) => { treeDiff.addedPaths.concat(treeDiff.changedPaths)
if (!this.fileRegistry[tsFilePath]) { .forEach((tsFilePath) => {
this.fileRegistry[tsFilePath] = {version: 0}; if (!this.fileRegistry[tsFilePath]) {
this.rootFilePaths.push(tsFilePath); this.fileRegistry[tsFilePath] = {version: 0};
} else { this.rootFilePaths.push(tsFilePath);
this.fileRegistry[tsFilePath].version++; } else {
} this.fileRegistry[tsFilePath].version++;
}
pathsToEmit.push(tsFilePath); pathsToEmit.push(tsFilePath);
}); });
treeDiff.removedPaths.forEach((tsFilePath) => { treeDiff.removedPaths.forEach((tsFilePath) => {
console.log('removing outputs for', tsFilePath); console.log('removing outputs for', tsFilePath);

View File

@ -16,29 +16,31 @@ class DiffingTraceurCompiler implements DiffingBroccoliPlugin {
static includeExtensions = ['.js', '.es6', '.cjs']; static includeExtensions = ['.js', '.es6', '.cjs'];
rebuild(treeDiff: DiffResult) { rebuild(treeDiff: DiffResult) {
treeDiff.changedPaths.forEach((changedFilePath) => { treeDiff.addedPaths.concat(treeDiff.changedPaths)
var traceurOpts = xtend({filename: changedFilePath}, this.options.traceurOptions); .forEach((changedFilePath) => {
var traceurOpts = xtend({filename: changedFilePath}, this.options.traceurOptions);
var fsOpts = {encoding: 'utf-8'}; var fsOpts = {encoding: 'utf-8'};
var absoluteInputFilePath = path.join(this.inputPath, changedFilePath); var absoluteInputFilePath = path.join(this.inputPath, changedFilePath);
var sourcecode = fs.readFileSync(absoluteInputFilePath, fsOpts); var sourcecode = fs.readFileSync(absoluteInputFilePath, fsOpts);
var result = traceur.compile(traceurOpts, changedFilePath, sourcecode); var result = traceur.compile(traceurOpts, changedFilePath, sourcecode);
// TODO: we should fix the sourceMappingURL written by Traceur instead of overriding // TODO: we should fix the sourceMappingURL written by Traceur instead of overriding
// (but we might switch to typescript first) // (but we might switch to typescript first)
var mapFilepath = changedFilePath.replace(/\.\w+$/, '') + this.options.destSourceMapExtension; var mapFilepath =
result.js = result.js + '\n//# sourceMappingURL=./' + path.basename(mapFilepath); changedFilePath.replace(/\.\w+$/, '') + this.options.destSourceMapExtension;
result.js = result.js + '\n//# sourceMappingURL=./' + path.basename(mapFilepath);
var destFilepath = changedFilePath.replace(/\.\w+$/, this.options.destExtension); var destFilepath = changedFilePath.replace(/\.\w+$/, this.options.destExtension);
var destFile = path.join(this.cachePath, destFilepath); var destFile = path.join(this.cachePath, destFilepath);
fse.mkdirsSync(path.dirname(destFile)); fse.mkdirsSync(path.dirname(destFile));
fs.writeFileSync(destFile, result.js, fsOpts); fs.writeFileSync(destFile, result.js, fsOpts);
var destMap = path.join(this.cachePath, mapFilepath); var destMap = path.join(this.cachePath, mapFilepath);
result.sourceMap.file = destFilepath; result.sourceMap.file = destFilepath;
fs.writeFileSync(destMap, JSON.stringify(result.sourceMap), fsOpts); fs.writeFileSync(destMap, JSON.stringify(result.sourceMap), fsOpts);
}); });
treeDiff.removedPaths.forEach((removedFilePath) => { treeDiff.removedPaths.forEach((removedFilePath) => {
var destFilepath = removedFilePath.replace(/\.\w+$/, this.options.destExtension); var destFilepath = removedFilePath.replace(/\.\w+$/, this.options.destExtension);

View File

@ -11,9 +11,9 @@ describe('TreeDiffer', () => {
afterEach(() => mockfs.restore()); afterEach(() => mockfs.restore());
describe('diff of changed files', () => { describe('diff of added and changed files', () => {
it('should list all files but no directories during the first diff', () => { it('should list all files (but no directories) during the first diff', () => {
let testDir = { let testDir = {
'dir1': { 'dir1': {
'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}), 'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)}),
@ -30,9 +30,10 @@ describe('TreeDiffer', () => {
let diffResult = differ.diffTree(); let diffResult = differ.diffTree();
expect(diffResult.changedPaths) expect(diffResult.addedPaths)
.toEqual(['file-1.txt', 'file-2.txt', 'subdir-1/file-1.1.txt']); .toEqual(['file-1.txt', 'file-2.txt', 'subdir-1/file-1.1.txt']);
expect(diffResult.changedPaths).toEqual([]);
expect(diffResult.removedPaths).toEqual([]); expect(diffResult.removedPaths).toEqual([]);
}); });
@ -53,11 +54,13 @@ describe('TreeDiffer', () => {
let diffResult = differ.diffTree(); let diffResult = differ.diffTree();
expect(diffResult.changedPaths).not.toEqual([]); expect(diffResult.addedPaths).not.toEqual([]);
expect(diffResult.changedPaths).toEqual([]);
expect(diffResult.removedPaths).toEqual([]); expect(diffResult.removedPaths).toEqual([]);
diffResult = differ.diffTree(); diffResult = differ.diffTree();
expect(diffResult.addedPaths).toEqual([]);
expect(diffResult.changedPaths).toEqual([]); expect(diffResult.changedPaths).toEqual([]);
expect(diffResult.removedPaths).toEqual([]); expect(diffResult.removedPaths).toEqual([]);
}); });
@ -80,7 +83,7 @@ describe('TreeDiffer', () => {
let diffResult = differ.diffTree(); let diffResult = differ.diffTree();
expect(diffResult.changedPaths) expect(diffResult.addedPaths)
.toEqual(['file-1.txt', 'file-2.txt', 'subdir-1/file-1.1.txt']); .toEqual(['file-1.txt', 'file-2.txt', 'subdir-1/file-1.1.txt']);
// change two files // change two files
@ -126,7 +129,7 @@ describe('TreeDiffer', () => {
let diffResult = differ.diffTree(); let diffResult = differ.diffTree();
expect(diffResult.changedPaths) expect(diffResult.addedPaths)
.toEqual(['file-1.txt', 'file-2.txt', 'subdir-1/file-1.1.txt']); .toEqual(['file-1.txt', 'file-2.txt', 'subdir-1/file-1.1.txt']);
// change two files // change two files
@ -138,8 +141,8 @@ describe('TreeDiffer', () => {
diffResult = differ.diffTree(); diffResult = differ.diffTree();
expect(diffResult.addedPaths).toEqual([]);
expect(diffResult.changedPaths).toEqual(['file-1.txt', 'subdir-1/file-1.1.txt']); expect(diffResult.changedPaths).toEqual(['file-1.txt', 'subdir-1/file-1.1.txt']);
expect(diffResult.removedPaths).toEqual([]); expect(diffResult.removedPaths).toEqual([]);
// change one file // change one file
@ -155,6 +158,7 @@ describe('TreeDiffer', () => {
mockfs(testDir); mockfs(testDir);
diffResult = differ.diffTree(); diffResult = differ.diffTree();
expect(diffResult.addedPaths).toEqual([]);
expect(diffResult.changedPaths).toEqual([]); expect(diffResult.changedPaths).toEqual([]);
expect(diffResult.removedPaths).toEqual(['file-1.txt']); expect(diffResult.removedPaths).toEqual(['file-1.txt']);
@ -171,7 +175,8 @@ describe('TreeDiffer', () => {
mockfs(testDir); mockfs(testDir);
diffResult = differ.diffTree(); diffResult = differ.diffTree();
expect(diffResult.changedPaths).toEqual(['file-1.txt']); expect(diffResult.addedPaths).toEqual(['file-1.txt']);
expect(diffResult.changedPaths).toEqual([]);
expect(diffResult.removedPaths).toEqual([]); expect(diffResult.removedPaths).toEqual([]);
}); });
@ -204,7 +209,9 @@ describe('TreeDiffer', () => {
let diffResult = differ.diffTree(); let diffResult = differ.diffTree();
expect(diffResult.changedPaths).toEqual(['file-1.js', 'file-3.coffee']); expect(diffResult.addedPaths).toEqual(['file-1.js', 'file-3.coffee']);
expect(diffResult.changedPaths).toEqual([]);
expect(diffResult.removedPaths).toEqual([]);
// change two files // change two files
testDir['dir1']['file-1.js'] = mockfs.file({content: 'new content', mtime: new Date(1000)}); testDir['dir1']['file-1.js'] = mockfs.file({content: 'new content', mtime: new Date(1000)});
@ -216,8 +223,8 @@ describe('TreeDiffer', () => {
diffResult = differ.diffTree(); diffResult = differ.diffTree();
expect(diffResult.addedPaths).toEqual([]);
expect(diffResult.changedPaths).toEqual(['file-1.js', 'file-3.coffee']); expect(diffResult.changedPaths).toEqual(['file-1.js', 'file-3.coffee']);
expect(diffResult.removedPaths).toEqual([]); expect(diffResult.removedPaths).toEqual([]);
// change one file // change one file
@ -250,7 +257,7 @@ describe('TreeDiffer', () => {
let diffResult = differ.diffTree(); let diffResult = differ.diffTree();
expect(diffResult.changedPaths) expect(diffResult.addedPaths)
.toEqual(['file-1.cs', 'file-1.ts', 'file-1d.cs', 'file-3.ts']); .toEqual(['file-1.cs', 'file-1.ts', 'file-1d.cs', 'file-3.ts']);
// change two files // change two files
@ -265,8 +272,8 @@ describe('TreeDiffer', () => {
diffResult = differ.diffTree(); diffResult = differ.diffTree();
expect(diffResult.addedPaths).toEqual([]);
expect(diffResult.changedPaths).toEqual(['file-1.cs', 'file-1.ts', 'file-3.ts']); expect(diffResult.changedPaths).toEqual(['file-1.cs', 'file-1.ts', 'file-3.ts']);
expect(diffResult.removedPaths).toEqual([]); expect(diffResult.removedPaths).toEqual([]);
// change one file // change one file
@ -280,7 +287,7 @@ describe('TreeDiffer', () => {
describe('diff of new files', () => { describe('diff of new files', () => {
it('should detect file additions and report them as changed files', () => { it('should detect file additions', () => {
let testDir = { let testDir = {
'dir1': 'dir1':
{'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)})} {'file-1.txt': mockfs.file({content: 'file-1.txt content', mtime: new Date(1000)})}
@ -294,7 +301,9 @@ describe('TreeDiffer', () => {
mockfs(testDir); mockfs(testDir);
let diffResult = differ.diffTree(); let diffResult = differ.diffTree();
expect(diffResult.changedPaths).toEqual(['file-2.txt']); expect(diffResult.addedPaths).toEqual(['file-2.txt']);
expect(diffResult.changedPaths).toEqual([]);
expect(diffResult.removedPaths).toEqual([]);
}); });
@ -313,7 +322,8 @@ describe('TreeDiffer', () => {
mockfs(testDir); mockfs(testDir);
let diffResult = differ.diffTree(); let diffResult = differ.diffTree();
expect(diffResult.changedPaths).toEqual(['file-1.txt', 'file-2.txt']); expect(diffResult.addedPaths).toEqual(['file-2.txt']);
expect(diffResult.changedPaths).toEqual(['file-1.txt']);
}); });
}); });
@ -358,7 +368,8 @@ describe('TreeDiffer', () => {
mockfs(testDir); mockfs(testDir);
let diffResult = differ.diffTree(); let diffResult = differ.diffTree();
expect(diffResult.changedPaths).toEqual(['file-1.txt', 'file-3.txt']); expect(diffResult.addedPaths).toEqual(['file-3.txt']);
expect(diffResult.changedPaths).toEqual(['file-1.txt']);
expect(diffResult.removedPaths).toEqual(['file-2.txt']); expect(diffResult.removedPaths).toEqual(['file-2.txt']);
}); });
}); });

View File

@ -66,8 +66,14 @@ export class TreeDiffer {
if (!(this.include && !absolutePath.match(this.include)) && if (!(this.include && !absolutePath.match(this.include)) &&
!(this.exclude && absolutePath.match(this.exclude))) { !(this.exclude && absolutePath.match(this.exclude))) {
result.filesChecked++; result.filesChecked++;
if (this.isFileDirty(absolutePath, pathStat)) { let relativeFilePath = path.relative(this.rootPath, absolutePath);
result.changedPaths.push(path.relative(this.rootPath, absolutePath));
switch (this.isFileDirty(absolutePath, pathStat)) {
case FileStatus.ADDED:
result.addedPaths.push(relativeFilePath);
break;
case FileStatus.CHANGED:
result.changedPaths.push(relativeFilePath);
} }
} }
} }
@ -77,7 +83,7 @@ export class TreeDiffer {
} }
private isFileDirty(path: string, stat: fs.Stats): boolean { private isFileDirty(path: string, stat: fs.Stats): FileStatus {
let oldFingerprint = this.fingerprints[path]; let oldFingerprint = this.fingerprints[path];
let newFingerprint = `${stat.mtime.getTime()} # ${stat.size}`; let newFingerprint = `${stat.mtime.getTime()} # ${stat.size}`;
@ -88,11 +94,13 @@ export class TreeDiffer {
if (oldFingerprint === newFingerprint) { if (oldFingerprint === newFingerprint) {
// nothing changed // nothing changed
return false; return FileStatus.UNCHANGED;
} }
return FileStatus.CHANGED;
} }
return true; return FileStatus.ADDED;
} }
@ -114,6 +122,7 @@ export class TreeDiffer {
export interface DiffResult { export interface DiffResult {
addedPaths: string[];
changedPaths: string[]; changedPaths: string[];
removedPaths: string[]; removedPaths: string[];
log(verbose: boolean): void; log(verbose: boolean): void;
@ -124,6 +133,7 @@ export interface DiffResult {
class DirtyCheckingDiffResult { class DirtyCheckingDiffResult {
public filesChecked: number = 0; public filesChecked: number = 0;
public directoriesChecked: number = 0; public directoriesChecked: number = 0;
public addedPaths: string[] = [];
public changedPaths: string[] = []; public changedPaths: string[] = [];
public removedPaths: string[] = []; public removedPaths: string[] = [];
public startTime: number = Date.now(); public startTime: number = Date.now();
@ -133,13 +143,14 @@ class DirtyCheckingDiffResult {
toString() { toString() {
return `${pad(this.label, 30)}, ${pad(this.endTime - this.startTime, 5)}ms, ` + return `${pad(this.label, 30)}, ${pad(this.endTime - this.startTime, 5)}ms, ` +
`${pad(this.changedPaths.length + this.removedPaths.length, 5)} changes ` + `${pad(this.addedPaths.length + this.changedPaths.length + this.removedPaths.length, 5)} changes ` +
`(files: ${pad(this.filesChecked, 5)}, dirs: ${pad(this.directoriesChecked, 4)})`; `(files: ${pad(this.filesChecked, 5)}, dirs: ${pad(this.directoriesChecked, 4)})`;
} }
log(verbose) { log(verbose) {
let prefixedPaths = let prefixedPaths = this.addedPaths.map(p => `+ ${p}`)
this.changedPaths.map((p) => `* ${p}`).concat(this.removedPaths.map((p) => `- ${p}`)); .concat(this.changedPaths.map(p => `* ${p}`))
.concat(this.removedPaths.map(p => `- ${p}`));
console.log(`Tree diff: ${this}` + ((verbose && prefixedPaths.length) ? console.log(`Tree diff: ${this}` + ((verbose && prefixedPaths.length) ?
` [\n ${prefixedPaths.join('\n ')}\n]` : ` [\n ${prefixedPaths.join('\n ')}\n]` :
'')); ''));
@ -153,3 +164,6 @@ function pad(value, length) {
whitespaceLength = whitespaceLength + 1; whitespaceLength = whitespaceLength + 1;
return new Array(whitespaceLength).join(' ') + value; return new Array(whitespaceLength).join(' ') + value;
} }
enum FileStatus { ADDED, UNCHANGED, CHANGED }