implement a basic DAG diffing algorithm
License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>
Jeromy committed
Aug 10, 2015 at 17:47 UTC
ed4274c9b75b513672fa674884a5df2cdb45276c
1 file changed
+152
merkledag/utils/diff.go
new
+152
@@ -0,0 +1,152 @@
1
+package dagutils
2
+
3
+import (
4
+ "bytes"
5
+ "fmt"
6
+ "path"
7
+
8
+ key "github.com/ipfs/go-ipfs/blocks/key"
9
+ dag "github.com/ipfs/go-ipfs/merkledag"
10
+ context "golang.org/x/net/context"
11
+)
12
+
13
+const (
14
+ Add = iota
15
+ Remove
16
+ Mod
17
+)
18
+
19
+type Change struct {
20
+ Type int
21
+ Path string
22
+ Before key.Key
23
+ After key.Key
24
+}
25
+
26
+func (c *Change) String() string {
27
+ switch c.Type {
28
+ case Add:
29
+ return fmt.Sprintf("Added %s at %s", c.After.B58String()[:6], c.Path)
30
+ case Remove:
31
+ return fmt.Sprintf("Removed %s from %s", c.Before.B58String()[:6], c.Path)
32
+ case Mod:
33
+ return fmt.Sprintf("Changed %s to %s at %s", c.Before.B58String()[:6], c.After.B58String()[:6], c.Path)
34
+ default:
35
+ panic("nope")
36
+ }
37
+}
38
+
39
+func ApplyChange(ctx context.Context, ds dag.DAGService, nd *dag.Node, cs []*Change) (*dag.Node, error) {
40
+ e := NewDagEditor(ds, nd)
41
+ for _, c := range cs {
42
+ switch c.Type {
43
+ case Add:
44
+ err := e.InsertNodeAtPath(ctx, c.Path, c.After, nil)
45
+ if err != nil {
46
+ return nil, err
47
+ }
48
+
49
+ case Remove:
50
+ err := e.RmLink(ctx, c.Path)
51
+ if err != nil {
52
+ return nil, err
53
+ }
54
+
55
+ case Mod:
56
+ err := e.RmLink(ctx, c.Path)
57
+ if err != nil {
58
+ return nil, err
59
+ }
60
+ err = e.InsertNodeAtPath(ctx, c.Path, c.After, nil)
61
+ if err != nil {
62
+ return nil, err
63
+ }
64
+ }
65
+ }
66
+ return e.GetNode(), nil
67
+}
68
+
69
+func Diff(ctx context.Context, ds dag.DAGService, a, b *dag.Node) []*Change {
70
+ if len(a.Links) == 0 && len(b.Links) == 0 {
71
+ ak, _ := a.Key()
72
+ bk, _ := b.Key()
73
+ return []*Change{
74
+ &Change{
75
+ Type: Mod,
76
+ Before: ak,
77
+ After: bk,
78
+ },
79
+ }
80
+ }
81
+
82
+ var out []*Change
83
+ clean_a := a.Copy()
84
+ clean_b := b.Copy()
85
+
86
+ // strip out unchanged stuff
87
+ for _, lnk := range a.Links {
88
+ l, err := b.GetNodeLink(lnk.Name)
89
+ if err == nil {
90
+ if bytes.Equal(l.Hash, lnk.Hash) {
91
+ // no change... ignore it
92
+ } else {
93
+ anode, _ := lnk.GetNode(ctx, ds)
94
+ bnode, _ := l.GetNode(ctx, ds)
95
+ sub := Diff(ctx, ds, anode, bnode)
96
+
97
+ for _, subc := range sub {
98
+ subc.Path = path.Join(lnk.Name, subc.Path)
99
+ out = append(out, subc)
100
+ }
101
+ }
102
+ clean_a.RemoveNodeLink(l.Name)
103
+ clean_b.RemoveNodeLink(l.Name)
104
+ }
105
+ }
106
+
107
+ for _, lnk := range clean_a.Links {
108
+ out = append(out, &Change{
109
+ Type: Remove,
110
+ Path: lnk.Name,
111
+ Before: key.Key(lnk.Hash),
112
+ })
113
+ }
114
+ for _, lnk := range clean_b.Links {
115
+ out = append(out, &Change{
116
+ Type: Add,
117
+ Path: lnk.Name,
118
+ After: key.Key(lnk.Hash),
119
+ })
120
+ }
121
+
122
+ return out
123
+}
124
+
125
+type Conflict struct {
126
+ A *Change
127
+ B *Change
128
+}
129
+
130
+func MergeDiffs(a, b []*Change) ([]*Change, []Conflict) {
131
+ var out []*Change
132
+ var conflicts []Conflict
133
+ paths := make(map[string]*Change)
134
+ for _, c := range a {
135
+ paths[c.Path] = c
136
+ }
137
+
138
+ for _, c := range b {
139
+ if ca, ok := paths[c.Path]; ok {
140
+ conflicts = append(conflicts, Conflict{
141
+ A: ca,
142
+ B: c,
143
+ })
144
+ } else {
145
+ out = append(out, c)
146
+ }
147
+ }
148
+ for _, c := range paths {
149
+ out = append(out, c)
150
+ }
151
+ return out, conflicts
152
+}