basic implementation of object diff
License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>
Jeromy committed
Apr 14, 2016 at 13:06 UTC
bf7da8522cf05df21c77a6b3d2c5e0c5ad560069
4 files changed
+192
-15
core/commands/object/diff.go
new
+85
@@ -0,0 +1,85 @@
1
+package objectcmd
2
+
3
+import (
4
+ "bytes"
5
+ "fmt"
6
+ "io"
7
+
8
+ cmds "github.com/ipfs/go-ipfs/commands"
9
+ dagutils "github.com/ipfs/go-ipfs/merkledag/utils"
10
+ path "github.com/ipfs/go-ipfs/path"
11
+)
12
+
13
+var ObjectDiffCmd = &cmds.Command{
14
+ Helptext: cmds.HelpText{
15
+ Tagline: "takes a diff of the two given objects",
16
+ ShortDescription: `
17
+`,
18
+ },
19
+ Arguments: []cmds.Argument{
20
+ cmds.StringArg("obj_a", true, false, "object to diff against"),
21
+ cmds.StringArg("obj_b", true, false, "object to diff"),
22
+ },
23
+ Run: func(req cmds.Request, res cmds.Response) {
24
+ node, err := req.InvocContext().GetNode()
25
+ if err != nil {
26
+ res.SetError(err, cmds.ErrNormal)
27
+ return
28
+ }
29
+
30
+ a := req.Arguments()[0]
31
+ b := req.Arguments()[1]
32
+
33
+ pa, err := path.ParsePath(a)
34
+ if err != nil {
35
+ res.SetError(err, cmds.ErrNormal)
36
+ return
37
+ }
38
+
39
+ pb, err := path.ParsePath(b)
40
+ if err != nil {
41
+ res.SetError(err, cmds.ErrNormal)
42
+ return
43
+ }
44
+
45
+ ctx := req.Context()
46
+
47
+ obj_a, err := node.Resolver.ResolvePath(ctx, pa)
48
+ if err != nil {
49
+ res.SetError(err, cmds.ErrNormal)
50
+ return
51
+ }
52
+
53
+ obj_b, err := node.Resolver.ResolvePath(ctx, pb)
54
+ if err != nil {
55
+ res.SetError(err, cmds.ErrNormal)
56
+ return
57
+ }
58
+
59
+ changes, err := dagutils.Diff(ctx, node.DAG, obj_a, obj_b)
60
+ if err != nil {
61
+ res.SetError(err, cmds.ErrNormal)
62
+ return
63
+ }
64
+
65
+ res.SetOutput(changes)
66
+ },
67
+ Type: []*dagutils.Change{},
68
+ Marshalers: cmds.MarshalerMap{
69
+ cmds.Text: func(res cmds.Response) (io.Reader, error) {
70
+ changes := res.Output().([]*dagutils.Change)
71
+ buf := new(bytes.Buffer)
72
+ for _, change := range changes {
73
+ switch change.Type {
74
+ case dagutils.Add:
75
+ fmt.Fprintf(buf, "added new link %q pointing to %s\n", change.Path, change.After)
76
+ case dagutils.Mod:
77
+ fmt.Fprintf(buf, "changed %q from %s to %s\n", change.Path, change.Before, change.After)
78
+ case dagutils.Remove:
79
+ fmt.Fprintf(buf, "removed link %q (was %s)\n", change.Path, change.Before)
80
+ }
81
+ }
82
+ return buf, nil
83
+ },
84
+ },
85
+}
core/commands/object/object.go
+9
-7
@@ -48,13 +48,14 @@ var ObjectCmd = &cmds.Command{
48
'ipfs object' is a plumbing command used to manipulate DAG objects
49
directly.`,
50
Synopsis: `
51
-ipfs object data <key> - Outputs raw bytes in an object
52
-ipfs object get <key> - Get the DAG node named by <key>
53
-ipfs object links <key> - Outputs links pointed to by object
54
-ipfs object new <template> - Create new ipfs objects
55
-ipfs object patch <args> - Create new object from old ones
56
-ipfs object put <data> - Stores input, outputs its key
57
-ipfs object stat <key> - Outputs statistics of object
51
+ipfs object data <key> - Outputs raw bytes in an object
52
+ipfs object get <key> - Get the DAG node named by <key>
53
+ipfs object links <key> - Outputs links pointed to by object
54
+ipfs object new <template> - Create new ipfs objects
55
+ipfs object patch <args> - Create new object from old ones
56
+ipfs object put <data> - Stores input, outputs its key
57
+ipfs object stat <key> - Outputs statistics of object
58
+ipfs object diff <key1> <key2> - Diffs two given objects
59
`,
60
},
61
@@ -66,6 +67,7 @@ ipfs object stat <key> - Outputs statistics of object
67
"patch": ObjectPatchCmd,
68
"put": ObjectPutCmd,
69
"stat": ObjectStatCmd,
70
+ "diff": ObjectDiffCmd,
71
},
72
}
73
merkledag/utils/diff.go
+27
-8
@@ -75,17 +75,25 @@ func ApplyChange(ctx context.Context, ds dag.DAGService, nd *dag.Node, cs []*Cha
75
return e.Finalize(ds)
76
}
77
78
-func Diff(ctx context.Context, ds dag.DAGService, a, b *dag.Node) []*Change {
78
+func Diff(ctx context.Context, ds dag.DAGService, a, b *dag.Node) ([]*Change, error) {
79
if len(a.Links) == 0 && len(b.Links) == 0 {
80
- ak, _ := a.Key()
81
- bk, _ := b.Key()
80
+ ak, err := a.Key()
81
+ if err != nil {
82
+ return nil, err
83
+ }
84
+
85
+ bk, err := b.Key()
86
+ if err != nil {
87
+ return nil, err
88
+ }
89
+
90
return []*Change{
91
&Change{
92
Type: Mod,
93
Before: ak,
94
After: bk,
95
},
88
- }
96
+ }, nil
97
}
98
99
var out []*Change
@@ -99,9 +107,20 @@ func Diff(ctx context.Context, ds dag.DAGService, a, b *dag.Node) []*Change {
107
if bytes.Equal(l.Hash, lnk.Hash) {
108
// no change... ignore it
109
} else {
102
- anode, _ := lnk.GetNode(ctx, ds)
103
- bnode, _ := l.GetNode(ctx, ds)
104
- sub := Diff(ctx, ds, anode, bnode)
110
+ anode, err := lnk.GetNode(ctx, ds)
111
+ if err != nil {
112
+ return nil, err
113
+ }
114
+
115
+ bnode, err := l.GetNode(ctx, ds)
116
+ if err != nil {
117
+ return nil, err
118
+ }
119
+
120
+ sub, err := Diff(ctx, ds, anode, bnode)
121
+ if err != nil {
122
+ return nil, err
123
+ }
124
125
for _, subc := range sub {
126
subc.Path = path.Join(lnk.Name, subc.Path)
@@ -128,7 +147,7 @@ func Diff(ctx context.Context, ds dag.DAGService, a, b *dag.Node) []*Change {
147
})
148
}
149
131
- return out
150
+ return out, nil
151
}
152
153
type Conflict struct {
test/sharness/t0052-object-diff.sh
new
+71
@@ -0,0 +1,71 @@
1
+#!/bin/sh
2
+#
3
+# Copyright (c) 2016 Jeromy Johnson
4
+# MIT Licensed; see the LICENSE file in this repository.
5
+#
6
+
7
+test_description="Test object diff command"
8
+
9
+. lib/test-lib.sh
10
+
11
+test_init_ipfs
12
+
13
+test_expect_success "create some objects for testing diffs" '
14
+ mkdir foo &&
15
+ echo "stuff" > foo/bar &&
16
+ mkdir foo/baz &&
17
+ A=$(ipfs add -r -q foo | tail -n1) &&
18
+ echo "more things" > foo/cat &&
19
+ B=$(ipfs add -r -q foo | tail -n1) &&
20
+ echo "nested" > foo/baz/dog &&
21
+ C=$(ipfs add -r -q foo | tail -n1)
22
+ echo "changed" > foo/bar &&
23
+ D=$(ipfs add -r -q foo | tail -n1)
24
+'
25
+
26
+test_expect_success "diff against self is empty" '
27
+ ipfs object diff $A $A > diff_out
28
+'
29
+
30
+test_expect_success "identity diff output looks good" '
31
+ printf "" > diff_exp &&
32
+ test_cmp diff_exp diff_out
33
+'
34
+
35
+test_expect_success "diff added link works" '
36
+ ipfs object diff $A $B > diff_out
37
+'
38
+
39
+test_expect_success "diff added link looks right" '
40
+ echo added new link \"cat\" pointing to QmUSvcqzhdfYM1KLDbM76eLPdS9ANFtkJvFuPYeZt73d7A > diff_exp &&
41
+ test_cmp diff_exp diff_out
42
+'
43
+
44
+test_expect_success "diff removed link works" '
45
+ ipfs object diff $B $A > diff_out
46
+'
47
+
48
+test_expect_success "diff removed link looks right" '
49
+ echo removed link \"cat\" \(was QmUSvcqzhdfYM1KLDbM76eLPdS9ANFtkJvFuPYeZt73d7A\) > diff_exp &&
50
+ test_cmp diff_exp diff_out
51
+'
52
+
53
+test_expect_success "diff nested add works" '
54
+ ipfs object diff $B $C > diff_out
55
+'
56
+
57
+test_expect_success "diff looks right" '
58
+ echo added new link \"baz/dog\" pointing to QmdNJQUTZuDpsUcec7YDuCfRfvw1w4J13DCm7YcU4VMZdS > diff_exp &&
59
+ test_cmp diff_exp diff_out
60
+'
61
+
62
+test_expect_success "diff changed link works" '
63
+ ipfs object diff $C $D > diff_out
64
+'
65
+
66
+test_expect_success "diff looks right" '
67
+ echo changed \"bar\" from QmNgd5cz2jNftnAHBhcRUGdtiaMzb5Rhjqd4etondHHST8 to QmRfFVsjSXkhFxrfWnLpMae2M4GBVsry6VAuYYcji5MiZb > diff_exp &&
68
+ test_cmp diff_exp diff_out
69
+'
70
+
71
+test_done