From 91b068ad3a600f8b1e486ac6d846547ec3ceb52f Mon Sep 17 00:00:00 2001
From: Mateusz Pabian <pabian.mateusz@gmail.com>
Date: Tue, 13 Mar 2018 00:40:19 +0100
Subject: [PATCH] sync: implement --ignore-errors - fixes #642

---
 fs/config.go                         |  1 +
 fs/config/configflags/configflags.go |  1 +
 fs/sync/sync.go                      |  8 ++--
 fs/sync/sync_test.go                 | 72 ++++++++++++++++++++++++++++
 4 files changed, 78 insertions(+), 4 deletions(-)

diff --git a/fs/config.go b/fs/config.go
index a7c0e6f08..3617e98cd 100644
--- a/fs/config.go
+++ b/fs/config.go
@@ -33,6 +33,7 @@ type ConfigInfo struct {
 	SizeOnly              bool
 	IgnoreTimes           bool
 	IgnoreExisting        bool
+	IgnoreErrors          bool
 	ModifyWindow          time.Duration
 	Checkers              int
 	Transfers             int
diff --git a/fs/config/configflags/configflags.go b/fs/config/configflags/configflags.go
index c93232eb3..623b9c98b 100644
--- a/fs/config/configflags/configflags.go
+++ b/fs/config/configflags/configflags.go
@@ -43,6 +43,7 @@ func AddFlags(flagSet *pflag.FlagSet) {
 	flags.BoolVarP(flagSet, &fs.Config.SizeOnly, "size-only", "", fs.Config.SizeOnly, "Skip based on size only, not mod-time or checksum")
 	flags.BoolVarP(flagSet, &fs.Config.IgnoreTimes, "ignore-times", "I", fs.Config.IgnoreTimes, "Don't skip files that match size and time - transfer all files")
 	flags.BoolVarP(flagSet, &fs.Config.IgnoreExisting, "ignore-existing", "", fs.Config.IgnoreExisting, "Skip all files that exist on destination")
+	flags.BoolVarP(flagSet, &fs.Config.IgnoreErrors, "ignore-errors", "", fs.Config.IgnoreErrors, "delete even if there are I/O errors")
 	flags.BoolVarP(flagSet, &fs.Config.DryRun, "dry-run", "n", fs.Config.DryRun, "Do a trial run with no permanent changes")
 	flags.DurationVarP(flagSet, &fs.Config.ConnectTimeout, "contimeout", "", fs.Config.ConnectTimeout, "Connect timeout")
 	flags.DurationVarP(flagSet, &fs.Config.Timeout, "timeout", "", fs.Config.Timeout, "IO idle timeout")
diff --git a/fs/sync/sync.go b/fs/sync/sync.go
index dee90525c..405191f6e 100644
--- a/fs/sync/sync.go
+++ b/fs/sync/sync.go
@@ -399,7 +399,7 @@ func (s *syncCopyMove) stopDeleters() {
 // checkSrcMap is clear then it assumes that the any source files that
 // have been found have been removed from dstFiles already.
 func (s *syncCopyMove) deleteFiles(checkSrcMap bool) error {
-	if accounting.Stats.Errored() {
+	if accounting.Stats.Errored() && !fs.Config.IgnoreErrors {
 		fs.Errorf(s.fdst, "%v", fs.ErrorNotDeleting)
 		return fs.ErrorNotDeleting
 	}
@@ -430,7 +430,7 @@ func deleteEmptyDirectories(f fs.Fs, entries fs.DirEntries) error {
 	if len(entries) == 0 {
 		return nil
 	}
-	if accounting.Stats.Errored() {
+	if accounting.Stats.Errored() && !fs.Config.IgnoreErrors {
 		fs.Errorf(f, "%v", fs.ErrorNotDeletingDirs)
 		return fs.ErrorNotDeletingDirs
 	}
@@ -624,7 +624,7 @@ func (s *syncCopyMove) run() error {
 
 	// Delete files after
 	if s.deleteMode == fs.DeleteModeAfter {
-		if s.currentError() != nil {
+		if s.currentError() != nil && !fs.Config.IgnoreErrors {
 			fs.Errorf(s.fdst, "%v", fs.ErrorNotDeleting)
 		} else {
 			s.processError(s.deleteFiles(false))
@@ -633,7 +633,7 @@ func (s *syncCopyMove) run() error {
 
 	// Prune empty directories
 	if s.deleteMode != fs.DeleteModeOff {
-		if s.currentError() != nil {
+		if s.currentError() != nil && !fs.Config.IgnoreErrors {
 			fs.Errorf(s.fdst, "%v", fs.ErrorNotDeletingDirs)
 		} else {
 			s.processError(deleteEmptyDirectories(s.fdst, s.dstEmptyDirs))
diff --git a/fs/sync/sync_test.go b/fs/sync/sync_test.go
index 687bd67e8..e88990c84 100644
--- a/fs/sync/sync_test.go
+++ b/fs/sync/sync_test.go
@@ -283,6 +283,78 @@ func TestSyncIgnoreExisting(t *testing.T) {
 	fstest.CheckItems(t, r.Fremote, file1)
 }
 
+func TestSyncIgnoreErrors(t *testing.T) {
+	r := fstest.NewRun(t)
+	fs.Config.IgnoreErrors = true
+	defer func() {
+		fs.Config.IgnoreErrors = false
+		r.Finalise()
+	}()
+	file1 := r.WriteFile("a/potato2", "------------------------------------------------------------", t1)
+	file2 := r.WriteObject("b/potato", "SMALLER BUT SAME DATE", t2)
+	file3 := r.WriteBoth("c/non empty space", "AhHa!", t2)
+	require.NoError(t, operations.Mkdir(r.Fremote, "d"))
+
+	fstest.CheckListingWithPrecision(
+		t,
+		r.Flocal,
+		[]fstest.Item{
+			file1,
+			file3,
+		},
+		[]string{
+			"a",
+			"c",
+		},
+		fs.Config.ModifyWindow,
+	)
+	fstest.CheckListingWithPrecision(
+		t,
+		r.Fremote,
+		[]fstest.Item{
+			file2,
+			file3,
+		},
+		[]string{
+			"b",
+			"c",
+			"d",
+		},
+		fs.Config.ModifyWindow,
+	)
+
+	accounting.Stats.ResetCounters()
+	fs.CountError(nil)
+	assert.NoError(t, Sync(r.Fremote, r.Flocal))
+
+	fstest.CheckListingWithPrecision(
+		t,
+		r.Flocal,
+		[]fstest.Item{
+			file1,
+			file3,
+		},
+		[]string{
+			"a",
+			"c",
+		},
+		fs.Config.ModifyWindow,
+	)
+	fstest.CheckListingWithPrecision(
+		t,
+		r.Fremote,
+		[]fstest.Item{
+			file1,
+			file3,
+		},
+		[]string{
+			"a",
+			"c",
+		},
+		fs.Config.ModifyWindow,
+	)
+}
+
 func TestSyncAfterChangingModtimeOnly(t *testing.T) {
 	r := fstest.NewRun(t)
 	defer r.Finalise()