This repository has been archived by the owner on Aug 28, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 266
/
three_way_list.go
133 lines (118 loc) · 4.88 KB
/
three_way_list.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright 2016 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
package merge
import (
"fmt"
"github.com/attic-labs/noms/go/d"
"github.com/attic-labs/noms/go/types"
)
func threeWayListMerge(a, b, parent types.List) (merged types.List, err error) {
aSpliceChan, bSpliceChan := make(chan types.Splice), make(chan types.Splice)
aStopChan, bStopChan := make(chan struct{}, 1), make(chan struct{}, 1)
go func() {
a.Diff(parent, aSpliceChan, aStopChan)
close(aSpliceChan)
}()
go func() {
b.Diff(parent, bSpliceChan, bStopChan)
close(bSpliceChan)
}()
stopAndDrain := func(stop chan<- struct{}, drain <-chan types.Splice) {
close(stop)
for range drain {
}
}
defer stopAndDrain(aStopChan, aSpliceChan)
defer stopAndDrain(bStopChan, bSpliceChan)
// The algorithm below relies on determining whether one splice "comes before" another, and whether the splices coming from the two diffs remove/add precisely the same elements. Unfortunately, the Golang zero-value for types.Splice (which is what gets read out of a/bSpliceChan when they're closed) is actaually a valid splice, albeit a meaningless one that indicates a no-op. It "comes before" any other splice, so having it in play really gums up the logic below. Rather than specifically checking for it all over the place, swap the zero-splice out for one full of SPLICE_UNASSIGNED, which is really the proper invalid splice value. That splice doesn't come before ANY valid splice, so the logic below can flow more clearly.
zeroSplice := types.Splice{}
zeroToInvalid := func(sp types.Splice) types.Splice {
if sp == zeroSplice {
return types.Splice{
SpAt: types.SPLICE_UNASSIGNED,
SpRemoved: types.SPLICE_UNASSIGNED,
SpAdded: types.SPLICE_UNASSIGNED,
SpFrom: types.SPLICE_UNASSIGNED,
}
}
return sp
}
invalidSplice := zeroToInvalid(types.Splice{})
merged = parent
offset := uint64(0)
aSplice, bSplice := invalidSplice, invalidSplice
for {
// Get the next splice from both a and b. If either diff(a, parent) or diff(b, parent) is complete, aSplice or bSplice will get an invalid types.Splice. Generally, though, this allows us to proceed through both diffs in (index) order, considering the "current" splice from both diffs at the same time.
if aSplice == invalidSplice {
aSplice = zeroToInvalid(<-aSpliceChan)
}
if bSplice == invalidSplice {
bSplice = zeroToInvalid(<-bSpliceChan)
}
// Both channels are producing zero values, so we're done.
if aSplice == invalidSplice && bSplice == invalidSplice {
break
}
if overlap(aSplice, bSplice) {
if canMerge(a, b, aSplice, bSplice) {
splice := merge(aSplice, bSplice)
merged = apply(a, merged, offset, splice)
offset += splice.SpAdded - splice.SpRemoved
aSplice, bSplice = invalidSplice, invalidSplice
continue
}
return parent, newMergeConflict("Overlapping splices: %s vs %s", describeSplice(aSplice), describeSplice(bSplice))
}
if aSplice.SpAt < bSplice.SpAt {
merged = apply(a, merged, offset, aSplice)
offset += aSplice.SpAdded - aSplice.SpRemoved
aSplice = invalidSplice
continue
}
merged = apply(b, merged, offset, bSplice)
offset += bSplice.SpAdded - bSplice.SpRemoved
bSplice = invalidSplice
}
return merged, nil
}
func overlap(s1, s2 types.Splice) bool {
earlier, later := s1, s2
if s2.SpAt < s1.SpAt {
earlier, later = s2, s1
}
return s1.SpAt == s2.SpAt || earlier.SpAt+earlier.SpRemoved > later.SpAt
}
// canMerge returns whether aSplice and bSplice can be merged into a single splice that can be applied to parent. Currently, we're only willing to do this if the two splices do _precisely_ the same thing -- that is, remove the same number of elements from the same starting index and insert the exact same list of new elements.
func canMerge(a, b types.List, aSplice, bSplice types.Splice) bool {
if aSplice != bSplice {
return false
}
aIter, bIter := a.IteratorAt(aSplice.SpFrom), b.IteratorAt(bSplice.SpFrom)
for count := uint64(0); count < aSplice.SpAdded; count++ {
aVal, bVal := aIter.Next(), bIter.Next()
if aVal == nil || bVal == nil || !aVal.Equals(bVal) {
return false
}
}
return true
}
// Since merge() is only called when canMerge() is true, we know s1 and s2 are exactly equal.
func merge(s1, s2 types.Splice) types.Splice {
return s1
}
func apply(source, target types.List, offset uint64, s types.Splice) types.List {
toAdd := make([]types.Valuable, s.SpAdded)
iter := source.IteratorAt(s.SpFrom)
for i := 0; uint64(i) < s.SpAdded; i++ {
v := iter.Next()
if v == nil {
d.Panic("List diff returned a splice that inserts a nonexistent element.")
}
toAdd[i] = v
}
return target.Edit().Splice(s.SpAt+offset, s.SpRemoved, toAdd...).List()
}
func describeSplice(s types.Splice) string {
return fmt.Sprintf("%d elements removed at %d; adding %d elements", s.SpRemoved, s.SpAt, s.SpAdded)
}