Repository: rclone/rclone (language: go)

Issue:
bisync: trailing slash in local path arg on Windows causes error
#### The associated forum post URL from `https://forum.rclone.org`

https://forum.rclone.org/t/rclone-cant-create-file-system/43471/18?u=nielash

#### What is the problem you are having with rclone?

A trailing slash in a quoted local path command arg on Windows causes a fatal error. Example:

```
bisync --resync QuickAccess: "U:\Documents\OneDrive1\" --config "U:\Settings\Rclone\config" -vv
```

results in an error like:

```
2023/12/15 12:05:36 Failed to bisync: prior lock file found: C:\Users\Graeme\AppData\Local\rclone\bisync\C__Users_Graeme_QuickAccess..U__Documents_OneDrive1"_--config_U__Settings_Rclone_config_-vv.lck
```

Reported on the forum by a user, and I was able to reproduce it. Removing the trailing slash resolves the issue (confirmed by user). While the fix is easy, it's not immediately obvious, so I think it would be worth trying to catch this and handle it better. Looks like possibly the backslash is being interpreted as escaping the quote -- probably an easy fix. 

#### What is your rclone version (output from `rclone version`)

```
rclone v1.65.0
- os/version: Microsoft Windows 11 Home 23H2 (64 bit)
- os/kernel: 10.0.22631.2861 (x86_64)
- os/type: windows
- os/arch: amd64
- go/version: go1.21.4
- go/linking: static
- go/tags: cmount
```


<!--- Please keep the note below for others who read your bug report. -->

#### How to use GitHub

* Please use the 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to show that you are affected by the same issue.
* Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
* Subscribe to receive notifications on status change and new comments.



Discussion / hints:

Fix patch:
diff --git a/cmd/bisync/operations.go b/cmd/bisync/operations.go
index 7b0fc00337ebb..15c9e32ffc57a 100644
--- a/cmd/bisync/operations.go
+++ b/cmd/bisync/operations.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"runtime"
 	"strings"
 	gosync "sync"
 	"time"
@@ -101,6 +102,11 @@ func Bisync(ctx context.Context, fs1, fs2 fs.Fs, optArg *Options) (err error) {
 	b.newListing2 = b.listing2 + "-new"
 	b.aliases = bilib.AliasMap{}
 
+	err = b.checkSyntax()
+	if err != nil {
+		return err
+	}
+
 	// Handle lock file
 	err = b.setLockFile()
 	if err != nil {
@@ -248,6 +254,14 @@ func (b *bisyncRun) runLocked(octx context.Context) (err error) {
 	b.octx = octx
 	b.fctx = fctx
 
+	// overlapping paths check
+	err = b.overlappingPathsCheck(fctx, b.fs1, b.fs2)
+	if err != nil {
+		b.critical = true
+		b.retryable = true
+		return err
+	}
+
 	// Generate Path1 and Path2 listings and copy any unique Path2 files to Path1
 	if opt.Resync {
 		return b.resync(octx, fctx)
@@ -676,12 +690,68 @@ func (b *bisyncRun) setBackupDir(ctx context.Context, destPath int) context.Cont
 		ci.BackupDir = b.opt.BackupDir1
 	}
 	if destPath == 2 && b.opt.BackupDir2 != "" {
-		ci.BackupDir = b.opt.BackupDir1
+		ci.BackupDir = b.opt.BackupDir2
 	}
 	fs.Debugf(ci.BackupDir, "updated backup-dir for Path%d", destPath)
 	return ctx
 }
 
+func (b *bisyncRun) overlappingPathsCheck(fctx context.Context, fs1, fs2 fs.Fs) error {
+	if operations.OverlappingFilterCheck(fctx, fs2, fs1) {
+		err = fmt.Errorf(Color(terminal.RedFg, "Overlapping paths detected. Cannot bisync between paths that overlap, unless excluded by filters."))
+		return err
+	}
+	// need to test our BackupDirs too, as sync will be fooled by our --files-from filters
+	testBackupDir := func(ctx context.Context, destPath int) error {
+		src := fs1
+		dst := fs2
+		if destPath == 1 {
+			src = fs2
+			dst = fs1
+		}
+		ctxBackupDir := b.setBackupDir(ctx, destPath)
+		ci := fs.GetConfig(ctxBackupDir)
+		if ci.BackupDir != "" {
+			// operations.BackupDir should return an error if not properly excluded
+			_, err = operations.BackupDir(fctx, dst, src, "")
+			return err
+		}
+		return nil
+	}
+	err = testBackupDir(fctx, 1)
+	if err != nil {
+		return err
+	}
+	err = testBackupDir(fctx, 2)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (b *bisyncRun) checkSyntax() error {
+	// check for odd number of quotes in path, usually indicating an escaping issue
+	path1 := bilib.FsPath(b.fs1)
+	path2 := bilib.FsPath(b.fs2)
+	if strings.Count(path1, `"`)%2 != 0 || strings.Count(path2, `"`)%2 != 0 {
+		return fmt.Errorf(Color(terminal.RedFg, `detected an odd number of quotes in your path(s). This is usually a mistake indicating incorrect escaping.
+			 Please check your command and try again. Note that on Windows, quoted paths must not have a trailing slash, or it will be interpreted as escaping the quote. path1: %v path2: %v`), path1, path2)
+	}
+	// check for other syntax issues
+	_, err = os.Stat(b.basePath)
+	if err != nil {
+		if strings.Contains(err.Error(), "syntax is incorrect") {
+			return fmt.Errorf(Color(terminal.RedFg, `syntax error detected in your path(s). Please check your command and try again.
+				 Note that on Windows, quoted paths must not have a trailing slash, or it will be interpreted as escaping the quote. path1: %v path2: %v error: %v`), path1, path2, err)
+		}
+	}
+	if runtime.GOOS == "windows" && (strings.Contains(path1, " --") || strings.Contains(path2, " --")) {
+		return fmt.Errorf(Color(terminal.RedFg, `detected possible flags in your path(s). This is usually a mistake indicating incorrect escaping or quoting (possibly closing quote is missing?).
+			 Please check your command and try again. Note that on Windows, quoted paths must not have a trailing slash, or it will be interpreted as escaping the quote. path1: %v path2: %v`), path1, path2)
+	}
+	return nil
+}
+
 func (b *bisyncRun) debug(nametocheck, msgiftrue string) {
 	if b.DebugName != "" && b.DebugName == nametocheck {
 		fs.Infof(Color(terminal.MagentaBg, "DEBUGNAME "+b.DebugName), Color(terminal.MagentaBg, msgiftrue))


Test patch:
diff --git a/cmd/bisync/bisync_test.go b/cmd/bisync/bisync_test.go
index d16e7d6a65aae..68df9683896d3 100644
--- a/cmd/bisync/bisync_test.go
+++ b/cmd/bisync/bisync_test.go
@@ -837,6 +837,10 @@ func (b *bisyncTest) runBisync(ctx context.Context, args []string) (err error) {
 		case "subdir":
 			fs1 = addSubdir(b.path1, val)
 			fs2 = addSubdir(b.path2, val)
+		case "backupdir1":
+			opt.BackupDir1 = val
+		case "backupdir2":
+			opt.BackupDir2 = val
 		case "ignore-listing-checksum":
 			opt.IgnoreListingChecksum = true
 		case "no-norm":
@@ -1168,6 +1172,10 @@ func (b *bisyncTest) storeGolden() {
 		if fileType(fileName) == "lock" {
 			continue
 		}
+		if fileName == "backupdirs" {
+			log.Printf("skipping: %v", fileName)
+			continue
+		}
 		goldName := b.toGolden(fileName)
 		if goldName != fileName {
 			targetPath := filepath.Join(b.workDir, goldName)
@@ -1189,6 +1197,10 @@ func (b *bisyncTest) storeGolden() {
 		if fileType(fileName) == "lock" {
 			continue
 		}
+		if fileName == "backupdirs" {
+			log.Printf("skipping: %v", fileName)
+			continue
+		}
 		text := b.mangleResult(b.goldenDir, fileName, true)
 
 		goldName := b.toGolden(fileName)
@@ -1205,6 +1217,9 @@ func (b *bisyncTest) storeGolden() {
 
 // mangleResult prepares test logs or listings for comparison
 func (b *bisyncTest) mangleResult(dir, file string, golden bool) string {
+	if file == "backupdirs" {
+		return "skipping backupdirs"
+	}
 	buf, err := os.ReadFile(filepath.Join(dir, file))
 	require.NoError(b.t, err)
 
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.copy1to2.que b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.copy1to2.que
new file mode 100644
index 0000000000000..5725b81842f15
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.copy1to2.que
@@ -0,0 +1,5 @@
+"file11.txt"
+"file2.txt"
+"file4.txt"
+"file5.txt..path1"
+"file7.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.copy2to1.que b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.copy2to1.que
new file mode 100644
index 0000000000000..3b79e78bd1166
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.copy2to1.que
@@ -0,0 +1,5 @@
+"file1.txt"
+"file10.txt"
+"file3.txt"
+"file5.txt..path2"
+"file6.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.delete1.que b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.delete1.que
new file mode 100644
index 0000000000000..8a5f92a1ad55d
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.delete1.que
@@ -0,0 +1,1 @@
+"file3.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.delete2.que b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.delete2.que
new file mode 100644
index 0000000000000..3bf899b253f45
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.delete2.que
@@ -0,0 +1,1 @@
+"file4.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path1.lst-err b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path1.lst-err
new file mode 100644
index 0000000000000..19e9623a54214
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path1.lst-err
@@ -0,0 +1,10 @@
+# bisync listing v1 from test
+-      109 - - 2000-01-01T00:00:00.000000000+0000 "RCLONE_TEST"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file1.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file10.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file11.txt"
+-       13 - - 2001-01-02T00:00:00.000000000+0000 "file2.txt"
+-       39 - - 2001-03-04T00:00:00.000000000+0000 "file5.txt..path1"
+-       39 - - 2001-01-02T00:00:00.000000000+0000 "file5.txt..path2"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file6.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file7.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path1.lst-new b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path1.lst-new
new file mode 100644
index 0000000000000..19e9623a54214
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path1.lst-new
@@ -0,0 +1,10 @@
+# bisync listing v1 from test
+-      109 - - 2000-01-01T00:00:00.000000000+0000 "RCLONE_TEST"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file1.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file10.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file11.txt"
+-       13 - - 2001-01-02T00:00:00.000000000+0000 "file2.txt"
+-       39 - - 2001-03-04T00:00:00.000000000+0000 "file5.txt..path1"
+-       39 - - 2001-01-02T00:00:00.000000000+0000 "file5.txt..path2"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file6.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file7.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path1.lst-old b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path1.lst-old
new file mode 100644
index 0000000000000..19e9623a54214
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path1.lst-old
@@ -0,0 +1,10 @@
+# bisync listing v1 from test
+-      109 - - 2000-01-01T00:00:00.000000000+0000 "RCLONE_TEST"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file1.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file10.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file11.txt"
+-       13 - - 2001-01-02T00:00:00.000000000+0000 "file2.txt"
+-       39 - - 2001-03-04T00:00:00.000000000+0000 "file5.txt..path1"
+-       39 - - 2001-01-02T00:00:00.000000000+0000 "file5.txt..path2"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file6.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file7.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path2.lst-err b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path2.lst-err
new file mode 100644
index 0000000000000..19e9623a54214
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path2.lst-err
@@ -0,0 +1,10 @@
+# bisync listing v1 from test
+-      109 - - 2000-01-01T00:00:00.000000000+0000 "RCLONE_TEST"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file1.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file10.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file11.txt"
+-       13 - - 2001-01-02T00:00:00.000000000+0000 "file2.txt"
+-       39 - - 2001-03-04T00:00:00.000000000+0000 "file5.txt..path1"
+-       39 - - 2001-01-02T00:00:00.000000000+0000 "file5.txt..path2"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file6.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file7.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path2.lst-new b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path2.lst-new
new file mode 100644
index 0000000000000..19e9623a54214
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path2.lst-new
@@ -0,0 +1,10 @@
+# bisync listing v1 from test
+-      109 - - 2000-01-01T00:00:00.000000000+0000 "RCLONE_TEST"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file1.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file10.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file11.txt"
+-       13 - - 2001-01-02T00:00:00.000000000+0000 "file2.txt"
+-       39 - - 2001-03-04T00:00:00.000000000+0000 "file5.txt..path1"
+-       39 - - 2001-01-02T00:00:00.000000000+0000 "file5.txt..path2"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file6.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file7.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path2.lst-old b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path2.lst-old
new file mode 100644
index 0000000000000..19e9623a54214
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/_testdir_path1.._testdir_path2.path2.lst-old
@@ -0,0 +1,10 @@
+# bisync listing v1 from test
+-      109 - - 2000-01-01T00:00:00.000000000+0000 "RCLONE_TEST"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file1.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file10.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file11.txt"
+-       13 - - 2001-01-02T00:00:00.000000000+0000 "file2.txt"
+-       39 - - 2001-03-04T00:00:00.000000000+0000 "file5.txt..path1"
+-       39 - - 2001-01-02T00:00:00.000000000+0000 "file5.txt..path2"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file6.txt"
+-       19 - - 2001-01-02T00:00:00.000000000+0000 "file7.txt"
diff --git a/cmd/bisync/testdata/test_backupdir/golden/backupdirs/backupdir1/file1.txt b/cmd/bisync/testdata/test_backupdir/golden/backupdirs/backupdir1/file1.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/golden/backupdirs/backupdir1/file3.txt b/cmd/bisync/testdata/test_backupdir/golden/backupdirs/backupdir1/file3.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/golden/backupdirs/backupdir2/file2.txt b/cmd/bisync/testdata/test_backupdir/golden/backupdirs/backupdir2/file2.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/golden/backupdirs/backupdir2/file4.txt b/cmd/bisync/testdata/test_backupdir/golden/backupdirs/backupdir2/file4.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/golden/test.log b/cmd/bisync/testdata/test_backupdir/golden/test.log
new file mode 100644
index 0000000000000..ab8170d50ec27
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/golden/test.log
@@ -0,0 +1,153 @@
+[36m(01)  :[0m [34mtest backupdir[0m
+
+
+[36m(02)  :[0m [34mtest initial bisync[0m
+[36m(03)  :[0m [34mbisync resync backupdir1={workdir/}backupdirs/backupdir1 backupdir2={workdir/}backupdirs/backupdir2[0m
+INFO  : [2mSetting --ignore-listing-checksum as neither --checksum nor --compare checksum are set.[0m
+INFO  : Bisyncing with Comparison Settings:
+{
+"Modtime": true,
+"Size": true,
+"Checksum": false,
+"NoSlowHash": false,
+"SlowHashSyncOnly": false,
+"DownloadHash": false
+}
+INFO  : Synching Path1 "{path1/}" with Path2 "{path2/}"
+INFO  : Copying unique Path2 files to Path1
+INFO  : - [34mPath2[0m    [35mResync is copying UNIQUE files to[0m  - [36mPath1[0m
+INFO  : - [36mPath1[0m    [35mResync is copying UNIQUE OR DIFFERING files to[0m - [36mPath2[0m
+INFO  : Resync updating listings
+INFO  : [32mBisync successful[0m
+
+[36m(04)  :[0m [34mtest make modifications on both paths[0m
+[36m(05)  :[0m [34mtest new on path2 - file10[0m
+[36m(06)  :[0m [34mtouch-copy 2001-01-02 {datadir/}file10.txt {path2/}[0m
+
+[36m(07)  :[0m [34mtest newer on path2 - file1[0m
+[36m(08)  :[0m [34mtouch-copy 2001-01-02 {datadir/}file1.txt {path2/}[0m
+
+[36m(09)  :[0m [34mtest new on path1 - file11[0m
+[36m(10)  :[0m [34mtouch-copy 2001-01-02 {datadir/}file11.txt {path1/}[0m
+
+[36m(11)  :[0m [34mtest newer on path1 - file2[0m
+[36m(12)  :[0m [34mtouch-copy 2001-01-02 {datadir/}file2.txt {path1/}[0m
+
+[36m(13)  :[0m [34mtest deleted on path2 - file3[0m
+[36m(14)  :[0m [34mdelete-file {path2/}file3.txt[0m
+
+[36m(15)  :[0m [34mtest deleted on path1 - file4[0m
+[36m(16)  :[0m [34mdelete-file {path1/}file4.txt[0m
+
+[36m(17)  :[0m [34mtest deleted on both paths - file8[0m
+[36m(18)  :[0m [34mdelete-file {path1/}file8.txt[0m
+[36m(19)  :[0m [34mdelete-file {path2/}file8.txt[0m
+
+[36m(20)  :[0m [34mtest changed on both paths - file5 (file5R, file5L)[0m
+[36m(21)  :[0m [34mtouch-glob 2001-01-02 {datadir/} file5R.txt[0m
+[36m(22)  :[0m [34mcopy-as {datadir/}file5R.txt {path2/} file5.txt[0m
+[36m(23)  :[0m [34mtouch-glob 2001-03-04 {datadir/} file5L.txt[0m
+[36m(24)  :[0m [34mcopy-as {datadir/}file5L.txt {path1/} file5.txt[0m
+
+[36m(25)  :[0m [34mtest newer on path2 and deleted on path1 - file6[0m
+[36m(26)  :[0m [34mtouch-copy 2001-01-02 {datadir/}file6.txt {path2/}[0m
+[36m(27)  :[0m [34mdelete-file {path1/}file6.txt[0m
+
+[36m(28)  :[0m [34mtest newer on path1 and deleted on path2 - file7[0m
+[36m(29)  :[0m [34mtouch-copy 2001-01-02 {datadir/}file7.txt {path1/}[0m
+[36m(30)  :[0m [34mdelete-file {path2/}file7.txt[0m
+
+[36m(31)  :[0m [34mtest bisync run[0m
+[36m(32)  :[0m [34mbisync backupdir1={workdir/}backupdirs/backupdir1 backupdir2={workdir/}backupdirs/backupdir2[0m
+INFO  : [2mSetting --ignore-listing-checksum as neither --checksum nor --compare checksum are set.[0m
+INFO  : Bisyncing with Comparison Settings:
+{
+"Modtime": true,
+"Size": true,
+"Checksum": false,
+"NoSlowHash": false,
+"SlowHashSyncOnly": false,
+"DownloadHash": false
+}
+INFO  : Synching Path1 "{path1/}" with Path2 "{path2/}"
+INFO  : Building Path1 and Path2 listings
+INFO  : Path1 checking for diffs
+INFO  : - [36mPath1[0m    [35m[33mFile changed: [35msize (larger)[0m, [35mtime (newer)[0m[0m[0m - [36mfile2.txt[0m
+INFO  : - [36mPath1[0m    [35m[31mFile was deleted[0m[0m          - [36mfile4.txt[0m
+INFO  : - [36mPath1[0m    [35m[33mFile changed: [35msize (larger)[0m, [35mtime (newer)[0m[0m[0m - [36mfile5.txt[0m
+INFO  : - [36mPath1[0m    [35m[31mFile was deleted[0m[0m          - [36mfile6.txt[0m
+INFO  : - [36mPath1[0m    [35m[33mFile changed: [35msize (larger)[0m, [35mtime (newer)[0m[0m[0m - [36mfile7.txt[0m
+INFO  : - [36mPath1[0m    [35m[31mFile was deleted[0m[0m          - [36mfile8.txt[0m
+INFO  : - [36mPath1[0m    [35m[32mFile is new[0m[0m               - [36mfile11.txt[0m
+INFO  : Path1:    7 changes: [32m   1 new[0m, [33m   3 modified[0m, [31m   3 deleted[0m
+INFO  : ([33mModified[0m: [36m   3 newer[0m, [34m   0 older[0m, [36m   3 larger[0m, [34m   0 smaller[0m)
+INFO  : Path2 checking for diffs
+INFO  : - [34mPath2[0m    [35m[33mFile changed: [35msize (larger)[0m, [35mtime (newer)[0m[0m[0m - [36mfile1.txt[0m
+INFO  : - [34mPath2[0m    [35m[31mFile was deleted[0m[0m          - [36mfile3.txt[0m
+INFO  : - [34mPath2[0m    [35m[33mFile changed: [35msize (larger)[0m, [35mtime (newer)[0m[0m[0m - [36mfile5.txt[0m
+INFO  : - [34mPath2[0m    [35m[33mFile changed: [35msize (larger)[0m, [35mtime (newer)[0m[0m[0m - [36mfile6.txt[0m
+INFO  : - [34mPath2[0m    [35m[31mFile was deleted[0m[0m          - [36mfile7.txt[0m
+INFO  : - [34mPath2[0m    [35m[31mFile was deleted[0m[0m          - [36mfile8.txt[0m
+INFO  : - [34mPath2[0m    [35m[32mFile is new[0m[0m               - [36mfile10.txt[0m
+INFO  : Path2:    7 changes: [32m   1 new[0m, [33m   3 modified[0m, [31m   3 deleted[0m
+INFO  : ([33mModified[0m: [36m   3 newer[0m, [34m   0 older[0m, [36m   3 larger[0m, [34m   0 smaller[0m)
+INFO  : Applying changes
+INFO  : Checking potential conflicts...
+ERROR : file5.txt: md5 differ
+NOTICE: {path2String}: 1 differences found
+NOTICE: {path2String}: 1 errors while checking
+INFO  : Finished checking the potential conflicts. 1 differences found
+INFO  : - [36mPath1[0m    [35m[32mQueue copy to[0m Path2[0m       - [36m{path2/}file11.txt[0m
+INFO  : - [36mPath1[0m    [35m[32mQueue copy to[0m Path2[0m       - [36m{path2/}file2.txt[0m
+INFO  : - [34mPath2[0m    [35m[31mQueue delete[0m[0m              - [36m{path2/}file4.txt[0m
+NOTICE: - [34mWARNING[0m  [35mNew or changed in both paths[0m       - [36mfile5.txt[0m
+NOTICE: - [36mPath1[0m    [35mRenaming Path1 copy[0m                - [36m{path1/}file5.txt..path1[0m
+NOTICE: - [36mPath1[0m    [35m[32mQueue copy to[0m Path2[0m       - [36m{path2/}file5.txt..path1[0m
+NOTICE: - [34mPath2[0m    [35mRenaming Path2 copy[0m                - [36m{path2/}file5.txt..path2[0m
+NOTICE: - [34mPath2[0m    [35m[32mQueue copy to[0m Path1[0m       - [36m{path1/}file5.txt..path2[0m
+INFO  : - [34mPath2[0m    [35m[32mQueue copy to[0m Path1[0m       - [36m{path1/}file6.txt[0m
+INFO  : - [36mPath1[0m    [35m[32mQueue copy to[0m Path2[0m       - [36m{path2/}file7.txt[0m
+INFO  : - [34mPath2[0m    [35m[32mQueue copy to[0m Path1[0m       - [36m{path1/}file1.txt[0m
+INFO  : - [34mPath2[0m    [35m[32mQueue copy to[0m Path1[0m       - [36m{path1/}file10.txt[0m
+INFO  : - [36mPath1[0m    [35m[31mQueue delete[0m[0m              - [36m{path1/}file3.txt[0m
+INFO  : - [34mPath2[0m    [35mDo queued copies to[0m                - [36mPath1[0m
+INFO  : - [36mPath1[0m    [35mDo queued copies to[0m                - [36mPath2[0m
+INFO  : Updating listings
+INFO  : Validating listings for Path1 "{path1/}" vs Path2 "{path2/}"
+INFO  : [32mBisync successful[0m
+[36m(33)  :[0m [34mbisync backupdir1={workdir/}backupdirs/backupdir1 backupdir2={workdir/}backupdirs/backupdir2[0m
+INFO  : [2mSetting --ignore-listing-checksum as neither --checksum nor --compare checksum are set.[0m
+INFO  : Bisyncing with Comparison Settings:
+{
+"Modtime": true,
+"Size": true,
+"Checksum": false,
+"NoSlowHash": false,
+"SlowHashSyncOnly": false,
+"DownloadHash": false
+}
+INFO  : Synching Path1 "{path1/}" with Path2 "{path2/}"
+INFO  : Building Path1 and Path2 listings
+INFO  : Path1 checking for diffs
+INFO  : Path2 checking for diffs
+INFO  : No changes found
+INFO  : Updating listings
+INFO  : Validating listings for Path1 "{path1/}" vs Path2 "{path2/}"
+INFO  : [32mBisync successful[0m
+
+[36m(34)  :[0m [34mtest overlapping path -- should fail[0m
+[36m(35)  :[0m [34mbisync backupdir1={path1/}subdir/backupdir1 backupdir2={path2/}subdir/backupdir2[0m
+INFO  : [2mSetting --ignore-listing-checksum as neither --checksum nor --compare checksum are set.[0m
+INFO  : Bisyncing with Comparison Settings:
+{
+"Modtime": true,
+"Size": true,
+"Checksum": false,
+"NoSlowHash": false,
+"SlowHashSyncOnly": false,
+"DownloadHash": false
+}
+INFO  : Synching Path1 "{path1/}" with Path2 "{path2/}"
+ERROR : [31mBisync critical error: destination and parameter to --backup-dir mustn't overlap[0m
+ERROR : [31mBisync aborted. Must run --resync to recover.[0m
+Bisync error: bisync aborted
diff --git a/cmd/bisync/testdata/test_backupdir/initial/RCLONE_TEST b/cmd/bisync/testdata/test_backupdir/initial/RCLONE_TEST
new file mode 100644
index 0000000000000..d8ca97c2a4dbe
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/initial/RCLONE_TEST
@@ -0,0 +1,1 @@
+This file is used for testing the health of rclone accesses to the local/remote file system.  Do not delete.
diff --git a/cmd/bisync/testdata/test_backupdir/initial/file1.txt b/cmd/bisync/testdata/test_backupdir/initial/file1.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/initial/file2.txt b/cmd/bisync/testdata/test_backupdir/initial/file2.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/initial/file3.txt b/cmd/bisync/testdata/test_backupdir/initial/file3.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/initial/file4.txt b/cmd/bisync/testdata/test_backupdir/initial/file4.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/initial/file5.txt b/cmd/bisync/testdata/test_backupdir/initial/file5.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/initial/file6.txt b/cmd/bisync/testdata/test_backupdir/initial/file6.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/initial/file7.txt b/cmd/bisync/testdata/test_backupdir/initial/file7.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/initial/file8.txt b/cmd/bisync/testdata/test_backupdir/initial/file8.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/cmd/bisync/testdata/test_backupdir/modfiles/file1.txt b/cmd/bisync/testdata/test_backupdir/modfiles/file1.txt
new file mode 100644
index 0000000000000..464147f09c0c8
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/modfiles/file1.txt
@@ -0,0 +1,1 @@
+This file is newer
diff --git a/cmd/bisync/testdata/test_backupdir/modfiles/file10.txt b/cmd/bisync/testdata/test_backupdir/modfiles/file10.txt
new file mode 100644
index 0000000000000..464147f09c0c8
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/modfiles/file10.txt
@@ -0,0 +1,1 @@
+This file is newer
diff --git a/cmd/bisync/testdata/test_backupdir/modfiles/file11.txt b/cmd/bisync/testdata/test_backupdir/modfiles/file11.txt
new file mode 100644
index 0000000000000..464147f09c0c8
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/modfiles/file11.txt
@@ -0,0 +1,1 @@
+This file is newer
diff --git a/cmd/bisync/testdata/test_backupdir/modfiles/file2.txt b/cmd/bisync/testdata/test_backupdir/modfiles/file2.txt
new file mode 100644
index 0000000000000..0fd70321acd6b
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/modfiles/file2.txt
@@ -0,0 +1,1 @@
+Newer version
\ No newline at end of file
diff --git a/cmd/bisync/testdata/test_backupdir/modfiles/file5L.txt b/cmd/bisync/testdata/test_backupdir/modfiles/file5L.txt
new file mode 100644
index 0000000000000..43ceff1dbe273
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/modfiles/file5L.txt
@@ -0,0 +1,1 @@
+This file is newer and not equal to 5R
diff --git a/cmd/bisync/testdata/test_backupdir/modfiles/file5R.txt b/cmd/bisync/testdata/test_backupdir/modfiles/file5R.txt
new file mode 100644
index 0000000000000..a928fcf1382e0
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/modfiles/file5R.txt
@@ -0,0 +1,1 @@
+This file is newer and not equal to 5L
diff --git a/cmd/bisync/testdata/test_backupdir/modfiles/file6.txt b/cmd/bisync/testdata/test_backupdir/modfiles/file6.txt
new file mode 100644
index 0000000000000..464147f09c0c8
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/modfiles/file6.txt
@@ -0,0 +1,1 @@
+This file is newer
diff --git a/cmd/bisync/testdata/test_backupdir/modfiles/file7.txt b/cmd/bisync/testdata/test_backupdir/modfiles/file7.txt
new file mode 100644
index 0000000000000..464147f09c0c8
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/modfiles/file7.txt
@@ -0,0 +1,1 @@
+This file is newer
diff --git a/cmd/bisync/testdata/test_backupdir/scenario.txt b/cmd/bisync/testdata/test_backupdir/scenario.txt
new file mode 100644
index 0000000000000..0b8a53e0dcc98
--- /dev/null
+++ b/cmd/bisync/testdata/test_backupdir/scenario.txt
@@ -0,0 +1,59 @@
+test backupdir
+# Exercise all of the various file change scenarios
+# - New on Path2                            file10
+# - Newer on Path2                          file1
+# - New on Path1                            file11
+# - Newer on Path1                          file2
+# - Deleted on Path2                        file3
+# - Deleted on Path1                        file4
+# - Changed on Path2 and on Path1           file5 (file5r, file5l)
+# - Newer on Path2 and deleted on Path1     file6
+# - Newer on Path1 and deleted on Path2     file7
+# - Deleted on both paths                   file8
+
+test initial bisync
+bisync resync backupdir1={workdir/}backupdirs/backupdir1 backupdir2={workdir/}backupdirs/backupdir2
+
+test make modifications on both paths
+test new on path2 - file10
+touch-copy 2001-01-02 {datadir/}file10.txt {path2/}
+
+test newer on path2 - file1
+touch-copy 2001-01-02 {datadir/}file1.txt {path2/}
+
+test new on path1 - file11
+touch-copy 2001-01-02 {datadir/}file11.txt {path1/}
+
+test newer on path1 - file2
+touch-copy 2001-01-02 {datadir/}file2.txt {path1/}
+
+test deleted on path2 - file3
+delete-file {path2/}file3.txt
+
+test deleted on path1 - file4
+delete-file {path1/}file4.txt
+
+test deleted on both paths - file8
+delete-file {path1/}file8.txt
+delete-file {path2/}file8.txt
+
+test changed on both paths - file5 (file5R, file5L)
+touch-glob 2001-01-02 {datadir/} file5R.txt
+copy-as {datadir/}file5R.txt {path2/} file5.txt
+touch-glob 2001-03-04 {datadir/} file5L.txt
+copy-as {datadir/}file5L.txt {path1/} file5.txt
+
+test newer on path2 and deleted on path1 - file6
+touch-copy 2001-01-02 {datadir/}file6.txt {path2/}
+delete-file {path1/}file6.txt
+
+test newer on path1 and deleted on path2 - file7
+touch-copy 2001-01-02 {datadir/}file7.txt {path1/}
+delete-file {path2/}file7.txt
+
+test bisync run
+bisync backupdir1={workdir/}backupdirs/backupdir1 backupdir2={workdir/}backupdirs/backupdir2
+bisync backupdir1={workdir/}backupdirs/backupdir1 backupdir2={workdir/}backupdirs/backupdir2
+
+test overlapping path -- should fail
+bisync backupdir1={path1/}subdir/backupdir1 backupdir2={path2/}subdir/backupdir2