- Das Gradle-Team wollte Jujutsu (jj), ein Git-kompatibles VCS, in die tägliche Entwicklung einführen, verschob die Einführung jedoch wegen der Zeilenumbruchbehandlung von
gradlew.bat für Windows
- Das Kernproblem:
jj kann dateispezifische EOL-Regeln in .gitattributes nicht lesen und daher *.bat text eol=crlf nicht anwenden
- Git normalisiert
gradlew.bat im Repository auf LF und checkt es im Arbeitsverzeichnis mit CRLF aus; jj erkennt das jedoch als Änderung und erzeugt fortlaufend eine Phantom Modification
- Man könnte es umgehen, indem man
gradlew.bat unverändert mit CRLF committet und auf *.bat -text umstellt; wenn ein Editor die Datei aber auf LF umschreibt, stellt Git sie nicht automatisch wieder her, wodurch die Ausführungssicherheit unter Windows ins Wanken geraten kann
- Das Gradle-Team betreibt vorerst mehrere Arbeitsverzeichnisse mit
git worktree und will es erneut versuchen, sobald jj-Unterstützung für eol implementiert ist
Der tatsächliche Grund, der die Einführung verhinderte: gradlew.bat und .gitattributes
- Jujutsu (
jj) ist ein Git-kompatibles Versionsverwaltungssystem, das bei Verwendung des Git-Backends Commits und Dateien in Git speichert und mit bestehenden Git-Repositories zusammenarbeitet
jj schafft die Staging Area ab und stellt die Arbeitskopie als echten Commit dar, der fortlaufend aktualisiert wird
- Nach dem Bearbeiten oder Umordnen von Commits rebased es nachfolgende Commits automatisch; Konflikte können in der Historie festgehalten werden, statt die Arbeit anzuhalten, und Änderungen lassen sich über das Operation Log zurücknehmen
- Der Versuch eines Gradle-Engineers,
git durch jj zu ersetzen, scheiterte nicht am Modell von jj, sondern an einem kleinen Detail der Art, wie das Gradle-Projekt auf der Festplatte liegt
Unter Windows braucht gradlew.bat CRLF
- Ein typisches Gradle-Repository enthält zusammen mit dem Gradle Wrapper auch
gradlew.bat
gradlew.bat verwendet Labels und goto, und cmd.exe kann Batch-Dateien mit reinen LF-Zeilenumbrüchen falsch verarbeiten
- Für eine zuverlässige Ausführung unter Windows muss
gradlew.bat mit CRLF ausgecheckt werden
- Gradle-Projekte erzwingen diese Anforderung über
.gitattributes
*.bat text eol=crlf
- In Git ist dieses Verhalten ein Standardablauf
- Dateien werden in normalisierter Form im Index gespeichert
- Das Attribut
eol=crlf materialisiert beim Checkout CRLF in der Arbeitskopie
- Bearbeitungen werden beim erneuten Speichern wieder normalisiert
Fehlende .gitattributes-Unterstützung in jj erzeugt Phantom-Diffs
jj liest .gitattributes nicht und kann daher keine dateispezifischen Zeilenumbruchregeln anwenden
- Die zugehörige Funktion wird im alten Issue jj-vcs/jj#53 verfolgt; zumindest Unterstützung für das Attribut
eol ist in Diskussion
- Das einzige derzeit in
jj verfügbare Mittel ist die globale Einstellung working-copy.eol-conversion
- Wie Git
core.autocrlf wird sie pauschal auf alle Dateien angewendet
.bat-Dateien lassen sich nicht separat festlegen
- In einem colocated Repo tritt das Problem sofort zutage
- Git speichert
gradlew.bat als LF und checkt es mit CRLF aus
- Da
jj dieses Attribut nicht respektiert, zeichnet es die CRLF auf der Festplatte als Änderung gegenüber dem LF-Blob in Git auf
- In betroffenen Gradle-Projekten entsteht dadurch eine dauerhafte Scheinänderung an
gradlew.bat
- Dieses Problem ist kein Sonderfall des Gradle-Teams
- Ein Nutzer gab die Verwendung von
jj auf, nachdem bei einer sechsstelligen Zahl von Dateien Phantom Changes auftraten
Vorgeschlagene Workarounds und ihre Grenzen
- Der Autor von
jj schlug im Issue einen non-colocated Workspace als möglichen Weg vor
- Beispiel:
jj git init --git-repo=<path> außerhalb der Git-Arbeitskopie verwenden
- Der Gradle-Engineer sieht darin keine Lösung
- Wenn
jj gradlew.bat aus dem gespeicherten LF-Blob materialisiert, landet es mit LF auf der Festplatte
- Eine
gradlew.bat mit LF läuft unter Windows nicht
- Mit der globalen Einstellung
working-copy.eol-conversion = input-output ließe sich CRLF erzwingen
- Sie schreibt jedoch beim Checkout alle Textdateien wieder mit CRLF, was eigene Probleme verursacht
Eine funktionierende, aber fragile Lösung
- Tatsächlich funktioniert es,
gradlew.bat im Repository unverändert mit CRLF zu committen und die Normalisierung zu beenden
- Die Regel ändert sich dann wie folgt
- Bisher:
*.bat text eol=crlf
- Neu:
*.bat -text
- Damit stimmen Git und
jj sowohl im gespeicherten Tree als auch in der Arbeitskopie bei CRLF überein
- Es gibt keine Umwandlung mehr
- Auch der Phantom-Diff verschwindet
- Die Ausführung unter Windows bleibt erhalten
- Im Gegenzug gibt
*.bat -text die automatische Wiederherstellung auf, die text eol=crlf bot
- Früher wurde versehentlich eingebrachtes LF beim Commit stillschweigend wieder normalisiert
- Jetzt kann LF unverändert verbleiben und
gradlew.bat unter Windows unbemerkt kaputt machen
- Diese Umstellung muss nur einmal erfolgen, und fehlerhafte Bearbeitungen lassen sich normalerweise im Review erkennen
- Trotzdem kam das Gradle-Team zu dem Schluss, dass es schwer vertretbar ist, das Ökosystem auf die Annahme zu stützen, dass alle Gradle-Projekte CRLF direkt committen und kein Editor sie stillschweigend umschreibt
Vorerst beibehaltene Alternative: git worktree
- Der Workspace von
jj bietet mehrere Arbeitsverzeichnisse, die sich ein Repository teilen, sodass lange CI-Läufe oder laufende Änderungen die nächste Aufgabe nicht blockieren
- Das Gradle-Team deckt denselben Bedarf bereits ausreichend mit
git worktree ab
git worktree add ../gradle-feature-x feature-x
git worktree add ../gradle-hotfix hotfix-7.6
- Dieser Ansatz nutzt zwei Checkouts und einen gemeinsamen Object Store
- Paralleles Arbeiten und ein langfristig gepflegter
main-Checkout sind damit möglich, ohne stash oder einen zweiten Clone zu benötigen
- Das Gradle-Team verwendet dieses Muster seit Jahren
Möglichkeit einer erneuten Prüfung von jj
- Dieser Fall ist keine endgültige Bewertung von
jj selbst für das Gradle-Projekt
- Schon das Operation Log von
jj ist eine vertiefte Prüfung wert
eol-Unterstützung gilt als lösbares Problem, und im Issue gibt es auch Diskussionen über eine Implementierung über gix-filter
- Sobald diese Unterstützung verfügbar ist, will das Gradle-Team einen neuen Versuch starten
- Bis dahin bleibt es wegen
gradlew.bat bei Git, und git worktree übernimmt die benötigte Rolle
1 Kommentare
Kommentare auf Lobste.rs
Ich halte Gits
autocrlf-Einstellung und die anschließende.gitattributes-Konfiguration von Anfang an für einen Fehler, und das wird auch so bleiben.Ein Versionsverwaltungssystem sollte Dateien im Repository nicht anders speichern als die Dateien auf der Festplatte.
Wenn man ein Dateiformat erzwingen will, ist das Aufgabe von Lintern, Commit-Hooks oder Merge-Hooks, nicht der Versionsverwaltung.
core.{eol, safecrlf, autocrlf}zur Abschaffung vorsehen.Zeilenendekonvertierung wurde nur wegen Windows hinzugefügt; reines ␍ war schon vor Git tot, und außer Windows haben sich alle auf reines ␊ eingependelt.
Heute dürften Batch-Dateien so ziemlich der wichtigste verbleibende Fall sein, in dem ␍␊ tatsächlich eine Rolle spielt.
Alle Sprachen bemühen sich, ␍␊ zu unterstützen, aber kaum etwas verlangt oder erzeugt es noch.
Wenn Notepad immer noch ␍␊ als Standard nutzt oder nur das unterstützt, dann müssen die Leute, die Notepad verwenden, mit den Folgen leben.
Der interessantere Bereich bei Windows-Kompatibilität sind Dateimodi.
Git hat Unix-Modi übernommen, und deren Spuren sind geblieben, sodass bei der Arbeit unter Windows manchmal unbeabsichtigte Modusfehler entstehen.
Wenn die Tools gut genug würden, um beim Checkout automatisch Leerzeichen zu verwenden oder auf irgendeine seltsam spezifische Anzahl von Leerzeichen zu normalisieren, auf der jemand stark beharrt, könnten wir die Tabs-vs.-Spaces-Debatte beenden.
Man könnte sogar noch weiter gehen und sagen, dass ein Versionsverwaltungssystem nicht den Quelltext, sondern den abstrakten Syntaxbaum (AST) speichern sollte.
Heute wäre das schwer zu vermitteln, aber man kann sich durchaus andere Zeitlinien oder eine Zukunft vorstellen, in der dieser Ansatz sehr natürlich wirkt.
Ich weiß nicht, wo man eine
.bat-Datei ausführen würde, wenn nicht unter Windows.Ich frage mich auch, in welchem Fall ein anderes Verhalten auch nur ansatzweise wünschenswert wäre.
Das Argument, man dürfe eine Datei nicht einfach mit genau dem benötigten Zeilenendeformat committen, überzeugt mich nicht.
Der Kern scheint zu sein: „Jemand macht die Zeilenenden kaputt, committet das, und ein kaputtes Skript kann durch das Code-Review hineingelangen.“
Alles richtig, aber genau dafür gibt es doch CI-Lint-Prüfungen, oder?
Wenn man in der betreffenden Datei nach Zeilenenden sucht, die nicht CRLF sind, und die CI fehlschlagen lässt, sobald welche gefunden werden, kann eine Änderung, die die Zeilenenden kaputtmacht, die Pull-Request-Phase nicht bestehen.
„Dann fügt einfach CI hinzu“ ist keine ausreichende Antwort.
Bei den betroffenen Projekten kann es sich auch um Minecraft-Mods handeln, die von relativen Anfängerinnen und Anfängern erstellt werden, und irgendjemand davon könnte
gradle.batkaputtmachen, ohne es zu bemerken.Auch ich trage Verantwortung dafür, jj als Alternative beworben zu haben, die für Einsteiger deutlich besser sei als CLI-Git.
Wäre es eine Option, von
gradlew.batzugradlew.ps1zu wechseln, also voncmd.exe/Batch-Dateien zu PowerShell?Heute kann man VIELLEICHT davon ausgehen, dass 90 % der Windows-Installationsbasis eine ausreichend aktuelle PowerShell haben.
Es gibt aber immer noch Fälle, in denen man die standardmäßigen Ausführungsrechte anheben muss, und in Unternehmensumgebungen kann sie gesperrt sein.
Ich weiß nicht, wie viel PowerShell du benutzt hast, aber es fühlte sich nicht an wie „es gibt bash, also führe einfach eine
.sh-Datei aus“.Je nach Windows-Version gab es unterschiedliche Funktionsumfänge; es fühlte sich eher an wie ein Vergleich zwischen
kshundbash4.