fix(git-sync): review #4404 batch — sanitize-title echo, per-space gate, move-echo, merge-agreement, fence-aware conflict scan, e2e asserts

Addresses reviewer comment #4404 (critical + blocking):

- Critical #2: renamePage skips the echo where the incoming title equals
  sanitizeTitle(current title) — a Docmost title with FS-hostile chars (: / " |,
  newlines, double-space, >120) was pulled to a sanitized stem then written back,
  permanently corrupting the real title. (datasource)
- Blocking #3: runOnce enforces per-space settings.gitSync.enabled (the event
  path bypassed opt-in; any edited space would git-init + export). (orchestrator)
- Blocking #6: movePage no-ops the position-less same-parent echo that clobbered
  the user's chosen sibling order. (datasource)
- Blocking #9: hasConflictMarkers is fence-aware — '<<<<<<< HEAD' inside a code
  block (git-tutorial page) no longer trips the all-or-nothing gate that froze
  the whole space's refs. (push.ts)
- Blocking #11: three-way tryMergeRegion short-circuits when live==target (diff3
  agreement) instead of logging a false 'same-block conflict resolved to git' —
  the echo noise that masked real data-loss signals. (three-way-merge)
- Blocking #12/#13: e2e-advanced — drop the delete-cap block (no such feature;
  failed with a scary '(data loss!)'); non-member assert now expects 404 (existence
  not leaked), not 403.

Verified on stand: sanitized-title rename preserves DB title (vault file
sanitized); non-enabled space creates no vault; fenced conflict markers ingest
without jamming; build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
claude-stand
2026-07-02 17:52:07 +03:00
parent f2d12fd2cd
commit c838fdeebe
6 changed files with 117 additions and 30 deletions
+35 -3
View File
@@ -1159,15 +1159,47 @@ export function isPageFile(path: string): boolean {
* `|||||||` and `=======`): the base text is neither side's current content, so
* keeping it would inject obsolete lines AND leak a raw `|||||||` marker.
*/
const CONFLICT_BEGIN_RE = /^<{7}/m;
const CONFLICT_END_RE = /^>{7}/m;
const CONFLICT_BEGIN_LINE_RE = /^<{7}/;
const CONFLICT_BASE_LINE_RE = /^\|{7}/;
const CONFLICT_SEP_LINE_RE = /^={7}/;
const CONFLICT_END_LINE_RE = /^>{7}/;
// A code-fence open/close line (``` or ~~~), matching markdown-to-prosemirror's
// CODE_FENCE_RE. Used to make conflict-marker detection fence-aware.
const CONFLICT_CODE_FENCE_RE = /^(\s*)(`{3,}|~{3,})/;
export function hasConflictMarkers(body: string): boolean {
return CONFLICT_BEGIN_RE.test(body) && CONFLICT_END_RE.test(body);
// Fence-aware scan (review #9). A page documenting git (a ```-fenced block that
// literally contains `<<<<<<< HEAD` ... `>>>>>>>`) must NOT be flagged as a
// conflict: with autoMergeConflicts OFF the all-or-nothing gate would then jam
// the ENTIRE space's refs forever, re-failing the batch every cycle. Only count
// begin/end marker lines that live OUTSIDE a fenced code block.
let inFence = false;
let fenceMarker = "";
let sawBegin = false;
for (const line of body.split("\n")) {
const fence = line.match(CONFLICT_CODE_FENCE_RE);
if (inFence) {
// Close on a fence line of the same marker char, length >= the opener.
if (
fence &&
fence[2][0] === fenceMarker[0] &&
fence[2].length >= fenceMarker.length
) {
inFence = false;
fenceMarker = "";
}
continue; // everything inside a fence is inert
}
if (fence) {
inFence = true;
fenceMarker = fence[2];
continue;
}
if (CONFLICT_BEGIN_LINE_RE.test(line)) sawBegin = true;
else if (sawBegin && CONFLICT_END_LINE_RE.test(line)) return true;
}
return false;
}
function stripConflictMarkers(body: string): string {