This repository has been archived by the owner on Aug 28, 2021. It is now read-only.
/
diff.go
273 lines (248 loc) · 8.62 KB
/
diff.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// 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 diff
import (
"github.com/attic-labs/noms/go/types"
)
type (
diffFunc func(changeChan chan<- types.ValueChanged, stopChan <-chan struct{})
pathPartFunc func(v types.Value) types.PathPart
valueFunc func(k types.Value) types.Value
)
// Difference represents a "diff" between two Noms graphs.
type Difference struct {
// Path to the Value that has changed
Path types.Path
// ChangeType indicates the type of diff: modified, added, deleted
ChangeType types.DiffChangeType
// OldValue is Value before the change, can be nil if Value was added
OldValue types.Value
// NewValue is Value after the change, can be nil if Value was removed
NewValue types.Value
// NewKeyValue is used for when elements are added to diffs with a
// non-primitive key. The new key must available when the map gets updated.
NewKeyValue types.Value
}
func (dif Difference) IsEmpty() bool {
return dif.Path == nil && dif.OldValue == nil && dif.NewValue == nil
}
// differ is used internally to hold information necessary for diffing two graphs.
type differ struct {
// Channel used to send Difference objects back to caller
diffChan chan<- Difference
// Channel that caller should close() to terminate Diff function.
stopChan chan struct{}
// Use LeftRight diff as opposed to TopDown
leftRight bool
}
// Diff traverses two graphs simultaneously looking for differences. It returns
// two channels: a DiffReceiveChan that the caller can use to iterate over the
// diffs in the graph and a StopSendChanel that a caller can use to signal the
// Diff function to stop processing.
// Diff returns the Differences in depth-first first order. A 'diff' is defined
// as one of the following conditions:
// * a Value is Added or Removed from a node in the graph
// * the type of a Value has changed in the graph
// * a primitive (i.e. Bool, Number, String, Ref or Blob) Value has changed
//
// A Difference is not returned when a non-primitive value has been modified. For
// example, a struct field has been changed from one Value of type Employee to
// another. Those modifications are accounted for by the Differences described
// above at a lower point in the graph.
//
// If leftRight is true then the left-right diff is used for ordered sequences
// - see Diff vs DiffLeftRight in Set and Map.
//
// Note: the function sends messages on diffChan and checks whether stopChan has
// been closed to know if it needs to terminate diffing early. To function
// properly it needs to be executed concurrently with code that reads values from
// diffChan. The following is a typical invocation of Diff():
// dChan := make(chan Difference)
// sChan := make(chan struct{})
// go func() {
// d.Diff(s3, s4, dChan, sChan, leftRight)
// close(dChan)
// }()
// for dif := range dChan {
// <some code>
// }
func Diff(v1, v2 types.Value, dChan chan<- Difference, stopChan chan struct{}, leftRight bool) {
d := differ{diffChan: dChan, stopChan: stopChan, leftRight: leftRight}
if !v1.Equals(v2) {
if !shouldDescend(v1, v2) {
d.sendDiff(Difference{Path: nil, ChangeType: types.DiffChangeModified, OldValue: v1, NewValue: v2})
} else {
d.diff(nil, v1, v2)
}
}
}
func (d differ) diff(p types.Path, v1, v2 types.Value) bool {
switch v1.Kind() {
case types.ListKind:
return d.diffLists(p, v1.(types.List), v2.(types.List))
case types.MapKind:
return d.diffMaps(p, v1.(types.Map), v2.(types.Map))
case types.SetKind:
return d.diffSets(p, v1.(types.Set), v2.(types.Set))
case types.StructKind:
return d.diffStructs(p, v1.(types.Struct), v2.(types.Struct))
default:
panic("Unrecognized type in diff function")
}
}
func (d differ) diffLists(p types.Path, v1, v2 types.List) (stop bool) {
spliceChan := make(chan types.Splice)
stopChan := make(chan struct{}, 1) // buffer size of 1s, so this won't block if diff already finished
go func() {
v2.Diff(v1, spliceChan, stopChan)
close(spliceChan)
}()
for splice := range spliceChan {
if stop {
break
}
if splice.SpRemoved == splice.SpAdded {
// Heuristic: list only has modifications.
for i := uint64(0); i < splice.SpRemoved; i++ {
lastEl := v1.Get(splice.SpAt + i)
newEl := v2.Get(splice.SpFrom + i)
if shouldDescend(lastEl, newEl) {
idx := types.Number(splice.SpAt + i)
stop = d.diff(append(p, types.NewIndexPath(idx)), lastEl, newEl)
} else {
p1 := p.Append(types.NewIndexPath(types.Number(splice.SpAt + i)))
dif := Difference{p1, types.DiffChangeModified, v1.Get(splice.SpAt + i), v2.Get(splice.SpFrom + i), nil}
stop = !d.sendDiff(dif)
}
}
continue
}
// Heuristic: list only has additions/removals.
for i := uint64(0); i < splice.SpRemoved && !stop; i++ {
p1 := p.Append(types.NewIndexPath(types.Number(splice.SpAt + i)))
dif := Difference{Path: p1, ChangeType: types.DiffChangeRemoved, OldValue: v1.Get(splice.SpAt + i), NewValue: nil}
stop = !d.sendDiff(dif)
}
for i := uint64(0); i < splice.SpAdded && !stop; i++ {
p1 := p.Append(types.NewIndexPath(types.Number(splice.SpFrom + i)))
dif := Difference{Path: p1, ChangeType: types.DiffChangeAdded, OldValue: nil, NewValue: v2.Get(splice.SpFrom + i)}
stop = !d.sendDiff(dif)
}
}
if stop {
stopChan <- struct{}{}
// Wait for diff to stop.
for range spliceChan {
}
}
return
}
func (d differ) diffMaps(p types.Path, v1, v2 types.Map) bool {
return d.diffOrdered(p,
func(v types.Value) types.PathPart {
if types.ValueCanBePathIndex(v) {
return types.NewIndexPath(v)
} else {
return types.NewHashIndexPath(v.Hash())
}
},
func(cc chan<- types.ValueChanged, sc <-chan struct{}) {
if d.leftRight {
v2.DiffLeftRight(v1, cc, sc)
} else {
v2.DiffHybrid(v1, cc, sc)
}
},
func(k types.Value) types.Value { return k },
func(k types.Value) types.Value { return v1.Get(k) },
func(k types.Value) types.Value { return v2.Get(k) },
)
}
func (d differ) diffStructs(p types.Path, v1, v2 types.Struct) bool {
str := func(v types.Value) string {
return string(v.(types.String))
}
return d.diffOrdered(p,
func(v types.Value) types.PathPart { return types.NewFieldPath(str(v)) },
func(cc chan<- types.ValueChanged, sc <-chan struct{}) {
v2.Diff(v1, cc, sc)
},
func(k types.Value) types.Value { return k },
func(k types.Value) types.Value { return v1.Get(str(k)) },
func(k types.Value) types.Value { return v2.Get(str(k)) },
)
}
func (d differ) diffSets(p types.Path, v1, v2 types.Set) bool {
return d.diffOrdered(p,
func(v types.Value) types.PathPart {
if types.ValueCanBePathIndex(v) {
return types.NewIndexPath(v)
}
return types.NewHashIndexPath(v.Hash())
},
func(cc chan<- types.ValueChanged, sc <-chan struct{}) {
if d.leftRight {
v2.DiffLeftRight(v1, cc, sc)
} else {
v2.DiffHybrid(v1, cc, sc)
}
},
func(k types.Value) types.Value { return k },
func(k types.Value) types.Value { return k },
func(k types.Value) types.Value { return k },
)
}
func (d differ) diffOrdered(p types.Path, ppf pathPartFunc, df diffFunc, kf, v1, v2 valueFunc) (stop bool) {
changeChan := make(chan types.ValueChanged)
stopChan := make(chan struct{}, 1) // buffer size of 1, so this won't block if diff already finished
go func() {
df(changeChan, stopChan)
close(changeChan)
}()
for change := range changeChan {
if stop {
break
}
k := kf(change.Key)
p1 := p.Append(ppf(k))
switch change.ChangeType {
case types.DiffChangeAdded:
dif := Difference{Path: p1, ChangeType: types.DiffChangeAdded, OldValue: nil, NewValue: v2(change.Key), NewKeyValue: k}
stop = !d.sendDiff(dif)
case types.DiffChangeRemoved:
dif := Difference{Path: p1, ChangeType: types.DiffChangeRemoved, OldValue: v1(change.Key), NewValue: nil}
stop = !d.sendDiff(dif)
case types.DiffChangeModified:
c1, c2 := v1(change.Key), v2(change.Key)
if shouldDescend(c1, c2) {
stop = d.diff(p1, c1, c2)
} else {
dif := Difference{Path: p1, ChangeType: types.DiffChangeModified, OldValue: c1, NewValue: c2}
stop = !d.sendDiff(dif)
}
default:
panic("unknown change type")
}
}
if stop {
stopChan <- struct{}{}
for range changeChan {
}
}
return
}
// shouldDescend returns true, if Value is not primitive or is a Ref.
func shouldDescend(v1, v2 types.Value) bool {
kind := v1.Kind()
return !types.IsPrimitiveKind(kind) && kind == v2.Kind() && kind != types.RefKind
}
// stopSent returns true if a message has been sent to this StopChannel
func (d differ) sendDiff(dif Difference) bool {
select {
case <-d.stopChan:
return false
case d.diffChan <- dif:
return true
}
}