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