Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ipfs/kubo
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3f90ef46b437^
Choose a base ref
...
head repository: ipfs/kubo
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 4c1dace86941
Choose a head ref
  • 4 commits
  • 2 files changed
  • 1 contributor

Commits on Jun 20, 2015

  1. test/sharness/t0051-object.sh: Collect OUTPUT tests together

    Pull the independent multi-layer test added in d585e20 (allow patch
    add-link to add at a path, 2015-06-19, #1404) out into a separate
    block, so it's easier to read the single-layer tests that share the
    OUTPUT variable.
    
    License: MIT
    Signed-off-by: W. Trevor King <wking@tremily.us>
    wking committed Jun 20, 2015
    Copy the full SHA
    3f90ef4 View commit details
  2. core/commands/object: Intermediate-node creation for patch add-link

    Because:
    
      ipfs object patch $ROOT add-link a/b/c $FILE
    
    is a lot easier than:
    
      EMPTY=$(ipfs object new unixfs-dir) &&
      A=$(ipfs object patch $EMPTY add-link b $EMPTY) &&
      R=$(ipfs object patch $ROOT add-link a $A) &&
      ipfs object patch $R add-link a/b/c $FILE
    
    and the long form isn't even checking to see if the original $ROOT has
    descendents a or a/b.
    
    Note that these are just Merkle nodes, not Unix-FS directories,
    because 'ipfs object ...' is a Merkle-level tool.  I'd like to make
    this flexible enough that we could have a Unix-FS-level 'ipfs file
    patch ...' with similar semantics except that it operates on
    Unix-FS-level nodes (e.g. it creates intermediate *directories*, turns
    directories into files if you use 'set-data', manages the '*-data'
    commands without clobbering the type information unixfs stores in
    Data, etc.).  But I don't want that Unix-FS abstraction stuff sneaking
    into the Merkle-level command we're working on here.
    
    License: MIT
    Signed-off-by: W. Trevor King <wking@tremily.us>
    wking committed Jun 20, 2015
    Copy the full SHA
    7473870 View commit details
  3. core/commands/object: Add bubbling to rm-link

    And now that we have several multi-layer patch tests, disambiguate the
    name of the add-without-autocreation test.
    
    License: MIT
    Signed-off-by: W. Trevor King <wking@tremily.us>
    wking committed Jun 20, 2015
    Copy the full SHA
    88397ac View commit details
  4. core/commands/object: List all actions and their arguments for 'patch'

    Also fix a copy/paste error in an appendDataCaller error messages so
    it references append-data instead of set-data.
    
    License: MIT
    Signed-off-by: W. Trevor King <wking@tremily.us>
    wking committed Jun 20, 2015
    Copy the full SHA
    4c1dace View commit details
Showing with 149 additions and 42 deletions.
  1. +79 −28 core/commands/object.go
  2. +70 −14 test/sharness/t0051-object.sh
107 changes: 79 additions & 28 deletions core/commands/object.go
Original file line number Diff line number Diff line change
@@ -431,9 +431,31 @@ var objectPatchCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Create a new merkledag object based on an existing one",
ShortDescription: `
'ipfs object patch <root> [add-link|rm-link] <args>' is a plumbing command used to
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.
'ipfs object patch <root> [action] <args>' is a plumbing command used
to build custom DAG objects. It adds and removes links from objects or
manipulates their data, creating new objects as a result. This is the
merkle-dag version of modifying an object.
Actions and their expected arguments:
* add-link PATH LINK_HASH
Creates a new link referencing LINK_HASH named after the final
segment of PATH and bubbles the Merkle node changes up the path
to return the new root. If some intermediate nodes in PATH are
missing, add-link will automatically create new nodes for them.
* rm-link PATH
Removes any links named after the final segment of PATH and
bubbles the Merkle node changes up the path to return the new
root.
* set-data BINARY_DATA
Set the root node's data to BINARY_DATA.
* append-data BINARY_DATA
Append BINARY_DATA to the root node's existing data.
The nodes auto-created by add-link are basic Merkle nodes, not the
directory nodes used for filesystem entries. To auto-create those
you'd need a filesystem-level version of the patch command (which
hasn't been written yet).
Examples:
@@ -529,7 +551,7 @@ resulting object hash.

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")
return "", fmt.Errorf("not enough arguments for append-data")
}

nd, err := req.Context().GetNode()
@@ -577,19 +599,15 @@ func rmLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
return "", err
}

name := req.Arguments()[2]

err = root.RemoveNodeLink(name)
if err != nil {
return "", err
}
ctx := req.Context().Context
path := req.Arguments()[2]
parts := strings.Split(path, "/")

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

return newkey, nil
return newRoot.Key()
}

func addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
@@ -602,12 +620,18 @@ func addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
return "", err
}

ctx := req.Context().Context
path := req.Arguments()[2]
childk := key.B58KeyDecode(req.Arguments()[3])
insertk := key.B58KeyDecode(req.Arguments()[3])

toinsert, err := nd.DAG.Get(ctx, insertk)
if err != nil {
return "", err
}

parts := strings.Split(path, "/")

nnode, err := insertNodeAtPath(req.Context().Context, nd.DAG, root, parts, childk)
nnode, err := insertNodeAtPath(ctx, nd.DAG, root, parts, toinsert)
if err != nil {
return "", err
}
@@ -635,32 +659,59 @@ func addLink(ctx context.Context, ds dag.DAGService, root *dag.Node, childname s
return root, nil
}

func insertNodeAtPath(ctx context.Context, ds dag.DAGService, root *dag.Node, path []string, toinsert key.Key) (*dag.Node, error) {
// insertNodeAtPath follows the relative 'path' from 'root', creating
// empty nodes as needed, and adjusts the final link to reference
// 'toinsert'. Then it bubbles that change back up 'path', returning
// the new root. If 'toinsert' is nil, the final path segment will be
// removed, and that change will be bubbled up to a new root.
//
// This mutates the in-memory root *dag.Node, but the immutable DAG
// service will contain both the original objects (e.g. root, a, b,
// and c) and the new objects (e.g. root', a', b', and c') so
// <root>/a/b/c will reference the old content (if there was any) and
// <root'>/a/b/c will reference the inserted content.
func insertNodeAtPath(ctx context.Context, ds dag.DAGService, root *dag.Node, path []string, toinsert *dag.Node) (*dag.Node, error) {
if len(path) == 1 {
return addLink(ctx, ds, root, path[0], toinsert)
}

child, err := root.GetNodeLink(path[0])
if err != nil {
return nil, err
if toinsert == nil {
err := root.RemoveNodeLink(path[0])
if err != nil {
return nil, err
}
} else {
err := root.AddNodeLinkClean(path[0], toinsert)
if err != nil {
return nil, err
}
}
_, err := ds.Add(root)
if err != nil {
return nil, err
}
return root, nil
}

nd, err := child.GetNode(ctx, ds)
if err != nil {
return nil, err
link, err := root.GetNodeLink(path[0])
var child *dag.Node
if err == nil {
child, err = link.GetNode(ctx, ds)
if err != nil {
return nil, err
}
} else {
child = new(dag.Node)
}

ndprime, err := insertNodeAtPath(ctx, ds, nd, path[1:], toinsert)
newChild, err := insertNodeAtPath(ctx, ds, child, path[1:], toinsert)
if err != nil {
return nil, err
}

err = root.RemoveNodeLink(path[0])
if err != nil {
if err != nil && err != dag.ErrNotFound {
return nil, err
}

err = root.AddNodeLinkClean(path[0], ndprime)
err = root.AddNodeLinkClean(path[0], newChild)
if err != nil {
return nil, err
}
84 changes: 70 additions & 14 deletions test/sharness/t0051-object.sh
Original file line number Diff line number Diff line change
@@ -100,20 +100,6 @@ test_object_cmd() {
OUTPUT=$(ipfs object patch $EMPTY_DIR add-link foo $EMPTY_DIR)
'

test_expect_success "multilayer ipfs patch works" '
echo "hello world" > hwfile &&
FILE=$(ipfs add -q hwfile) &&
EMPTY=$(ipfs object new unixfs-dir) &&
ONE=$(ipfs object patch $EMPTY add-link b $EMPTY) &&
TWO=$(ipfs object patch $EMPTY add-link a $ONE) &&
ipfs object patch $TWO add-link a/b/c $FILE > multi_patch
'

test_expect_success "output looks good" '
ipfs cat $(cat multi_patch)/a/b/c > hwfile_out &&
test_cmp hwfile hwfile_out
'

test_expect_success "should have created dir within a dir" '
ipfs ls $OUTPUT > patched_output
'
@@ -131,6 +117,76 @@ test_object_cmd() {
echo QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn > rmlink_exp &&
test_cmp rmlink_exp rmlink_output
'

test_expect_success "multilayer patch add-link auto-creates directories" '
echo "hello world" > hwfile &&
FILE=$(ipfs add -q hwfile) &&
EMPTY=$(ipfs object new unixfs-dir) &&
ipfs object patch $EMPTY add-link a/b/c $FILE >multi_patch_add_auto_create_directories
'

test_expect_success "multilayer patch add-link auto-create leaf looks good" '
ipfs cat $(cat multi_patch_add_auto_create_directories)/a/b/c >multi_patch_add_auto_create_leaf &&
test_cmp hwfile multi_patch_add_auto_create_leaf
'

test_expect_success "multilayer patch add-link auto-create intermediate looks good" '
cat <<-\EOF >multi_patch_add_auto_create_intermediate_expected_newline &&
{
"Links": [
{
"Name": "c",
"Hash": "QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o",
"Size": 20
}
],
"Data": ""
}
EOF
printf %s "$(cat multi_patch_add_auto_create_intermediate_expected_newline)" >multi_patch_add_auto_create_intermediate_expected &&
ipfs object get $(cat multi_patch_add_auto_create_directories)/a/b >multi_patch_add_auto_create_intermediate &&
test_cmp multi_patch_add_auto_create_intermediate_expected multi_patch_add_auto_create_intermediate
'

test_expect_success "multilayer patch add works" '
echo "hello world" > hwfile &&
FILE=$(ipfs add -q hwfile) &&
EMPTY=$(ipfs object new unixfs-dir) &&
ONE=$(ipfs object patch $EMPTY add-link b $EMPTY) &&
TWO=$(ipfs object patch $EMPTY add-link a $ONE) &&
ipfs object patch $TWO add-link a/b/c $FILE > multi_patch
'

test_expect_success "multilayer patch add leaf looks good" '
ipfs cat $(cat multi_patch)/a/b/c > hwfile_out &&
test_cmp hwfile hwfile_out
'

test_expect_success "multilayer patch rm-link works" '
echo "hello world" > hwfile &&
FILE=$(ipfs add -q hwfile) &&
EMPTY=$(ipfs object new unixfs-dir) &&
ROOT=$(ipfs object patch $EMPTY add-link a/b/c/d $FILE) &&
ipfs object patch $ROOT rm-link a/b/c >multi_patch_remove
'

test_expect_success "multilayer patch rm-link looks good" '
cat <<-\EOF >multi_patch_remove_expected_newline &&
{
"Links": [
{
"Name": "c",
"Hash": "QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o",
"Size": 20
}
],
"Data": ""
}
EOF
printf %s "$(cat multi_patch_remove_expected_newline)" >multi_patch_remove_expected &&
ipfs object get $(cat multi_patch_remove)/a/b > multi_patch_remove_actual &&
test_cmp hwfile hwfile_out
'
}

# should work offline