diff --git a/docs/content/docs.md b/docs/content/docs.md index 8a0446237..f65289032 100644 --- a/docs/content/docs.md +++ b/docs/content/docs.md @@ -500,6 +500,12 @@ Do a trial run with no permanent changes. Use this to see what rclone would do without actually doing it. Useful when setting up the `sync` command which deletes files in the destination. +### --ignore-case-sync ### + +Using this option will cause rclone to ignore the case of the files +when synchronizing so files will not be copied/synced when the +existing filenames are the same, even if the casing is different. + ### --ignore-checksum ### Normally rclone will check that the checksums of transferred files diff --git a/fs/config.go b/fs/config.go index f6b8eb1d9..e3700517b 100644 --- a/fs/config.go +++ b/fs/config.go @@ -62,6 +62,7 @@ type ConfigInfo struct { MaxDepth int IgnoreSize bool IgnoreChecksum bool + IgnoreCaseSync bool NoTraverse bool NoUpdateModTime bool DataRateUnit string diff --git a/fs/config/configflags/configflags.go b/fs/config/configflags/configflags.go index 57a36001c..ef8de0493 100644 --- a/fs/config/configflags/configflags.go +++ b/fs/config/configflags/configflags.go @@ -64,6 +64,7 @@ func AddFlags(flagSet *pflag.FlagSet) { flags.IntVarP(flagSet, &fs.Config.MaxDepth, "max-depth", "", fs.Config.MaxDepth, "If set limits the recursion depth to this.") flags.BoolVarP(flagSet, &fs.Config.IgnoreSize, "ignore-size", "", false, "Ignore size when skipping use mod-time or checksum.") flags.BoolVarP(flagSet, &fs.Config.IgnoreChecksum, "ignore-checksum", "", fs.Config.IgnoreChecksum, "Skip post copy check of checksums.") + flags.BoolVarP(flagSet, &fs.Config.IgnoreCaseSync, "ignore-case-sync", "", fs.Config.IgnoreCaseSync, "Ignore case when synchronizing") flags.BoolVarP(flagSet, &fs.Config.NoTraverse, "no-traverse", "", fs.Config.NoTraverse, "Don't traverse destination file system on copy.") flags.BoolVarP(flagSet, &fs.Config.NoUpdateModTime, "no-update-modtime", "", fs.Config.NoUpdateModTime, "Don't update destination mod-time if files identical.") flags.StringVarP(flagSet, &fs.Config.BackupDir, "backup-dir", "", fs.Config.BackupDir, "Make backups into hierarchy based in DIR.") diff --git a/fs/march/march.go b/fs/march/march.go index 01e1570ef..7b9ca5d53 100644 --- a/fs/march/march.go +++ b/fs/march/march.go @@ -58,7 +58,7 @@ func (m *March) init() { // | Yes | No | No | // | No | Yes | Yes | // | Yes | Yes | Yes | - if m.Fdst.Features().CaseInsensitive { + if m.Fdst.Features().CaseInsensitive || fs.Config.IgnoreCaseSync { m.transforms = append(m.transforms, strings.ToLower) } } diff --git a/fs/sync/sync_test.go b/fs/sync/sync_test.go index 3ec14dc07..658a22b46 100644 --- a/fs/sync/sync_test.go +++ b/fs/sync/sync_test.go @@ -1346,6 +1346,33 @@ func TestSyncImmutable(t *testing.T) { fstest.CheckItems(t, r.Fremote, file1) } +// Test --ignore-case-sync +func TestSyncIgnoreCase(t *testing.T) { + r := fstest.NewRun(t) + defer r.Finalise() + + // Only test if filesystems are case sensitive + if r.Fremote.Features().CaseInsensitive || r.Flocal.Features().CaseInsensitive { + t.Skip("Skipping test as local or remote are case-insensitive") + } + + fs.Config.IgnoreCaseSync = true + defer func() { fs.Config.IgnoreCaseSync = false }() + + // Create files with different filename casing + file1 := r.WriteFile("existing", "potato", t1) + fstest.CheckItems(t, r.Flocal, file1) + file2 := r.WriteObject("EXISTING", "potato", t1) + fstest.CheckItems(t, r.Fremote, file2) + + // Should not copy files that are differently-cased but otherwise identical + accounting.Stats.ResetCounters() + err := Sync(r.Fremote, r.Flocal, false) + require.NoError(t, err) + fstest.CheckItems(t, r.Flocal, file1) + fstest.CheckItems(t, r.Fremote, file2) +} + // Test that aborting on max upload works func TestAbort(t *testing.T) { r := fstest.NewRun(t)