Export schreibt bei jedem Commit alle Dateien des Ziels neu: commit= im Marker je Seite statt je Ziel #221

Closed
opened 2026-10-13 11:59:40 +00:00 by torben · 3 comments
Owner

Befund aus der Prüfung von Hand auf dem Windows-Arbeitsrechner (Betreiber, 2026-10-13, Release 10.0.0), Exportziel mit after_publish: true und [["collection:*"]] (#217). Entschieden am 2026-10-13: Variante (a), PATCH. Erledigt (2026-10-15): gebaut und publiziert in 10.1.0-beta.12, Commit 52078bb; CI grün auf 52078bb (Läufe 735 und 736).

Befund

Ein wiki-ingest hat eine neue Quelle angelegt und zwei Seiten geändert. Der Commit berührt unter kb/ genau entities/…/Inway Systems GmbH.md, entities/…/Inway D365 AX Connector for DocuWare.md, die neue Quellseite und die generierten Dateien. Der Export nach publish meldet aber:

shared -> <OneDrive>\Chemenu-Export (commit 113432f14312)
  new (1): Source - Sudhoff Erfolgsgeschichte DocuWare Connector (…)
  changed (12): D365 F&O DocuWare Connector_Löwenstein (…), Inway D365 AX Connector for DocuWare (…), Inway Systems GmbH (…), Inway VM Tools (…), OSLicense (…), Rechnungsimport aus DocuWare in Dynamics (…), Source - … (…), …
  unchanged: 0

Jede ausgewählte Seite galt als geändert, auch die zehn, die der Commit nicht berührt hatte.

Ursache (bis 10.1.0-beta.11)

  • page_export.py setzte für alle Dateien eines Ziels denselben Wert commit= in den Marker wikitool_export:. Der Wert war guideline_export.last_commit(selected), also der neueste Commit, der irgendeine ausgewählte Seite berührt hat.
  • Der Marker steht im Frontmatter jeder Datei, also änderte sich mit jedem solchen Commit der Inhalt jeder Datei, ihr sha256 wich vom Manifest ab, und sie wurde neu geschrieben.
  • Das war in #214 bewusst so entschieden („Dann ändern sich alle Dateien des Ziels. Das ist hinzunehmen, denn es ist derselbe Mechanismus wie bei GUIDELINES.md.“). Bei GUIDELINES.md ist das eine einzige Datei, bei einem Seitenexport sind es alle Seiten.

Warum das Zinsen trug

  • Mit after_publish: true und einer Auswahl der ganzen KB lud der Sync-Client nach jedem Ingest die ganze KB neu hoch. Bei ein paar tausend Seiten kostet das Bandbreite und Zeit, und OneDrive erzeugt eine neue Dateiversion je Seite.
  • Microsoft 365 indizierte dann jede Seite neu, nicht nur die geänderten.
  • Die Ausgabe „changed (N)“ sagte nichts mehr darüber, was sich inhaltlich geändert hat.

Invariante

Eine exportierte Datei ändert sich genau dann, wenn sich ihre gerenderte Fassung ohne den Marker geändert hat oder wenn ein Commit die Seite selbst berührt hat. Ein Commit, der nur andere Seiten berührt, lässt sie byte-gleich. Ausnahme: Ihre Darstellung hängt von diesen Seiten ab, etwa weil ein Link von Klartext zu einem relativen Link wird, wenn das Ziel neu in die Auswahl kommt. Das erkennt schon der Digest-Vergleich.

Entscheidung (Betreiber, 2026-10-13)

commit= je Seite. Der Marker einer Datei trägt den letzten Commit, der diese Seite berührt hat. Das Manifest .wikitool-export.json behält den Ziel-Commit wie bisher, also den neuesten Commit über alle ausgewählten Seiten. Ermittelt wird beides in einem Git-Aufruf über alle ausgewählten Pfade, nicht in einem Aufruf je Seite, weil unter Windows jeder Prozessstart spürbar Zeit kostet. Die Datei sagt damit ehrlich, welcher Stand der Seite in ihr steckt.

Verworfen: commit= ganz aus dem Marker der Datei nehmen und nur im Manifest führen. Das wäre einfacher, aber eine einzelne Datei sagte dann nicht mehr, aus welchem Stand sie stammt. Die Rückweg-Sperre braucht nur kind= und instance=, ihr schadet keine der beiden Varianten.

export guidelines bleibt unverändert (eine Datei, ein Commit; last_commit wie bisher).

Umsetzung (wie gebaut)

  • guideline_export.page_commits(pages, root) liefert (neuester Commit, {page.path: Commit der Seite}) aus einem einzigen git --literal-pathspecs log --stdin -z --format='commit %H' --name-only. Erster Treffer je Pfad ist der Commit der Seite, der erste Commit der Ausgabe ist derselbe wie last_commit (gleiche Auswahl der Commits).
  • Abweichung vom Entwurf, ohne Designfrage: Die Pfade gehen über stdin statt als Argumente. Der Grund ist die Grenze der Befehlszeile unter Windows (32767 Zeichen): Bei einer Auswahl der ganzen KB wäre sie ab etwa 500 Seiten überschritten. last_commit für export guidelines übergibt die Pfade weiter als Argumente, dort sind es wenige Seiten. --literal-pathspecs, damit ein [ im Titel kein Glob ist; -z, damit Umlaute nicht oktal gequotet ankommen.
  • Eine Seite, die kein gelisteter Commit nennt (nur die Auflösung eines Merges hat sie berührt), bekommt den neuesten Commit.
  • Beim Bau geprüft (Umbenennung): git log --name-only ohne --follow liefert für den neuen Pfad den Commit der Umbenennung als ersten Treffer, unabhängig von diff.renames. Die Ausweichregel war nicht nötig.
  • page_export.render nimmt die Commits je Seite; run ruft page_commits statt last_commit.

Akzeptanzkriterien

  • Nach einem Commit, der genau eine von drei ausgewählten Seiten ändert, meldet export run diese Seite als geändert und die beiden anderen als unverändert, und die beiden Dateien im Ziel sind byte-gleich mit mtime unverändert (test_a_commit_to_one_page_rewrites_that_file_alone).
  • Der Marker jeder Datei trägt den letzten Commit ihrer eigenen Seite; das Manifest trägt den neuesten Commit über alle ausgewählten Seiten (test_each_file_carries_its_own_pages_commit_and_the_manifest_the_newest, mit einem Pfad mit Umlaut und [).
  • Kommt eine Seite neu in die Auswahl, ändert sich eine andere Datei nur, wenn ihr gerenderter Text sich ändert (z. B. ein Link wird relativ). Sonst bleibt sie byte-gleich (test_a_page_entering_the_selection_rewrites_only_the_files_whose_text_changes).
  • Eine per rename umbenannte Seite trägt im Marker den Commit der Umbenennung (test_a_renamed_page_carries_the_commit_of_the_rename).
  • Die Zahl der Git-Aufrufe für die Commits ist unabhängig von der Zahl der Seiten (test_the_git_calls_do_not_grow_with_the_pages, gezählter _git, 2 gegen 32 Seiten, genau ein log).
  • Der erste Lauf nach dem Update schreibt jede Datei einmal neu (neuer Marker). Das steht in der Changelog-Zeile von 10.1.0-beta.12 und im How-to.
  • docs/how-to/export-to-a-synced-folder.md § What a run does to the folder beschreibt das neue Verhalten. #214 hat einen Verweis bekommen, als Kommentar: Sein Body bleibt das Protokoll der damaligen Entscheidung 7.
  • pytest (3085 bestanden, 4 übersprungen), docs verify, instructions verify grün (lokal); CI grün auf 52078bb (Lauf 735, Lauf 736).

Beim Abschluss geprüft, ohne Nachzug: docs/explanation/architecture.md (Seitenexport) und tools/README.md sagen nichts über den Commit im Marker und bleiben richtig. Den Eintrag in CHANGES.md habe ich gegen den gebauten Stand gelesen.

Geänderte Dateien

tools/chemenu/guideline_export.py (page_commits, _git mit stdin), tools/chemenu/page_export.py (Marker je Seite), tools/chemenu/tests/test_export_run.py, tools/chemenu/commands/export_cmd.py (Notiz von export run) mit neu erzeugtem tools/CONTRACT.md und docs/reference/commands/exports.md, docs/reference/configuration.md, docs/how-to/export-to-a-synced-folder.md, CHANGES.md/VERSION über version bump.

Versionsteil

PATCH (entschieden, gebaut als 10.1.0-beta.12): gleiche Schnittstelle, gleiche Sektion, nur der Wert commit= im Marker wird genauer. Drop-in in beide Richtungen; eine ältere Version, die über ein so beschriebenes Ziel läuft, schreibt einmal alle Dateien mit ihrem Ziel-Commit neu. Der einmalige Neu-Upload aller Dateien nach dem Update ist hinzunehmen und wird in der Changelog-Zeile genannt.

Bezug

#214 (Exporte), #217 (Auswahl der ganzen KB)

Befund aus der Prüfung von Hand auf dem Windows-Arbeitsrechner (Betreiber, 2026-10-13, Release 10.0.0), Exportziel mit `after_publish: true` und `[["collection:*"]]` (#217). **Entschieden am 2026-10-13: Variante (a), PATCH.** **Erledigt (2026-10-15):** gebaut und publiziert in 10.1.0-beta.12, Commit `52078bb`; CI grün auf `52078bb` (Läufe 735 und 736). ## Befund Ein `wiki-ingest` hat eine neue Quelle angelegt und zwei Seiten geändert. Der Commit berührt unter `kb/` genau `entities/…/Inway Systems GmbH.md`, `entities/…/Inway D365 AX Connector for DocuWare.md`, die neue Quellseite und die generierten Dateien. Der Export nach `publish` meldet aber: ``` shared -> <OneDrive>\Chemenu-Export (commit 113432f14312) new (1): Source - Sudhoff Erfolgsgeschichte DocuWare Connector (…) changed (12): D365 F&O DocuWare Connector_Löwenstein (…), Inway D365 AX Connector for DocuWare (…), Inway Systems GmbH (…), Inway VM Tools (…), OSLicense (…), Rechnungsimport aus DocuWare in Dynamics (…), Source - … (…), … unchanged: 0 ``` Jede ausgewählte Seite galt als geändert, auch die zehn, die der Commit nicht berührt hatte. ## Ursache (bis 10.1.0-beta.11) - `page_export.py` setzte für **alle** Dateien eines Ziels denselben Wert `commit=` in den Marker `wikitool_export:`. Der Wert war `guideline_export.last_commit(selected)`, also der neueste Commit, der **irgendeine** ausgewählte Seite berührt hat. - Der Marker steht im Frontmatter jeder Datei, also änderte sich mit jedem solchen Commit der Inhalt jeder Datei, ihr sha256 wich vom Manifest ab, und sie wurde neu geschrieben. - Das war in #214 bewusst so entschieden („Dann ändern sich alle Dateien des Ziels. Das ist hinzunehmen, denn es ist derselbe Mechanismus wie bei `GUIDELINES.md`.“). Bei `GUIDELINES.md` ist das eine einzige Datei, bei einem Seitenexport sind es alle Seiten. ## Warum das Zinsen trug - Mit `after_publish: true` und einer Auswahl der ganzen KB lud der Sync-Client nach **jedem** Ingest die ganze KB neu hoch. Bei ein paar tausend Seiten kostet das Bandbreite und Zeit, und OneDrive erzeugt eine neue Dateiversion je Seite. - Microsoft 365 indizierte dann jede Seite neu, nicht nur die geänderten. - Die Ausgabe „changed (N)“ sagte nichts mehr darüber, was sich inhaltlich geändert hat. ## Invariante **Eine exportierte Datei ändert sich genau dann, wenn sich ihre gerenderte Fassung ohne den Marker geändert hat oder wenn ein Commit die Seite selbst berührt hat.** Ein Commit, der nur andere Seiten berührt, lässt sie byte-gleich. Ausnahme: Ihre Darstellung hängt von diesen Seiten ab, etwa weil ein Link von Klartext zu einem relativen Link wird, wenn das Ziel neu in die Auswahl kommt. Das erkennt schon der Digest-Vergleich. ## Entscheidung (Betreiber, 2026-10-13) **`commit=` je Seite.** Der Marker einer Datei trägt den letzten Commit, der **diese** Seite berührt hat. Das Manifest `.wikitool-export.json` behält den Ziel-Commit wie bisher, also den neuesten Commit über alle ausgewählten Seiten. Ermittelt wird beides in **einem** Git-Aufruf über alle ausgewählten Pfade, nicht in einem Aufruf je Seite, weil unter Windows jeder Prozessstart spürbar Zeit kostet. Die Datei sagt damit ehrlich, welcher Stand der Seite in ihr steckt. Verworfen: `commit=` ganz aus dem Marker der Datei nehmen und nur im Manifest führen. Das wäre einfacher, aber eine einzelne Datei sagte dann nicht mehr, aus welchem Stand sie stammt. Die Rückweg-Sperre braucht nur `kind=` und `instance=`, ihr schadet keine der beiden Varianten. `export guidelines` bleibt unverändert (eine Datei, ein Commit; `last_commit` wie bisher). ## Umsetzung (wie gebaut) - `guideline_export.page_commits(pages, root)` liefert `(neuester Commit, {page.path: Commit der Seite})` aus einem einzigen `git --literal-pathspecs log --stdin -z --format='commit %H' --name-only`. Erster Treffer je Pfad ist der Commit der Seite, der erste Commit der Ausgabe ist derselbe wie `last_commit` (gleiche Auswahl der Commits). - **Abweichung vom Entwurf, ohne Designfrage:** Die Pfade gehen über **stdin** statt als Argumente. Der Grund ist die Grenze der Befehlszeile unter Windows (32767 Zeichen): Bei einer Auswahl der ganzen KB wäre sie ab etwa 500 Seiten überschritten. `last_commit` für `export guidelines` übergibt die Pfade weiter als Argumente, dort sind es wenige Seiten. `--literal-pathspecs`, damit ein `[` im Titel kein Glob ist; `-z`, damit Umlaute nicht oktal gequotet ankommen. - Eine Seite, die kein gelisteter Commit nennt (nur die Auflösung eines Merges hat sie berührt), bekommt den neuesten Commit. - **Beim Bau geprüft (Umbenennung):** `git log --name-only` ohne `--follow` liefert für den neuen Pfad den Commit der Umbenennung als ersten Treffer, unabhängig von `diff.renames`. Die Ausweichregel war nicht nötig. - `page_export.render` nimmt die Commits je Seite; `run` ruft `page_commits` statt `last_commit`. ## Akzeptanzkriterien - [x] Nach einem Commit, der genau eine von drei ausgewählten Seiten ändert, meldet `export run` diese Seite als geändert und die beiden anderen als unverändert, und die beiden Dateien im Ziel sind byte-gleich mit mtime unverändert (`test_a_commit_to_one_page_rewrites_that_file_alone`). - [x] Der Marker jeder Datei trägt den letzten Commit ihrer eigenen Seite; das Manifest trägt den neuesten Commit über alle ausgewählten Seiten (`test_each_file_carries_its_own_pages_commit_and_the_manifest_the_newest`, mit einem Pfad mit Umlaut und `[`). - [x] Kommt eine Seite neu in die Auswahl, ändert sich eine andere Datei nur, wenn ihr gerenderter Text sich ändert (z. B. ein Link wird relativ). Sonst bleibt sie byte-gleich (`test_a_page_entering_the_selection_rewrites_only_the_files_whose_text_changes`). - [x] Eine per `rename` umbenannte Seite trägt im Marker den Commit der Umbenennung (`test_a_renamed_page_carries_the_commit_of_the_rename`). - [x] Die Zahl der Git-Aufrufe für die Commits ist unabhängig von der Zahl der Seiten (`test_the_git_calls_do_not_grow_with_the_pages`, gezählter `_git`, 2 gegen 32 Seiten, genau ein `log`). - [x] Der erste Lauf nach dem Update schreibt jede Datei einmal neu (neuer Marker). Das steht in der Changelog-Zeile von 10.1.0-beta.12 und im How-to. - [x] `docs/how-to/export-to-a-synced-folder.md` § What a run does to the folder beschreibt das neue Verhalten. #214 hat einen Verweis bekommen, als [Kommentar](https://gitea.nehmer.net/torben/chemenu/issues/214#issuecomment-4127): Sein Body bleibt das Protokoll der damaligen Entscheidung 7. - [x] `pytest` (3085 bestanden, 4 übersprungen), `docs verify`, `instructions verify` grün (lokal); CI grün auf `52078bb` ([Lauf 735](https://gitea.nehmer.net/torben/chemenu/actions/runs/735), [Lauf 736](https://gitea.nehmer.net/torben/chemenu/actions/runs/736)). Beim Abschluss geprüft, ohne Nachzug: `docs/explanation/architecture.md` (Seitenexport) und `tools/README.md` sagen nichts über den Commit im Marker und bleiben richtig. Den Eintrag in `CHANGES.md` habe ich gegen den gebauten Stand gelesen. ## Geänderte Dateien `tools/chemenu/guideline_export.py` (`page_commits`, `_git` mit stdin), `tools/chemenu/page_export.py` (Marker je Seite), `tools/chemenu/tests/test_export_run.py`, `tools/chemenu/commands/export_cmd.py` (Notiz von `export run`) mit neu erzeugtem `tools/CONTRACT.md` und `docs/reference/commands/exports.md`, `docs/reference/configuration.md`, `docs/how-to/export-to-a-synced-folder.md`, `CHANGES.md`/`VERSION` über `version bump`. ## Versionsteil **PATCH** (entschieden, gebaut als 10.1.0-beta.12): gleiche Schnittstelle, gleiche Sektion, nur der Wert `commit=` im Marker wird genauer. Drop-in in beide Richtungen; eine ältere Version, die über ein so beschriebenes Ziel läuft, schreibt einmal alle Dateien mit ihrem Ziel-Commit neu. Der einmalige Neu-Upload aller Dateien nach dem Update ist hinzunehmen und wird in der Changelog-Zeile genannt. ## Bezug #214 (Exporte), #217 (Auswahl der ganzen KB)
torben added the prio/plannedsize/Sarea/kbkind/decision labels 2026-10-13 11:59:40 +00:00
torben added kind/build and removed kind/decision labels 2026-10-13 12:01:46 +00:00
Author
Owner

Changelog: Der Betreiber hat Variante (a) und PATCH entschieden (2026-10-13, „221 ok“). Aus dem Vorschlag sind Entscheidung und Versionsteil geworden, (b) steht als verworfen da. Neu sind ein Prüfpunkt für den Bau (Commit einer umbenannten Seite) und das passende Kriterium. kind/decision → kind/build.

**Changelog:** Der Betreiber hat Variante (a) und PATCH entschieden (2026-10-13, „221 ok“). Aus dem Vorschlag sind Entscheidung und Versionsteil geworden, (b) steht als verworfen da. Neu sind ein Prüfpunkt für den Bau (Commit einer umbenannten Seite) und das passende Kriterium. `kind/decision` → `kind/build`.
Author
Owner

Changelog: Gebaut und publiziert in 10.1.0-beta.12 (52078bb), CI grün (Läufe 735, 736). Neu: Abschnitt „Umsetzung (wie gebaut)“ mit einer Abweichung ohne Designfrage: Die Pfade gehen über stdin an git log (Grenze der Befehlszeile unter Windows), mit --literal-pathspecs und -z. Die Prüfung zur Umbenennung ist erledigt, die Ausweichregel war nicht nötig. Kriterien 1–6 und 8 abgehakt, Kriterium 7 bis auf den Verweis in #214 (für stack-close). Ursache und Folgen stehen jetzt in der Vergangenheit.

**Changelog:** Gebaut und publiziert in 10.1.0-beta.12 (`52078bb`), CI grün (Läufe 735, 736). Neu: Abschnitt „Umsetzung (wie gebaut)“ mit einer Abweichung ohne Designfrage: Die Pfade gehen über stdin an `git log` (Grenze der Befehlszeile unter Windows), mit `--literal-pathspecs` und `-z`. Die Prüfung zur Umbenennung ist erledigt, die Ausweichregel war nicht nötig. Kriterien 1–6 und 8 abgehakt, Kriterium 7 bis auf den Verweis in #214 (für `stack-close`). Ursache und Folgen stehen jetzt in der Vergangenheit.
Author
Owner

Changelog: Abgeschlossen. Kriterium 7 erledigt: Verweis in #214 als Kommentar 4127, der Body von #214 bleibt das Protokoll der damaligen Entscheidung. Geprüft, ohne Nachzug: Erklärseite architecture.md, tools/README.md und der Eintrag in CHANGES.md. Alle Kriterien abgehakt, Kopf auf „Erledigt“.

**Changelog:** Abgeschlossen. Kriterium 7 erledigt: Verweis in #214 als Kommentar 4127, der Body von #214 bleibt das Protokoll der damaligen Entscheidung. Geprüft, ohne Nachzug: Erklärseite `architecture.md`, `tools/README.md` und der Eintrag in `CHANGES.md`. Alle Kriterien abgehakt, Kopf auf „Erledigt“.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#221