mirror of
https://github.com/rclone/rclone.git
synced 2025-08-15 00:02:35 +02:00
sync: don't set dir modtimes if already set
Before this change, directory modtimes (and metadata) were always synced from src to dst, even if already in sync (i.e. their modtimes already matched.) This potentially required excessive API calls, made logs noisy, and was potentially problematic for backends that create "versions" or otherwise log activity updates when modtime/metadata is updated. After this change, a new DirsEqual function is added to check whether dirs are equal based on a number of factors such as ModifyWindow and sync flags in use. If the dirs are equal, the modtime/metadata update is skipped. For backends that require setDirModTimeAfter, the "after" sync is performed only for dirs that could have been changed by the sync (i.e. dirs containing files that were created/updated.) Note that dir metadata (other than modtime) is not currently considered by DirsEqual, consistent with how object metadata is synced (only when objects are unequal for reasons other than metadata). To sync dir modtimes and metadata unconditionally (the previous behavior), use --ignore-times.
This commit is contained in:
@ -19,6 +19,7 @@ import (
|
||||
mutex "sync" // renamed as "sync" already in use
|
||||
|
||||
_ "github.com/rclone/rclone/backend/all" // import all backends
|
||||
"github.com/rclone/rclone/cmd/bisync/bilib"
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/fs/accounting"
|
||||
"github.com/rclone/rclone/fs/filter"
|
||||
@ -2499,6 +2500,74 @@ func TestSyncConcurrentTruncate(t *testing.T) {
|
||||
testSyncConcurrent(t, "truncate")
|
||||
}
|
||||
|
||||
// Tests that nothing is transferred when src and dst already match
|
||||
// Run the same sync twice, ensure no action is taken the second time
|
||||
func TestNothingToTransfer(t *testing.T) {
|
||||
accounting.GlobalStats().ResetCounters()
|
||||
ctx, _ := fs.AddConfig(context.Background())
|
||||
r := fstest.NewRun(t)
|
||||
file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
|
||||
file2 := r.WriteFile("sub dir2/very/very/very/very/very/nested/subdir/hello world", "hello world", t1)
|
||||
r.CheckLocalItems(t, file1, file2)
|
||||
_, err := operations.SetDirModTime(ctx, r.Flocal, nil, "sub dir", t2)
|
||||
if err != nil && !errors.Is(err, fs.ErrorNotImplemented) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
r.Mkdir(ctx, r.Fremote)
|
||||
_, err = operations.MkdirModTime(ctx, r.Fremote, "sub dir", t3)
|
||||
require.NoError(t, err)
|
||||
|
||||
// set logging
|
||||
// (this checks log output as DirModtime operations do not yet have stats, and r.CheckDirectoryModTimes also does not tell us what actions were taken)
|
||||
oldLogLevel := fs.GetConfig(context.Background()).LogLevel
|
||||
defer func() { fs.GetConfig(context.Background()).LogLevel = oldLogLevel }() // reset to old val after test
|
||||
// need to do this as fs.Infof only respects the globalConfig
|
||||
fs.GetConfig(context.Background()).LogLevel = fs.LogLevelInfo
|
||||
|
||||
accounting.GlobalStats().ResetCounters()
|
||||
ctx = predictDstFromLogger(ctx)
|
||||
output := bilib.CaptureOutput(func() {
|
||||
err = CopyDir(ctx, r.Fremote, r.Flocal, true)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
require.NotNil(t, output)
|
||||
testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
|
||||
r.CheckLocalItems(t, file1, file2)
|
||||
r.CheckRemoteItems(t, file1, file2)
|
||||
// Check that the modtimes of the directories are as expected
|
||||
r.CheckDirectoryModTimes(t, "sub dir")
|
||||
|
||||
// check that actions were taken
|
||||
assert.True(t, strings.Contains(string(output), "Copied"), `expected to find at least one "Copied" log: `+string(output))
|
||||
if r.Fremote.Features().DirSetModTime != nil || r.Fremote.Features().MkdirMetadata != nil {
|
||||
assert.True(t, strings.Contains(string(output), "Set directory modification time"), `expected to find at least one "Set directory modification time" log: `+string(output))
|
||||
}
|
||||
assert.False(t, strings.Contains(string(output), "There was nothing to transfer"), `expected to find no "There was nothing to transfer" logs, but found one: `+string(output))
|
||||
assert.Equal(t, int64(2), accounting.GlobalStats().GetTransfers())
|
||||
|
||||
// run it again and make sure no actions were taken
|
||||
accounting.GlobalStats().ResetCounters()
|
||||
ctx = predictDstFromLogger(ctx)
|
||||
output = bilib.CaptureOutput(func() {
|
||||
err = CopyDir(ctx, r.Fremote, r.Flocal, true)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
require.NotNil(t, output)
|
||||
testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
|
||||
r.CheckLocalItems(t, file1, file2)
|
||||
r.CheckRemoteItems(t, file1, file2)
|
||||
// Check that the modtimes of the directories are as expected
|
||||
r.CheckDirectoryModTimes(t, "sub dir")
|
||||
|
||||
// check that actions were NOT taken
|
||||
assert.False(t, strings.Contains(string(output), "Copied"), `expected to find no "Copied" logs, but found one: `+string(output))
|
||||
if r.Fremote.Features().DirSetModTime != nil || r.Fremote.Features().MkdirMetadata != nil {
|
||||
assert.False(t, strings.Contains(string(output), "Set directory modification time"), `expected to find no "Set directory modification time" logs, but found one: `+string(output))
|
||||
}
|
||||
assert.True(t, strings.Contains(string(output), "There was nothing to transfer"), `expected to find a "There was nothing to transfer" log: `+string(output))
|
||||
assert.Equal(t, int64(0), accounting.GlobalStats().GetTransfers())
|
||||
}
|
||||
|
||||
// for testing logger:
|
||||
func predictDstFromLogger(ctx context.Context) context.Context {
|
||||
opt := operations.NewLoggerOpt()
|
||||
|
Reference in New Issue
Block a user