Unverified Commit bf514432 authored by Keenan Nemetz's avatar Keenan Nemetz Committed by GitHub

Fix bug in dagutils MergeDiffs. (#59)

parent 8f475e53
...@@ -102,64 +102,62 @@ func ApplyChange(ctx context.Context, ds ipld.DAGService, nd *dag.ProtoNode, cs ...@@ -102,64 +102,62 @@ func ApplyChange(ctx context.Context, ds ipld.DAGService, nd *dag.ProtoNode, cs
// 2. both of two nodes are ProtoNode. // 2. both of two nodes are ProtoNode.
// Otherwise, it compares the cid and emits a Mod change object. // Otherwise, it compares the cid and emits a Mod change object.
func Diff(ctx context.Context, ds ipld.DAGService, a, b ipld.Node) ([]*Change, error) { func Diff(ctx context.Context, ds ipld.DAGService, a, b ipld.Node) ([]*Change, error) {
// Base case where both nodes are leaves, just compare if a.Cid() == b.Cid() {
// their CIDs. return []*Change{}, nil
if len(a.Links()) == 0 && len(b.Links()) == 0 {
return getChange(a, b)
} }
var out []*Change
cleanA, okA := a.Copy().(*dag.ProtoNode) cleanA, okA := a.Copy().(*dag.ProtoNode)
cleanB, okB := b.Copy().(*dag.ProtoNode) cleanB, okB := b.Copy().(*dag.ProtoNode)
if !okA || !okB {
return getChange(a, b) linksA := a.Links()
linksB := b.Links()
if !okA || !okB || (len(linksA) == 0 && len(linksB) == 0) {
return []*Change{{Type: Mod, Before: a.Cid(), After: b.Cid()}}, nil
} }
// strip out unchanged stuff var out []*Change
for _, lnk := range a.Links() { for _, linkA := range linksA {
l, _, err := b.ResolveLink([]string{lnk.Name}) linkB, _, err := b.ResolveLink([]string{linkA.Name})
if err == nil {
if l.Cid.Equals(lnk.Cid) {
// no change... ignore it
} else {
anode, err := lnk.GetNode(ctx, ds)
if err != nil { if err != nil {
return nil, err continue
}
cleanA.RemoveNodeLink(linkA.Name)
cleanB.RemoveNodeLink(linkA.Name)
if linkA.Cid == linkB.Cid {
continue
} }
bnode, err := l.GetNode(ctx, ds) nodeA, err := linkA.GetNode(ctx, ds)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sub, err := Diff(ctx, ds, anode, bnode) nodeB, err := linkB.GetNode(ctx, ds)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, subc := range sub { sub, err := Diff(ctx, ds, nodeA, nodeB)
subc.Path = path.Join(lnk.Name, subc.Path) if err != nil {
out = append(out, subc) return nil, err
}
} }
_ = cleanA.RemoveNodeLink(l.Name)
_ = cleanB.RemoveNodeLink(l.Name) for _, c := range sub {
c.Path = path.Join(linkA.Name, c.Path)
} }
out = append(out, sub...)
} }
for _, lnk := range cleanA.Links() { for _, l := range cleanA.Links() {
out = append(out, &Change{ out = append(out, &Change{Type: Remove, Path: l.Name, Before: l.Cid})
Type: Remove,
Path: lnk.Name,
Before: lnk.Cid,
})
} }
for _, lnk := range cleanB.Links() {
out = append(out, &Change{ for _, l := range cleanB.Links() {
Type: Add, out = append(out, &Change{Type: Add, Path: l.Name, After: l.Cid})
Path: lnk.Name,
After: lnk.Cid,
})
} }
return out, nil return out, nil
...@@ -177,38 +175,26 @@ type Conflict struct { ...@@ -177,38 +175,26 @@ type Conflict struct {
// A slice of Conflicts is returned and contains pointers to the // A slice of Conflicts is returned and contains pointers to the
// Changes involved (which share the same path). // Changes involved (which share the same path).
func MergeDiffs(a, b []*Change) ([]*Change, []Conflict) { func MergeDiffs(a, b []*Change) ([]*Change, []Conflict) {
var out []*Change
var conflicts []Conflict
paths := make(map[string]*Change) paths := make(map[string]*Change)
for _, c := range a { for _, c := range a {
paths[c.Path] = c paths[c.Path] = c
} }
for _, c := range b { var changes []*Change
if ca, ok := paths[c.Path]; ok { var conflicts []Conflict
conflicts = append(conflicts, Conflict{
A: ca, for _, changeB := range b {
B: c, if changeA, ok := paths[changeB.Path]; ok {
}) conflicts = append(conflicts, Conflict{changeA, changeB})
} else { } else {
out = append(out, c) changes = append(changes, changeB)
} }
delete(paths, changeB.Path)
} }
for _, c := range paths { for _, c := range paths {
out = append(out, c) changes = append(changes, c)
} }
return out, conflicts
}
func getChange(a, b ipld.Node) ([]*Change, error) { return changes, conflicts
if a.Cid().Equals(b.Cid()) {
return []*Change{}, nil
}
return []*Change{
{
Type: Mod,
Before: a.Cid(),
After: b.Cid(),
},
}, nil
} }
package dagutils
import (
"context"
"testing"
cid "github.com/ipfs/go-cid"
ipld "github.com/ipfs/go-ipld-format"
dag "github.com/ipfs/go-merkledag"
mdtest "github.com/ipfs/go-merkledag/test"
)
func TestMergeDiffs(t *testing.T) {
node1 := dag.NodeWithData([]byte("one"))
node2 := dag.NodeWithData([]byte("two"))
node3 := dag.NodeWithData([]byte("three"))
node4 := dag.NodeWithData([]byte("four"))
changesA := []*Change{
{Add, "one", cid.Cid{}, node1.Cid()},
{Remove, "two", node2.Cid(), cid.Cid{}},
{Mod, "three", node3.Cid(), node4.Cid()},
}
changesB := []*Change{
{Mod, "two", node2.Cid(), node3.Cid()},
{Add, "four", cid.Cid{}, node4.Cid()},
}
changes, conflicts := MergeDiffs(changesA, changesB)
if len(changes) != 3 {
t.Fatal("unexpected merge changes")
}
expect := []*Change{
changesB[1],
changesA[0],
changesA[2],
}
for i, change := range changes {
if change.Type != expect[i].Type {
t.Error("unexpected diff change type")
}
if change.Path != expect[i].Path {
t.Error("unexpected diff change path")
}
if change.Before != expect[i].Before {
t.Error("unexpected diff change before")
}
if change.After != expect[i].After {
t.Error("unexpected diff change before")
}
}
if len(conflicts) != 1 {
t.Fatal("unexpected merge conflicts")
}
if conflicts[0].A != changesA[1] {
t.Error("unexpected merge conflict a")
}
if conflicts[0].B != changesB[0] {
t.Error("unexpected merge conflict b")
}
}
func TestDiff(t *testing.T) {
ctx := context.Background()
ds := mdtest.Mock()
rootA := &dag.ProtoNode{}
rootB := &dag.ProtoNode{}
child1 := dag.NodeWithData([]byte("one"))
child2 := dag.NodeWithData([]byte("two"))
child3 := dag.NodeWithData([]byte("three"))
child4 := dag.NodeWithData([]byte("four"))
rootA.AddNodeLink("one", child1)
rootA.AddNodeLink("two", child2)
rootB.AddNodeLink("one", child3)
rootB.AddNodeLink("four", child4)
nodes := []ipld.Node{child1, child2, child3, child4, rootA, rootB}
if err := ds.AddMany(ctx, nodes); err != nil {
t.Fatal("failed to add nodes")
}
changes, err := Diff(ctx, ds, rootA, rootB)
if err != nil {
t.Fatal("unexpected diff error")
}
if len(changes) != 3 {
t.Fatal("unexpected diff changes")
}
expect := []Change{
{Mod, "one", child1.Cid(), child3.Cid()},
{Remove, "two", child2.Cid(), cid.Cid{}},
{Add, "four", cid.Cid{}, child4.Cid()},
}
for i, change := range changes {
if change.Type != expect[i].Type {
t.Error("unexpected diff change type")
}
if change.Path != expect[i].Path {
t.Error("unexpected diff change path")
}
if change.Before != expect[i].Before {
t.Error("unexpected diff change before")
}
if change.After != expect[i].After {
t.Error("unexpected diff change before")
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment