feat(git-sync): drop legacy docmost:meta back-compat (vaults wipe+rebuild)

Per owner: test data, no migration. parsePageFile no longer reads the old
docmost:meta block — a file without a gitmost_id frontmatter is simply un-tracked
(adopt). Vaults are a cache: rm -rf on the transition, rebuilt native from
Docmost. Simplifies the format work (no fallback). Doc updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-24 04:28:35 +03:00
parent d8007480ac
commit 0c916ca086
3 changed files with 19 additions and 46 deletions

View File

@@ -95,15 +95,12 @@ Obsidian резолвит `[[Заметка]]` по **basename** (не по по
- `.obsidian/`, аттачменты, dot-файлы, любые не-`.md`**игнор** (не страницы), - `.obsidian/`, аттачменты, dot-файлы, любые не-`.md`**игнор** (не страницы),
лежат в гите как есть, Obsidian ими владеет. Без `.gitignore`. лежат в гите как есть, Obsidian ими владеет. Без `.gitignore`.
## Миграция со старого формата ## Без обратной совместимости
Существующие волты несут `docmost:meta` в файлах. Старый `docmost:meta` формат НЕ поддерживаем (данные тестовые). Волт — кэш: на
переходе `rm -rf` волты спейсов, они пересобираются из Docmost сразу в native-
- На первом цикле нового движка: если у файла нет frontmatter, но есть формате. `parsePageFile` не читает `docmost:meta`; файл без `gitmost_id` frontmatter
`docmost:meta` → читаем pageId оттуда, переписываем файл в native-формат — это голый/рукописный файл → адопция (не legacy-страница).
(frontmatter id + чистое тело + folder-note layout), разовый «normalize» коммит.
- **Фолбэк навсегда**: `docmost:meta` всё ещё парсится как источник id, если
frontmatter нет (файл со старой системы). (Реализовано в `parsePageFile`.)
## Краевые случаи ## Краевые случаи
@@ -117,11 +114,11 @@ Obsidian резолвит `[[Заметка]]` по **basename** (не по по
## План фаз (каждая — юниты движка + браузерный e2e + изолированные shell-e2e) ## План фаз (каждая — юниты движка + браузерный e2e + изолированные shell-e2e)
1. ✅ Формат файла: `parsePageFile`/`serializePageFile` (frontmatter id + тело, 1. ✅ Формат файла: `parsePageFile`/`serializePageFile` (frontmatter id + тело,
фолбэк на legacy `docmost:meta`). Юниты. Без смены поведения. (готово) `gitmost_id` frontmatter + тело). Юниты. Без смены поведения. (готово)
2. PULL пишет native-формат (frontmatter + folder-note layout) + миграция. 2. PULL пишет native-формат (frontmatter + folder-note layout). Волты wipe+rebuild.
3. PUSH берёт идентичность из frontmatter, родителя из пути. 3. PUSH берёт идентичность из frontmatter, родителя из пути.
4. Адопция голых файлов/папок. 4. Адопция голых файлов/папок.
5. Чистка: убрать `docmost:meta` из генерации (оставить фолбэк-парсер). 5. Чистка: выпилить старый `docmost:meta` формат-код целиком.
6. Ссылки: конвертер Docmost-mention ↔ `[[wikilink]]` + переписывание при retitle. 6. Ссылки: конвертер Docmost-mention ↔ `[[wikilink]]` + переписывание при retitle.
## Риски ## Риски

View File

@@ -1,25 +1,22 @@
import { parseDocmostMarkdown } from "./markdown-document";
/** /**
* The THIN page-file format (design: docs/backlog/git-sync-thin-meta.md, option * The native-Obsidian page-file format (design: docs/backlog/git-sync-thin-meta.md).
* C). A page file is CLEAN markdown with a minimal YAML frontmatter carrying ONLY * A page file is CLEAN markdown with a minimal YAML frontmatter carrying ONLY the
* the page's durable identity: * page's durable identity:
* *
* --- * ---
* id: 019ef6fc-2638-7ce1-9ce3-2756ce038480 * gitmost_id: 019ef6fc-2638-7ce1-9ce3-2756ce038480
* --- * ---
* <clean markdown body> * <clean markdown body>
* *
* Everything else is derived (title = filename, parentPageId = enclosing folder, * Everything else is derived (title = filename, parentPageId = enclosing folder,
* spaceId = the vault, updatedAt = git). The `id` (a Docmost pageId) is the only * spaceId = the vault, updatedAt = git). `gitmost_id` (a Docmost pageId) is the
* non-derivable bit and travels WITH the file so identity survives any move, * only non-derivable bit and travels WITH the file so identity survives any move,
* even one git's rename detection misses. Third-party editors (Obsidian, …) see * even one git's rename detection misses. Third-party editors (Obsidian, …) see
* clean markdown; the frontmatter is hidden in their preview. * clean markdown; the frontmatter is hidden in their preview.
* *
* MIGRATION: a file may still carry the LEGACY `<!-- docmost:meta {…} -->` block * No backward-compat with the old `docmost:meta` format: vaults are a cache, wiped
* (the pre-thin format). `parsePageFile` reads the id from the frontmatter first, * and rebuilt native. A file WITHOUT a `gitmost_id` frontmatter is an un-tracked
* then falls back to the legacy meta — so old vaults keep working and a re-sync * (e.g. hand-written) file -> the caller ADOPTS it (creates a page, writes the id).
* rewrites them into the thin format.
*/ */
/** /**
@@ -56,23 +53,12 @@ export function parsePageFile(full: string): {
} { } {
const text = (full ?? "").replace(/\r\n/g, "\n"); const text = (full ?? "").replace(/\r\n/g, "\n");
// 1. Thin format: YAML frontmatter. // Native format: a `gitmost_id` YAML frontmatter. Anything else (no frontmatter,
// or frontmatter without the key) is an un-tracked file -> adopt.
const fm = text.match(FRONTMATTER_RE); const fm = text.match(FRONTMATTER_RE);
if (fm) { if (fm) {
return { id: readIdFromYaml(fm[1]), body: text.slice(fm[0].length).trim() }; return { id: readIdFromYaml(fm[1]), body: text.slice(fm[0].length).trim() };
} }
// 2. Legacy format: `<!-- docmost:meta -->` block (migration fallback).
if (/^\s*<!--\s*docmost:meta/.test(text)) {
try {
const { meta, body } = parseDocmostMarkdown(text);
return { id: meta?.pageId ?? null, body };
} catch {
// a corrupt legacy block -> treat as an un-tracked plain file (adopt).
}
}
// 3. Plain markdown — un-tracked (no identity yet).
return { id: null, body: text.trim() }; return { id: null, body: text.trim() };
} }

View File

@@ -1,6 +1,5 @@
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import { parsePageFile, serializePageFile } from "../src/lib/page-file"; import { parsePageFile, serializePageFile } from "../src/lib/page-file";
import { serializeDocmostMarkdownBody } from "../src/lib/index";
describe("page-file thin format", () => { describe("page-file thin format", () => {
it("round-trips id frontmatter + clean body", () => { it("round-trips id frontmatter + clean body", () => {
@@ -20,15 +19,6 @@ describe("page-file thin format", () => {
expect(parsePageFile("---\ngitmost_id: 'xyz'\n---\nbody").id).toBe("xyz"); expect(parsePageFile("---\ngitmost_id: 'xyz'\n---\nbody").id).toBe("xyz");
}); });
it("MIGRATION: falls back to a legacy docmost:meta block for the id", () => {
const legacy = serializeDocmostMarkdownBody(
{ version: 1, pageId: "legacy-1", title: "T", spaceId: "sp" },
"old body",
);
const { id, body } = parsePageFile(legacy);
expect(id).toBe("legacy-1");
expect(body).toContain("old body");
});
it("ADOPT: a plain hand-written file has no id and keeps its whole body", () => { it("ADOPT: a plain hand-written file has no id and keeps its whole body", () => {
const { id, body } = parsePageFile("# Just a note\n\nwritten in Obsidian"); const { id, body } = parsePageFile("# Just a note\n\nwritten in Obsidian");