From de67f29b3fe3693739c8d431ce4156dce73a7d6d Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 6 May 2026 17:01:07 +0100 Subject: [PATCH] sync: fix --fix-case rename failing on backends that can't update modtime When --fix-case was used (e.g. by bisync) on backends that can't set modification times in place - such as Dropbox - files whose content matched but whose modtimes differed would fail to rename with a "from_lookup/not_found" error and abort the operation. This happened because operations.NeedTransfer was called before the fix-case rename. NeedTransfer's equality check would delete the destination as a precursor to re-uploading it (the standard way to update a modtime on these backends), so by the time the rename ran the file no longer existed on the remote. Fix by running the fix-case rename first, so that any subsequent delete/re-upload happens at the correctly-cased destination path. See: #8881 --- fs/sync/sync.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/fs/sync/sync.go b/fs/sync/sync.go index 6ec0c074f..b3eca0a86 100644 --- a/fs/sync/sync.go +++ b/fs/sync/sync.go @@ -380,6 +380,20 @@ func (s *syncCopyMove) pairChecker(in *pipe, out *pipe, fraction int, wg *sync.W tr := accounting.Stats(s.ctx).NewCheckingTransfer(src, "checking") // Check to see if can store this if src.Storable() { + // Fix case for case insensitive filesystems before checking + // whether a transfer is needed, since NeedTransfer may delete + // the destination (when content matches but modtime can't be + // set without re-upload), which would cause the rename below + // to fail with a "not found" error. + 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 + } + } needTransfer := operations.NeedTransfer(s.ctx, pair.Dst, pair.Src) if needTransfer { NoNeedTransfer, err := operations.CompareOrCopyDest(s.ctx, s.fdst, pair.Dst, pair.Src, s.compareCopyDest, s.backupDir) @@ -391,16 +405,6 @@ func (s *syncCopyMove) pairChecker(in *pipe, out *pipe, fraction int, wg *sync.W needTransfer = false } } - // 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 needTransfer { // If files are treated as immutable, fail if destination exists and does not match if s.ci.Immutable && pair.Dst != nil {