Skip to content

fix(merge): replace two-way DMP patch with proper three-way merge — prevents silent data loss on conflict (#353, #180)#375

Draft
iamhyc wants to merge 1 commit into
masterfrom
fix/three-way-merge-conflicts
Draft

fix(merge): replace two-way DMP patch with proper three-way merge — prevents silent data loss on conflict (#353, #180)#375
iamhyc wants to merge 1 commit into
masterfrom
fix/three-way-merge-conflicts

Conversation

@iamhyc
Copy link
Copy Markdown
Member

@iamhyc iamhyc commented May 8, 2026

Problem

When both a local user and the remote Overleaf server edit the same file, the
extension's sync/merge logic silently overwrites local changes with the
remote version. No conflict detection, no user notification, no chance to
recover lost work.

This is reported in:

Root cause: all three merge code paths used a flawed two-way dmp.patch_apply pattern:

// OLD (broken): patches from localCache→remoteCache, blindly applied to user content
const patches = dmp.patch_make(doc.localCache, doc.remoteCache);
const [merged] = dmp.patch_apply(patches, userContent);
// ↑ silently corrupts or discards local edits when patches don't apply cleanly

diff-match-patch's patch_apply does fuzzy matching and can produce garbled results when local and remote edits overlap — with zero indication that data was lost.


Solution

Replaced all merge paths with a proper three-way merge (diff3-style) engine, analogous to what Git uses internally.

New: src/utils/threeWayMerge.ts

A line-level three-way merge that:

  1. Computes edit hunks from base→local and base→remote using DMP's line-mode diff
  2. Groups overlapping hunks transitively — if both sides touched the sameregion → conflict
  3. Emits standard Git conflict markers (<<<<<<< Local / ======= / >>>>>>> Remote) that VS Code natively understands and provides a merge-conflict resolution UI for
  4. Auto-merges non-overlapping changes
  5. tryTrivialMerge() fast-path (O(1)) covers ~95% of real-world saves: only one side changed, or both made the same edit

Updated code paths

File Change
src/core/remoteFileSystemProvider.ts writeFile() — three-way merge; on conflict, writes markers to disk, shows warning, skips OT update to prevent corrupted server state
src/scm/localReplicaSCM.ts overwrite() — three-way merge; on conflict, writes markers to local only, warns user, blocks push to remote
src/api/socketioAlt.ts edited handler — three-way merge; warns on conflict; removed unused DMP import

Tests: test/threeWayMerge.test.ts

26 unit tests, all passing:


Algorithm

Phase 1 — Hunk grouping (O(N·D + H²)):
  computeHunks(base→local), computeHunks(base→remote)
  → sort all hunks by base position
  → transitively group overlapping hunks
  → classify each group: clean (one side only) or conflict (both sides)

Phase 2 — Apply (O(L)):
  walk base text, apply clean hunks, emit conflict markers for conflicts

tryTrivialMerge (O(1)):
  base===local → use remote
  base===remote → use local
  local===remote → use either

Behavior change summary

Scenario Before (broken) After (fixed)
Only local changed ✓ works ✓ works (trivial merge)
Only remote changed ✓ works ✓ works (trivial merge)
Both changed, different lines ✓ auto-merged ✓ auto-merged
Both changed, same line local edit silently lost ✅ conflict markers shown, user resolves
Both insert at same position one insert silently lost ✅ conflict markers shown
Local delete, remote modify same line corrupted content ✅ conflict markers shown

Verification

  • npm run compile — ✅ passes (full project + chat-view sub-project)
  • test/threeWayMerge.test.ts — ✅ 26/26 tests pass
  • Manual review of conflict marker format matches Git's standard that VS Code recognizes

Issues Closed

@iamhyc iamhyc requested a review from QianrenLi May 8, 2026 15:41
Replace the flawed two-way diff-match-patch patch_apply logic with a
proper three-way merge (like Git's diff3) across all merge code paths.

Problem:
The original code used dmp.patch_make(A,B) + dmp.patch_apply(patches, C)
which silently corrupted or lost local edits when both local and remote
changed the same region. No conflict detection existed (#353, #180).

Changes:
- src/utils/threeWayMerge.ts: line-level three-way merge engine
  that computes edit hunks from base->local and base->remote, detects
  overlapping hunks as conflicts, emits standard Git conflict markers
  (<<<<<<<, =======, >>>>>>>) that VS Code natively understands.
- src/core/remoteFileSystemProvider.ts: writeFile() uses three-way
  merge. On conflict, writes markers + warns, preserves server state
  in _otBase field for correct post-resolution OT op computation.
  Zero duplication — existing OT update code reused via _otBase.
- src/scm/localReplicaSCM.ts: overwrite() uses three-way merge.
  On conflict, writes markers locally only, blocks remote push.
- src/api/socketioAlt.ts: edited-handler uses three-way merge.
  Removed unused DiffMatchPatch import.
- test/threeWayMerge.test.ts: 29 unit tests covering trivial merges,
  one-side changes, non-overlapping auto-merge, 5 conflict scenarios,
  LaTeX content, and 3 post-conflict OT correctness invariants.

Fixes #353, fixes #180
@iamhyc iamhyc force-pushed the fix/three-way-merge-conflicts branch from 01c8c71 to 776073f Compare May 10, 2026 07:40
@iamhyc iamhyc marked this pull request as draft May 10, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Local changes are sometimes overwritten by the remote Overleaf version Local Changes are being Overridden By Remote Versions of Files

1 participant