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