feat(git-sync): three-way body merge using the last-synced base (no edit loss)
Upgrades the 2-way body merge to a real diff3 three-way merge (review #5), so a block ONLY the human changed is KEPT when git changed a DIFFERENT block — the 2-way merge would revert it to git's stale version. Engine: the push update loop reads the last-synced pre-image (`git.showFileAtRef(refs/docmost/last-pushed, path)`) and passes it as the optional `baseMarkdown` to `client.importPageMarkdown` (the common ancestor). Server: gitmost-datasource converts base+incoming, and writeBody runs a block- level diff3 (new three-way-merge.ts `diff3Plan`): live-only change -> keep live, git-only change -> take git, both-changed -> git wins (conflict policy), inserts/ deletes from either side preserved. Without a base (createPage) it falls back to the 2-way merge. Crash-safety unchanged (docs built before the connection opens). Tests: three-way-merge.spec.ts (14 — every diff3 case incl. the cross-block preservation and conflict policy), yjs-body-merge 3-way (real Y.Docs: human's block instance preserved while git's block is applied), plus an engine test that the base is forwarded from showFileAtRef. Existing push assertions updated for the new base arg. git-sync 589 pass; server merge/datasource/gate 62 pass; typecheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -552,7 +552,18 @@ export async function applyPushActions(
|
||||
for (const u of actions.updates) {
|
||||
try {
|
||||
const fullMarkdown = await deps.readFile(u.path);
|
||||
const result = await client.importPageMarkdown(u.pageId, fullMarkdown);
|
||||
// The last-synced version of this file (pre-image) is the common ancestor
|
||||
// for a 3-way merge against the live page, so concurrent human edits are
|
||||
// not clobbered (review #5). Null when the file is new at last-pushed.
|
||||
const baseMarkdown = await deps.git.showFileAtRef(
|
||||
LAST_PUSHED_REF,
|
||||
u.path,
|
||||
);
|
||||
const result = await client.importPageMarkdown(
|
||||
u.pageId,
|
||||
fullMarkdown,
|
||||
baseMarkdown,
|
||||
);
|
||||
updated++;
|
||||
// §10 loop-guard data: hash the body we pushed + capture `updatedAt`.
|
||||
pushed.push({
|
||||
|
||||
Reference in New Issue
Block a user