publish hält eine ungeborene main für einen detached HEAD - der erste Commit einer neuen Instanz scheitert am dokumentierten Weg
#96
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Befund
Beim Einrichten einer frischen 5.0.0-Instanz aus dem Release-Tarball (Weg A aus
INSTALL.md) verweigerte Schritt 14 ausinstructions/setup-instance.mdden ersten Commit:HEAD war dabei nicht detached, sondern ein ungeborener Branch (
git symbolic-ref HEAD->refs/heads/main,git status -sb->## No commits yet on main).Betroffene Version: 5.0.0. Behoben in 5.0.1-beta.1, Commit
9ef021b.Warum das zählt
instructions/setup-instance.mdSchritt 14 undINSTALL.md(Weg A und B) nennenpublish --messageals den Weg zum ersten Commit einer neuen Instanz, unddoctorsagt an derselben Stellefix: Make the first commit via publish once ready. Genau dieser dokumentierte Befehl schlug fehl - bei jeder neuen Instanz, beim ersten schreibendenwikitool-Aufruf überhaupt. Invariante 5 schnitt den naheliegenden Ausweg ab: ein Agent darf nicht aufgit commitvon Hand ausweichen. Zusammen mit #97 hieß das: eine neue Instanz mit leerem Remote ließ sich mit 5.0.0 über keinen dokumentierten Weg initial veröffentlichen.Ursache - verifiziert
Die ursprüngliche Vermutung traf zu.
current_branch()fragtegit rev-parse --abbrev-ref HEAD; auf einem ungeborenen Branch endet der Aufruf mit Exit 128 ("ambiguous argument 'HEAD': unknown revision") und fiel damit in denselbenNone-Zweig wie ein echter detached HEAD.branch_mismatch_message()formulierte daraus die detached-HEAD-Ablehnung,publish_command()brach damit ab.Gemessen in einem frisch initialisierten Repo:
rev-parse --abbrev-ref HEADsymbolic-ref --short -q HEADgit init -b main)HEADmaingit checkout <sha>)HEADmainmainsymbolic-refunterscheidet die beiden Zustände genau dort, worev-parsesie zusammenwirft, und liefert im dritten Fall dasselbe.Entscheidung und Umsetzung
Frage 1 (trifft die Ableitung zu?) - ja, siehe Tabelle.
Frage 2 (soll die Prüfung im ungeborenen Fall greifen?) - nein, und mit der gewählten Umsetzung stellte sie sich nicht mehr. Das Motiv der Prüfung (
git push <remote> <branch>pusht den benannten Ref, nichtHEAD) existiert im ungeborenen Fall nicht: es gibt genau einen Branch, der Commit entsteht auf ihm,originhat den Ref noch gar nicht. Die Prüfung wurde deshalb nicht ausgesetzt, sondern korrekt beantwortet:current_branch()liefert auf ungeborenemmainjetzt"main", der Vergleichchecked_out != branchgeht von selbst durch. Ein Sonderfall im Aufrufer entfiel.Umgesetzt:
current_branch()liestgit symbolic-ref --short -q HEADstattgit rev-parse --abbrev-ref HEAD; leere Ausgabe oder rc != 0 bleibtNone(= detached HEAD). Der Docstring hält fest, warumsymbolic-refund nichtrev-parse- dass es liest, wasHEADbenennt, statt worauf es zeigt.Blast radius, wie erwartet klein:
current_branch()hat genau einen Aufrufer, die Branch-Prüfung inpublish_command(). Der restliche Publish-Pfad trug den ungeborenen Fall bereits:_numstat()dokumentiert ihn ausdrücklich und liefert{},_changed_files()liestgit status --porcelain, das ohne Commit funktioniert, undreconcile()fällt gegen ein leeres Remote aufno-remote-or-fetch-failedzurück. Es war keine weitere Anpassung nötig.Akzeptanzkriterien
git init -b mainohne jeden Commit legttools/wikitool publish --message "<text>"den Commit aufmainan und pusht ihn nachorigin/main- ohne--no-pushund ohne handgemachtesgit commit. Test:test_publish_makes_the_first_commit_of_a_new_instance.branch_mismatch_message()blieb unverändert; es änderte sich nur, welcher ZustandNoneerzeugt. Test:test_current_branch_still_reports_a_real_detached_head_as_none.--branch-Hinweis, auch auf einem ungeborenen Branch. Test:test_publish_on_an_unborn_branch_still_refuses_a_different_target_branch.test_the_gate_still_holds_on_a_fresh_instance_and_stages_nothing.tools/chemenu/tests/test_git_publish.pyträgt je einen Test für den ungeborenen Branch und den echten detached HEAD. Umgesetzt über ein eigenesfresh_instance-Fixture (ungeborener Branch, leeres bare-Remote, kein Commit) - derrepo-Fixture taugte wie erwartet nicht, weil er einen Init-Commit anlegt und pusht.tools/CONTRACT.mdbenennt die Branch-Prüfung jetzt in derpublish-Kommandozeile und als eigenständigen Exit-1-Grund im Fehlerkontrakt, jeweils mit dem ungeborenen Branch als ausdrücklicher Ausnahme.instructions/setup-instance.mdSchritt 2 und 14 sowieINSTALL.mdblieben unverändert: geprüft und inhaltlich richtig - sie beschrieben nie den Workaround, sondern den Weg, der jetzt tatsächlich funktioniert.--no-pushmusste beim ersten Commit folglich nicht erwähnt werden.Verifikation
pytestintools/: 1204 Tests grün, davon 96 intest_git_publish.py.test_current_branch_names_an_unborn_branch_rather_than_calling_it_detached,test_publish_makes_the_first_commit_of_a_new_instance,test_the_gate_still_holds_on_a_fresh_instance_and_stages_nothing,test_publish_pushes_a_committed_stand_to_a_remote_that_has_no_branch_yet); die Schutztests bestehen erwartungsgemäß in beide Richtungen.instructions/dev/testing-conventions.mdSchritt 6 (env -iohne HOME/Git-Config): identisches Ergebnis, 1204 grün - kein Leck über die Umgebung.tools/wikitool docs verifyundtools/wikitool instructions verify: beide OK.Beteiligte Dateien
tools/chemenu/commands/git_publish.py-current_branch()tools/chemenu/tests/test_git_publish.py-fresh_instance-Fixture,_remote_log(), vier Teststools/CONTRACT.md-publish-Kommandozeile und Fehlerkontrakt-ZeileBeziehung zu #97
Gemeinsam umgesetzt, ein Commit (
9ef021b), ein Version-Bump (--patch, 5.0.0 -> 5.0.1-beta.1): verschiedene Funktionen und Ursachen, aber dieselbe Datei, dieselbe Testdatei, dieselbe Doku-Zeile, und die Kette ließ sich erst gemeinsam als Ganzes testen.--patchwar die richtige Stufe - reine Fehlerbehebung, keine Schnittstellenänderung, in beide Richtungen drop-in.Der
--no-push-Workaround aus diesem Issue führt in einen zweiten, eigenständigen Bug: #97. Zusammen verhindern beide jeden dokumentierten ersten Publish einer neuen Instanz gegen ein leeres Remote.Changelog: Verdacht gegen den Baum geprüft und bestätigt (
rev-parse --abbrev-ref HEADendet auf ungeborenem Branch mit rc 128) -status/unconfirmedentfällt,prio/planned->prio/blocking. Beide offenen Fragen beantwortet und als Abschnitt "Entscheidung" in den Body geschrieben: Fix pergit symbolic-ref --short -q HEAD, wodurch sich Frage 2 (Prüfung im ungeborenen Fall aussetzen?) auflöst statt beantwortet werden zu müssen. Blast radius ergänzt (ein einziger Aufrufer; der restliche Publish-Pfad trägt den Fall bereits). Akzeptanzkriterien um den--branch-Fall auf ungeborenem Branch und den Fixture-Hinweis erweitert. Schnitt festgehalten: gemeinsame Umsetzung mit #97, ein--patch-Bump.Changelog: Umgesetzt und veröffentlicht (
9ef021b, 5.0.0 -> 5.0.1-beta.1). Body auf den Endstand geschrieben: alle sechs Akzeptanzkriterien abgehakt und je mit dem Test benannt, der sie hält; Abschnitt "Verifikation" ergänzt (1204 Tests grün, Gegenprobe gegen den zurückgedrehten Fix, Leere-Maschine-Lauf,docs verify/instructions verify). Ergebnis des letzten Kriteriums festgehalten:tools/CONTRACT.mdwurde in beiden Tabellen nachgezogen,instructions/setup-instance.mdundINSTALL.mdblieben nach Prüfung unverändert ---no-pushmusste dort nicht erwähnt werden. Keinedocs/-Seite betroffen: die Gates sind unverändert,docs/why-gates-are-code.mdträgt keine Aussage, die sich bewegt hätte.