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> {
let args = ['-w'];
treeDiff.changedPaths.forEach((changedFile) => {
let sourcePath = path.join(this.inputPath, changedFile);
let destPath = path.join(this.cachePath, changedFile);
if (/\.dart$/.test(changedFile)) args.push(destPath);
fse.copySync(sourcePath, destPath);
});
treeDiff.addedPaths.concat(treeDiff.changedPaths)
.forEach((changedFile) => {
let sourcePath = path.join(this.inputPath, changedFile);
let destPath = path.join(this.cachePath, changedFile);
if (/\.dart$/.test(changedFile)) args.push(destPath);
fse.copySync(sourcePath, destPath);
});
treeDiff.removedPaths.forEach((removedFile) => {
let destPath = path.join(this.cachePath, removedFile);
fse.removeSync(destPath);

View File

@ -15,13 +15,14 @@ class DestCopy implements DiffingBroccoliPlugin {
rebuild(treeDiff: DiffResult) {
treeDiff.changedPaths.forEach((changedFilePath) => {
var destFilePath = path.join(this.outputRoot, changedFilePath);
treeDiff.addedPaths.concat(treeDiff.changedPaths)
.forEach((changedFilePath) => {
var destFilePath = path.join(this.outputRoot, changedFilePath);
var destDirPath = path.dirname(destFilePath);
fse.mkdirsSync(destDirPath);
fse.copySync(path.join(this.inputPath, changedFilePath), destFilePath);
});
var destDirPath = path.dirname(destFilePath);
fse.mkdirsSync(destDirPath);
fse.copySync(path.join(this.inputPath, changedFilePath), destFilePath);
});
treeDiff.removedPaths.forEach((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']);
});
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 path = require('path');
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 {
constructor(private inputPath, private cachePath, private options) {}
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 destFilePath = path.join(this.cachePath, path.basename(changedFilePath));
var destDirPath = path.dirname(destFilePath);
@ -21,9 +33,11 @@ export class DiffingFlatten implements DiffingBroccoliPlugin {
fse.mkdirpSync(destDirPath);
}
// TODO: once we have addedPaths support, we should throw dupes are found
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);
};
treeDiff.changedPaths.forEach(processFile);
treeDiff.addedPaths.concat(treeDiff.changedPaths).forEach(processFile);
treeDiff.removedPaths.forEach(removeFile);
}
}

View File

@ -45,7 +45,7 @@ describe('MergeTrees', () => {
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 = {
'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)})},
@ -54,15 +54,34 @@ describe('MergeTrees', () => {
mockfs(testDir);
let treeDiffer = MakeTreeDiffers(['tree1', 'tree2', 'tree3']);
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'];
delete testDir.tree3['foo.js'];
testDir = {
'tree1': {'foo.js': mockfs.file({content: 'tree1/foo.js content', mtime: new Date(1000)})},
'tree2': {},
'tree3': {}
};
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();
testDir.tree2['foo.js'] = mockfs.file({content: 'tree2/foo.js content', mtime: new Date(1000)});
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;
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
var isWindows = process.platform === 'win32';
interface MergeTreesOptions {
overwrite?: boolean;
}
@ -43,10 +45,12 @@ export class MergeTrees implements DiffingBroccoliPlugin {
};
if (this.firstBuild) {
this.firstBuild = false;
// Build initial cache
treeDiffs.reverse().forEach((treeDiff: DiffResult, index) => {
index = treeDiffs.length - 1 - index;
treeDiff.changedPaths.forEach((changedPath) => {
treeDiff.addedPaths.forEach((changedPath) => {
let cache = this.pathCache[changedPath];
if (cache === undefined) {
this.pathCache[changedPath] = [index];
@ -59,7 +63,7 @@ export class MergeTrees implements DiffingBroccoliPlugin {
}
});
});
this.firstBuild = false;
} else {
// Update cache
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];
if (cache === undefined) {
// File was added

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,8 +66,14 @@ export class TreeDiffer {
if (!(this.include && !absolutePath.match(this.include)) &&
!(this.exclude && absolutePath.match(this.exclude))) {
result.filesChecked++;
if (this.isFileDirty(absolutePath, pathStat)) {
result.changedPaths.push(path.relative(this.rootPath, absolutePath));
let relativeFilePath = 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 newFingerprint = `${stat.mtime.getTime()} # ${stat.size}`;
@ -88,11 +94,13 @@ export class TreeDiffer {
if (oldFingerprint === newFingerprint) {
// 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 {
addedPaths: string[];
changedPaths: string[];
removedPaths: string[];
log(verbose: boolean): void;
@ -124,6 +133,7 @@ export interface DiffResult {
class DirtyCheckingDiffResult {
public filesChecked: number = 0;
public directoriesChecked: number = 0;
public addedPaths: string[] = [];
public changedPaths: string[] = [];
public removedPaths: string[] = [];
public startTime: number = Date.now();
@ -133,13 +143,14 @@ class DirtyCheckingDiffResult {
toString() {
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)})`;
}
log(verbose) {
let prefixedPaths =
this.changedPaths.map((p) => `* ${p}`).concat(this.removedPaths.map((p) => `- ${p}`));
let prefixedPaths = this.addedPaths.map(p => `+ ${p}`)
.concat(this.changedPaths.map(p => `* ${p}`))
.concat(this.removedPaths.map(p => `- ${p}`));
console.log(`Tree diff: ${this}` + ((verbose && prefixedPaths.length) ?
` [\n ${prefixedPaths.join('\n ')}\n]` :
''));
@ -153,3 +164,6 @@ function pad(value, length) {
whitespaceLength = whitespaceLength + 1;
return new Array(whitespaceLength).join(' ') + value;
}
enum FileStatus { ADDED, UNCHANGED, CHANGED }