diff.go 3.49 KB
Newer Older
1 2 3 4 5 6 7
package dagutils

import (
	"fmt"
	"path"

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

9
	context "context"
10
	cid "gx/ipfs/QmcEcrBAMrwMyhSjXt4yfyPpzgSuV8HLHavnfmiKCSRqZU/go-cid"
11 12 13 14 15 16 17 18 19 20 21
)

const (
	Add = iota
	Remove
	Mod
)

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

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

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

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

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

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

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

	return e.Finalize(ds)
88 89
}

90 91
func Diff(ctx context.Context, ds dag.DAGService, a, b *dag.ProtoNode) ([]*Change, error) {
	if len(a.Links()) == 0 && len(b.Links()) == 0 {
92 93 94
		return []*Change{
			&Change{
				Type:   Mod,
Jeromy's avatar
Jeromy committed
95 96
				Before: a.Cid(),
				After:  b.Cid(),
97
			},
Jeromy's avatar
Jeromy committed
98
		}, nil
99 100 101
	}

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

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

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

122 123 124 125 126 127 128 129 130 131 132
				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
133 134 135
				if err != nil {
					return nil, err
				}
136 137 138 139 140 141 142 143 144 145 146

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

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

Jeromy's avatar
Jeromy committed
162
	return out, nil
163 164 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
}

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
}