@@ -4,81 +4,114 @@ import path = require('path');
44var symlinkOrCopySync = require ( 'symlink-or-copy' ) . sync ;
55import { wrapDiffingPlugin , DiffingBroccoliPlugin , DiffResult } from './diffing-broccoli-plugin' ;
66
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 ;
7+ interface MergeTreesOptions {
8+ overwrite ?: boolean ;
189}
1910
2011function outputFileSync ( sourcePath , destPath ) {
2112 let dirname = path . dirname ( destPath ) ;
2213 fse . mkdirsSync ( dirname , { fs : fs } ) ;
23- fse . removeSync ( destPath ) ;
2414 symlinkOrCopySync ( sourcePath , destPath ) ;
2515}
2616
2717export class MergeTrees implements DiffingBroccoliPlugin {
28- private mergedPaths : { [ key : string ] : number } = Object . create ( null ) ;
18+ private pathCache : { [ key : string ] : number [ ] } = Object . create ( null ) ;
19+ public options : MergeTreesOptions ;
20+ private firstBuild : boolean = true ;
2921
30- constructor ( public inputPaths : string [ ] , public cachePath : string , public options ) { }
22+ constructor ( public inputPaths : string [ ] , public cachePath : string ,
23+ options : MergeTreesOptions = { } ) {
24+ this . options = options || { } ;
25+ }
3126
3227 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 ;
28+ let overwrite = this . options . overwrite ;
29+ let pathsToEmit : string [ ] = [ ] ;
30+ let pathsToRemove : string [ ] = [ ] ;
31+ let emitted : { [ key : string ] : boolean } = Object . create ( null ) ;
32+ let contains = ( cache , val ) => {
33+ for ( let i = 0 , ii = cache . length ; i < ii ; ++ i ) {
34+ if ( cache [ i ] === val ) return true ;
35+ }
36+ return false ;
37+ } ;
38+
39+ let emit = ( relativePath ) => {
40+ // ASSERT(!emitted[relativePath]);
41+ pathsToEmit . push ( relativePath ) ;
42+ emitted [ relativePath ] = true ;
43+ } ;
44+
45+ if ( this . firstBuild ) {
46+ // Build initial cache
47+ treeDiffs . reverse ( ) . forEach ( ( treeDiff : DiffResult , index ) => {
48+ index = treeDiffs . length - 1 - index ;
49+ treeDiff . changedPaths . forEach ( ( changedPath ) => {
50+ let cache = this . pathCache [ changedPath ] ;
51+ if ( cache === undefined ) {
52+ this . pathCache [ changedPath ] = [ index ] ;
53+ pathsToEmit . push ( changedPath ) ;
54+ } else if ( overwrite ) {
55+ // ASSERT(contains(pathsToEmit, changedPath));
56+ cache . unshift ( index ) ;
57+ } else {
58+ throw new Error ( "`overwrite` option is required for handling duplicates." ) ;
59+ }
60+ } ) ;
61+ } ) ;
62+ this . firstBuild = false ;
63+ } else {
64+ // Update cache
65+ treeDiffs . reverse ( ) . forEach ( ( treeDiff : DiffResult , index ) => {
66+ index = treeDiffs . length - 1 - index ;
67+ treeDiff . removedPaths . forEach ( ( removedPath ) => {
68+ let cache = this . pathCache [ removedPath ] ;
69+ // ASSERT(cache !== undefined);
70+ // ASSERT(contains(cache, index));
71+ if ( cache [ cache . length - 1 ] === index ) {
72+ pathsToRemove . push ( path . join ( this . cachePath , removedPath ) ) ;
73+ cache . pop ( ) ;
74+ if ( cache . length === 0 ) {
75+ this . pathCache [ removedPath ] = undefined ;
76+ } else if ( ! emitted [ removedPath ] ) {
77+ if ( cache . length === 1 && ! overwrite ) {
78+ throw new Error ( "`overwrite` option is required for handling duplicates." ) ;
79+ }
80+ emit ( removedPath ) ;
81+ }
3982 }
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 ;
83+ } ) ;
84+ treeDiff . changedPaths . forEach ( ( changedPath ) => {
85+ let cache = this . pathCache [ changedPath ] ;
86+ if ( cache === undefined ) {
87+ // File was added
88+ this . pathCache [ changedPath ] = [ index ] ;
89+ emit ( changedPath ) ;
90+ } else if ( ! contains ( cache , index ) ) {
91+ cache . push ( index ) ;
92+ cache . sort ( ( a , b ) => a - b ) ;
93+ if ( cache . length > 1 && ! overwrite ) {
94+ throw new Error ( "`overwrite` option is required for handling duplicates." ) ;
95+ }
96+ if ( cache [ cache . length - 1 ] === index && ! emitted [ changedPath ] ) {
97+ emit ( changedPath ) ;
98+ }
4799 }
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- }
100+ } ) ;
59101 } ) ;
102+ }
60103
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 ) ;
104+ pathsToRemove . forEach ( ( destPath ) => fse . removeSync ( destPath ) ) ;
105+ pathsToEmit . forEach ( ( emittedPath ) => {
106+ let cache = this . pathCache [ emittedPath ] ;
107+ let destPath = path . join ( this . cachePath , emittedPath ) ;
108+ let sourceIndex = cache [ cache . length - 1 ] ;
109+ let sourceInputPath = this . inputPaths [ sourceIndex ] ;
110+ let sourcePath = path . join ( sourceInputPath , emittedPath ) ;
111+ if ( cache . length > 1 ) {
69112 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- } ) ;
113+ }
114+ outputFileSync ( sourcePath , destPath ) ;
82115 } ) ;
83116 }
84117}
0 commit comments