Commit 5af10f58 authored by Jeromy's avatar Jeromy

implement a basic DAG diffing algorithm

License: MIT
Signed-off-by: default avatarJeromy <jeromyj@gmail.com>
parent d28e7cb7
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
}
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