@@ -16,8 +16,6 @@ import {
16
16
normalize ,
17
17
virtualFs ,
18
18
} from '@angular-devkit/core' ;
19
- import { ReadonlyHost } from '../../../core/src/virtual-fs/host' ;
20
- import { CordHostRecord } from '../../../core/src/virtual-fs/host/record' ;
21
19
import {
22
20
ContentHasMutatedException ,
23
21
FileAlreadyExistException ,
@@ -95,9 +93,10 @@ export class HostDirEntry implements DirEntry {
95
93
96
94
97
95
export class HostTree implements Tree {
98
- private _id = _uniqueId ++ ;
96
+ private readonly _id = -- _uniqueId ;
99
97
private _record : virtualFs . CordHost ;
100
98
private _recordSync : virtualFs . SyncDelegateHost ;
99
+ private _ancestry = new Set < number > ( ) ;
101
100
102
101
private _dirCache = new Map < Path , HostDirEntry > ( ) ;
103
102
@@ -116,79 +115,28 @@ export class HostTree implements Tree {
116
115
}
117
116
118
117
protected _willCreate ( path : Path ) {
119
- let current : ReadonlyHost = this . _record ;
120
- while ( current && current != this . _backend ) {
121
- if ( ! ( current instanceof virtualFs . CordHost ) ) {
122
- break ;
123
- }
124
-
125
- if ( current . willCreate ( path ) ) {
126
- return true ;
127
- }
128
-
129
- current = current . backend ;
130
- }
131
-
132
- return false ;
118
+ return this . _record . willCreate ( path ) ;
133
119
}
134
- protected _willOverwrite ( path : Path ) {
135
- let current : ReadonlyHost = this . _record ;
136
- while ( current && current != this . _backend ) {
137
- if ( ! ( current instanceof virtualFs . CordHost ) ) {
138
- break ;
139
- }
140
120
141
- if ( current . willOverwrite ( path ) ) {
142
- return true ;
143
- }
144
-
145
- current = current . backend ;
146
- }
147
-
148
- return false ;
121
+ protected _willOverwrite ( path : Path ) {
122
+ return this . _record . willOverwrite ( path ) ;
149
123
}
150
- protected _willDelete ( path : Path ) {
151
- let current : ReadonlyHost = this . _record ;
152
- while ( current && current != this . _backend ) {
153
- if ( ! ( current instanceof virtualFs . CordHost ) ) {
154
- break ;
155
- }
156
124
157
- if ( current . willDelete ( path ) ) {
158
- return true ;
159
- }
160
-
161
- current = current . backend ;
162
- }
163
-
164
- return false ;
125
+ protected _willDelete ( path : Path ) {
126
+ return this . _record . willDelete ( path ) ;
165
127
}
166
- protected _willRename ( path : Path ) {
167
- let current : ReadonlyHost = this . _record ;
168
- while ( current && current != this . _backend ) {
169
- if ( ! ( current instanceof virtualFs . CordHost ) ) {
170
- break ;
171
- }
172
-
173
- if ( current . willRename ( path ) ) {
174
- return true ;
175
- }
176
128
177
- current = current . backend ;
178
- }
179
-
180
- return false ;
129
+ protected _willRename ( path : Path ) {
130
+ return this . _record . willRename ( path ) ;
181
131
}
182
132
183
-
184
133
branch ( ) : Tree {
185
- // Freeze our own records, and swap. This is so the branch and this Tree don't share the same
186
- // history anymore.
187
- const record = this . _record ;
188
- this . _record = new virtualFs . CordHost ( record ) ;
189
- this . _recordSync = new virtualFs . SyncDelegateHost ( this . _record ) ;
134
+ const branchedTree = new HostTree ( this . _backend ) ;
135
+ branchedTree . _record = this . _record . clone ( ) ;
136
+ branchedTree . _recordSync = new virtualFs . SyncDelegateHost ( branchedTree . _record ) ;
137
+ branchedTree . _ancestry = new Set ( this . _ancestry ) . add ( this . _id ) ;
190
138
191
- return new HostTree ( record ) ;
139
+ return branchedTree ;
192
140
}
193
141
194
142
merge ( other : Tree , strategy : MergeStrategy = MergeStrategy . Default ) : void {
@@ -197,6 +145,12 @@ export class HostTree implements Tree {
197
145
return ;
198
146
}
199
147
148
+ if ( other instanceof HostTree && other . _ancestry . has ( this . _id ) ) {
149
+ // Workaround for merging a branch back into one of its ancestors
150
+ // More complete branch point tracking is required to avoid
151
+ strategy |= MergeStrategy . Overwrite ;
152
+ }
153
+
200
154
const creationConflictAllowed =
201
155
( strategy & MergeStrategy . AllowCreationConflict ) == MergeStrategy . AllowCreationConflict ;
202
156
const overwriteConflictAllowed =
@@ -205,15 +159,17 @@ export class HostTree implements Tree {
205
159
( strategy & MergeStrategy . AllowOverwriteConflict ) == MergeStrategy . AllowDeleteConflict ;
206
160
207
161
other . actions . forEach ( action => {
208
- if ( action . id === this . _id ) {
209
- return ;
210
- }
211
-
212
162
switch ( action . kind ) {
213
163
case 'c' : {
214
164
const { path, content } = action ;
215
165
216
166
if ( ( this . _willCreate ( path ) || this . _willOverwrite ( path ) ) ) {
167
+ const existingContent = this . read ( path ) ;
168
+ if ( existingContent && content . equals ( existingContent ) ) {
169
+ // Identical outcome; no action required
170
+ return ;
171
+ }
172
+
217
173
if ( ! creationConflictAllowed ) {
218
174
throw new MergeConflictException ( path ) ;
219
175
}
@@ -228,21 +184,41 @@ export class HostTree implements Tree {
228
184
229
185
case 'o' : {
230
186
const { path, content } = action ;
187
+ if ( this . _willDelete ( path ) && ! overwriteConflictAllowed ) {
188
+ throw new MergeConflictException ( path ) ;
189
+ }
231
190
232
191
// Ignore if content is the same (considered the same change).
233
- if ( this . _willOverwrite ( path ) && ! overwriteConflictAllowed ) {
234
- throw new MergeConflictException ( path ) ;
192
+ if ( this . _willOverwrite ( path ) ) {
193
+ const existingContent = this . read ( path ) ;
194
+ if ( existingContent && content . equals ( existingContent ) ) {
195
+ // Identical outcome; no action required
196
+ return ;
197
+ }
198
+
199
+ if ( ! overwriteConflictAllowed ) {
200
+ throw new MergeConflictException ( path ) ;
201
+ }
235
202
}
236
203
// We use write here as merge validation has already been done, and we want to let
237
204
// the CordHost do its job.
238
- this . _record . overwrite ( path , content as { } as virtualFs . FileBuffer ) . subscribe ( ) ;
205
+ this . _record . write ( path , content as { } as virtualFs . FileBuffer ) . subscribe ( ) ;
239
206
240
207
return ;
241
208
}
242
209
243
210
case 'r' : {
244
211
const { path, to } = action ;
212
+ if ( this . _willDelete ( path ) ) {
213
+ throw new MergeConflictException ( path ) ;
214
+ }
215
+
245
216
if ( this . _willRename ( path ) ) {
217
+ if ( this . _record . willRenameTo ( path , to ) ) {
218
+ // Identical outcome; no action required
219
+ return ;
220
+ }
221
+
246
222
// No override possible for renaming.
247
223
throw new MergeConflictException ( path ) ;
248
224
}
@@ -253,9 +229,16 @@ export class HostTree implements Tree {
253
229
254
230
case 'd' : {
255
231
const { path } = action ;
256
- if ( this . _willDelete ( path ) && ! deleteConflictAllowed ) {
232
+ if ( this . _willDelete ( path ) ) {
233
+ // TODO: This should technically check the content (e.g., hash on delete)
234
+ // Identical outcome; no action required
235
+ return ;
236
+ }
237
+
238
+ if ( ! this . exists ( path ) && ! deleteConflictAllowed ) {
257
239
throw new MergeConflictException ( path ) ;
258
240
}
241
+
259
242
this . _recordSync . delete ( path ) ;
260
243
261
244
return ;
@@ -372,16 +355,7 @@ export class HostTree implements Tree {
372
355
get actions ( ) : Action [ ] {
373
356
// Create a list of all records until we hit our original backend. This is to support branches
374
357
// that diverge from each others.
375
- const allRecords : CordHostRecord [ ] = [ ...this . _record . records ( ) ] ;
376
- let current = this . _record . backend ;
377
- while ( current != this . _backend ) {
378
- if ( ! ( current instanceof virtualFs . CordHost ) ) {
379
- break ;
380
- }
381
-
382
- allRecords . unshift ( ...current . records ( ) ) ;
383
- current = current . backend ;
384
- }
358
+ const allRecords = [ ...this . _record . records ( ) ] ;
385
359
386
360
return clean (
387
361
allRecords
@@ -426,3 +400,17 @@ export class HostTree implements Tree {
426
400
) ;
427
401
}
428
402
}
403
+
404
+ export class HostCreateTree extends HostTree {
405
+ constructor ( host : virtualFs . ReadonlyHost ) {
406
+ super ( ) ;
407
+
408
+ const tempHost = new HostTree ( host ) ;
409
+ tempHost . visit ( path => {
410
+ const content = tempHost . read ( path ) ;
411
+ if ( content ) {
412
+ this . create ( path , content ) ;
413
+ }
414
+ } ) ;
415
+ }
416
+ }
0 commit comments