Skip to content

fix(libsync): folder move or rename data loss.#9998

Open
camilasan wants to merge 6 commits into
masterfrom
bugfix/sync-move-data-loss
Open

fix(libsync): folder move or rename data loss.#9998
camilasan wants to merge 6 commits into
masterfrom
bugfix/sync-move-data-loss

Conversation

@camilasan

@camilasan camilasan commented May 7, 2026

Copy link
Copy Markdown
Member

Resolves

When a parent folder is renamed during sync, file changes inside it can be silently lost in 2 ways:

  1. Stale lock tokens: renaming a parent copies child journal records to the new path but keeps the old WebDAV lock token. The next upload sends it at the new URL and the server rejects it with 412/423.
    Clear _lockToken and _locked when writing a renamed child's journal record.
    And No 412/423 recovery: the error handler didn't clear the stale token, so every retry failed the same way.
    Clear the token from the journal on 412/423 and schedule rediscovery.
    fix(file-provider): invalidate lock tokens when file paths change.  #10135

  2. Path prefix bug: removeRecursively used startsWith(dir) instead of startsWith(dir + '/'), causing false prefix matches on sibling paths like A/BC when processing A/B.
    Use startsWith(dir + '/').
    See fix(file-provider): use slash prefix for child item queries. #10130

Steps to test it

Scenario 1 — Stale lock token after folder rename (the main symptom)

  • What it tests: commits 5d8e64c757 and f1a6079 (lock token clearing + 412/423 recovery)
  • Prerequisites: LibreOffice (or any WebDAV-locking editor), a Nextcloud server with file locking enabled, two clients or one client + the web UI.
  1. Sync a folder containing an .odt or .xlsx file, e.g. Work/report.odt.
  2. Open report.odt in LibreOffice — this acquires a WebDAV lock token stored in the journal.
  3. From the web UI or a second client, rename the parent folder: Work → Work2.
  4. Let the desktop client sync the rename (the folder appears as Work2 locally).
  5. In LibreOffice, make a change and Save.
  6. Save. LibreOffice may ask "The file has been changed since it was opened — save anyway?" Click Save anyway.
    See fix(file-provider): invalidate lock tokens when file paths change.  #10135
    ✅ Expected (with fix): save succeeds, Work2/report.odt contents do not get lost.
    🔴 Regression (without fix): the upload fails with 412 or 423, LibreOffice shows a save error, and every subsequent save attempt fails identically because the stale token is never cleared.

Scenario 2 — Path prefix matching (sibling folders not clobbered)

  • What it tests: commit c168186 (slash-prefix fix in removeRecursively)
  1. Create two sibling folders locally: D/ and D2/, each containing a file. Sync both.
  2. Rename D → E locally.
  3. Sync.
    ✅ Expected (with fix): D2/ and its contents remain fully intact after the sync.
    🔴 Regression (without fix): because "D2".startsWith("D") is true, the old logic could mark D2's entries as part of the renamed tree and remove them from the journal.

Checklist

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@camilasan camilasan added this to the 33.0.5 milestone May 7, 2026
@camilasan camilasan self-assigned this May 7, 2026
@camilasan camilasan changed the title fix(file-provider): folder move or rename data loss. fix(libsync): folder move or rename data loss. May 7, 2026
@camilasan camilasan force-pushed the bugfix/sync-move-data-loss branch 2 times, most recently from 1fef7ae to e14e53b Compare May 7, 2026 18:43
@sonarqubecloud

sonarqubecloud Bot commented May 7, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
75.0% Coverage on New Code (required ≥ 80%)
65 New Code Smells (required ≤ 0)
D Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@camilasan

Copy link
Copy Markdown
Member Author

/backport to stable-33.0

@camilasan camilasan modified the milestones: 33.0.5, 33.0.6 May 18, 2026
@camilasan camilasan force-pushed the bugfix/sync-move-data-loss branch 4 times, most recently from f4873b5 to e5c6a8e Compare June 11, 2026 19:18
@camilasan camilasan marked this pull request as ready for review June 11, 2026 19:18
@camilasan camilasan force-pushed the bugfix/sync-move-data-loss branch 8 times, most recently from 8db218a to 534aca4 Compare June 15, 2026 09:23
@camilasan camilasan force-pushed the bugfix/sync-move-data-loss branch 3 times, most recently from e22d5c0 to 6923867 Compare June 15, 2026 18:21
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
D Maintainability Rating on New Code (required ≥ A)
29 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@camilasan camilasan force-pushed the bugfix/sync-move-data-loss branch 5 times, most recently from 8a1fd92 to 383d274 Compare June 17, 2026 15:14
- startsWith(deletedDir) without a trailing slash would match sibling
directories sharing a common prefix (e.g. "A/B" matching "A/BC"),
causing their journal records to be skipped in the failure-cleanup
loop. Append '/' before the comparison.
- Add test to cover removeRecursively sibling prefix guard.
- Add isPathInsideDeletedDir as an inline header helper and a unit test
verifying a sibling such as "A/BC" is not treated as a child of "A/B",
guarding the journal cleanup after a failed local remove. Use qWarning
instead of the deprecated QWARN in the testMovedWithError data rows.

Assisted-by: Claude Code:claude-opus-4-8
Signed-off-by: Camila Ayres <hello@camilasan.com>
WebDAV lock tokens are bound to the URL they were issued for. When a
parent directory is renamed, PropagateLocalRename and
PropagateRemoteMove both copy child journal records to the new path
while preserving the old token. Subsequent uploads send the stale
token and receive 412/423 from the server.

Clear the token from the journal on either status code so the next
sync retries without it. Schedule rediscovery on 412 since the server
state is uncertain.

Force remote rediscovery on both 412 and 423 upload errors so the lock
state cleared from the journal is refreshed from the server, not only the
bad etag on 412. Strengthen the 412 test to assert the parent folder etag
is invalidated.

Add a matching Q_OS_WIN guard in testLockedStateClearedOnClientInitiatedRename
to clear the ACL before the local directory rename, which Windows requires
when files inside have an ACL-based read-only restriction.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Camila Ayres <hello@camilasan.com>
When a remote directory move is finalized, iterate all children
recorded in the sync journal and rewrite their paths to reflect the
new location, clearing stale lock state. Without this, children
retained the old path in the database and could be treated as new
uploads or silently lost on the next sync cycle.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Camila Ayres <hello@camilasan.com>
Signed-off-by: Camila Ayres <hello@camilasan.com>
Signed-off-by: Camila Ayres <hello@camilasan.com>
@camilasan camilasan force-pushed the bugfix/sync-move-data-loss branch from 79d783e to f19c243 Compare June 17, 2026 18:31
Signed-off-by: Camila Ayres <hello@camilasan.com>
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-9998.zip

Digest: sha256:cb5340712a5855a75b02833cf2f94625b73147cb18bd095262c6621cd0b40c80

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant