1 Punkte von GN⁺ 9 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • 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

 
GN⁺ 9 시간 전
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.

    • Ich tendiere ebenfalls dazu, es als Fehler von Anfang an zu sehen, aber inzwischen sollte man klar erklären, dass es ein Fehler war, und insbesondere 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.
    • Ich empfinde es eher genau umgekehrt: Versionsverwaltung sollte solche Dinge nicht weniger, sondern mehr tun.
      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.

    • Der Zweck dieses Beitrags scheint nicht nur zu sein, jj bei Gradle zu behandeln, sondern zu zeigen, dass dieses Problem alle Menschen und Projekte betrifft, die im JVM-Ökosystem jj verwenden wollen.
      „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.bat kaputtmachen, 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.bat zu gradlew.ps1 zu wechseln, also von cmd.exe/Batch-Dateien zu PowerShell?

    • Ich war nicht an Gradle beteiligt, habe aber vor ein paar Jahren bei Leiningen versucht, genau das zu tun, und damals war es ein komplettes Chaos und überhaupt nicht praktikabel.
      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 ksh und bash4.