mirror of
https://github.com/laurent22/joplin.git
synced 2026-10-25 02:47:28 +00:00
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
This commit is contained in:
1 parent
618f7e4ff7
commit
aaa6f8e3ae
1 file changed
+38
@@ -0,0 +1,38 @@
|
||||
# Conflict auto-merge
|
||||
|
||||
When the same note is edited on two devices before they sync, if a change is made when one of the devices was not fully synced, Joplin creates a conflict note. Auto-merge avoids this when the edits don't overlap, using a three-way merge. It runs on the client that syncs second; other devices just receive the merged note.
|
||||
|
||||
## Base version
|
||||
|
||||
The merge needs three versions: **base** (the last version this device and the sync target agreed on), **local** and **remote**.
|
||||
|
||||
The base is stored per sync target in `sync_items.base_body` and `base_title`. It is recorded after an upload, after a download, and after a conflict, whichever version is left in the original note. If a downloaded note is encrypted, the base is cleared until it is decrypted, so that a conflict before decryption creates a normal conflict note.
|
||||
|
||||
## Merge
|
||||
|
||||
The merge is line-based. Regions changed by only one side are taken as-is. Regions changed by both sides are auto-merged if both made the same change, otherwise they become a conflict. Edits on the same or adjacent lines therefore conflict. Word-level merging was tried and dropped because it could silently duplicate text. Titles follow the same rule.
|
||||
|
||||
If everything merges, the merged note is saved over the local note, becomes the new base, and its `updated_time` is moved past the remote time so it gets uploaded. If some regions conflict, a conflict note is still created, but both notes already include the non-conflicting changes, so they differ only where there's a real conflict.
|
||||
|
||||
## Fallback to a conflict note
|
||||
|
||||
The merge is skipped, and a normal conflict note is created, when:
|
||||
|
||||
- `sync.autoMergeConflicts` setting is off
|
||||
- there is no recorded base
|
||||
- the item is read-only or either note is locked
|
||||
- either note cannot be decrypted
|
||||
- an edit which touches identical lines, since the diff can't tell which one changed and may duplicate content.
|
||||
- the diff exceeds its bounds (`maxEditLength` 5000, timeout 1000ms)
|
||||
|
||||
## E2EE
|
||||
|
||||
An encrypted remote note is decrypted in memory for the merge without being saved. If a conflict note is created, the original note is saved with the decrypted (and partially merged) remote content.
|
||||
|
||||
## Code
|
||||
|
||||
- `services/conflict/diffNotes.ts`: merge engine (`autoMerge()`) and the duplicate-line guard
|
||||
- `services/conflict/boundedDiff3.ts`: fork of `node-diff3`'s `diff3MergeRegions()` using a bounded diff
|
||||
- `services/conflict/autoMergeNote.ts`: merges title and body of a note
|
||||
- `services/synchronizer/utils/handleConflictAction.ts`: runs the merge during sync and applies the result
|
||||
- `models/BaseItem.ts`: stores the base and keeps it when a sync doesn't provide a new one
|
||||
Reference in new issue
Block a user