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
75167672 Andy McCall 1 month ago
62
Zombie {
63
deleted: Vec< AtomKey> ,
64
inserted: Vec< AtomKey> ,
65
} ,
ff0af1ee unknown 1 month ago
66
}
67
68
# [ derive ( Clone, Debug, PartialEq, Eq, Default ) ]
69
pub struct Materialized {
70
pub segments : Vec< Segment> ,
71
}
72
73
impl Materialized {
74
pub fn is_conflicted ( & self ) -> bool {
75167672 Andy McCall 1 month ago
75
self .segments
76
.iter ( )
77
.any ( | s | ! matches! ( s, Segment:: Lines( _ ) ) )
ff0af1ee unknown 1 month ago
78
}
79
pub fn conflict_count ( & self ) -> usize {
75167672 Andy McCall 1 month ago
80
self .segments
81
.iter ( )
82
.filter ( | s | ! matches! ( s, Segment:: Lines( _ ) ) )
83
.count ( )
ff0af1ee unknown 1 month ago
84
}
85
86
pub fn live_atoms ( & self ) -> Vec< AtomKey> {
87
let mut out = Vec :: new( ) ;
88
for s in & self .segments {
89
match s {
90
Segment:: Lines( a) => out.extend ( a) ,
91
Segment:: Conflict( sides) => sides.iter ( ) .for_each ( | x | out.extend ( x) ) ,
92
Segment:: Zombie { inserted, .. } => out.extend ( inserted) ,
93
}
94
}
95
out
96
}
97
}
98
99
impl FileGraph {
100
pub fn new ( ) -> Self {
75167672 Andy McCall 1 month ago
101
let mut g = FileGraph {
102
atoms: HashMap:: new( ) ,
103
succ: HashMap:: new( ) ,
104
pred: HashMap:: new( ) ,
105
} ;
ff0af1ee unknown 1 month ago
106
g.edge ( Vertex:: Start, Vertex:: End) ;
107
g
108
}
109
110
fn edge ( & mut self , a : Vertex, b : Vertex) {
111
self .succ.entry ( a) .or_default ( ) .insert ( b) ;
112
self .pred.entry ( b) .or_default ( ) .insert ( a) ;
113
self .succ.entry ( b) .or_default ( ) ;
114
self .pred.entry ( a) .or_default ( ) ;
115
}
116
117
75167672 Andy McCall 1 month ago
118
pub fn insert (
119
& mut self ,
120
change : ChangeHash,
121
first_index : u32 ,
122
after : Vertex,
123
before : Vertex,
124
lines : & [Vec< u8 > ],
125
) {
ff0af1ee unknown 1 month ago
126
let mut prev = after;
127
for ( i, line) in lines.iter ( ) .enumerate ( ) {
128
let key = ( change, first_index + i as u32 ) ;
129
self .atoms.entry ( key) .or_default ( ) .content = Some ( line.clone ( ) ) ;
130
let v = Vertex:: Atom( key) ;
131
self .edge ( prev, v) ;
132
prev = v;
133
}
134
self .edge ( prev, before) ;
135
}
136
137
138
pub fn delete ( & mut self , by : ChangeHash, range : & AtomRange) {
139
for i in range.from.. range.to {
75167672 Andy McCall 1 month ago
140
self .atoms
141
.entry ( ( range.change, i) )
142
.or_default ( )
143
.deleted_by
144
.insert ( by) ;
ff0af1ee unknown 1 month ago
145
}
146
}
147
148
pub fn is_live ( & self , k : & AtomKey) -> bool {
75167672 Andy McCall 1 month ago
149
self .atoms
150
.get ( k)
151
.is_some_and ( | a | a.content.is_some ( ) & & a.deleted_by.is_empty ( ) )
ff0af1ee unknown 1 month ago
152
}
153
154
pub fn content ( & self , k : & AtomKey) -> Option< & [ u8 ] > {
155
self .atoms.get ( k) .and_then ( | a | a.content.as_deref ( ) )
156
}
157
158
pub fn deleters ( & self , k : & AtomKey) -> impl Iterator< Item = & ChangeHash> {
75167672 Andy McCall 1 month ago
159
self .atoms
160
.get ( k)
161
.into_iter ( )
162
.flat_map ( | a | a.deleted_by.iter ( ) )
ff0af1ee unknown 1 month ago
163
}
164
165
166
pub fn atom_count ( & self ) -> usize {
167
self .atoms.len ( )
168
}
169
62794ecc Andy McCall 1 month ago ↶
170
171
172
173
pub fn live_successor_changes ( & self , k : & AtomKey) -> Vec< ChangeHash> {
174
self .succ
175
.get ( & Vertex:: Atom( * k) )
176
.map ( | ss | {
177
ss.iter ( )
178
.filter_map ( | s | match s {
179
Vertex:: Atom( y) if self .is_live ( y) => Some ( y.0 ) ,
180
_ => None ,
181
} )
182
.collect ( )
183
} )
184
.unwrap_or_default ( )
185
}
186
ff0af1ee unknown 1 month ago
187
fn is_ghost ( & self , k : & AtomKey, info : & AtomInfo, deps : & dyn DepOracle) -> bool {
23b217d7 Andy McCall 1 month ago ↶
188
189
190
191
ff0af1ee unknown 1 month ago
192
self .succ.get ( & Vertex:: Atom( * k) ) .is_some_and ( | ss | {
193
ss.iter ( ) .any ( | s | match s {
75167672 Andy McCall 1 month ago
194
Vertex:: Atom( y) if self .is_live ( y) => ! info
195
.deleted_by
196
.iter ( )
197
.any ( | d | deps.depends_on ( d, & y.0 ) | | deps.depends_on ( & y.0 , d) ) ,
ff0af1ee unknown 1 month ago
198
_ => false ,
199
} )
200
} )
201
}
202
203
75167672 Andy McCall 1 month ago
204
fn reduced_succ (
205
& self ,
206
v : Vertex,
207
kept : & HashSet< Vertex> ,
208
memo : & mut HashMap< Vertex, BTreeSet< Vertex> > ,
209
) -> BTreeSet< Vertex> {
ff0af1ee unknown 1 month ago
210
let mut out = BTreeSet:: new( ) ;
75167672 Andy McCall 1 month ago
211
let mut stack: Vec< Vertex> = self
212
.succ
213
.get ( & v)
214
.map ( | s | s.iter ( ) .copied ( ) .collect ( ) )
215
.unwrap_or_default ( ) ;
ff0af1ee unknown 1 month ago
216
let mut seen = HashSet:: new( ) ;
217
while let Some ( s) = stack.pop ( ) {
218
if kept.contains ( & s) {
219
out.insert ( s) ;
220
} else if seen.insert ( s) {
221
if let Some ( m) = memo.get ( & s) {
222
out.extend ( m.iter ( ) .copied ( ) ) ;
223
} else if let Some ( next) = self .succ.get ( & s) {
224
stack.extend ( next.iter ( ) .copied ( ) ) ;
225
}
226
}
227
}
228
out
229
}
230
231
232
pub fn materialize ( & self , deps : & dyn DepOracle) -> Materialized {
233
234
let mut kept: HashSet< Vertex> = HashSet:: new( ) ;
235
let mut ghosts: HashSet< AtomKey> = HashSet:: new( ) ;
236
kept.insert ( Vertex:: Start) ;
237
kept.insert ( Vertex:: End) ;
238
for ( k, info) in & self .atoms {
239
if info.content.is_none ( ) {
240
continue ;
241
}
242
if info.deleted_by.is_empty ( ) {
243
kept.insert ( Vertex:: Atom( * k) ) ;
244
} else if self .is_ghost ( k, info, deps) {
245
kept.insert ( Vertex:: Atom( * k) ) ;
246
ghosts.insert ( * k) ;
247
}
248
}
249
250
251
let mut memo = HashMap:: new( ) ;
252
let mut rsucc: HashMap< Vertex, BTreeSet< Vertex> > = HashMap:: new( ) ;
253
let mut indeg: HashMap< Vertex, usize > = HashMap:: new( ) ;
254
for & u in & kept {
255
indeg.entry ( u) .or_insert ( 0 ) ;
256
let rs = self .reduced_succ ( u, & kept, & mut memo) ;
257
for & w in & rs {
258
* indeg.entry ( w) .or_insert ( 0 ) + = 1 ;
259
}
260
rsucc.insert ( u, rs) ;
261
}
262
263
75167672 Andy McCall 1 month ago
264
let mut ready: BTreeSet< Vertex> = indeg
265
.iter ( )
266
.filter ( | ( _, &d ) | d = = 0 )
267
.map ( | ( v , _) | * v)
268
.collect ( ) ;
ff0af1ee unknown 1 month ago
269
let mut side: HashMap< Vertex, u32 > = HashMap:: new( ) ;
270
let mut joins: HashSet< Vertex> = HashSet:: new( ) ;
271
let mut next_side = 0 u32 ;
272
let mut plain: Vec< Vertex> = Vec :: new( ) ;
273
let mut conflict: Option< BTreeMap< u32 , Vec< Vertex> > > = None ;
274
let mut segments: Vec< Segment> = Vec :: new( ) ;
275
276
let flush_plain = | plain : & mut Vec< Vertex> , segments : & mut Vec< Segment> | {
75167672 Andy McCall 1 month ago
277
let atoms: Vec< AtomKey> = plain
278
.iter ( )
279
.filter_map ( | v | {
280
if let Vertex:: Atom( k) = v {
281
Some ( * k)
282
} else {
283
None
284
}
285
} )
286
.collect ( ) ;
ff0af1ee unknown 1 month ago
287
plain.clear ( ) ;
288
if ! atoms.is_empty ( ) {
289
segments.extend ( split_zombies ( atoms, & ghosts) ) ;
290
}
291
} ;
292
let finish_conflict = | c : BTreeMap< u32 , Vec< Vertex> > , segments : & mut Vec< Segment> | {
293
let sides: Vec< Vec< AtomKey> > = c
294
.into_values ( )
295
.map ( | vs | {
296
vs.into_iter ( )
297
.filter_map ( | v | match v {
298
Vertex:: Atom( k) if ! ghosts.contains ( & k) => Some ( k) ,
299
_ => None ,
300
} )
301
.collect:: < Vec< _ > > ( )
302
} )
303
.filter ( | s | ! s.is_empty ( ) )
304
.collect ( ) ;
305
match sides.len ( ) {
306
0 => { }
307
1 => segments.push ( Segment:: Lines( sides.into_iter ( ) .next ( ) .unwrap ( ) ) ) ,
308
_ => segments.push ( Segment:: Conflict( sides) ) ,
309
}
310
} ;
311
312
while ! ready.is_empty ( ) {
313
if ready.len ( ) > = 2 {
314
if conflict.is_none ( ) {
315
flush_plain ( & mut plain, & mut segments) ;
316
conflict = Some ( BTreeMap:: new( ) ) ;
317
}
318
for r in & ready {
319
if ! side.contains_key ( r) {
320
side.insert ( * r, next_side) ;
321
next_side + = 1 ;
322
}
323
}
324
}
325
let v = ready.pop_first ( ) .unwrap ( ) ;
326
let my_side = side.get ( & v) .copied ( ) ;
327
match ( & mut conflict, my_side) {
328
( Some ( c) , Some ( s) ) => c.entry ( s) .or_default ( ) .push ( v) ,
329
( Some ( _ ) , None ) => {
330
let c = conflict.take ( ) .unwrap ( ) ;
331
finish_conflict ( c, & mut segments) ;
332
plain.push ( v) ;
333
}
334
( None , _ ) => plain.push ( v) ,
335
}
336
for & w in & rsucc[ & v] {
337
let d = indeg.get_mut ( & w) .unwrap ( ) ;
338
* d - = 1 ;
339
if conflict.is_some ( ) & & ! joins.contains ( & w) {
340
match ( my_side, side.get ( & w) .copied ( ) ) {
341
( Some ( s) , None ) => {
342
side.insert ( w, s) ;
343
}
344
( Some ( s) , Some ( t) ) if s ! = t => {
345
side.remove ( & w) ;
346
joins.insert ( w) ;
347
}
348
( None , Some ( _ ) ) => {
349
side.remove ( & w) ;
350
joins.insert ( w) ;
351
}
352
_ => { }
353
}
354
}
355
if * d = = 0 {
356
ready.insert ( w) ;
357
}
358
}
359
}
360
if let Some ( c) = conflict.take ( ) {
361
finish_conflict ( c, & mut segments) ;
362
}
363
flush_plain ( & mut plain, & mut segments) ;
364
Materialized { segments }
365
}
366
367
368
369
pub fn render ( & self , m : & Materialized, label : & dyn Fn( &ChangeHash ) -> String) -> Vec< u8 > {
370
let total = m.conflict_count ( ) ;
371
let mut n = 0 ;
372
let mut out: Vec< u8 > = Vec :: new( ) ;
373
let push_atoms = | out : & mut Vec< u8 > , atoms : & [AtomKey]| {
374
for a in atoms {
375
if let Some ( c) = self .content ( a) {
376
out.extend_from_slice ( c) ;
377
}
378
}
379
} ;
380
let newline_if_needed = | out : & mut Vec< u8 > | {
381
if ! out.is_empty ( ) & & * out.last ( ) .unwrap ( ) ! = b ' \n ' {
382
out.push ( b ' \n ' ) ;
383
}
384
} ;
385
for seg in & m.segments {
386
match seg {
387
Segment:: Lines( a) => push_atoms ( & mut out, a) ,
388
Segment:: Conflict( sides) => {
389
n + = 1 ;
390
let k = sides.len ( ) ;
391
for ( i, s) in sides.iter ( ) .enumerate ( ) {
392
newline_if_needed ( & mut out) ;
393
let who = s.first ( ) .map ( | a | label ( & a.0 ) ) .unwrap_or_default ( ) ;
394
let marker = if i = = 0 { " <<<<<<<" } else { " =======" } ;
395
out.extend_from_slice (
396
format! ( " {marker} oxy conflict {n} of {total} , side {} of {k} : change {who} \n " , i + 1 ) .as_bytes ( ) ,
397
) ;
398
push_atoms ( & mut out, s) ;
399
}
400
newline_if_needed ( & mut out) ;
75167672 Andy McCall 1 month ago
401
out.extend_from_slice (
402
format! ( " >>>>>>> oxy conflict {n} of {total} end\n " ) .as_bytes ( ) ,
403
) ;
ff0af1ee unknown 1 month ago
404
}
405
Segment:: Zombie { deleted, inserted } => {
406
n + = 1 ;
407
newline_if_needed ( & mut out) ;
408
let deleter = deleted
409
.first ( )
410
.and_then ( | a | self .deleters ( a) .next ( ) .copied ( ) )
411
.map ( | d | label ( & d) )
412
.unwrap_or_default ( ) ;
413
let inserter = inserted.first ( ) .map ( | a | label ( & a.0 ) ) .unwrap_or_default ( ) ;
414
out.extend_from_slice (
415
format! ( " <<<<<<< oxy conflict {n} of {total} , side 1 of 2: deleted by change {deleter} \n -------\n " ) .as_bytes ( ) ,
416
) ;
417
push_atoms ( & mut out, deleted) ;
418
newline_if_needed ( & mut out) ;
75167672 Andy McCall 1 month ago
419
out.extend_from_slice (
420
format! (
421
" ======= oxy conflict {n} of {total} , side 2 of 2: change {inserter} \n "
422
)
423
.as_bytes ( ) ,
424
) ;
ff0af1ee unknown 1 month ago
425
push_atoms ( & mut out, inserted) ;
426
newline_if_needed ( & mut out) ;
75167672 Andy McCall 1 month ago
427
out.extend_from_slice (
428
format! ( " >>>>>>> oxy conflict {n} of {total} end\n " ) .as_bytes ( ) ,
429
) ;
ff0af1ee unknown 1 month ago
430
}
431
}
432
}
433
out
434
}
435
}
436
252e8cb2 Andy McCall 1 month ago ↶
437
fn vertex_value ( v : & Vertex) -> crate:: cbor:: Value {
438
use crate :: cbor:: Value;
439
match v {
440
Vertex:: Start => Value:: Uint( 0 ) ,
441
Vertex:: End => Value:: Uint( 1 ) ,
75167672 Andy McCall 1 month ago
442
Vertex:: Atom( ( c, i) ) => {
443
Value:: Array( vec! [ Value:: Bytes( c.0. to_vec ( ) ) , Value:: Uint( * i as u64 ) ] )
444
}
252e8cb2 Andy McCall 1 month ago ↶
445
}
446
}
447
448
fn vertex_from ( v : & crate:: cbor:: Value) -> Result< Vertex, crate:: objects:: ObjectError> {
449
use crate :: cbor:: Value;
450
use crate :: objects:: ObjectError;
451
match v {
452
Value:: Uint( 0 ) => Ok ( Vertex:: Start) ,
453
Value:: Uint( 1 ) => Ok ( Vertex:: End) ,
75167672 Andy McCall 1 month ago
454
Value:: Array( a) if a.len ( ) = = 2 => Ok ( Vertex:: Atom( (
455
crate :: objects:: oref( & a[ 0 ] , " vertex" ) ? ,
456
crate :: objects:: u32_( & a[ 1 ] , " vertex" ) ? ,
457
) ) ) ,
458
_ => Err ( ObjectError:: Schema(
459
" vertex: expected 0, 1, or [hash, index]" .into ( ) ,
460
) ) ,
252e8cb2 Andy McCall 1 month ago ↶
461
}
462
}
463
464
impl FileGraph {
465
466
pub fn to_value ( & self ) -> crate:: cbor:: Value {
467
use crate :: cbor:: Value;
468
let mut atoms: Vec< ( & AtomKey, & AtomInfo) > = self .atoms.iter ( ) .collect ( ) ;
469
atoms.sort_by_key ( | ( k , _) | * * k) ;
75167672 Andy McCall 1 month ago
470
let mut edges: Vec< ( Vertex, Vertex) > = self
471
.succ
472
.iter ( )
473
.flat_map ( | ( a , bs ) | bs.iter ( ) .map ( move | b| ( * a, * b) ) )
474
.collect ( ) ;
252e8cb2 Andy McCall 1 month ago ↶
475
edges.sort ( ) ;
476
Value:: map( [
477
(
478
0 ,
479
Value:: Array(
480
atoms
481
.into_iter ( )
482
.map ( | ( (c , i ) , info) | {
483
Value:: Array( vec! [
484
Value:: Bytes( c.0. to_vec ( ) ) ,
485
Value:: Uint( * i as u64 ) ,
75167672 Andy McCall 1 month ago
486
info.content
487
.as_ref ( )
488
.map ( | b | Value:: Bytes( b.clone ( ) ) )
489
.unwrap_or ( Value:: Null) ,
490
Value:: Array(
491
info.deleted_by
492
.iter ( )
493
.map ( | d | Value:: Bytes( d.0. to_vec ( ) ) )
494
.collect ( ) ,
495
) ,
252e8cb2 Andy McCall 1 month ago ↶
496
] )
497
} )
498
.collect ( ) ,
499
) ,
500
),
75167672 Andy McCall 1 month ago
501
(
502
1 ,
503
Value:: Array(
504
edges
505
.iter ( )
506
.map ( | ( a , b ) | Value:: Array( vec! [ vertex_value ( a) , vertex_value ( b) ] ) )
507
.collect ( ) ,
508
) ,
509
) ,
252e8cb2 Andy McCall 1 month ago ↶
510
] )
511
}
512
513
pub fn from_value ( v : & crate:: cbor:: Value) -> Result< FileGraph, crate:: objects:: ObjectError> {
514
use crate :: objects:: { array, bytes, field, map, oref, u32_, ObjectError} ;
515
let m = map ( v, " file graph" ) ? ;
75167672 Andy McCall 1 month ago
516
let mut g = FileGraph {
517
atoms: HashMap:: new( ) ,
518
succ: HashMap:: new( ) ,
519
pred: HashMap:: new( ) ,
520
} ;
252e8cb2 Andy McCall 1 month ago ↶
521
for a in array ( field ( m, 0 , " atoms" ) ? , " atoms" ) ? {
522
let e = array ( a, " atom" ) ? ;
523
if e.len ( ) ! = 4 {
524
return Err ( ObjectError:: Schema( " atom: expected 4 fields" .into ( ) ) ) ;
525
}
526
let key = ( oref ( & e[ 0 ] , " atom change" ) ? , u32_ ( & e[ 1 ] , " atom index" ) ? ) ;
75167672 Andy McCall 1 month ago
527
let content = if e[ 2 ] .is_null ( ) {
528
None
529
} else {
530
Some ( bytes ( & e[ 2 ] , " atom content" ) ? .to_vec ( ) )
531
} ;
532
let deleted_by = array ( & e[ 3 ] , " deleters" ) ?
533
.iter ( )
534
.map ( | d | oref ( d, " deleter" ) )
535
.collect:: < Result< BTreeSet< _ > , _ > > ( ) ? ;
536
g.atoms.insert (
537
key,
538
AtomInfo {
539
content,
540
deleted_by,
541
} ,
542
) ;
252e8cb2 Andy McCall 1 month ago ↶
543
}
544
for e in array ( field ( m, 1 , " edges" ) ? , " edges" ) ? {
545
let pair = array ( e, " edge" ) ? ;
546
if pair.len ( ) ! = 2 {
547
return Err ( ObjectError:: Schema( " edge: expected [from, to]" .into ( ) ) ) ;
548
}
549
g.edge ( vertex_from ( & pair[ 0 ] ) ? , vertex_from ( & pair[ 1 ] ) ? ) ;
550
}
551
Ok ( g)
552
}
553
}
554
ff0af1ee unknown 1 month ago
555
556
fn split_zombies ( atoms : Vec< AtomKey> , ghosts : & HashSet< AtomKey> ) -> Vec< Segment> {
557
let mut out = Vec :: new( ) ;
558
let mut lines: Vec< AtomKey> = Vec :: new( ) ;
559
let mut i = 0 ;
560
while i < atoms.len ( ) {
561
if ! ghosts.contains ( & atoms[ i] ) {
562
lines.push ( atoms[ i] ) ;
563
i + = 1 ;
564
continue ;
565
}
566
if ! lines.is_empty ( ) {
567
out.push ( Segment:: Lines( std:: mem:: take( & mut lines) ) ) ;
568
}
569
let mut deleted = Vec :: new( ) ;
570
while i < atoms.len ( ) & & ghosts.contains ( & atoms[ i] ) {
571
deleted.push ( atoms[ i] ) ;
572
i + = 1 ;
573
}
574
let mut inserted = Vec :: new( ) ;
575
if i < atoms.len ( ) {
576
let ch = atoms[ i] .0 ;
577
while i < atoms.len ( ) & & atoms[ i] .0 = = ch & & ! ghosts.contains ( & atoms[ i] ) {
578
inserted.push ( atoms[ i] ) ;
579
i + = 1 ;
580
}
581
}
582
out.push ( Segment:: Zombie { deleted, inserted } ) ;
583
}
584
if ! lines.is_empty ( ) {
585
out.push ( Segment:: Lines( lines) ) ;
586
}
587
out
588
}
589
590
# [ cfg ( test ) ]
591
mod tests {
592
use super :: * ;
593
use crate :: hash:: ObjectRef;
594
595
fn h ( n : u8 ) -> ChangeHash {
596
ObjectRef( [ n; 32 ] )
597
}
598
fn lines ( v : & [& str ]) -> Vec< Vec< u8 > > {
599
v.iter ( ) .map ( | s | s.as_bytes ( ) .to_vec ( ) ) .collect ( )
600
}
601
fn text ( g : & FileGraph, m : & Materialized) -> String {
602
String :: from_utf8( g.render ( m, & | c| c.short ( 4 ) ) ) .unwrap ( )
603
}
604
605
struct Deps ( HashMap< ChangeHash, Vec< ChangeHash> > ) ;
606
impl DepOracle for Deps {
607
fn depends_on ( & self , a : & ChangeHash, b : & ChangeHash) -> bool {
608
if a = = b {
609
return true ;
610
}
611
let mut stack = vec! [ * a] ;
612
let mut seen = HashSet:: new( ) ;
613
while let Some ( x) = stack.pop ( ) {
614
if let Some ( ds) = self .0. get ( & x) {
615
for d in ds {
616
if d = = b {
617
return true ;
618
}
619
if seen.insert ( * d) {
620
stack.push ( * d) ;
621
}
622
}
623
}
624
}
625
false
626
}
627
}
628
629
# [ test ]
630
fn sequential_inserts_and_deletes ( ) {
631
let mut g = FileGraph:: new( ) ;
75167672 Andy McCall 1 month ago
632
g.insert (
633
h ( 1 ) ,
634
0 ,
635
Vertex:: Start,
636
Vertex:: End,
637
& lines ( & [ " a\n " , " b\n " , " c\n " ] ) ,
638
) ;
ff0af1ee unknown 1 month ago
639
let m = g.materialize ( & NoDeps) ;
640
assert! ( ! m.is_conflicted ( ) ) ;
641
assert_eq! ( text ( & g, & m) , " a\n b\n c\n " ) ;
642
643
75167672 Andy McCall 1 month ago
644
g.insert (
645
h ( 2 ) ,
646
0 ,
647
Vertex:: Atom( ( h ( 1 ) , 1 ) ) ,
648
Vertex:: Atom( ( h ( 1 ) , 2 ) ) ,
649
& lines ( & [ " x\n " ] ) ,
650
) ;
651
g.delete (
652
h ( 2 ) ,
653
& AtomRange {
654
change: h ( 1 ) ,
655
from: 0 ,
656
to: 1 ,
657
} ,
658
) ;
ff0af1ee unknown 1 month ago
659
let deps = Deps( [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ) ;
660
let m = g.materialize ( & deps) ;
661
assert! ( ! m.is_conflicted ( ) ) ;
662
assert_eq! ( text ( & g, & m) , " b\n x\n c\n " ) ;
663
assert_eq! ( m.live_atoms ( ) , vec! [ ( h ( 1 ) , 1 ) , ( h ( 2 ) , 0 ) , ( h ( 1 ) , 2 ) ] ) ;
664
}
665
666
# [ test ]
667
fn concurrent_inserts_at_same_anchor_conflict ( ) {
668
let mut g = FileGraph:: new( ) ;
669
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " ] ) ) ;
75167672 Andy McCall 1 month ago
670
g.insert (
671
h ( 2 ) ,
672
0 ,
673
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
674
Vertex:: Atom( ( h ( 1 ) , 1 ) ) ,
675
& lines ( & [ " from-2\n " ] ) ,
676
) ;
677
g.insert (
678
h ( 3 ) ,
679
0 ,
680
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
681
Vertex:: Atom( ( h ( 1 ) , 1 ) ) ,
682
& lines ( & [ " from-3\n " , " more-3\n " ] ) ,
683
) ;
684
let deps = Deps(
685
[ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ]
686
.into_iter ( )
687
.collect ( ) ,
688
) ;
ff0af1ee unknown 1 month ago
689
let m = g.materialize ( & deps) ;
690
assert! ( m.is_conflicted ( ) ) ;
691
assert_eq! ( m.conflict_count ( ) , 1 ) ;
692
assert_eq! (
693
m.segments,
694
vec! [
695
Segment:: Lines( vec! [ ( h ( 1 ) , 0 ) ] ) ,
696
Segment:: Conflict( vec! [ vec! [ ( h ( 2 ) , 0 ) ] , vec! [ ( h ( 3 ) , 0 ) , ( h ( 3 ) , 1 ) ] ] ) ,
697
Segment:: Lines( vec! [ ( h ( 1 ) , 1 ) ] ) ,
698
]
699
) ;
700
let t = text ( & g, & m) ;
701
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}" ) ;
702
}
703
704
# [ test ]
705
fn concurrent_inserts_at_different_anchors_do_not_conflict ( ) {
706
let mut g = FileGraph:: new( ) ;
75167672 Andy McCall 1 month ago
707
g.insert (
708
h ( 1 ) ,
709
0 ,
710
Vertex:: Start,
711
Vertex:: End,
712
& lines ( & [ " a\n " , " b\n " , " c\n " ] ) ,
713
) ;
714
g.insert (
715
h ( 2 ) ,
716
0 ,
717
Vertex:: Start,
718
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
719
& lines ( & [ " top\n " ] ) ,
720
) ;
721
g.insert (
722
h ( 3 ) ,
723
0 ,
724
Vertex:: Atom( ( h ( 1 ) , 2 ) ) ,
725
Vertex:: End,
726
& lines ( & [ " bottom\n " ] ) ,
727
) ;
728
let deps = Deps(
729
[ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ]
730
.into_iter ( )
731
.collect ( ) ,
732
) ;
ff0af1ee unknown 1 month ago
733
let m = g.materialize ( & deps) ;
734
assert! ( ! m.is_conflicted ( ) ) ;
735
assert_eq! ( text ( & g, & m) , " top\n a\n b\n c\n bottom\n " ) ;
736
}
737
738
# [ test ]
739
fn concurrent_deletes_of_the_same_line_agree ( ) {
740
let mut g = FileGraph:: new( ) ;
741
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " ] ) ) ;
75167672 Andy McCall 1 month ago
742
g.delete (
743
h ( 2 ) ,
744
& AtomRange {
745
change: h ( 1 ) ,
746
from: 0 ,
747
to: 1 ,
748
} ,
749
) ;
750
g.delete (
751
h ( 3 ) ,
752
& AtomRange {
753
change: h ( 1 ) ,
754
from: 0 ,
755
to: 1 ,
756
} ,
757
) ;
758
let deps = Deps(
759
[ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ]
760
.into_iter ( )
761
.collect ( ) ,
762
) ;
ff0af1ee unknown 1 month ago
763
let m = g.materialize ( & deps) ;
764
assert! ( ! m.is_conflicted ( ) ) ;
765
assert_eq! ( text ( & g, & m) , " b\n " ) ;
766
}
767
768
# [ test ]
769
fn delete_insert_conflict_is_a_zombie ( ) {
770
let mut g = FileGraph:: new( ) ;
771
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " ] ) ) ;
772
75167672 Andy McCall 1 month ago
773
g.delete (
774
h ( 2 ) ,
775
& AtomRange {
776
change: h ( 1 ) ,
777
from: 0 ,
778
to: 1 ,
779
} ,
780
) ;
781
g.insert (
782
h ( 3 ) ,
783
0 ,
784
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
785
Vertex:: Atom( ( h ( 1 ) , 1 ) ) ,
786
& lines ( & [ " after-a\n " ] ) ,
787
) ;
788
let deps = Deps(
789
[ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ]
790
.into_iter ( )
791
.collect ( ) ,
792
) ;
ff0af1ee unknown 1 month ago
793
let m = g.materialize ( & deps) ;
794
assert! ( m.is_conflicted ( ) ) ;
795
assert_eq! (
796
m.segments,
797
vec! [
75167672 Andy McCall 1 month ago
798
Segment:: Zombie {
799
deleted: vec! [ ( h ( 1 ) , 0 ) ] ,
800
inserted: vec! [ ( h ( 3 ) , 0 ) ]
801
} ,
ff0af1ee unknown 1 month ago
802
Segment:: Lines( vec! [ ( h ( 1 ) , 1 ) ] ) ,
803
]
804
) ;
805
let t = text ( & g, & m) ;
806
assert! ( t.contains ( " -------\n a\n =======" ) , " {t}" ) ;
807
assert! ( t.contains ( " after-a\n >>>>>>>" ) , " {t}" ) ;
808
809
75167672 Andy McCall 1 month ago
810
let deps = Deps(
811
[ ( h ( 2 ) , vec! [ h ( 1 ) , h ( 3 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ]
812
.into_iter ( )
813
.collect ( ) ,
814
) ;
ff0af1ee unknown 1 month ago
815
let m = g.materialize ( & deps) ;
816
assert! ( ! m.is_conflicted ( ) ) ;
817
assert_eq! ( text ( & g, & m) , " after-a\n b\n " ) ;
818
}
819
820
# [ test ]
23b217d7 Andy McCall 1 month ago ↶
821
fn insert_after_a_line_deleted_by_a_dependency_is_not_a_conflict ( ) {
822
823
824
let mut g = FileGraph:: new( ) ;
825
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " ] ) ) ;
75167672 Andy McCall 1 month ago
826
g.delete (
827
h ( 2 ) ,
828
& AtomRange {
829
change: h ( 1 ) ,
830
from: 1 ,
831
to: 2 ,
832
} ,
833
) ;
834
g.insert (
835
h ( 3 ) ,
836
0 ,
837
Vertex:: Atom( ( h ( 1 ) , 1 ) ) ,
838
Vertex:: End,
839
& lines ( & [ " c\n " ] ) ,
840
) ;
841
let deps = Deps(
842
[ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 2 ) ] ) ]
843
.into_iter ( )
844
.collect ( ) ,
845
) ;
23b217d7 Andy McCall 1 month ago ↶
846
let m = g.materialize ( & deps) ;
847
assert! ( ! m.is_conflicted ( ) , " {:?}" , m.segments) ;
848
assert_eq! ( text ( & g, & m) , " a\n c\n " ) ;
849
850
851
let mut g = FileGraph:: new( ) ;
852
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " x\n " ] ) ) ;
75167672 Andy McCall 1 month ago
853
g.delete (
854
h ( 1 ) ,
855
& AtomRange {
856
change: h ( 1 ) ,
857
from: 0 ,
858
to: 1 ,
859
} ,
860
) ;
861
g.insert (
862
h ( 2 ) ,
863
0 ,
864
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
865
Vertex:: End,
866
& lines ( & [ " y\n " ] ) ,
867
) ;
23b217d7 Andy McCall 1 month ago ↶
868
let deps = Deps( [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) ] .into_iter ( ) .collect ( ) ) ;
869
let m = g.materialize ( & deps) ;
870
assert! ( ! m.is_conflicted ( ) ) ;
871
assert_eq! ( text ( & g, & m) , " y\n " ) ;
872
}
873
874
# [ test ]
ff0af1ee unknown 1 month ago
875
fn resolution_collapses_a_conflict ( ) {
876
let mut g = FileGraph:: new( ) ;
877
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " ] ) ) ;
75167672 Andy McCall 1 month ago
878
g.insert (
879
h ( 2 ) ,
880
0 ,
881
Vertex:: Start,
882
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
883
& lines ( & [ " x\n " ] ) ,
884
) ;
885
g.insert (
886
h ( 3 ) ,
887
0 ,
888
Vertex:: Start,
889
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
890
& lines ( & [ " y\n " ] ) ,
891
) ;
892
let mut deps: HashMap< _ , _ > = [ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ]
893
.into_iter ( )
894
.collect ( ) ;
ff0af1ee unknown 1 month ago
895
assert! ( g.materialize ( & Deps( deps.clone ( ) ) ) .is_conflicted ( ) ) ;
896
75167672 Andy McCall 1 month ago
897
g.delete (
898
h ( 4 ) ,
899
& AtomRange {
900
change: h ( 2 ) ,
901
from: 0 ,
902
to: 1 ,
903
} ,
904
) ;
ff0af1ee unknown 1 month ago
905
deps.insert ( h ( 4 ) , vec! [ h ( 2 ) , h ( 3 ) ] ) ;
906
let m = g.materialize ( & Deps( deps) ) ;
907
assert! ( ! m.is_conflicted ( ) ) ;
908
assert_eq! ( text ( & g, & m) , " y\n a\n " ) ;
909
}
910
911
# [ test ]
912
fn empty_and_unterminated_files ( ) {
913
let g = FileGraph:: new( ) ;
914
let m = g.materialize ( & NoDeps) ;
915
assert_eq! ( m.segments, vec! [ ] ) ;
916
assert_eq! ( text ( & g, & m) , " " ) ;
917
let mut g = FileGraph:: new( ) ;
918
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " no newline" ] ) ) ;
919
assert_eq! ( text ( & g, & g.materialize ( & NoDeps) ) , " no newline" ) ;
920
}
921
252e8cb2 Andy McCall 1 month ago ↶
922
# [ test ]
923
fn serialization_round_trip ( ) {
924
let mut g = FileGraph:: new( ) ;
925
g.insert ( h ( 1 ) , 0 , Vertex:: Start, Vertex:: End, & lines ( & [ " a\n " , " b\n " ] ) ) ;
75167672 Andy McCall 1 month ago
926
g.insert (
927
h ( 2 ) ,
928
0 ,
929
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
930
Vertex:: Atom( ( h ( 1 ) , 1 ) ) ,
931
& lines ( & [ " x\n " ] ) ,
932
) ;
933
g.delete (
934
h ( 3 ) ,
935
& AtomRange {
936
change: h ( 1 ) ,
937
from: 1 ,
938
to: 2 ,
939
} ,
940
) ;
252e8cb2 Andy McCall 1 month ago ↶
941
let v = g.to_value ( ) ;
942
let bytes = crate :: cbor:: encode( & v) ;
943
let back = FileGraph:: from_value( & crate :: cbor:: decode( & bytes) .unwrap ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
944
let deps = Deps(
945
[ ( h ( 2 ) , vec! [ h ( 1 ) ] ) , ( h ( 3 ) , vec! [ h ( 1 ) ] ) ]
946
.into_iter ( )
947
.collect ( ) ,
948
) ;
252e8cb2 Andy McCall 1 month ago ↶
949
assert_eq! ( back.materialize ( & deps) , g.materialize ( & deps) ) ;
950
assert_eq! ( text ( & back, & back.materialize ( & deps) ) , " a\n x\n " ) ;
75167672 Andy McCall 1 month ago
951
assert_eq! (
952
crate :: cbor:: encode( & back.to_value ( ) ) ,
953
bytes,
954
" deterministic"
955
) ;
252e8cb2 Andy McCall 1 month ago ↶
956
}
957
ff0af1ee unknown 1 month ago
958
959
# [ test ]
960
fn application_order_is_irrelevant ( ) {
961
use rand:: seq:: SliceRandom;
75167672 Andy McCall 1 month ago
962
type Op = (
963
ChangeHash,
964
Vec< ( u32 , Vertex, Vertex, Vec< Vec< u8 > > ) > ,
965
Vec< AtomRange> ,
966
) ;
ff0af1ee unknown 1 month ago
967
let ops: Vec< Op> = vec! [
75167672 Andy McCall 1 month ago
968
(
969
h ( 1 ) ,
970
vec! [ ( 0 , Vertex:: Start, Vertex:: End, lines ( & [ " a\n " , " b\n " , " c\n " ] ) ) ] ,
971
vec! [ ] ,
972
) ,
973
(
974
h ( 2 ) ,
975
vec! [ (
976
0 ,
977
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
978
Vertex:: Atom( ( h ( 1 ) , 1 ) ) ,
979
lines ( & [ " p\n " ] ) ,
980
) ] ,
981
vec! [ ] ,
982
) ,
983
(
984
h ( 3 ) ,
985
vec! [ (
986
0 ,
987
Vertex:: Atom( ( h ( 1 ) , 0 ) ) ,
988
Vertex:: Atom( ( h ( 1 ) , 1 ) ) ,
989
lines ( & [ " q\n " , " r\n " ] ) ,
990
) ] ,
991
vec! [ AtomRange {
992
change: h ( 1 ) ,
993
from: 2 ,
994
to: 3 ,
995
} ] ,
996
) ,
997
(
998
h ( 4 ) ,
999
vec! [ (
1000
0 ,
1001
Vertex:: Atom( ( h ( 3 ) , 1 ) ) ,
1002
Vertex:: Atom( ( h ( 1 ) , 1 ) ) ,
1003
lines ( & [ " s\n " ] ) ,
1004
) ] ,
1005
vec! [ AtomRange {
1006
change: h ( 1 ) ,
1007
from: 1 ,
1008
to: 2 ,
1009
} ] ,
1010
) ,
1011
(
1012
h ( 5 ) ,
1013
vec! [ ( 0 , Vertex:: Atom( ( h ( 1 ) , 2 ) ) , Vertex:: End, lines ( & [ " z\n " ] ) ) ] ,
1014
vec! [ ] ,
1015
) ,
ff0af1ee unknown 1 month ago
1016
] ;
1017
let deps = Deps(
75167672 Andy McCall 1 month ago
1018
[
1019
( h ( 2 ) , vec! [ h ( 1 ) ] ) ,
1020
( h ( 3 ) , vec! [ h ( 1 ) ] ) ,
1021
( h ( 4 ) , vec! [ h ( 3 ) , h ( 1 ) ] ) ,
1022
( h ( 5 ) , vec! [ h ( 1 ) ] ) ,
1023
]
1024
.into_iter ( )
1025
.collect ( ) ,
ff0af1ee unknown 1 month ago
1026
) ;
1027
let build = | order : & [usize ]| {
1028
let mut g = FileGraph:: new( ) ;
1029
for & i in order {
1030
let ( c, ins, dels) = & ops[ i] ;
1031
for ( idx, a, b, l) in ins {
1032
g.insert ( * c, * idx, * a, * b, l) ;
1033
}
1034
for d in dels {
1035
g.delete ( * c, d) ;
1036
}
1037
}
1038
let m = g.materialize ( & deps) ;
1039
( m.clone ( ) , text ( & g, & m) )
1040
} ;
1041
let reference = build ( & [ 0 , 1 , 2 , 3 , 4 ] ) ;
1042
assert! ( reference.0. is_conflicted ( ) ) ;
1043
let mut rng = rand:: thread_rng( ) ;
1044
let mut order: Vec< usize > = ( 0 .. ops.len ( ) ) .collect ( ) ;
1045
for _ in 0 .. 50 {
1046
order.shuffle ( & mut rng) ;
1047
assert_eq! ( build ( & order) , reference, " order {order:?}" ) ;
1048
}
1049
}
1050
}