diff.go 4.46 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 11
	ipld "gx/ipfs/QmWi2BYBL5gJ3CiAiQchg6rn1A8iBsrWy51EYxvHVjFvLb/go-ipld-format"
	cid "gx/ipfs/QmapdYm1b22Frv3k17fqrBYTFRxwiaVJkB299Mfn33edeB/go-cid"
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 99
	// Base case where both nodes are leaves, just compare
	// their CIDs.
100
	if len(a.Links()) == 0 && len(b.Links()) == 0 {
101 102 103
		if a.Cid().Equals(b.Cid()) {
			return []*Change{}, nil
		}
104 105 106
		return []*Change{
			&Change{
				Type:   Mod,
Jeromy's avatar
Jeromy committed
107 108
				Before: a.Cid(),
				After:  b.Cid(),
109
			},
Jeromy's avatar
Jeromy committed
110
		}, nil
111 112 113
	}

	var out []*Change
114 115
	cleanA := a.Copy().(*dag.ProtoNode)
	cleanB := b.Copy().(*dag.ProtoNode)
116 117

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

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

134 135 136 137 138 139 140 141 142 143 144
				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
145 146 147
				if err != nil {
					return nil, err
				}
148 149 150 151 152 153

				for _, subc := range sub {
					subc.Path = path.Join(lnk.Name, subc.Path)
					out = append(out, subc)
				}
			}
154 155
			cleanA.RemoveNodeLink(l.Name)
			cleanB.RemoveNodeLink(l.Name)
156 157 158
		}
	}

159
	for _, lnk := range cleanA.Links() {
160 161 162
		out = append(out, &Change{
			Type:   Remove,
			Path:   lnk.Name,
163
			Before: lnk.Cid,
164 165
		})
	}
166
	for _, lnk := range cleanB.Links() {
167 168 169
		out = append(out, &Change{
			Type:  Add,
			Path:  lnk.Name,
170
			After: lnk.Cid,
171 172 173
		})
	}

Jeromy's avatar
Jeromy committed
174
	return out, nil
175 176
}

177
// Conflict represents two incompatible changes and is returned by MergeDiffs().
178 179 180 181 182
type Conflict struct {
	A *Change
	B *Change
}

183 184 185 186 187
// 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).
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
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
}