ff0af1ee unknown 1 month ago
1
2
3
4
5
6
7
use crate :: hash:: ChangeHash;
8
use crate :: objects:: AtomRange;
9
use std:: collections:: { BTreeMap, BTreeSet, HashMap, HashSet} ;
10
11
12
pub type AtomKey = ( ChangeHash, u32 ) ;
13
14
# [ derive ( Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug ) ]
15
pub enum Vertex {
16
Start,
17
Atom( AtomKey) ,
18
End,
19
}
20
21
22
pub trait DepOracle {
23
fn depends_on ( & self , a : & ChangeHash, b : & ChangeHash) -> bool ;
24
}
25
26
27
pub struct NoDeps ;
28
impl DepOracle for NoDeps {
29
fn depends_on ( & self , a : & ChangeHash, b : & ChangeHash) -> bool {
30
a = = b
31
}
32
}
33
34
# [ derive ( Default, Clone, Debug ) ]
35
struct AtomInfo {
36
37
content : Option< Vec< u8 > > ,
38
deleted_by : BTreeSet< ChangeHash> ,
39
}
40
41
# [ derive ( Clone, Debug ) ]
42
pub struct FileGraph {
43
atoms : HashMap< AtomKey, AtomInfo> ,
44
succ : HashMap< Vertex, BTreeSet< Vertex> > ,
45
pred : HashMap< Vertex, BTreeSet< Vertex> > ,
46
}
47
48
impl Default for FileGraph {
49
fn default ( ) -> Self {
50
Self :: new( )
51
}
52
}
53
54
55
# [ derive ( Clone, Debug, PartialEq, Eq ) ]
56
pub enum Segment {
57
Lines( Vec< AtomKey> ) ,
58
59
Conflict( Vec< Vec< AtomKey> > ) ,
60
61
62
Zombie { deleted: Vec< AtomKey> , inserted: Vec< AtomKey> } ,
63
}
64
65
# [ derive ( Clone, Debug, PartialEq, Eq, Default ) ]
66
pub struct Materialized {
67
pub segments : Vec< Segment> ,
68
}
69
70
impl Materialized {
71
pub fn is_conflicted ( & self ) -> bool {
72
self .segments.iter ( ) .any ( | s | ! matches! ( s, Segment:: Lines( _ ) ) )
73
}
74
pub fn conflict_count ( & self ) -> usize {
75
self .segments.iter ( ) .filter ( | s | ! matches! ( s, Segment:: Lines( _ ) ) ) .count ( )
76
}
77
78
pub fn live_atoms ( & self ) -> Vec< AtomKey> {
79
let mut out = Vec :: new( ) ;
80
for s in & self .segments {
81
match s {
82
Segment:: Lines( a) => out.extend ( a) ,
83
Segment:: Conflict( sides) => sides.iter ( ) .for_each ( | x | out.extend ( x) ) ,
84
Segment:: Zombie { inserted, .. } => out.extend ( inserted) ,
85
}
86
}
87
out
88
}
89
}
90
91
impl FileGraph {
92
pub fn new ( ) -> Self {
93
let mut g = FileGraph { atoms: HashMap:: new( ) , succ: HashMap:: new( ) , pred: HashMap:: new( ) } ;
94
g.edge ( Vertex:: Start, Vertex:: End) ;
95
g
96
}
97
98
fn edge ( & mut self , a : Vertex, b : Vertex) {
99
self .succ.entry ( a) .or_default ( ) .insert ( b) ;
100
self .pred.entry ( b) .or_default ( ) .insert ( a) ;
101
self .succ.entry ( b) .or_default ( ) ;
102
self .pred.entry ( a) .or_default ( ) ;
103
}
104
105
106
pub fn insert ( & mut self , change : ChangeHash, first_index : u32 , after : Vertex, before : Vertex, lines : & [Vec< u8 > ]) {
107
let mut prev = after;
108
for ( i, line) in lines.iter ( ) .enumerate ( ) {
109
let key = ( change, first_index + i as u32 ) ;
110
self .atoms.entry ( key) .or_default ( ) .content = Some ( line.clone ( ) ) ;
111
let v = Vertex:: Atom( key) ;
112
self .edge ( prev, v) ;
113
prev = v;
114
}
115
self .edge ( prev, before) ;
116
}
117
118
119
pub fn delete ( & mut self , by : ChangeHash, range : & AtomRange) {
120
for i in range.from.. range.to {
121
self .atoms.entry ( ( range.change, i) ) .or_default ( ) .deleted_by.insert ( by) ;
122
}
123
}
124
125
pub fn is_live ( & self , k : & AtomKey) -> bool {
126
self .atoms.get ( k) .is_some_and ( | a | a.content.is_some ( ) & & a.deleted_by.is_empty ( ) )
127
}
128
129
pub fn content ( & self , k : & AtomKey) -> Option< & [ u8 ] > {
130
self .atoms.get ( k) .and_then ( | a | a.content.as_deref ( ) )
131
}
132
133
pub fn deleters ( & self , k : & AtomKey) -> impl Iterator< Item = & ChangeHash> {
134
self .atoms.get ( k) .into_iter ( ) .flat_map ( | a | a.deleted_by.iter ( ) )
135
}
136
137
138
pub fn atom_count ( & self ) -> usize {
139
self .atoms.len ( )
140
}
141
142
fn is_ghost ( & self , k : & AtomKey, info : & AtomInfo, deps : & dyn DepOracle) -> bool {
143
144
145
self .succ.get ( & Vertex:: Atom( * k) ) .is_some_and ( | ss | {
146
ss.iter ( ) .any ( | s | match s {
147
Vertex:: Atom( y) if self .is_live ( y) => ! info.deleted_by.iter ( ) .any ( | d | deps.depends_on ( d, & y.0 ) ) ,
148
_ => false ,
149
} )
150
} )
151
}
152
153
154
fn reduced_succ ( & self , v : Vertex, kept : & HashSet< Vertex> , memo : & mut HashMap< Vertex, BTreeSet< Vertex> > ) -> BTreeSet< Vertex> {
155
let mut out = BTreeSet:: new( ) ;
156
let mut stack: Vec< Vertex> = self .succ.get ( & v) .map ( | s | s.iter ( ) .copied ( ) .collect ( ) ) .unwrap_or_default ( ) ;
157
let mut seen = HashSet:: new( ) ;
158
while let Some ( s) = stack.pop ( ) {
159
if kept.contains ( & s) {
160
out.insert ( s) ;
161
} else if seen.insert ( s) {
162
if let Some ( m) = memo.get ( & s) {
163
out.extend ( m.iter ( ) .copied ( ) ) ;
164
} else if let Some ( next) = self .succ.get ( & s) {
165
stack.extend ( next.iter ( ) .copied ( ) ) ;
166
}
167
}
168
}
169
out
170
}
171
172
173
pub fn materialize ( & self , deps : & dyn DepOracle) -> Materialized {
174
175
let mut kept: HashSet< Vertex> = HashSet:: new( ) ;
176
let mut ghosts: HashSet< AtomKey> = HashSet:: new( ) ;
177
kept.insert ( Vertex:: Start) ;
178
kept.insert ( Vertex:: End) ;
179
for ( k, info) in & self .atoms {
180
if info.content.is_none ( ) {
181
continue ;
182
}
183
if info.deleted_by.is_empty ( ) {
184
kept.insert ( Vertex:: Atom( * k) ) ;
185
} else if self .is_ghost ( k, info, deps) {
186
kept.insert ( Vertex:: Atom( * k) ) ;
187
ghosts.insert ( * k) ;
188
}
189
}
190
191
192
let mut memo = HashMap:: new( ) ;
193
let mut rsucc: HashMap< Vertex, BTreeSet< Vertex> > = HashMap:: new( ) ;
194
let mut indeg: HashMap< Vertex, usize > = HashMap:: new( ) ;
195
for & u in & kept {
196
indeg.entry ( u) .or_insert ( 0 ) ;
197
let rs = self .reduced_succ ( u, & kept, & mut memo) ;
198
for & w in & rs {
199
* indeg.entry ( w) .or_insert ( 0 ) + = 1 ;
200
}
201
rsucc.insert ( u, rs) ;
202
}
203
204
205
let mut ready: BTreeSet< Vertex> = indeg.iter ( ) .filter ( | ( _, &d ) | d = = 0 ) .map ( | ( v , _) | * v) .collect ( ) ;
206
let mut side: HashMap< Vertex, u32 > = HashMap:: new( ) ;
207
let mut joins: HashSet< Vertex> = HashSet:: new( ) ;
208
let mut next_side = 0 u32 ;
209
let mut plain: Vec< Vertex> = Vec :: new( ) ;
210
let mut conflict: Option< BTreeMap< u32 , Vec< Vertex> > > = None ;
211
let mut segments: Vec< Segment> = Vec :: new( ) ;
212
213
let flush_plain = | plain : & mut Vec< Vertex> , segments : & mut Vec< Segment> | {
214
let atoms: Vec< AtomKey> = plain.iter ( ) .filter_map ( | v | if let Vertex:: Atom( k) = v { Some ( * k) } else { None } ) .collect ( ) ;
215
plain.clear ( ) ;
216
if ! atoms.is_empty ( ) {
217
segments.extend ( split_zombies ( atoms, & ghosts) ) ;
218
}
219
} ;
220
let finish_conflict = | c : BTreeMap< u32 , Vec< Vertex> > , segments : & mut Vec< Segment> | {
221
let sides: Vec< Vec< AtomKey> > = c
222
.into_values ( )
223
.map ( | vs | {
224
vs.into_iter ( )
225
.filter_map ( | v | match v {
226
Vertex:: Atom( k) if ! ghosts.contains ( & k) => Some ( k) ,
227
_ => None ,
228
} )
229
.collect:: < Vec< _ > > ( )
230
} )
231
.filter ( | s | ! s.is_empty ( ) )
232
.collect ( ) ;
233
match sides.len ( ) {
234
0 => { }
235
1 => segments.push ( Segment:: Lines( sides.into_iter ( ) .next ( ) .unwrap ( ) ) ) ,
236
_ => segments.push ( Segment:: Conflict( sides) ) ,
237
}
238
} ;
239
240
while ! ready.is_empty ( ) {
241
if ready.len ( ) > = 2 {
242
if conflict.is_none ( ) {
243
flush_plain ( & mut plain, & mut segments) ;
244
conflict = Some ( BTreeMap:: new( ) ) ;
245
}
246
for r in & ready {
247
if ! side.contains_key ( r) {
248
side.insert ( * r, next_side) ;
249
next_side + = 1 ;
250
}
251
}
252
}
253
let v = ready.pop_first ( ) .unwrap ( ) ;
254
let my_side = side.get ( & v) .copied ( ) ;
255
match ( & mut conflict, my_side) {
256
( Some ( c) , Some ( s) ) => c.entry ( s) .or_default ( ) .push ( v) ,
257
( Some ( _ ) , None ) => {
258
let c = conflict.take ( ) .unwrap ( ) ;
259
finish_conflict ( c, & mut segments) ;
260
plain.push ( v) ;
261
}
262
( None , _ ) => plain.push ( v) ,
263
}
264
for & w in & rsucc[ & v] {
265
let d = indeg.get_mut ( & w) .unwrap ( ) ;
266
* d - = 1 ;
267
if conflict.is_some ( ) & & ! joins.contains ( & w) {
268
match ( my_side, side.get ( & w) .copied ( ) ) {
269
( Some ( s) , None ) => {
270
side.insert ( w, s) ;
271
}
272
( Some ( s) , Some ( t) ) if s ! = t => {
273
side.remove ( & w) ;
274
joins.insert ( w) ;
275
}
276
( None , Some ( _ ) ) => {
277
side.remove ( & w) ;
278
joins.insert ( w) ;
279
}
280
_ => { }
281
}
282
}
283
if * d = = 0 {
284
ready.insert ( w) ;
285
}
286
}
287
}
288
if let Some ( c) = conflict.take ( ) {
289
finish_conflict ( c, & mut segments) ;
290
}
291
flush_plain ( & mut plain, & mut segments) ;
292
Materialized { segments }
293
}
294
295
296
297
pub fn render ( & self , m : & Materialized, label : & dyn Fn( &ChangeHash ) -> String) -> Vec< u8 > {
298
let total = m.conflict_count ( ) ;
299
let mut n = 0 ;
300
let mut out: Vec< u8 > = Vec :: new( ) ;
301
let push_atoms = | out : & mut Vec< u8 > , atoms : & [AtomKey]| {
302
for a in atoms {
303
if let Some ( c) = self .content ( a) {
304
out.extend_from_slice ( c) ;
305
}
306
}
307
} ;
308
let newline_if_needed = | out : & mut Vec< u8 > | {
309
if ! out.is_empty ( ) & & * out.last ( ) .unwrap ( ) ! = b ' \n ' {
310
out.push ( b ' \n ' ) ;
311
}
312
} ;
313
for seg in & m.segments {
314
match seg {
315
Segment:: Lines( a) => push_atoms ( & mut out, a) ,
316
Segment:: Conflict( sides) => {
317
n + = 1 ;
318
let k = sides.len ( ) ;
319
for ( i, s) in sides.iter ( ) .enumerate ( ) {
320
newline_if_needed ( & mut out) ;
321
let who = s.first ( ) .map ( | a | label ( & a.0 ) ) .unwrap_or_default ( ) ;
322
let marker = if i = = 0 { " <<<<<<<" } else { " =======" } ;
323
out.extend_from_slice (
324
format! ( " {marker} oxy conflict {n} of {total} , side {} of {k} : change {who} \n " , i + 1 ) .as_bytes ( ) ,
325
) ;
326
push_atoms ( & mut out, s) ;
327
}
328
newline_if_needed ( & mut out) ;
329
out.extend_from_slice ( format! ( " >>>>>>> oxy conflict {n} of {total} end\n " ) .as_bytes ( ) ) ;
330
}
331
Segment:: Zombie { deleted, inserted } => {
332
n + = 1 ;
333
newline_if_needed ( & mut out) ;
334
let deleter = deleted
335
.first ( )
336
.and_then ( | a | self .deleters ( a) .next ( ) .copied ( ) )
337
.map ( | d | label ( & d) )
338
.unwrap_or_default ( ) ;
339
let inserter = inserted.first ( ) .map ( | a | label ( & a.0 ) ) .unwrap_or_default ( ) ;
340
out.extend_from_slice (
341
format! ( " <<<<<<< oxy conflict {n} of {total} , side 1 of 2: deleted by change {deleter} \n -------\n " ) .as_bytes ( ) ,
342
) ;
343
push_atoms ( & mut out, deleted) ;
344
newline_if_needed ( & mut out) ;
345
out.extend_from_slice ( format! ( " ======= oxy conflict {n} of {total} , side 2 of 2: change {inserter} \n " ) .as_bytes ( ) ) ;
346
push_atoms ( & mut out, inserted) ;
347
newline_if_needed ( & mut out) ;
348
out.extend_from_slice ( format! ( " >>>>>>> oxy conflict {n} of {total} end\n " ) .as_bytes ( ) ) ;
349
}
350
}
351
}
352
out
353
}
354
}
355
252e8cb2 Andy McCall 1 month ago ↶
356
fn vertex_value ( v : & Vertex) -> crate:: cbor:: Value {
357
use crate :: cbor:: Value;
358
match v {
359
Vertex:: Start => Value:: Uint( 0 ) ,
360
Vertex:: End => Value:: Uint( 1 ) ,
361
Vertex:: Atom( ( c, i) ) => Value:: Array( vec! [ Value:: Bytes( c.0. to_vec ( ) ) , Value:: Uint( * i as u64 ) ] ) ,
362
}
363
}
364
365
fn vertex_from ( v : & crate:: cbor:: Value) -> Result< Vertex, crate:: objects:: ObjectError> {
366
use crate :: cbor:: Value;
367
use crate :: objects:: ObjectError;
368
match v {
369
Value:: Uint( 0 ) => Ok ( Vertex:: Start) ,
370
Value:: Uint( 1 ) => Ok ( Vertex:: End) ,
371
Value:: Array( a) if a.len ( ) = = 2 => Ok ( Vertex:: Atom( ( crate :: objects:: oref( & a[ 0 ] , " vertex" ) ? , crate :: objects:: u32_( & a[ 1 ] , " vertex" ) ? ) ) ) ,
372
_ => Err ( ObjectError:: Schema( " vertex: expected 0, 1, or [hash, index]" .into ( ) ) ) ,
373
}
374
}
375
376
impl FileGraph {
377
378
pub fn to_value ( & self ) -> crate:: cbor:: Value {
379
use crate :: cbor:: Value;
380
let mut atoms: Vec< ( & AtomKey, & AtomInfo) > = self .atoms.iter ( ) .collect ( ) ;
381
atoms.sort_by_key ( | ( k , _) | * * k) ;
382
let mut edges: Vec< ( Vertex, Vertex) > = self .succ.iter ( ) .flat_map ( | ( a , bs ) | bs.iter ( ) .map ( move | b| ( * a, * b) ) ) .collect ( ) ;
383
edges.sort ( ) ;
384
Value:: map( [
385
(
386
0 ,
387
Value:: Array(
388
atoms
389
.into_iter ( )
390
.map ( | ( (c , i ) , info) | {
391
Value:: Array( vec! [
392
Value:: Bytes( c.0. to_vec ( ) ) ,
393
Value:: Uint( * i as u64 ) ,
394
info.content.as_ref ( ) .map ( | b | Value:: Bytes( b.clone ( ) ) ) .unwrap_or ( Value:: Null) ,
395
Value:: Array( info.deleted_by.iter ( ) .map ( | d | Value:: Bytes( d.0. to_vec ( ) ) ) .collect ( ) ) ,
396
] )
397
} )
398
.collect ( ) ,
399
) ,
400
),
401
( 1 , Value:: Array( edges.iter ( ) .map ( | ( a , b ) | Value:: Array( vec! [ vertex_value ( a) , vertex_value ( b) ] ) ) .collect ( ) ) ) ,
402
] )
403
}
404
405
pub fn from_value ( v : & crate:: cbor:: Value) -> Result< FileGraph, crate:: objects:: ObjectError> {
406
use crate :: objects:: { array, bytes, field, map, oref, u32_, ObjectError} ;
407
let m = map ( v, " file graph" ) ? ;
408
let mut g = FileGraph { atoms: HashMap:: new( ) , succ: HashMap:: new( ) , pred: HashMap:: new( ) } ;
409
for a in array ( field ( m, 0 , " atoms" ) ? , " atoms" ) ? {
410
let e = array ( a, " atom" ) ? ;
411
if e.len ( ) ! = 4 {
412
return Err ( ObjectError:: Schema( " atom: expected 4 fields" .into ( ) ) ) ;
413
}
414
let key = ( oref ( & e[ 0 ] , " atom change" ) ? , u32_ ( & e[ 1 ] , " atom index" ) ? ) ;
415
let content = if e[ 2 ] .is_null ( ) { None } else { Some ( bytes ( & e[ 2 ] , " atom content" ) ? .to_vec ( ) ) } ;
416
let deleted_by = array ( & e[ 3 ] , " deleters" ) ? .iter ( ) .map ( | d | oref ( d, " deleter" ) ) .collect:: < Result< BTreeSet< _ > , _ > > ( ) ? ;
417
g.atoms.insert ( key, AtomInfo { content, deleted_by } ) ;
418
}
419
for e in array ( field ( m, 1 , " edges" ) ? , " edges" ) ? {
420
let pair = array ( e, " edge" ) ? ;
421
if pair.len ( ) ! = 2 {
422
return Err ( ObjectError:: Schema( " edge: expected [from, to]" .into ( ) ) ) ;
423
}
424
g.edge ( vertex_from ( & pair[ 0 ] ) ? , vertex_from ( & pair[ 1 ] ) ? ) ;
425
}
426
Ok ( g)
427
}
428
}
429
ff0af1ee unknown 1 month ago
430
431
fn split_zombies ( atoms : Vec< AtomKey> , ghosts : & HashSet< AtomKey> ) -> Vec< Segment> {
432
let mut out = Vec :: new( ) ;
433
let mut lines: Vec< AtomKey> = Vec :: new( ) ;
434
let mut i = 0 ;
435
while i < atoms.len ( ) {
436
if ! ghosts.contains ( & atoms[ i] ) {
437
lines.push ( atoms[ i] ) ;
438
i + = 1 ;
439
continue ;
440
}
441
if ! lines.is_empty ( ) {
442
out.push ( Segment:: Lines( std:: mem:: take( & mut lines) ) ) ;
443
}
444
let mut deleted = Vec :: new( ) ;
445
while i < atoms.len ( ) & & ghosts.contains ( & atoms[ i] ) {
446
deleted.push ( atoms[ i] ) ;
447
i + = 1 ;
448
}
449
let mut inserted = Vec :: new( ) ;
450
if i < atoms.len ( ) {
451
let ch = atoms[ i] .0 ;
452
while i < atoms.len ( ) & & atoms[ i] .0 = = ch & & ! ghosts.contains ( & atoms[ i] ) {
453
inserted.push ( atoms[ i] ) ;
454
i + = 1 ;
455
}
456
}
457
out.push ( Segment:: Zombie { deleted, inserted } ) ;
458
}
459
if ! lines.is_empty ( ) {
460
out.push ( Segment:: Lines( lines) ) ;
461
}
462
out
463
}
464
465
# [ cfg ( test ) ]
466
mod tests {
467
use super :: * ;
468
use crate :: hash:: ObjectRef;
469
470
fn h ( n : u8 ) -> ChangeHash {
471
ObjectRef( [ n; 32 ] )
472
}
473
fn lines ( v : & [& str ]) -> Vec< Vec< u8 > > {
474
v.iter ( ) .map ( | s | s.as_bytes ( ) .to_vec ( ) ) .collect ( )
475
}
476
fn text ( g : & FileGraph, m : & Materialized) -> String {
477
String :: from_utf8( g.render ( m, & | c| c.short ( 4 ) ) ) .unwrap ( )
478
}
479
480
struct Deps ( HashMap< ChangeHash, Vec< ChangeHash> > ) ;
481
impl DepOracle for Deps {
482
fn depends_on ( & self , a : & ChangeHash, b : & ChangeHash) -> bool {
483
if a = = b {
484
return true ;
485
}
486
let mut stack = vec! [ * a] ;
487
let mut seen = HashSet:: new( ) ;
488
while let Some ( x) = stack.pop ( ) {
489
if let Some ( ds) = self .0. get ( & x) {
490
for d in ds {
491
if d = = b {
492
return true ;
493
}
494
if seen.insert ( * d) {
495
stack.push ( * d) ;
496
}
497
}
498
}
499
}
500
false
501
}
502
}
503
504
# [ test ]
505
fn sequential_inserts_and_deletes ( ) {
506
let mut g = FileGraph:: new( ) ;
507
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " , " c\n " ] ) ) ;
508
let m = g.materialize ( & NoDeps) ;
509
assert! ( ! m.is_conflicted ( ) ) ;
510
assert_eq! ( text ( & g, & m) , " a\n b\n c\n " ) ;
511
512
513
g.insert ( h ( 2 ) , 0 , Vertex:: Atom( ( h ( 1 ) , 1 ) ) , Vertex:: Atom( ( h ( 1 ) , 2 ) ) , & lines ( & [ " x\n " ] ) ) ;
514
g.delete ( h ( 2 ) , & AtomRange { change: h ( 1 ) , from: 0 , to: 1 } ) ;
515
let deps = Deps( [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ) ;
516
let m = g.materialize ( & deps) ;
517
assert! ( ! m.is_conflicted ( ) ) ;
518
assert_eq! ( text ( & g, & m) , " b\n x\n c\n " ) ;
519
assert_eq! ( m.live_atoms ( ) , vec! [ ( h ( 1 ) , 1 ) , ( h ( 2 ) , 0 ) , ( h ( 1 ) , 2 ) ] ) ;
520
}
521
522
# [ test ]
523
fn concurrent_inserts_at_same_anchor_conflict ( ) {
524
let mut g = FileGraph:: new( ) ;
525
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " ] ) ) ;
526
g.insert ( h ( 2 ) , 0 , Vertex:: Atom( ( h ( 1 ) , 0 ) ) , Vertex:: Atom( ( h ( 1 ) , 1 ) ) , & lines ( & [ " from-2\n " ] ) ) ;
527
g.insert ( h ( 3 ) , 0 , Vertex:: Atom( ( h ( 1 ) , 0 ) ) , Vertex:: Atom( ( h ( 1 ) , 1 ) ) , & lines ( & [ " from-3\n " , " more-3\n " ] ) ) ;
528
let deps = Deps( [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ) ;
529
let m = g.materialize ( & deps) ;
530
assert! ( m.is_conflicted ( ) ) ;
531
assert_eq! ( m.conflict_count ( ) , 1 ) ;
532
assert_eq! (
533
m.segments,
534
vec! [
535
Segment:: Lines( vec! [ ( h ( 1 ) , 0 ) ] ) ,
536
Segment:: Conflict( vec! [ vec! [ ( h ( 2 ) , 0 ) ] , vec! [ ( h ( 3 ) , 0 ) , ( h ( 3 ) , 1 ) ] ] ) ,
537
Segment:: Lines( vec! [ ( h ( 1 ) , 1 ) ] ) ,
538
]
539
) ;
540
let t = text ( & g, & m) ;
541
assert! ( t.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1, side 1 of 2: change 0202\n from-2\n ======= oxy conflict 1 of 1, side 2 of 2: change 0303\n from-3\n more-3\n >>>>>>> oxy conflict 1 of 1 end\n b\n " ) , " {t}" ) ;
542
}
543
544
# [ test ]
545
fn concurrent_inserts_at_different_anchors_do_not_conflict ( ) {
546
let mut g = FileGraph:: new( ) ;
547
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " , " c\n " ] ) ) ;
548
g.insert ( h ( 2 ) , 0 , Vertex:: Start, Vertex:: Atom( ( h ( 1 ) , 0 ) ) , & lines ( & [ " top\n " ] ) ) ;
549
g.insert ( h ( 3 ) , 0 , Vertex:: Atom( ( h ( 1 ) , 2 ) ) , Vertex:: End, & lines ( & [ " bottom\n " ] ) ) ;
550
let deps = Deps( [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ) ;
551
let m = g.materialize ( & deps) ;
552
assert! ( ! m.is_conflicted ( ) ) ;
553
assert_eq! ( text ( & g, & m) , " top\n a\n b\n c\n bottom\n " ) ;
554
}
555
556
# [ test ]
557
fn concurrent_deletes_of_the_same_line_agree ( ) {
558
let mut g = FileGraph:: new( ) ;
559
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " ] ) ) ;
560
g.delete ( h ( 2 ) , & AtomRange { change: h ( 1 ) , from: 0 , to: 1 } ) ;
561
g.delete ( h ( 3 ) , & AtomRange { change: h ( 1 ) , from: 0 , to: 1 } ) ;
562
let deps = Deps( [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ) ;
563
let m = g.materialize ( & deps) ;
564
assert! ( ! m.is_conflicted ( ) ) ;
565
assert_eq! ( text ( & g, & m) , " b\n " ) ;
566
}
567
568
# [ test ]
569
fn delete_insert_conflict_is_a_zombie ( ) {
570
let mut g = FileGraph:: new( ) ;
571
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " ] ) ) ;
572
573
g.delete ( h ( 2 ) , & AtomRange { change: h ( 1 ) , from: 0 , to: 1 } ) ;
574
g.insert ( h ( 3 ) , 0 , Vertex:: Atom( ( h ( 1 ) , 0 ) ) , Vertex:: Atom( ( h ( 1 ) , 1 ) ) , & lines ( & [ " after-a\n " ] ) ) ;
575
let deps = Deps( [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ) ;
576
let m = g.materialize ( & deps) ;
577
assert! ( m.is_conflicted ( ) ) ;
578
assert_eq! (
579
m.segments,
580
vec! [
581
Segment:: Zombie { deleted: vec! [ ( h ( 1 ) , 0 ) ] , inserted: vec! [ ( h ( 3 ) , 0 ) ] } ,
582
Segment:: Lines( vec! [ ( h ( 1 ) , 1 ) ] ) ,
583
]
584
) ;
585
let t = text ( & g, & m) ;
586
assert! ( t.contains ( " -------\n a\n =======" ) , " {t}" ) ;
587
assert! ( t.contains ( " after-a\n >>>>>>>" ) , " {t}" ) ;
588
589
590
let deps = Deps( [ ( h ( 2 ) , vec! [ h ( 1 ) , h ( 3 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ) ;
591
let m = g.materialize ( & deps) ;
592
assert! ( ! m.is_conflicted ( ) ) ;
593
assert_eq! ( text ( & g, & m) , " after-a\n b\n " ) ;
594
}
595
596
# [ test ]
597
fn resolution_collapses_a_conflict ( ) {
598
let mut g = FileGraph:: new( ) ;
599
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " ] ) ) ;
600
g.insert ( h ( 2 ) , 0 , Vertex:: Start, Vertex:: Atom( ( h ( 1 ) , 0 ) ) , & lines ( & [ " x\n " ] ) ) ;
601
g.insert ( h ( 3 ) , 0 , Vertex:: Start, Vertex:: Atom( ( h ( 1 ) , 0 ) ) , & lines ( & [ " y\n " ] ) ) ;
602
let mut deps: HashMap< _ , _ > = [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ;
603
assert! ( g.materialize ( & Deps( deps.clone ( ) ) ) .is_conflicted ( ) ) ;
604
605
g.delete ( h ( 4 ) , & AtomRange { change: h ( 2 ) , from: 0 , to: 1 } ) ;
606
deps.insert ( h ( 4 ) , vec! [ h ( 2 ) , h ( 3 ) ] ) ;
607
let m = g.materialize ( & Deps( deps) ) ;
608
assert! ( ! m.is_conflicted ( ) ) ;
609
assert_eq! ( text ( & g, & m) , " y\n a\n " ) ;
610
}
611
612
# [ test ]
613
fn empty_and_unterminated_files ( ) {
614
let g = FileGraph:: new( ) ;
615
let m = g.materialize ( & NoDeps) ;
616
assert_eq! ( m.segments, vec! [ ] ) ;
617
assert_eq! ( text ( & g, & m) , " " ) ;
618
let mut g = FileGraph:: new( ) ;
619
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " no newline" ] ) ) ;
620
assert_eq! ( text ( & g, & g.materialize ( & NoDeps) ) , " no newline" ) ;
621
}
622
252e8cb2 Andy McCall 1 month ago ↶
623
# [ test ]
624
fn serialization_round_trip ( ) {
625
let mut g = FileGraph:: new( ) ;
626
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " ] ) ) ;
627
g.insert ( h ( 2 ) , 0 , Vertex:: Atom( ( h ( 1 ) , 0 ) ) , Vertex:: Atom( ( h ( 1 ) , 1 ) ) , & lines ( & [ " x\n " ] ) ) ;
628
g.delete ( h ( 3 ) , & AtomRange { change: h ( 1 ) , from: 1 , to: 2 } ) ;
629
let v = g.to_value ( ) ;
630
let bytes = crate :: cbor:: encode( & v) ;
631
let back = FileGraph:: from_value( & crate :: cbor:: decode( & bytes) .unwrap ( ) ) .unwrap ( ) ;
632
let deps = Deps( [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ) ;
633
assert_eq! ( back.materialize ( & deps) , g.materialize ( & deps) ) ;
634
assert_eq! ( text ( & back, & back.materialize ( & deps) ) , " a\n x\n " ) ;
635
assert_eq! ( crate :: cbor:: encode( & back.to_value ( ) ) , bytes, " deterministic" ) ;
636
}
637
ff0af1ee unknown 1 month ago
638
639
# [ test ]
640
fn application_order_is_irrelevant ( ) {
641
use rand:: seq:: SliceRandom;
642
type Op = ( ChangeHash, Vec< ( u32 , Vertex, Vertex, Vec< Vec< u8 > > ) > , Vec< AtomRange> ) ;
643
let ops: Vec< Op> = vec! [
644
( h ( 1 ) , vec! [ ( 0 , Vertex:: Start, Vertex:: End, lines ( & [ " a\n " , " b\n " , " c\n " ] ) ) ] , vec! [ ] ) ,
645
( h ( 2 ) , vec! [ ( 0 , Vertex:: Atom( ( h ( 1 ) , 0 ) ) , Vertex:: Atom( ( h ( 1 ) , 1 ) ) , lines ( & [ " p\n " ] ) ) ] , vec! [ ] ) ,
646
( h ( 3 ) , vec! [ ( 0 , Vertex:: Atom( ( h ( 1 ) , 0 ) ) , Vertex:: Atom( ( h ( 1 ) , 1 ) ) , lines ( & [ " q\n " , " r\n " ] ) ) ] , vec! [ AtomRange { change: h ( 1 ) , from: 2 , to: 3 } ] ) ,
647
( h ( 4 ) , vec! [ ( 0 , Vertex:: Atom( ( h ( 3 ) , 1 ) ) , Vertex:: Atom( ( h ( 1 ) , 1 ) ) , lines ( & [ " s\n " ] ) ) ] , vec! [ AtomRange { change: h ( 1 ) , from: 1 , to: 2 } ] ) ,
648
( h ( 5 ) , vec! [ ( 0 , Vertex:: Atom( ( h ( 1 ) , 2 ) ) , Vertex:: End, lines ( & [ " z\n " ] ) ) ] , vec! [ ] ) ,
649
] ;
650
let deps = Deps(
651
[ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) , ( h ( 4 ) , vec! [ h ( 3 ) , h ( 1 ) ] ) , ( h ( 5 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ,
652
) ;
653
let build = | order : & [usize ]| {
654
let mut g = FileGraph:: new( ) ;
655
for & i in order {
656
let ( c, ins, dels) = & ops[ i] ;
657
for ( idx, a, b, l) in ins {
658
g.insert ( * c, * idx, * a, * b, l) ;
659
}
660
for d in dels {
661
g.delete ( * c, d) ;
662
}
663
}
664
let m = g.materialize ( & deps) ;
665
( m.clone ( ) , text ( & g, & m) )
666
} ;
667
let reference = build ( & [ 0 , 1 , 2 , 3 , 4 ] ) ;
668
assert! ( reference.0. is_conflicted ( ) ) ;
669
let mut rng = rand:: thread_rng( ) ;
670
let mut order: Vec< usize > = ( 0 .. ops.len ( ) ) .collect ( ) ;
671
for _ in 0 .. 50 {
672
order.shuffle ( & mut rng) ;
673
assert_eq! ( build ( & order) , reference, " order {order:?}" ) ;
674
}
675
}
676
}