Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement patch command #1299

Merged
merged 1 commit into from
Jun 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 282 additions & 5 deletions core/commands/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ import (
"io/ioutil"
"strings"
"text/tabwriter"
"time"

mh "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"

key "github.com/ipfs/go-ipfs/blocks/key"
cmds "github.com/ipfs/go-ipfs/commands"
core "github.com/ipfs/go-ipfs/core"
dag "github.com/ipfs/go-ipfs/merkledag"
path "github.com/ipfs/go-ipfs/path"
ft "github.com/ipfs/go-ipfs/unixfs"
u "github.com/ipfs/go-ipfs/util"
)

// ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k
Expand Down Expand Up @@ -45,11 +50,13 @@ var ObjectCmd = &cmds.Command{
'ipfs object' is a plumbing command used to manipulate DAG objects
directly.`,
Synopsis: `
ipfs object get <key> - Get the DAG node named by <key>
ipfs object put <data> - Stores input, outputs its key
ipfs object data <key> - Outputs raw bytes in an object
ipfs object links <key> - Outputs links pointed to by object
ipfs object stat <key> - Outputs statistics of object
ipfs object get <key> - Get the DAG node named by <key>
ipfs object put <data> - Stores input, outputs its key
ipfs object data <key> - Outputs raw bytes in an object
ipfs object links <key> - Outputs links pointed to by object
ipfs object stat <key> - Outputs statistics of object
ipfs object new <template> - Create new ipfs objects
ipfs object patch <args> - Create new object from old ones
`,
},

Expand All @@ -59,6 +66,8 @@ ipfs object stat <key> - Outputs statistics of object
"get": objectGetCmd,
"put": objectPutCmd,
"stat": objectStatCmd,
"new": objectNewCmd,
"patch": objectPatchCmd,
},
}

Expand Down Expand Up @@ -348,6 +357,274 @@ Data should be in the format specified by the --inputenc flag.
Type: Object{},
}

var objectNewCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "creates a new object from an ipfs template",
ShortDescription: `
'ipfs object new' is a plumbing command for creating new DAG nodes.
`,
LongDescription: `
'ipfs object new' is a plumbing command for creating new DAG nodes.
By default it creates and returns a new empty merkledag node, but
you may pass an optional template argument to create a preformatted
node.
`,
},
Arguments: []cmds.Argument{
cmds.StringArg("template", false, false, "optional template to use"),
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.Context().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

node := new(dag.Node)
if len(req.Arguments()) == 1 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would grab template into a variable first, that way if arg num changes, can change it in only one place. i've been bitten by bugs when doing things this way.

var template string
if len(req.Arguments()) > 0 {
  template = req.Arguments()[0]
}

node := new(dag.Node)
if template != "" {
  ...
}

(not a big deal though)

template := req.Arguments()[0]
var err error
node, err = nodeFromTemplate(template)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
}

k, err := n.DAG.Add(node)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(&Object{Hash: k.B58String()})
},
Marshalers: cmds.MarshalerMap{
cmds.Text: func(res cmds.Response) (io.Reader, error) {
object := res.Output().(*Object)
return strings.NewReader(object.Hash + "\n"), nil
},
},
Type: Object{},
}

var objectPatchCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Create a new merkledag object based on an existing one",
ShortDescription: `
'ipfs patch <root> [add-link|rm-link] <args>' is a plumbing command used to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd love to be able to patch the data

echo meow | ipfs patch <root> data

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Mon, Jun 01, 2015 at 07:57:49PM -0700, Juan Batiz-Benet wrote:

+'ipfs patch [add-link|rm-link] ' is a plumbing command used to

i'd love to be able to patch the data

echo meow | ipfs patch <root> data

Is that going to be ‘append to Data’ or ‘replace Data entirely’? I'd
expect the latter. And this would be more useful if unixfs entries
didn't embed their magic type numbers in Data ;).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont know how to make the commands lib accept stdin on arbitrary arguments like that. I'll try hacking a few different things and see what works.

build custom DAG objects. It adds and removes links from objects, creating a new
object as a result. This is the merkle-dag version of modifying an object.

Examples:

EMPTY_DIR=$(ipfs object new unixfs-dir)
BAR=$(echo "bar" | ipfs add -q)
ipfs patch $EMPTY_DIR add-link foo $BAR
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still has inconsistent tab-vs.-space indenting. I think you should fix that, even if you don't use my object new suggestion for EMPTY_DIR ;).
#1299 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

  • fix inconsistent tab-vs.-space indenting


This takes an empty directory, and adds a link named foo under it, pointing to
a file containing 'bar', and returns the hash of the new object.

ipfs patch $FOO_BAR rm-link foo

This removes the link named foo from the hash in $FOO_BAR and returns the
resulting object hash.
`,
},
Options: []cmds.Option{},
Arguments: []cmds.Argument{
cmds.StringArg("root", true, false, "the hash of the node to modify"),
cmds.StringArg("command", true, false, "the operation to perform"),
cmds.StringArg("args", true, true, "extra arguments").EnableStdin(),
},
Type: key.Key(""),
Run: func(req cmds.Request, res cmds.Response) {
nd, err := req.Context().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

rhash := key.B58KeyDecode(req.Arguments()[0])
if rhash == "" {
res.SetError(fmt.Errorf("incorrectly formatted root hash"), cmds.ErrNormal)
return
}

ctx, cancel := context.WithTimeout(req.Context().Context, time.Second*30)
rnode, err := nd.DAG.Get(ctx, rhash)
if err != nil {
res.SetError(err, cmds.ErrNormal)
cancel()
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to cancel the context in this case too.

  • ctx cancel fix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its cancelled two lines down.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not if it returns early!

}
cancel()

action := req.Arguments()[1]

switch action {
case "add-link":
k, err := addLinkCaller(req, rnode)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(k)
case "rm-link":
k, err := rmLinkCaller(req, rnode)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(k)
case "set-data":
k, err := setDataCaller(req, rnode)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(k)
case "append-data":
k, err := appendDataCaller(req, rnode)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(k)
default:
res.SetError(fmt.Errorf("unrecognized subcommand"), cmds.ErrNormal)
return
}
},
Marshalers: cmds.MarshalerMap{
cmds.Text: func(res cmds.Response) (io.Reader, error) {
k, ok := res.Output().(key.Key)
if !ok {
return nil, u.ErrCast()
}

return strings.NewReader(k.B58String() + "\n"), nil
},
},
}

func appendDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
if len(req.Arguments()) < 3 {
return "", fmt.Errorf("not enough arguments for set-data")
}

nd, err := req.Context().GetNode()
if err != nil {
return "", err
}

root.Data = append(root.Data, []byte(req.Arguments()[2])...)

newkey, err := nd.DAG.Add(root)
if err != nil {
return "", err
}

return newkey, nil
}

func setDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
if len(req.Arguments()) < 3 {
return "", fmt.Errorf("not enough arguments for set-data")
}

nd, err := req.Context().GetNode()
if err != nil {
return "", err
}

root.Data = []byte(req.Arguments()[2])

newkey, err := nd.DAG.Add(root)
if err != nil {
return "", err
}

return newkey, nil
}

func rmLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
if len(req.Arguments()) < 3 {
return "", fmt.Errorf("not enough arguments for rm-link")
}

nd, err := req.Context().GetNode()
if err != nil {
return "", err
}

name := req.Arguments()[2]

err = root.RemoveNodeLink(name)
if err != nil {
return "", err
}

newkey, err := nd.DAG.Add(root)
if err != nil {
return "", err
}

return newkey, nil
}

func addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
if len(req.Arguments()) < 4 {
return "", fmt.Errorf("not enough arguments for add-link")
}

nd, err := req.Context().GetNode()
if err != nil {
return "", err
}

name := req.Arguments()[2]
childk := key.B58KeyDecode(req.Arguments()[3])

newkey, err := addLink(req.Context().Context, nd.DAG, root, name, childk)
if err != nil {
return "", err
}

return newkey, nil
}

func addLink(ctx context.Context, ds dag.DAGService, root *dag.Node, childname string, childk key.Key) (key.Key, error) {
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
childnd, err := ds.Get(ctx, childk)
if err != nil {
cancel()
return "", err
}
cancel()

err = root.AddNodeLinkClean(childname, childnd)
if err != nil {
return "", err
}

newkey, err := ds.Add(root)
if err != nil {
return "", err
}
return newkey, nil
}

func nodeFromTemplate(template string) (*dag.Node, error) {
switch template {
case "unixfs-dir":
nd := new(dag.Node)
nd.Data = ft.FolderPBData()
return nd, nil
default:
return nil, fmt.Errorf("template '%s' not found", template)
}
}

// ErrEmptyNode is returned when the input to 'ipfs object put' contains no data
var ErrEmptyNode = errors.New("no data or links in this node")

Expand Down
2 changes: 2 additions & 0 deletions routing/record/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (
proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"

key "github.com/ipfs/go-ipfs/blocks/key"
dag "github.com/ipfs/go-ipfs/merkledag"
ci "github.com/ipfs/go-ipfs/p2p/crypto"
pb "github.com/ipfs/go-ipfs/routing/dht/pb"
eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
)

var _ = dag.FetchGraph
var log = eventlog.Logger("routing/record")

// MakePutRecord creates and signs a dht record for the given key/value pair
Expand Down