1package mst
2
3import (
4 "context"
5 "fmt"
6
7 "github.com/bluesky-social/indigo/util"
8 cid "github.com/ipfs/go-cid"
9 cbor "github.com/ipfs/go-ipld-cbor"
10)
11
12type DiffOp struct {
13 Depth int
14 Op string
15 Rpath string
16 OldCid cid.Cid
17 NewCid cid.Cid
18}
19
20// TODO: this code isn't great, should be rewritten on top of the baseline datastructures once functional and correct
21func DiffTrees(ctx context.Context, bs cbor.IpldBlockstore, from, to cid.Cid) ([]*DiffOp, error) {
22 cst := util.CborStore(bs)
23
24 if from == cid.Undef {
25 return identityDiff(ctx, bs, to)
26 }
27
28 ft := LoadMST(cst, from)
29 tt := LoadMST(cst, to)
30
31 fents, err := ft.getEntries(ctx)
32 if err != nil {
33 return nil, err
34 }
35
36 tents, err := tt.getEntries(ctx)
37 if err != nil {
38 return nil, err
39 }
40
41 var ixf, ixt int
42 var out []*DiffOp
43 for ixf < len(fents) && ixt < len(tents) {
44 ef := fents[ixf]
45 et := tents[ixt]
46
47 if nodeEntriesEqual(&ef, &et) {
48 ixf++
49 ixt++
50 continue
51 }
52
53 if ef.isLeaf() && et.isLeaf() {
54 if ef.Key == et.Key {
55 if ef.Val == et.Val {
56 return nil, fmt.Errorf("hang on, why are these leaves equal?")
57 }
58
59 out = append(out, &DiffOp{
60 Op: "mut",
61 Rpath: ef.Key,
62 OldCid: ef.Val,
63 NewCid: et.Val,
64 })
65 ixf++
66 ixt++
67 continue
68 }
69 // different keys... what do?
70
71 // if the 'to' key is earlier than the 'from' key, call it an insert
72 // otherwise call it a deletion?
73
74 if ef.Key > et.Key {
75 out = append(out, &DiffOp{
76 Op: "add",
77 Rpath: et.Key,
78 NewCid: et.Val,
79 })
80 // only walk forward the pointer that was 'behind'
81 ixt++
82 } else {
83 out = append(out, &DiffOp{
84 Op: "del",
85 Rpath: ef.Key,
86 OldCid: ef.Val,
87 })
88 // only walk forward the pointer that was 'behind'
89 ixf++
90 }
91
92 // call it an insertion?
93 continue
94 }
95
96 if ef.isTree() {
97 sub, err := ef.Tree.getEntries(ctx)
98 if err != nil {
99 return nil, err
100 }
101
102 fents = append(sub, fents[ixf+1:]...)
103 ixf = 0
104 continue
105 }
106
107 if et.isTree() {
108 sub, err := et.Tree.getEntries(ctx)
109 if err != nil {
110 return nil, err
111 }
112
113 tents = append(sub, tents[ixt+1:]...)
114 ixt = 0
115 continue
116 }
117 }
118
119 for ; ixf < len(fents); ixf++ {
120 // deletions
121
122 e := fents[ixf]
123 if e.isLeaf() {
124 out = append(out, &DiffOp{
125 Op: "del",
126 Rpath: e.Key,
127 OldCid: e.Val,
128 })
129
130 } else if e.isTree() {
131 if err := e.Tree.WalkLeavesFrom(ctx, "", func(key string, val cid.Cid) error {
132 out = append(out, &DiffOp{
133 Op: "del",
134 Rpath: key,
135 OldCid: val,
136 })
137 return nil
138 }); err != nil {
139 return nil, err
140 }
141 }
142 }
143
144 for ; ixt < len(tents); ixt++ {
145 // insertions
146
147 e := tents[ixt]
148 if e.isLeaf() {
149 out = append(out, &DiffOp{
150 Op: "add",
151 Rpath: e.Key,
152 NewCid: e.Val,
153 })
154
155 } else if e.isTree() {
156 if err := e.Tree.WalkLeavesFrom(ctx, "", func(key string, val cid.Cid) error {
157 out = append(out, &DiffOp{
158 Op: "add",
159 Rpath: key,
160 NewCid: val,
161 })
162 return nil
163 }); err != nil {
164 return nil, err
165 }
166 }
167 }
168
169 return out, nil
170}
171
172func nodeEntriesEqual(a, b *nodeEntry) bool {
173 if !(a.Key == b.Key && a.Val == b.Val) {
174 return false
175 }
176
177 if a.Tree == nil && b.Tree == nil {
178 return true
179 }
180
181 if a.Tree != nil && b.Tree != nil && a.Tree.pointer == b.Tree.pointer {
182 return true
183 }
184
185 return false
186}
187
188func identityDiff(ctx context.Context, bs cbor.IpldBlockstore, root cid.Cid) ([]*DiffOp, error) {
189 cst := util.CborStore(bs)
190 tt := LoadMST(cst, root)
191
192 var ops []*DiffOp
193 if err := tt.WalkLeavesFrom(ctx, "", func(key string, val cid.Cid) error {
194 ops = append(ops, &DiffOp{
195 Op: "add",
196 Rpath: key,
197 NewCid: val,
198 })
199 return nil
200 }); err != nil {
201 return nil, err
202 }
203 return ops, nil
204}