sync: fix --fix-case rename on backends that need upload before overwrite

operations.NeedTransfer's equality check may have deleted pair.Dst as
a precursor to re-uploading it if SetModTime returns
ErrorCantSetModTimeWithoutDelete (e.g. Dropbox). If so skip the eager
delete of the destination if --fix-case will rename it to a different
name. The rename itself replaces the destination, and any subsequent
re-upload happens at the correctly-cased path.

See: #8881
This commit is contained in:
Nick Craig-Wood
2026-05-07 11:07:41 +01:00
parent 92058f15c4
commit f60213545b

View File

@@ -392,13 +392,26 @@ func (s *syncCopyMove) pairChecker(in *pipe, out *pipe, fraction int, wg *sync.W
}
}
// Fix case for case insensitive filesystems
if s.ci.FixCase && !s.ci.Immutable && src.Remote() != pair.Dst.Remote() {
if newDst, err := operations.Move(s.ctx, s.fdst, nil, src.Remote(), pair.Dst); err != nil {
fs.Errorf(pair.Dst, "Error while attempting to rename to %s: %v", src.Remote(), err)
s.processError(err)
} else {
fs.Infof(pair.Dst, "Fixed case by renaming to: %s", src.Remote())
pair.Dst = newDst
if s.ci.FixCase && !s.ci.Immutable && pair.Dst != nil && src.Remote() != pair.Dst.Remote() {
// NeedTransfer's equality check may have deleted pair.Dst as a precursor
// to re-uploading it (the way modtime updates are done on backends like
// Dropbox that can't set modtime in place). If so, there is nothing to
// rename - nil out pair.Dst so the upload below recreates the file at
// src.Remote() (the correctly-cased name).
if needTransfer {
if _, statErr := s.fdst.NewObject(s.ctx, pair.Dst.Remote()); errors.Is(statErr, fs.ErrorObjectNotFound) {
fs.Debugf(pair.Dst, "Skipping fix-case rename: destination removed for re-upload, will recreate at %s", src.Remote())
pair.Dst = nil
}
}
if pair.Dst != nil {
if newDst, err := operations.Move(s.ctx, s.fdst, nil, src.Remote(), pair.Dst); err != nil {
fs.Errorf(pair.Dst, "Error while attempting to rename to %s: %v", src.Remote(), err)
s.processError(err)
} else {
fs.Infof(pair.Dst, "Fixed case by renaming to: %s", src.Remote())
pair.Dst = newDst
}
}
}
if needTransfer {