diff --git a/core/commands/files.go b/core/commands/files.go index 8645301ef..dfa94d2a6 100644 --- a/core/commands/files.go +++ b/core/commands/files.go @@ -440,10 +440,10 @@ being GC'ed. cmds.StringArg("dest", true, false, "Destination within MFS."), }, Options: []cmds.Option{ + cmds.BoolOption(forceOptionName, "Force overwrite of existing files."), cmds.BoolOption(filesParentsOptionName, "p", "Make parent directories as needed."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { - mkParents, _ := req.Options[filesParentsOptionName].(bool) nd, err := cmdenv.GetNode(env) if err != nil { return err @@ -459,8 +459,6 @@ being GC'ed. return err } - flush, _ := req.Options[filesFlushOptionName].(bool) - src, err := checkPath(req.Arguments[0]) if err != nil { return err @@ -500,6 +498,7 @@ being GC'ed. return errFilesCpInvalidUnixFS } + mkParents, _ := req.Options[filesParentsOptionName].(bool) if mkParents { err := ensureContainingDirectoryExists(nd.FilesRoot, dst, prefix) if err != nil { @@ -507,11 +506,19 @@ being GC'ed. } } + force, _ := req.Options[forceOptionName].(bool) + if force { + if err = unlinkNodeIfExists(nd, dst); err != nil { + return fmt.Errorf("cp: cannot unlink existing file: %s", err) + } + } + err = mfs.PutNode(nd.FilesRoot, dst, node) if err != nil { return fmt.Errorf("cp: cannot put node in path %s: %s", dst, err) } + flush, _ := req.Options[filesFlushOptionName].(bool) if flush { if _, err := mfs.FlushPath(req.Context, nd.FilesRoot, dst); err != nil { return fmt.Errorf("cp: cannot flush the created file %s: %s", dst, err) @@ -546,6 +553,35 @@ func getNodeFromPath(ctx context.Context, node *core.IpfsNode, api iface.CoreAPI } } +func unlinkNodeIfExists(node *core.IpfsNode, path string) error { + dir, name := gopath.Split(path) + parent, err := mfs.Lookup(node.FilesRoot, dir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + + pdir, ok := parent.(*mfs.Directory) + if !ok { + return fmt.Errorf("not a directory: %s", dir) + } + + // Attempt to unlink if child is a file, ignore error since + // we are only concerned with unlinking an existing file. + child, err := pdir.Child(name) + if err != nil { + return nil // no child file, nothing to unlink + } + + if child.Type() != mfs.TFile { + return fmt.Errorf("not a file: %s", path) + } + + return pdir.Unlink(name) +} + type filesLsOutput struct { Entries []mfs.NodeListing } diff --git a/docs/changelogs/v0.36.md b/docs/changelogs/v0.36.md index 09369bcea..a81276a96 100644 --- a/docs/changelogs/v0.36.md +++ b/docs/changelogs/v0.36.md @@ -10,7 +10,8 @@ This release was brought to you by the [Shipyard](http://ipshipyard.com/) team. - [Overview](#overview) - [🔦 Highlights](#-highlights) - - [Update go-log to v2](#update-go-log-to-v2 + - [Update go-log to v2](#update-go-log-to-v2) + - [Overwrite option for files cp command](#overwrite-option-for-files-cp-command) - [Option for filestore command to remove bad blocks](#option-for-filestore-command-to-remove-bad-blocks) - [📦️ Important dependency updates](#-important-dependency-updates) - [📝 Changelog](#-changelog) @@ -29,6 +30,10 @@ go-log v2 has been out for quite a while now and it is time to deprecate v1. - Fixes `ipfs log tail` - Removes support for `ContextWithLoggable` as this is not needed for tracing-like functionality +#### Overwrite option for files cp command + +The `ipfs files cp` command has a `--force` option to allow it to overwrite existing files. Attempting to overwrite an existing directory results in an error. + #### Option for filestore command to remove bad blocks The `filestore` command has a new option, `--remove-bad-blocks`, to verify objects in the filestore and remove those that fail verification. diff --git a/test/sharness/t0250-files-api.sh b/test/sharness/t0250-files-api.sh index 63dacf7d4..b86ee56f5 100755 --- a/test/sharness/t0250-files-api.sh +++ b/test/sharness/t0250-files-api.sh @@ -674,6 +674,18 @@ test_files_api() { ipfs files ls /adir | grep foobar ' + test_expect_success "test copy --force overwrites files" ' + ipfs files cp /ipfs/$FILE1 /file1 && + ipfs files cp /ipfs/$FILE2 /file2 && + ipfs files cp --force /file1 /file2 && + test "`ipfs files read /file1`" = "`ipfs files read /file2`" + ' + + test_expect_success "clean up" ' + ipfs files rm /file1 && + ipfs files rm /file2 + ' + test_expect_success "should fail to write file and create intermediate directories with no --parents flag set $EXTRA" ' echo "ipfs rocks" | test_must_fail ipfs files write --create /parents/foo/ipfs.txt '