fix(git-sync): a git edit that drops the gitmost_id frontmatter is no longer lost (C10-D1)
If a git-side edit rewrote a page file WITHOUT its `gitmost_id` frontmatter (e.g. a tool that regenerates the whole file), the push planner's M (modify) branch found no pageId in the current meta and SKIPPED the file — then the next Docmost->git push overwrote it with the DB content, silently reverting the edit (data loss, found via web-test). Mirror the D (delete) branch: recover the identity from the PRE-IMAGE meta (the last-pushed version at the same path, which still carried the id) before skipping, and apply the body edit as an update. The pushed-back re-serialize restores the frontmatter next cycle, so the file self-heals. Only when the pre-image ALSO lacks an id (a never-tracked page) is it genuinely skipped. Verified on the stand: editing a synced page's file with the frontmatter removed now applies the edit (was reverted). Unit test: a modified file with no current pageId recovers it from the pre-image -> update. git-sync suite green (705). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -389,12 +389,25 @@ export function computePushActions(input: PushActionsInput): PushActions {
|
||||
} else if (pageId) {
|
||||
actions.updates.push({ pageId, path: change.path });
|
||||
} else {
|
||||
// A modified file with no pageId has no Docmost target to update.
|
||||
actions.skipped.push({
|
||||
path: change.path,
|
||||
status: "M",
|
||||
reason: "modified file has no pageId in meta",
|
||||
});
|
||||
// The current file has no `gitmost_id` — but it was MODIFIED, so a prior
|
||||
// version existed at this path. Recover the identity from the PRE-IMAGE
|
||||
// (the last-pushed version at the same path, which still carried the id),
|
||||
// mirroring the `D` branch. Without this, an edit that also dropped the
|
||||
// frontmatter (e.g. a tool that rewrote the whole file) is silently
|
||||
// skipped and then reverted by the next Docmost->git push — the edit is
|
||||
// lost (bug C10-D1). The pushed-back re-serialize restores the frontmatter
|
||||
// next cycle, so the file self-heals. If the pre-image ALSO lacked an id
|
||||
// (a page never tracked), there is genuinely nothing to update -> skip.
|
||||
const prevPageId = metaAt(change.path, "prev")?.pageId;
|
||||
if (prevPageId && !ghostMove.has(prevPageId)) {
|
||||
actions.updates.push({ pageId: prevPageId, path: change.path });
|
||||
} else {
|
||||
actions.skipped.push({
|
||||
path: change.path,
|
||||
status: "M",
|
||||
reason: "modified file has no pageId in meta (nor in pre-image)",
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('computePushActions — M (modified)', () => {
|
||||
expect(actions.skipped).toEqual([]);
|
||||
});
|
||||
|
||||
it('modified file with NO pageId -> skipped (no target to update)', () => {
|
||||
it('modified file with NO pageId in current OR pre-image -> skipped', () => {
|
||||
const changes: DiffEntry[] = [{ status: 'M', path: 'Untracked.md' }];
|
||||
const actions = computePushActions({ changes, metaAt: metaTable({}) });
|
||||
expect(actions.updates).toEqual([]);
|
||||
@@ -105,10 +105,24 @@ describe('computePushActions — M (modified)', () => {
|
||||
{
|
||||
path: 'Untracked.md',
|
||||
status: 'M',
|
||||
reason: 'modified file has no pageId in meta',
|
||||
reason: 'modified file has no pageId in meta (nor in pre-image)',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('modified file that DROPPED its pageId recovers it from the pre-image -> update (bug C10-D1)', () => {
|
||||
// The current file lost its `gitmost_id` frontmatter (a tool rewrote the whole
|
||||
// file), but the last-pushed version at this path still had it. Recover the id
|
||||
// and apply the body edit instead of silently skipping+reverting it.
|
||||
const changes: DiffEntry[] = [{ status: 'M', path: 'Doc.md' }];
|
||||
const metaAt = metaTable({
|
||||
// current side has no pageId; prev (pre-image) side does.
|
||||
'Doc.md|prev': meta({ pageId: 'p-doc' }),
|
||||
});
|
||||
const actions = computePushActions({ changes, metaAt });
|
||||
expect(actions.updates).toEqual([{ pageId: 'p-doc', path: 'Doc.md' }]);
|
||||
expect(actions.skipped).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computePushActions — D (deleted)', () => {
|
||||
|
||||
Reference in New Issue
Block a user