diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index ecd3aaadb8b..4865c496914 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -58,6 +58,38 @@ type BlockStat interface { Path() Path } +const ( + // DiffAdd is a Type of ObjectChange where a link was added to the graph + DiffAdd = iota + + // DiffRemove is a Type of ObjectChange where a link was removed from the graph + DiffRemove + + // DiffMod is a Type of ObjectChange where a link was changed in the graph + DiffMod +) + +// ObjectChange represents a change ia a graph +// TODO: do we want this to be an interface? +type ObjectChange struct { + // Type of the change, either: + // * DiffAdd - Added a link + // * DiffRemove - Removed a link + // * DiffMod - Modified a link + Type int + + // Path to the changed link + Path string + + // Before holds the link path before the change. Note that when a link is + // added, this will be nil. + Before Path + + // After holds the link path after the change. Note that when a link is + // removed, this will be nil. + After Path +} + // CoreAPI defines an unified interface to IPFS for Go programs. type CoreAPI interface { // Unixfs returns an implementation of Unixfs API. @@ -313,6 +345,10 @@ type ObjectAPI interface { // SetData sets the data contained in the node SetData(context.Context, Path, io.Reader) (Path, error) + + // Diff returns a set of changes needed to transform the first object into the + // second. + Diff(context.Context, Path, Path) ([]ObjectChange, error) } // ObjectStat provides information about dag nodes diff --git a/core/coreapi/object.go b/core/coreapi/object.go index d53ce9ceadb..1a232875d8f 100644 --- a/core/coreapi/object.go +++ b/core/coreapi/object.go @@ -300,6 +300,35 @@ func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.R return api.ParseCid(pbnd.Cid()), nil } +func (api *ObjectAPI) Diff(ctx context.Context, before coreiface.Path, after coreiface.Path) ([]coreiface.ObjectChange, error) { + beforeNd, err := api.core().ResolveNode(ctx, before) + if err != nil { + return nil, err + } + + afterNd, err := api.core().ResolveNode(ctx, after) + if err != nil { + return nil, err + } + + changes, err := dagutils.Diff(ctx, api.node.DAG, beforeNd, afterNd) + if err != nil { + return nil, err + } + + out := make([]coreiface.ObjectChange, len(changes)) + for i, change := range changes { + out[i] = coreiface.ObjectChange{ + Type: change.Type, + Path: change.Path, + Before: api.ParseCid(change.Before), + After: api.ParseCid(change.After), + } + } + + return out, nil +} + func (api *ObjectAPI) core() coreiface.CoreAPI { return api.CoreAPI } diff --git a/core/coreapi/object_test.go b/core/coreapi/object_test.go index 1ff90d32de6..5a1f13d7ef2 100644 --- a/core/coreapi/object_test.go +++ b/core/coreapi/object_test.go @@ -7,6 +7,8 @@ import ( "io/ioutil" "strings" "testing" + + "github.com/ipfs/go-ipfs/core/coreapi/interface" ) func TestNew(t *testing.T) { @@ -383,3 +385,42 @@ func TestObjectSetData(t *testing.T) { t.Error("unexpected data") } } + +func TestDiffTest(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`)) + if err != nil { + t.Fatal(err) + } + + p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bar"}`)) + if err != nil { + t.Fatal(err) + } + + changes, err := api.Object().Diff(ctx, p1, p2) + if err != nil { + t.Fatal(err) + } + + if len(changes) != 1 { + t.Fatal("unexpected changes len") + } + + if changes[0].Type != iface.DiffMod { + t.Fatal("unexpected change type") + } + + if changes[0].Before.String() != p1.String() { + t.Fatal("unexpected before path") + } + + if changes[0].After.String() != p2.String() { + t.Fatal("unexpected before path") + } +}