sync: add --track-renames-strategy leaf

See: https://forum.rclone.org/t/how-to-minimize-bandwith-w-r-t-renames-during-sync/16928/22
This commit is contained in:
Nick Craig-Wood 2020-06-09 20:40:03 +01:00
parent 2288a5c617
commit 8f42532b6d
4 changed files with 71 additions and 8 deletions

View file

@ -1324,13 +1324,25 @@ Note also that `--track-renames` is incompatible with
`--delete-before` and will select `--delete-after` instead of `--delete-before` and will select `--delete-after` instead of
`--delete-during`. `--delete-during`.
### --track-renames-strategy (hash,modtime) ### ### --track-renames-strategy (hash,modtime,leaf,size) ###
This option changes the matching criteria for `--track-renames` to match This option changes the matching criteria for `--track-renames`.
by any combination of modtime, hash, size. Matching by size is always enabled
no matter what option is selected here. This also means The matching is controlled by a comma separated selection of these tokens:
that it enables `--track-renames` support for encrypted destinations.
If nothing is specified, the default option is matching by hashes. - `modtime` - the modification time of the file - not supported on all backends
- `hash` - the hash of the file contents - not supported on all backends
- `leaf` - the name of the file not including its directory name
- `size` - the size of the file (this is always enabled)
So using `--track-renames-strategy modtime,leaf` would match files
based on modification time, the leaf of the file name and the size
only.
Using `--track-renames-strategy modtime` or `leaf` can enable
`--track-renames` support for encrypted destinations.
If nothing is specified, the default option is matching by `hash`es.
### --delete-(before,during,after) ### ### --delete-(before,during,after) ###

View file

@ -65,7 +65,7 @@ func AddFlags(flagSet *pflag.FlagSet) {
flags.BoolVarP(flagSet, &deleteAfter, "delete-after", "", false, "When synchronizing, delete files on destination after transferring (default)") flags.BoolVarP(flagSet, &deleteAfter, "delete-after", "", false, "When synchronizing, delete files on destination after transferring (default)")
flags.Int64VarP(flagSet, &fs.Config.MaxDelete, "max-delete", "", -1, "When synchronizing, limit the number of deletes") flags.Int64VarP(flagSet, &fs.Config.MaxDelete, "max-delete", "", -1, "When synchronizing, limit the number of deletes")
flags.BoolVarP(flagSet, &fs.Config.TrackRenames, "track-renames", "", fs.Config.TrackRenames, "When synchronizing, track file renames and do a server side move if possible") flags.BoolVarP(flagSet, &fs.Config.TrackRenames, "track-renames", "", fs.Config.TrackRenames, "When synchronizing, track file renames and do a server side move if possible")
flags.StringVarP(flagSet, &fs.Config.TrackRenamesStrategy, "track-renames-strategy", "", fs.Config.TrackRenamesStrategy, "Strategies to use when synchronizing using track-renames hash|modtime") flags.StringVarP(flagSet, &fs.Config.TrackRenamesStrategy, "track-renames-strategy", "", fs.Config.TrackRenamesStrategy, "Strategies to use when synchronizing using track-renames hash|modtime|leaf")
flags.IntVarP(flagSet, &fs.Config.LowLevelRetries, "low-level-retries", "", fs.Config.LowLevelRetries, "Number of low level retries to do.") flags.IntVarP(flagSet, &fs.Config.LowLevelRetries, "low-level-retries", "", fs.Config.LowLevelRetries, "Number of low level retries to do.")
flags.BoolVarP(flagSet, &fs.Config.UpdateOlder, "update", "u", fs.Config.UpdateOlder, "Skip files that are newer on the destination.") flags.BoolVarP(flagSet, &fs.Config.UpdateOlder, "update", "u", fs.Config.UpdateOlder, "Skip files that are newer on the destination.")
flags.BoolVarP(flagSet, &fs.Config.UseServerModTime, "use-server-modtime", "", fs.Config.UseServerModTime, "Use server modified time instead of object metadata") flags.BoolVarP(flagSet, &fs.Config.UseServerModTime, "use-server-modtime", "", fs.Config.UseServerModTime, "Use server modified time instead of object metadata")

View file

@ -76,6 +76,7 @@ type trackRenamesStrategy byte
const ( const (
trackRenamesStrategyHash trackRenamesStrategy = 1 << iota trackRenamesStrategyHash trackRenamesStrategy = 1 << iota
trackRenamesStrategyModtime trackRenamesStrategyModtime
trackRenamesStrategyLeaf
) )
func (strategy trackRenamesStrategy) hash() bool { func (strategy trackRenamesStrategy) hash() bool {
@ -86,6 +87,10 @@ func (strategy trackRenamesStrategy) modTime() bool {
return (strategy & trackRenamesStrategyModtime) != 0 return (strategy & trackRenamesStrategyModtime) != 0
} }
func (strategy trackRenamesStrategy) leaf() bool {
return (strategy & trackRenamesStrategyLeaf) != 0
}
func newSyncCopyMove(ctx context.Context, fdst, fsrc fs.Fs, deleteMode fs.DeleteMode, DoMove bool, deleteEmptySrcDirs bool, copyEmptySrcDirs bool) (*syncCopyMove, error) { func newSyncCopyMove(ctx context.Context, fdst, fsrc fs.Fs, deleteMode fs.DeleteMode, DoMove bool, deleteEmptySrcDirs bool, copyEmptySrcDirs bool) (*syncCopyMove, error) {
if (deleteMode != fs.DeleteModeOff || DoMove) && operations.Overlapping(fdst, fsrc) { if (deleteMode != fs.DeleteModeOff || DoMove) && operations.Overlapping(fdst, fsrc) {
return nil, fserrors.FatalError(fs.ErrorOverlapping) return nil, fserrors.FatalError(fs.ErrorOverlapping)
@ -609,6 +614,8 @@ func parseTrackRenamesStrategy(strategies string) (strategy trackRenamesStrategy
strategy |= trackRenamesStrategyHash strategy |= trackRenamesStrategyHash
case "modtime": case "modtime":
strategy |= trackRenamesStrategyModtime strategy |= trackRenamesStrategyModtime
case "leaf":
strategy |= trackRenamesStrategyLeaf
case "size": case "size":
// ignore // ignore
default: default:
@ -638,12 +645,18 @@ func (s *syncCopyMove) renameID(obj fs.Object, renamesStrategy trackRenamesStrat
return "" return ""
} }
fmt.Fprintf(&builder, ",%s", hash) builder.WriteRune(',')
builder.WriteString(hash)
} }
// for renamesStrategy.modTime() we don't add to the hash but we check the times in // for renamesStrategy.modTime() we don't add to the hash but we check the times in
// popRenameMap // popRenameMap
if renamesStrategy.leaf() {
builder.WriteRune(',')
builder.WriteString(path.Base(obj.Remote()))
}
return builder.String() return builder.String()
} }

View file

@ -1162,6 +1162,44 @@ func TestSyncWithTrackRenamesStrategyModtime(t *testing.T) {
} }
} }
func TestSyncWithTrackRenamesStrategyLeaf(t *testing.T) {
r := fstest.NewRun(t)
defer r.Finalise()
fs.Config.TrackRenames = true
fs.Config.TrackRenamesStrategy = "leaf"
defer func() {
fs.Config.TrackRenames = false
fs.Config.TrackRenamesStrategy = "hash"
}()
canTrackRenames := operations.CanServerSideMove(r.Fremote) && r.Fremote.Precision() != fs.ModTimeNotSupported
t.Logf("Can track renames: %v", canTrackRenames)
f1 := r.WriteFile("potato", "Potato Content", t1)
f2 := r.WriteFile("sub/yam", "Yam Content", t2)
accounting.GlobalStats().ResetCounters()
require.NoError(t, Sync(context.Background(), r.Fremote, r.Flocal, false))
fstest.CheckItems(t, r.Fremote, f1, f2)
fstest.CheckItems(t, r.Flocal, f1, f2)
// Now rename locally.
f2 = r.RenameFile(f2, "yam")
accounting.GlobalStats().ResetCounters()
require.NoError(t, Sync(context.Background(), r.Fremote, r.Flocal, false))
fstest.CheckItems(t, r.Fremote, f1, f2)
// Check we renamed something if we should have
if canTrackRenames {
renames := accounting.GlobalStats().Renames(0)
assert.Equal(t, canTrackRenames, renames != 0, fmt.Sprintf("canTrackRenames=%v, renames=%d", canTrackRenames, renames))
}
}
func toyFileTransfers(r *fstest.Run) int64 { func toyFileTransfers(r *fstest.Run) int64 {
remote := r.Fremote.Name() remote := r.Fremote.Name()
transfers := 1 transfers := 1