diff.go 4.32 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

Steven Allen's avatar
Steven Allen committed
10
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
11
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
12 13
)

14
// These constants define the changes that can be applied to a DAG.
15 16 17 18 19 20
const (
	Add = iota
	Remove
	Mod
)

21 22
// Change represents a change to a DAG and contains a reference to the old and
// new CIDs.
23 24 25
type Change struct {
	Type   int
	Path   string
Jeromy's avatar
Jeromy committed
26 27
	Before *cid.Cid
	After  *cid.Cid
28 29
}

30
// String prints a human-friendly line about a change.
31 32 33
func (c *Change) String() string {
	switch c.Type {
	case Add:
Jeromy's avatar
Jeromy committed
34
		return fmt.Sprintf("Added %s at %s", c.After.String(), c.Path)
35
	case Remove:
Jeromy's avatar
Jeromy committed
36
		return fmt.Sprintf("Removed %s from %s", c.Before.String(), c.Path)
37
	case Mod:
Jeromy's avatar
Jeromy committed
38
		return fmt.Sprintf("Changed %s to %s at %s", c.Before.String(), c.After.String(), c.Path)
39 40 41 42 43
	default:
		panic("nope")
	}
}

Steven Allen's avatar
Steven Allen committed
44
// ApplyChange applies the requested changes to the given node in the given dag.
45
func ApplyChange(ctx context.Context, ds ipld.DAGService, nd *dag.ProtoNode, cs []*Change) (*dag.ProtoNode, error) {
46
	e := NewDagEditor(nd, ds)
47 48 49
	for _, c := range cs {
		switch c.Type {
		case Add:
50 51 52 53
			child, err := ds.Get(ctx, c.After)
			if err != nil {
				return nil, err
			}
54 55 56 57 58 59 60

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

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

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

			err = e.InsertNodeAtPath(ctx, c.Path, childpb, nil)
87 88 89 90 91
			if err != nil {
				return nil, err
			}
		}
	}
92

93
	return e.Finalize(ctx, ds)
94 95
}

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

	var out []*Change
109 110
	cleanA := a.Copy().(*dag.ProtoNode)
	cleanB := b.Copy().(*dag.ProtoNode)
111 112

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

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

129 130 131 132 133 134 135 136 137 138 139
				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
140 141 142
				if err != nil {
					return nil, err
				}
143 144 145 146 147 148

				for _, subc := range sub {
					subc.Path = path.Join(lnk.Name, subc.Path)
					out = append(out, subc)
				}
			}
149 150
			cleanA.RemoveNodeLink(l.Name)
			cleanB.RemoveNodeLink(l.Name)
151 152 153
		}
	}

154
	for _, lnk := range cleanA.Links() {
155 156 157
		out = append(out, &Change{
			Type:   Remove,
			Path:   lnk.Name,
158
			Before: lnk.Cid,
159 160
		})
	}
161
	for _, lnk := range cleanB.Links() {
162 163 164
		out = append(out, &Change{
			Type:  Add,
			Path:  lnk.Name,
165
			After: lnk.Cid,
166 167 168
		})
	}

Jeromy's avatar
Jeromy committed
169
	return out, nil
170 171
}

172
// Conflict represents two incompatible changes and is returned by MergeDiffs().
173 174 175 176 177
type Conflict struct {
	A *Change
	B *Change
}

178 179 180 181 182
// MergeDiffs takes two slice of changes and adds them to a single slice.
// When a Change from b happens to the same path of an existing change in a,
// a conflict is created and b is not added to the merged slice.
// A slice of Conflicts is returned and contains pointers to the
// Changes involved (which share the same path).
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
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
}