@cryptotaxi247 / kubo / commits / ed4274c9b

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 +}