diff --git a/merkledag/utils/diff.go b/merkledag/utils/diff.go new file mode 100644 index 000000000..7bde3f1f3 --- /dev/null +++ b/merkledag/utils/diff.go @@ -0,0 +1,152 @@ +package dagutils + +import ( + "bytes" + "fmt" + "path" + + key "github.com/ipfs/go-ipfs/blocks/key" + dag "github.com/ipfs/go-ipfs/merkledag" + context "golang.org/x/net/context" +) + +const ( + Add = iota + Remove + Mod +) + +type Change struct { + Type int + Path string + Before key.Key + After key.Key +} + +func (c *Change) String() string { + switch c.Type { + case Add: + return fmt.Sprintf("Added %s at %s", c.After.B58String()[:6], c.Path) + case Remove: + return fmt.Sprintf("Removed %s from %s", c.Before.B58String()[:6], c.Path) + case Mod: + return fmt.Sprintf("Changed %s to %s at %s", c.Before.B58String()[:6], c.After.B58String()[:6], c.Path) + default: + panic("nope") + } +} + +func ApplyChange(ctx context.Context, ds dag.DAGService, nd *dag.Node, cs []*Change) (*dag.Node, error) { + e := NewDagEditor(ds, nd) + for _, c := range cs { + switch c.Type { + case Add: + err := e.InsertNodeAtPath(ctx, c.Path, c.After, nil) + if err != nil { + return nil, err + } + + case Remove: + err := e.RmLink(ctx, c.Path) + if err != nil { + return nil, err + } + + case Mod: + err := e.RmLink(ctx, c.Path) + if err != nil { + return nil, err + } + err = e.InsertNodeAtPath(ctx, c.Path, c.After, nil) + if err != nil { + return nil, err + } + } + } + return e.GetNode(), nil +} + +func Diff(ctx context.Context, ds dag.DAGService, a, b *dag.Node) []*Change { + if len(a.Links) == 0 && len(b.Links) == 0 { + ak, _ := a.Key() + bk, _ := b.Key() + return []*Change{ + &Change{ + Type: Mod, + Before: ak, + After: bk, + }, + } + } + + var out []*Change + clean_a := a.Copy() + clean_b := b.Copy() + + // strip out unchanged stuff + for _, lnk := range a.Links { + l, err := b.GetNodeLink(lnk.Name) + if err == nil { + if bytes.Equal(l.Hash, lnk.Hash) { + // no change... ignore it + } else { + anode, _ := lnk.GetNode(ctx, ds) + bnode, _ := l.GetNode(ctx, ds) + sub := Diff(ctx, ds, anode, bnode) + + for _, subc := range sub { + subc.Path = path.Join(lnk.Name, subc.Path) + out = append(out, subc) + } + } + clean_a.RemoveNodeLink(l.Name) + clean_b.RemoveNodeLink(l.Name) + } + } + + for _, lnk := range clean_a.Links { + out = append(out, &Change{ + Type: Remove, + Path: lnk.Name, + Before: key.Key(lnk.Hash), + }) + } + for _, lnk := range clean_b.Links { + out = append(out, &Change{ + Type: Add, + Path: lnk.Name, + After: key.Key(lnk.Hash), + }) + } + + return out +} + +type Conflict struct { + A *Change + B *Change +} + +func MergeDiffs(a, b []*Change) ([]*Change, []Conflict) { + var out []*Change + var conflicts []Conflict + paths := make(map[string]*Change) + for _, c := range a { + paths[c.Path] = c + } + + for _, c := range b { + if ca, ok := paths[c.Path]; ok { + conflicts = append(conflicts, Conflict{ + A: ca, + B: c, + }) + } else { + out = append(out, c) + } + } + for _, c := range paths { + out = append(out, c) + } + return out, conflicts +}