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

import (
	"bytes"
	"fmt"
	"path"

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

10
	context "context"
11
	cid "gx/ipfs/QmXUuRadqDq5BuFWzVU6VuKaSjTcNm1gNCtLvvP1TJCW4z/go-cid"
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 40
	default:
		panic("nope")
	}
}

func ApplyChange(ctx context.Context, ds dag.DAGService, nd *dag.Node, cs []*Change) (*dag.Node, error) {
41
	e := NewDagEditor(nd, ds)
42 43 44
	for _, c := range cs {
		switch c.Type {
		case Add:
45 46 47 48 49
			child, err := ds.Get(ctx, c.After)
			if err != nil {
				return nil, err
			}
			err = e.InsertNodeAtPath(ctx, c.Path, child, nil)
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
			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
			}
65 66 67 68 69
			child, err := ds.Get(ctx, c.After)
			if err != nil {
				return nil, err
			}
			err = e.InsertNodeAtPath(ctx, c.Path, child, nil)
70 71 72 73 74
			if err != nil {
				return nil, err
			}
		}
	}
75 76

	return e.Finalize(ds)
77 78
}

Jeromy's avatar
Jeromy committed
79
func Diff(ctx context.Context, ds dag.DAGService, a, b *dag.Node) ([]*Change, error) {
80 81 82 83
	if len(a.Links) == 0 && len(b.Links) == 0 {
		return []*Change{
			&Change{
				Type:   Mod,
Jeromy's avatar
Jeromy committed
84 85
				Before: a.Cid(),
				After:  b.Cid(),
86
			},
Jeromy's avatar
Jeromy committed
87
		}, nil
88 89 90 91 92 93 94 95 96 97 98 99 100
	}

	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 {
Jeromy's avatar
Jeromy committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114
				anode, err := lnk.GetNode(ctx, ds)
				if err != nil {
					return nil, err
				}

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

				sub, err := Diff(ctx, ds, anode, bnode)
				if err != nil {
					return nil, err
				}
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

				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,
Jeromy's avatar
Jeromy committed
130
			Before: cid.NewCidV0(lnk.Hash),
131 132 133 134 135 136
		})
	}
	for _, lnk := range clean_b.Links {
		out = append(out, &Change{
			Type:  Add,
			Path:  lnk.Name,
Jeromy's avatar
Jeromy committed
137
			After: cid.NewCidV0(lnk.Hash),
138 139 140
		})
	}

Jeromy's avatar
Jeromy committed
141
	return out, nil
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
}

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
}