From c32d5dd1f38d3fe6a3727bd2cf683d57ffc008ad Mon Sep 17 00:00:00 2001 From: Nathan Collins Date: Thu, 29 Jul 2021 12:42:55 -0400 Subject: [PATCH] fs: move with --ignore-existing will not delete skipped files - #5463 --- docs/content/docs.md | 4 +++ fs/operations/operations.go | 6 +++- fs/operations/operations_test.go | 26 ++++++++++++++ fs/sync/sync.go | 2 ++ fs/sync/sync_test.go | 59 ++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 1 deletion(-) diff --git a/docs/content/docs.md b/docs/content/docs.md index f18a12a58..3483b7b3f 100644 --- a/docs/content/docs.md +++ b/docs/content/docs.md @@ -910,6 +910,10 @@ While this isn't a generally recommended option, it can be useful in cases where your files change due to encryption. However, it cannot correct partial transfers in case a transfer was interrupted. +When performing a `move`/`moveto` command, this flag will leave skipped +files in the source location unchanged when a file with the same name +exists on the destination. + ### --ignore-size ### Normally rclone will look at modification time and size of files to diff --git a/fs/operations/operations.go b/fs/operations/operations.go index 3c2b681d7..6ee7be2bf 100644 --- a/fs/operations/operations.go +++ b/fs/operations/operations.go @@ -1811,7 +1811,11 @@ func moveOrCopyFile(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, dstFileName str } else { tr := accounting.Stats(ctx).NewCheckingTransfer(srcObj) if !cp { - err = DeleteFile(ctx, srcObj) + if ci.IgnoreExisting { + fs.Debugf(srcObj, "Not removing source file as destination file exists and --ignore-existing is set") + } else { + err = DeleteFile(ctx, srcObj) + } } tr.Done(ctx, err) } diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go index 3f39eb6fc..06883a7fe 100644 --- a/fs/operations/operations_test.go +++ b/fs/operations/operations_test.go @@ -811,6 +811,32 @@ func TestMoveFile(t *testing.T) { fstest.CheckItems(t, r.Fremote, file2) } +func TestMoveFileWithIgnoreExisting(t *testing.T) { + ctx := context.Background() + ctx, ci := fs.AddConfig(ctx) + r := fstest.NewRun(t) + defer r.Finalise() + file1 := r.WriteFile("file1", "file1 contents", t1) + fstest.CheckItems(t, r.Flocal, file1) + + ci.IgnoreExisting = true + + err := operations.MoveFile(ctx, r.Fremote, r.Flocal, file1.Path, file1.Path) + require.NoError(t, err) + fstest.CheckItems(t, r.Flocal) + fstest.CheckItems(t, r.Fremote, file1) + + // Recreate file with updated content + file1b := r.WriteFile("file1", "file1 modified", t2) + fstest.CheckItems(t, r.Flocal, file1b) + + // Ensure modified file did not transfer and was not deleted + err = operations.MoveFile(ctx, r.Fremote, r.Flocal, file1.Path, file1b.Path) + require.NoError(t, err) + fstest.CheckItems(t, r.Flocal, file1b) + fstest.CheckItems(t, r.Fremote, file1) +} + func TestCaseInsensitiveMoveFile(t *testing.T) { ctx := context.Background() r := fstest.NewRun(t) diff --git a/fs/sync/sync.go b/fs/sync/sync.go index 7ac9a8a59..96290e0a1 100644 --- a/fs/sync/sync.go +++ b/fs/sync/sync.go @@ -354,6 +354,8 @@ func (s *syncCopyMove) pairChecker(in *pipe, out *pipe, fraction int, wg *sync.W // Delete src if no error on copy if operations.SameObject(src, pair.Dst) { fs.Logf(src, "Not removing source file as it is the same file as the destination") + } else if s.ci.IgnoreExisting { + fs.Debugf(src, "Not removing source file as destination file exists and --ignore-existing is set") } else { s.processError(operations.DeleteFile(s.ctx, src)) } diff --git a/fs/sync/sync_test.go b/fs/sync/sync_test.go index f41efe914..ebe55da92 100644 --- a/fs/sync/sync_test.go +++ b/fs/sync/sync_test.go @@ -1342,6 +1342,65 @@ func TestMoveWithoutDeleteEmptySrcDirs(t *testing.T) { fstest.CheckItems(t, r.Fremote, file1, file2) } +func TestMoveWithIgnoreExisting(t *testing.T) { + ctx := context.Background() + ctx, ci := fs.AddConfig(ctx) + r := fstest.NewRun(t) + defer r.Finalise() + file1 := r.WriteFile("existing", "potato", t1) + file2 := r.WriteFile("existing-b", "tomato", t1) + + ci.IgnoreExisting = true + + accounting.GlobalStats().ResetCounters() + err := MoveDir(ctx, r.Fremote, r.Flocal, false, false) + require.NoError(t, err) + fstest.CheckListingWithPrecision( + t, + r.Flocal, + []fstest.Item{}, + []string{}, + fs.GetModifyWindow(ctx, r.Flocal), + ) + fstest.CheckListingWithPrecision( + t, + r.Fremote, + []fstest.Item{ + file1, + file2, + }, + []string{}, + fs.GetModifyWindow(ctx, r.Fremote), + ) + + // Recreate first file with modified content + file1b := r.WriteFile("existing", "newpotatoes", t2) + accounting.GlobalStats().ResetCounters() + err = MoveDir(ctx, r.Fremote, r.Flocal, false, false) + require.NoError(t, err) + // Source items should still exist in modified state + fstest.CheckListingWithPrecision( + t, + r.Flocal, + []fstest.Item{ + file1b, + }, + []string{}, + fs.GetModifyWindow(ctx, r.Flocal), + ) + // Dest items should not have changed + fstest.CheckListingWithPrecision( + t, + r.Fremote, + []fstest.Item{ + file1, + file2, + }, + []string{}, + fs.GetModifyWindow(ctx, r.Fremote), + ) +} + // Test a server-side move if possible, or the backup path if not func TestServerSideMove(t *testing.T) { ctx := context.Background()