Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1c1f9c6

Browse files
committedJan 3, 2016
Merge pull request #2154 from ipfs/fix/object
refactor object patch command to work more betterer
2 parents d4df3ec + d729462 commit 1c1f9c6

File tree

4 files changed

+324
-254
lines changed

4 files changed

+324
-254
lines changed
 

‎core/commands/object.go ‎core/commands/object/object.go

+14-247
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package commands
1+
package objectcmd
22

33
import (
44
"bytes"
@@ -13,14 +13,11 @@ import (
1313

1414
mh "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
1515

16-
key "github.com/ipfs/go-ipfs/blocks/key"
1716
cmds "github.com/ipfs/go-ipfs/commands"
1817
core "github.com/ipfs/go-ipfs/core"
1918
dag "github.com/ipfs/go-ipfs/merkledag"
20-
dagutils "github.com/ipfs/go-ipfs/merkledag/utils"
2119
path "github.com/ipfs/go-ipfs/path"
2220
ft "github.com/ipfs/go-ipfs/unixfs"
23-
u "github.com/ipfs/go-ipfs/util"
2421
)
2522

2623
// ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k
@@ -61,17 +58,17 @@ ipfs object patch <args> - Create new object from old ones
6158
},
6259

6360
Subcommands: map[string]*cmds.Command{
64-
"data": objectDataCmd,
65-
"links": objectLinksCmd,
66-
"get": objectGetCmd,
67-
"put": objectPutCmd,
68-
"stat": objectStatCmd,
69-
"new": objectNewCmd,
70-
"patch": objectPatchCmd,
61+
"data": ObjectDataCmd,
62+
"links": ObjectLinksCmd,
63+
"get": ObjectGetCmd,
64+
"put": ObjectPutCmd,
65+
"stat": ObjectStatCmd,
66+
"new": ObjectNewCmd,
67+
"patch": ObjectPatchCmd,
7168
},
7269
}
7370

74-
var objectDataCmd = &cmds.Command{
71+
var ObjectDataCmd = &cmds.Command{
7572
Helptext: cmds.HelpText{
7673
Tagline: "Outputs the raw bytes in an IPFS object",
7774
ShortDescription: `
@@ -109,7 +106,7 @@ output is the raw data of the object.
109106
},
110107
}
111108

112-
var objectLinksCmd = &cmds.Command{
109+
var ObjectLinksCmd = &cmds.Command{
113110
Helptext: cmds.HelpText{
114111
Tagline: "Outputs the links pointed to by the specified object",
115112
ShortDescription: `
@@ -158,7 +155,7 @@ multihash.
158155
Type: Object{},
159156
}
160157

161-
var objectGetCmd = &cmds.Command{
158+
var ObjectGetCmd = &cmds.Command{
162159
Helptext: cmds.HelpText{
163160
Tagline: "Get and serialize the DAG node named by <key>",
164161
ShortDescription: `
@@ -229,7 +226,7 @@ This command outputs data in the following encodings:
229226
},
230227
}
231228

232-
var objectStatCmd = &cmds.Command{
229+
var ObjectStatCmd = &cmds.Command{
233230
Helptext: cmds.HelpText{
234231
Tagline: "Get stats for the DAG node named by <key>",
235232
ShortDescription: `
@@ -290,7 +287,7 @@ var objectStatCmd = &cmds.Command{
290287
},
291288
}
292289

293-
var objectPutCmd = &cmds.Command{
290+
var ObjectPutCmd = &cmds.Command{
294291
Helptext: cmds.HelpText{
295292
Tagline: "Stores input as a DAG object, outputs its key",
296293
ShortDescription: `
@@ -377,7 +374,7 @@ and then run
377374
Type: Object{},
378375
}
379376

380-
var objectNewCmd = &cmds.Command{
377+
var ObjectNewCmd = &cmds.Command{
381378
Helptext: cmds.HelpText{
382379
Tagline: "creates a new object from an ipfs template",
383380
ShortDescription: `
@@ -430,235 +427,6 @@ Available templates:
430427
Type: Object{},
431428
}
432429

433-
var objectPatchCmd = &cmds.Command{
434-
Helptext: cmds.HelpText{
435-
Tagline: "Create a new merkledag object based on an existing one",
436-
ShortDescription: `
437-
'ipfs object patch <root> <cmd> <args>' is a plumbing command used to
438-
build custom DAG objects. It adds and removes links from objects, creating a new
439-
object as a result. This is the merkle-dag version of modifying an object. It
440-
can also set the data inside a node with 'set-data' and append to that data as
441-
well with 'append-data'.
442-
443-
Patch commands:
444-
add-link <name> <ref> - adds a link to a node
445-
rm-link <name> - removes a link from a node
446-
set-data - sets a nodes data from stdin
447-
append-data - appends to a nodes data from stdin
448-
449-
Examples:
450-
451-
EMPTY_DIR=$(ipfs object new unixfs-dir)
452-
BAR=$(echo "bar" | ipfs add -q)
453-
ipfs object patch $EMPTY_DIR add-link foo $BAR
454-
455-
This takes an empty directory, and adds a link named foo under it, pointing to
456-
a file containing 'bar', and returns the hash of the new object.
457-
458-
ipfs object patch $FOO_BAR rm-link foo
459-
460-
This removes the link named foo from the hash in $FOO_BAR and returns the
461-
resulting object hash.
462-
463-
The data inside the node can be modified as well:
464-
465-
ipfs object patch $FOO_BAR set-data < file.dat
466-
ipfs object patch $FOO_BAR append-data < file.dat
467-
468-
`,
469-
},
470-
Options: []cmds.Option{
471-
cmds.BoolOption("create", "p", "create intermediate directories on add-link"),
472-
},
473-
Arguments: []cmds.Argument{
474-
cmds.StringArg("root", true, false, "the hash of the node to modify"),
475-
cmds.StringArg("command", true, false, "the operation to perform"),
476-
cmds.StringArg("args", true, true, "extra arguments").EnableStdin(),
477-
},
478-
Type: Object{},
479-
Run: func(req cmds.Request, res cmds.Response) {
480-
nd, err := req.InvocContext().GetNode()
481-
if err != nil {
482-
res.SetError(err, cmds.ErrNormal)
483-
return
484-
}
485-
486-
rootarg := req.Arguments()[0]
487-
if strings.HasPrefix(rootarg, "/ipfs/") {
488-
rootarg = rootarg[6:]
489-
}
490-
rhash := key.B58KeyDecode(rootarg)
491-
if rhash == "" {
492-
res.SetError(fmt.Errorf("incorrectly formatted root hash: %s", req.Arguments()[0]), cmds.ErrNormal)
493-
return
494-
}
495-
496-
rnode, err := nd.DAG.Get(req.Context(), rhash)
497-
if err != nil {
498-
res.SetError(err, cmds.ErrNormal)
499-
return
500-
}
501-
502-
action := req.Arguments()[1]
503-
504-
switch action {
505-
case "add-link":
506-
k, err := addLinkCaller(req, rnode)
507-
if err != nil {
508-
res.SetError(err, cmds.ErrNormal)
509-
return
510-
}
511-
res.SetOutput(&Object{Hash: k.B58String()})
512-
case "rm-link":
513-
k, err := rmLinkCaller(req, rnode)
514-
if err != nil {
515-
res.SetError(err, cmds.ErrNormal)
516-
return
517-
}
518-
res.SetOutput(&Object{Hash: k.B58String()})
519-
case "set-data":
520-
k, err := setDataCaller(req, rnode)
521-
if err != nil {
522-
res.SetError(err, cmds.ErrNormal)
523-
return
524-
}
525-
res.SetOutput(&Object{Hash: k.B58String()})
526-
case "append-data":
527-
k, err := appendDataCaller(req, rnode)
528-
if err != nil {
529-
res.SetError(err, cmds.ErrNormal)
530-
return
531-
}
532-
res.SetOutput(&Object{Hash: k.B58String()})
533-
default:
534-
res.SetError(fmt.Errorf("unrecognized subcommand"), cmds.ErrNormal)
535-
return
536-
}
537-
},
538-
Marshalers: cmds.MarshalerMap{
539-
cmds.Text: func(res cmds.Response) (io.Reader, error) {
540-
o, ok := res.Output().(*Object)
541-
if !ok {
542-
return nil, u.ErrCast()
543-
}
544-
545-
return strings.NewReader(o.Hash + "\n"), nil
546-
},
547-
},
548-
}
549-
550-
func appendDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
551-
if len(req.Arguments()) < 3 {
552-
return "", fmt.Errorf("not enough arguments for set-data")
553-
}
554-
555-
nd, err := req.InvocContext().GetNode()
556-
if err != nil {
557-
return "", err
558-
}
559-
560-
root.Data = append(root.Data, []byte(req.Arguments()[2])...)
561-
562-
newkey, err := nd.DAG.Add(root)
563-
if err != nil {
564-
return "", err
565-
}
566-
567-
return newkey, nil
568-
}
569-
570-
func setDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
571-
if len(req.Arguments()) < 3 {
572-
return "", fmt.Errorf("not enough arguments for set-data")
573-
}
574-
575-
nd, err := req.InvocContext().GetNode()
576-
if err != nil {
577-
return "", err
578-
}
579-
580-
root.Data = []byte(req.Arguments()[2])
581-
582-
newkey, err := nd.DAG.Add(root)
583-
if err != nil {
584-
return "", err
585-
}
586-
587-
return newkey, nil
588-
}
589-
590-
func rmLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
591-
if len(req.Arguments()) < 3 {
592-
return "", fmt.Errorf("not enough arguments for rm-link")
593-
}
594-
595-
nd, err := req.InvocContext().GetNode()
596-
if err != nil {
597-
return "", err
598-
}
599-
600-
path := req.Arguments()[2]
601-
602-
e := dagutils.NewDagEditor(root, nd.DAG)
603-
604-
err = e.RmLink(req.Context(), path)
605-
if err != nil {
606-
return "", err
607-
}
608-
609-
nnode, err := e.Finalize(nd.DAG)
610-
if err != nil {
611-
return "", err
612-
}
613-
614-
return nnode.Key()
615-
}
616-
617-
func addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
618-
if len(req.Arguments()) < 4 {
619-
return "", fmt.Errorf("not enough arguments for add-link")
620-
}
621-
622-
nd, err := req.InvocContext().GetNode()
623-
if err != nil {
624-
return "", err
625-
}
626-
627-
path := req.Arguments()[2]
628-
childk := key.B58KeyDecode(req.Arguments()[3])
629-
630-
create, _, err := req.Option("create").Bool()
631-
if err != nil {
632-
return "", err
633-
}
634-
635-
var createfunc func() *dag.Node
636-
if create {
637-
createfunc = func() *dag.Node {
638-
return &dag.Node{Data: ft.FolderPBData()}
639-
}
640-
}
641-
642-
e := dagutils.NewDagEditor(root, nd.DAG)
643-
644-
childnd, err := nd.DAG.Get(req.Context(), childk)
645-
if err != nil {
646-
return "", err
647-
}
648-
649-
err = e.InsertNodeAtPath(req.Context(), path, childnd, createfunc)
650-
if err != nil {
651-
return "", err
652-
}
653-
654-
nnode, err := e.Finalize(nd.DAG)
655-
if err != nil {
656-
return "", err
657-
}
658-
659-
return nnode.Key()
660-
}
661-
662430
func nodeFromTemplate(template string) (*dag.Node, error) {
663431
switch template {
664432
case "unixfs-dir":
@@ -757,7 +525,6 @@ func getObjectEnc(o interface{}) objectEncoding {
757525
v, ok := o.(string)
758526
if !ok {
759527
// chosen as default because it's human readable
760-
log.Warning("option is not a string - falling back to json")
761528
return objectEncodingJSON
762529
}
763530

‎core/commands/object/patch.go

+302
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
package objectcmd
2+
3+
import (
4+
"io"
5+
"io/ioutil"
6+
"strings"
7+
8+
key "github.com/ipfs/go-ipfs/blocks/key"
9+
cmds "github.com/ipfs/go-ipfs/commands"
10+
core "github.com/ipfs/go-ipfs/core"
11+
dag "github.com/ipfs/go-ipfs/merkledag"
12+
dagutils "github.com/ipfs/go-ipfs/merkledag/utils"
13+
path "github.com/ipfs/go-ipfs/path"
14+
ft "github.com/ipfs/go-ipfs/unixfs"
15+
u "github.com/ipfs/go-ipfs/util"
16+
)
17+
18+
var ObjectPatchCmd = &cmds.Command{
19+
Helptext: cmds.HelpText{
20+
Tagline: "Create a new merkledag object based on an existing one",
21+
ShortDescription: `
22+
'ipfs object patch <root> <cmd> <args>' is a plumbing command used to
23+
build custom DAG objects. It mutates objects, creating new objects as a
24+
result. This is the merkle-dag version of modifying an object.
25+
`,
26+
},
27+
Arguments: []cmds.Argument{},
28+
Subcommands: map[string]*cmds.Command{
29+
"append-data": patchAppendDataCmd,
30+
"add-link": patchAddLinkCmd,
31+
"rm-link": patchRmLinkCmd,
32+
"set-data": patchSetDataCmd,
33+
},
34+
}
35+
36+
func objectMarshaler(res cmds.Response) (io.Reader, error) {
37+
o, ok := res.Output().(*Object)
38+
if !ok {
39+
return nil, u.ErrCast()
40+
}
41+
42+
return strings.NewReader(o.Hash + "\n"), nil
43+
}
44+
45+
var patchAppendDataCmd = &cmds.Command{
46+
Helptext: cmds.HelpText{
47+
Tagline: "Append data to the data segment of a dag node",
48+
ShortDescription: `
49+
Append data to what already exists in the data segment in the given object.
50+
51+
EXAMPLE:
52+
$ echo "hello" | ipfs object patch $HASH append-data
53+
54+
note: this does not append data to a 'file', it modifies the actual raw
55+
data within an object. Objects have a max size of 1MB and objects larger than
56+
the limit will not be respected by the network.
57+
`,
58+
},
59+
Arguments: []cmds.Argument{
60+
cmds.StringArg("root", true, false, "the hash of the node to modify"),
61+
cmds.FileArg("data", true, false, "data to append").EnableStdin(),
62+
},
63+
Run: func(req cmds.Request, res cmds.Response) {
64+
nd, err := req.InvocContext().GetNode()
65+
if err != nil {
66+
res.SetError(err, cmds.ErrNormal)
67+
return
68+
}
69+
70+
root, err := path.ParsePath(req.Arguments()[0])
71+
if err != nil {
72+
res.SetError(err, cmds.ErrNormal)
73+
return
74+
}
75+
76+
rootnd, err := core.Resolve(req.Context(), nd, root)
77+
if err != nil {
78+
res.SetError(err, cmds.ErrNormal)
79+
return
80+
}
81+
82+
data, err := ioutil.ReadAll(req.Files())
83+
if err != nil {
84+
res.SetError(err, cmds.ErrNormal)
85+
return
86+
}
87+
88+
rootnd.Data = append(rootnd.Data, data...)
89+
90+
newkey, err := nd.DAG.Add(rootnd)
91+
if err != nil {
92+
res.SetError(err, cmds.ErrNormal)
93+
return
94+
}
95+
96+
res.SetOutput(&Object{Hash: newkey.B58String()})
97+
},
98+
Type: Object{},
99+
Marshalers: cmds.MarshalerMap{
100+
cmds.Text: objectMarshaler,
101+
},
102+
}
103+
104+
var patchSetDataCmd = &cmds.Command{
105+
Helptext: cmds.HelpText{},
106+
Arguments: []cmds.Argument{
107+
cmds.StringArg("root", true, false, "the hash of the node to modify"),
108+
cmds.FileArg("data", true, false, "data fill with").EnableStdin(),
109+
},
110+
Run: func(req cmds.Request, res cmds.Response) {
111+
nd, err := req.InvocContext().GetNode()
112+
if err != nil {
113+
res.SetError(err, cmds.ErrNormal)
114+
return
115+
}
116+
117+
rp, err := path.ParsePath(req.Arguments()[0])
118+
if err != nil {
119+
res.SetError(err, cmds.ErrNormal)
120+
return
121+
}
122+
123+
root, err := core.Resolve(req.Context(), nd, rp)
124+
if err != nil {
125+
res.SetError(err, cmds.ErrNormal)
126+
return
127+
}
128+
129+
data, err := ioutil.ReadAll(req.Files())
130+
if err != nil {
131+
res.SetError(err, cmds.ErrNormal)
132+
return
133+
}
134+
135+
root.Data = data
136+
137+
newkey, err := nd.DAG.Add(root)
138+
if err != nil {
139+
res.SetError(err, cmds.ErrNormal)
140+
return
141+
}
142+
143+
res.SetOutput(&Object{Hash: newkey.B58String()})
144+
},
145+
Type: Object{},
146+
Marshalers: cmds.MarshalerMap{
147+
cmds.Text: objectMarshaler,
148+
},
149+
}
150+
151+
var patchRmLinkCmd = &cmds.Command{
152+
Helptext: cmds.HelpText{
153+
Tagline: "remove a link from an object",
154+
ShortDescription: `
155+
removes a link by the given name from root.
156+
`,
157+
},
158+
Arguments: []cmds.Argument{
159+
cmds.StringArg("root", true, false, "the hash of the node to modify"),
160+
cmds.StringArg("link", true, false, "name of the link to remove"),
161+
},
162+
Run: func(req cmds.Request, res cmds.Response) {
163+
nd, err := req.InvocContext().GetNode()
164+
if err != nil {
165+
res.SetError(err, cmds.ErrNormal)
166+
return
167+
}
168+
169+
rootp, err := path.ParsePath(req.Arguments()[0])
170+
if err != nil {
171+
res.SetError(err, cmds.ErrNormal)
172+
return
173+
}
174+
175+
root, err := core.Resolve(req.Context(), nd, rootp)
176+
if err != nil {
177+
res.SetError(err, cmds.ErrNormal)
178+
return
179+
}
180+
181+
path := req.Arguments()[1]
182+
183+
e := dagutils.NewDagEditor(root, nd.DAG)
184+
185+
err = e.RmLink(req.Context(), path)
186+
if err != nil {
187+
res.SetError(err, cmds.ErrNormal)
188+
return
189+
}
190+
191+
nnode, err := e.Finalize(nd.DAG)
192+
if err != nil {
193+
res.SetError(err, cmds.ErrNormal)
194+
return
195+
}
196+
197+
nk, err := nnode.Key()
198+
if err != nil {
199+
res.SetError(err, cmds.ErrNormal)
200+
return
201+
}
202+
203+
res.SetOutput(&Object{Hash: nk.B58String()})
204+
},
205+
Type: Object{},
206+
Marshalers: cmds.MarshalerMap{
207+
cmds.Text: objectMarshaler,
208+
},
209+
}
210+
211+
var patchAddLinkCmd = &cmds.Command{
212+
Helptext: cmds.HelpText{
213+
Tagline: "add a link to a given object",
214+
ShortDescription: `
215+
Add a merkle-link to the given object and return the hash of the result.
216+
217+
Examples:
218+
219+
EMPTY_DIR=$(ipfs object new unixfs-dir)
220+
BAR=$(echo "bar" | ipfs add -q)
221+
ipfs object patch $EMPTY_DIR add-link foo $BAR
222+
223+
This takes an empty directory, and adds a link named foo under it, pointing to
224+
a file containing 'bar', and returns the hash of the new object.
225+
`,
226+
},
227+
Options: []cmds.Option{
228+
cmds.BoolOption("p", "create", "create intermediary nodes"),
229+
},
230+
Arguments: []cmds.Argument{
231+
cmds.StringArg("root", true, false, "the hash of the node to modify"),
232+
cmds.StringArg("name", true, false, "name of link to create"),
233+
cmds.StringArg("ref", true, false, "ipfs object to add link to"),
234+
},
235+
Run: func(req cmds.Request, res cmds.Response) {
236+
nd, err := req.InvocContext().GetNode()
237+
if err != nil {
238+
res.SetError(err, cmds.ErrNormal)
239+
return
240+
}
241+
242+
rootp, err := path.ParsePath(req.Arguments()[0])
243+
if err != nil {
244+
res.SetError(err, cmds.ErrNormal)
245+
return
246+
}
247+
248+
root, err := core.Resolve(req.Context(), nd, rootp)
249+
if err != nil {
250+
res.SetError(err, cmds.ErrNormal)
251+
return
252+
}
253+
254+
path := req.Arguments()[1]
255+
childk := key.B58KeyDecode(req.Arguments()[2])
256+
257+
create, _, err := req.Option("create").Bool()
258+
if err != nil {
259+
res.SetError(err, cmds.ErrNormal)
260+
return
261+
}
262+
263+
var createfunc func() *dag.Node
264+
if create {
265+
createfunc = func() *dag.Node {
266+
return &dag.Node{Data: ft.FolderPBData()}
267+
}
268+
}
269+
270+
e := dagutils.NewDagEditor(root, nd.DAG)
271+
272+
childnd, err := nd.DAG.Get(req.Context(), childk)
273+
if err != nil {
274+
res.SetError(err, cmds.ErrNormal)
275+
return
276+
}
277+
278+
err = e.InsertNodeAtPath(req.Context(), path, childnd, createfunc)
279+
if err != nil {
280+
res.SetError(err, cmds.ErrNormal)
281+
return
282+
}
283+
284+
nnode, err := e.Finalize(nd.DAG)
285+
if err != nil {
286+
res.SetError(err, cmds.ErrNormal)
287+
return
288+
}
289+
290+
nk, err := nnode.Key()
291+
if err != nil {
292+
res.SetError(err, cmds.ErrNormal)
293+
return
294+
}
295+
296+
res.SetOutput(&Object{Hash: nk.B58String()})
297+
},
298+
Type: Object{},
299+
Marshalers: cmds.MarshalerMap{
300+
cmds.Text: objectMarshaler,
301+
},
302+
}

‎core/commands/root.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
cmds "github.com/ipfs/go-ipfs/commands"
88
files "github.com/ipfs/go-ipfs/core/commands/files"
9+
ocmd "github.com/ipfs/go-ipfs/core/commands/object"
910
unixfs "github.com/ipfs/go-ipfs/core/commands/unixfs"
1011
logging "github.com/ipfs/go-ipfs/vendor/QmQg1J6vikuXF9oDvm4wpdeAUvvkVEKW1EYDw9HhTMnP2b/go-log"
1112
)
@@ -107,7 +108,7 @@ var rootSubcommands = map[string]*cmds.Command{
107108
"ls": LsCmd,
108109
"mount": MountCmd,
109110
"name": NameCmd,
110-
"object": ObjectCmd,
111+
"object": ocmd.ObjectCmd,
111112
"pin": PinCmd,
112113
"ping": PingCmd,
113114
"refs": RefsCmd,
@@ -148,11 +149,11 @@ var rootROSubcommands = map[string]*cmds.Command{
148149
},
149150
"object": &cmds.Command{
150151
Subcommands: map[string]*cmds.Command{
151-
"data": objectDataCmd,
152-
"links": objectLinksCmd,
153-
"get": objectGetCmd,
154-
"stat": objectStatCmd,
155-
"patch": objectPatchCmd,
152+
"data": ocmd.ObjectDataCmd,
153+
"links": ocmd.ObjectLinksCmd,
154+
"get": ocmd.ObjectGetCmd,
155+
"stat": ocmd.ObjectStatCmd,
156+
"patch": ocmd.ObjectPatchCmd,
156157
},
157158
},
158159
"refs": RefsROCmd,

‎test/sharness/t0051-object.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ test_patch_create_path() {
1616
target=$3
1717

1818
test_expect_success "object patch --create works" '
19-
PCOUT=$(ipfs object patch --create $root add-link $name $target)
19+
PCOUT=$(ipfs object patch $root add-link --create $name $target)
2020
'
2121

2222
test_expect_success "output looks good" '

0 commit comments

Comments
 (0)
Please sign in to comment.