diff.go 3.63 KB
Newer Older
1 2 3
package dagutils

import (
Jeromy's avatar
Jeromy committed
4
	"context"
5 6 7 8
	"fmt"
	"path"

	dag "github.com/ipfs/go-ipfs/merkledag"
Jeromy's avatar
Jeromy committed
9

10
	cid "gx/ipfs/QmYhQaCYEcaPPjxJX7YcPcVKkQfRy6sJ7B3XmGFk82XYdQ/go-cid"
Jeromy's avatar
Jeromy committed
11
	node "gx/ipfs/Qmb3Hm9QDFmfYuET4pu7Kyg8JV78jFa1nvZx5vnCZsK4ck/go-ipld-format"
12 13 14 15 16 17 18 19 20 21 22
)

const (
	Add = iota
	Remove
	Mod
)

type Change struct {
	Type   int
	Path   string
Jeromy's avatar
Jeromy committed
23 24
	Before *cid.Cid
	After  *cid.Cid
25 26 27 28 29
}

func (c *Change) String() string {
	switch c.Type {
	case Add:
Jeromy's avatar
Jeromy committed
30
		return fmt.Sprintf("Added %s at %s", c.After.String(), c.Path)
31
	case Remove:
Jeromy's avatar
Jeromy committed
32
		return fmt.Sprintf("Removed %s from %s", c.Before.String(), c.Path)
33
	case Mod:
Jeromy's avatar
Jeromy committed
34
		return fmt.Sprintf("Changed %s to %s at %s", c.Before.String(), c.After.String(), c.Path)
35 36 37 38 39
	default:
		panic("nope")
	}
}

40
func ApplyChange(ctx context.Context, ds dag.DAGService, nd *dag.ProtoNode, cs []*Change) (*dag.ProtoNode, error) {
41
	e := NewDagEditor(nd, ds)
42 43 44
	for _, c := range cs {
		switch c.Type {
		case Add:
45 46 47 48
			child, err := ds.Get(ctx, c.After)
			if err != nil {
				return nil, err
			}
49 50 51 52 53 54 55

			childpb, ok := child.(*dag.ProtoNode)
			if !ok {
				return nil, dag.ErrNotProtobuf
			}

			err = e.InsertNodeAtPath(ctx, c.Path, childpb, nil)
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
			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
			}
71 72 73 74
			child, err := ds.Get(ctx, c.After)
			if err != nil {
				return nil, err
			}
75 76 77 78 79 80 81

			childpb, ok := child.(*dag.ProtoNode)
			if !ok {
				return nil, dag.ErrNotProtobuf
			}

			err = e.InsertNodeAtPath(ctx, c.Path, childpb, nil)
82 83 84 85 86
			if err != nil {
				return nil, err
			}
		}
	}
87 88

	return e.Finalize(ds)
89 90
}

Jeromy's avatar
Jeromy committed
91 92
// Diff returns a set of changes that transform node 'a' into node 'b'
func Diff(ctx context.Context, ds dag.DAGService, a, b node.Node) ([]*Change, error) {
93
	if len(a.Links()) == 0 && len(b.Links()) == 0 {
94 95 96
		return []*Change{
			&Change{
				Type:   Mod,
Jeromy's avatar
Jeromy committed
97 98
				Before: a.Cid(),
				After:  b.Cid(),
99
			},
Jeromy's avatar
Jeromy committed
100
		}, nil
101 102 103
	}

	var out []*Change
104 105
	clean_a := a.Copy().(*dag.ProtoNode)
	clean_b := b.Copy().(*dag.ProtoNode)
106 107

	// strip out unchanged stuff
108
	for _, lnk := range a.Links() {
Jeromy's avatar
Jeromy committed
109
		l, _, err := b.ResolveLink([]string{lnk.Name})
110
		if err == nil {
111
			if l.Cid.Equals(lnk.Cid) {
112 113
				// no change... ignore it
			} else {
Jeromy's avatar
Jeromy committed
114 115 116 117 118 119 120 121 122 123
				anode, err := lnk.GetNode(ctx, ds)
				if err != nil {
					return nil, err
				}

				bnode, err := l.GetNode(ctx, ds)
				if err != nil {
					return nil, err
				}

124 125 126 127 128 129 130 131 132 133 134
				anodepb, ok := anode.(*dag.ProtoNode)
				if !ok {
					return nil, dag.ErrNotProtobuf
				}

				bnodepb, ok := bnode.(*dag.ProtoNode)
				if !ok {
					return nil, dag.ErrNotProtobuf
				}

				sub, err := Diff(ctx, ds, anodepb, bnodepb)
Jeromy's avatar
Jeromy committed
135 136 137
				if err != nil {
					return nil, err
				}
138 139 140 141 142 143 144 145 146 147 148

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

149
	for _, lnk := range clean_a.Links() {
150 151 152
		out = append(out, &Change{
			Type:   Remove,
			Path:   lnk.Name,
153
			Before: lnk.Cid,
154 155
		})
	}
156
	for _, lnk := range clean_b.Links() {
157 158 159
		out = append(out, &Change{
			Type:  Add,
			Path:  lnk.Name,
160
			After: lnk.Cid,
161 162 163
		})
	}

Jeromy's avatar
Jeromy committed
164
	return out, nil
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
}

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
}