1 ポイント 投稿者 GN⁺ 9 시간 전 | 1件のコメント | WhatsAppで共有
  • Gradleチームは、Git互換VCSである Jujutsu(jj) を日常開発に導入しようとしたが、Windows向け gradlew.bat の改行処理のため採用を見送った
  • 核心的な問題は、jj.gitattributes のファイルごとのEOL規則を読めず、*.bat text eol=crlf を適用できない点にある
  • Gitは gradlew.bat をリポジトリ内ではLFに正規化し、作業ディレクトリにはCRLFでチェックアウトするが、jj はこれを変更として認識し、phantom modification を作り続ける
  • gradlew.bat をCRLFのままコミットし、*.bat -text に変更すれば回避できるが、エディタがLFに変えてもGitが自動復旧しなくなるため、Windowsでの実行安定性が揺らぐ可能性がある
  • Gradleチームは当面、git worktree で複数の作業ディレクトリを運用し、jjeol サポートが実装されたら再挑戦する予定だ

採用を阻んだ実際の原因: gradlew.bat.gitattributes

  • Jujutsu(jj)は、Gitバックエンドを使う際にコミットとファイルをGitに保存し、既存のGitリポジトリと共存して動作する Git互換バージョン管理システム である
  • jj はステージング領域をなくし、作業コピーを継続的に更新される実際のコミットとして表現する
  • コミットの編集や並べ替えの後には後続コミットを自動的にリベースし、競合は作業を止めるのではなく履歴に残せて、作業ログから巻き戻すこともできる
  • Gradleエンジニアが gitjj に置き換えようとした試みは、jj のモデルではなく、Gradleプロジェクトがディスク上に配置される方式の小さな詳細に阻まれた

Windowsでは gradlew.bat にCRLFが必要

  • 一般的なGradleリポジトリには、Gradle Wrapperとともに gradlew.bat が含まれる
  • gradlew.bat はlabelと goto を使用しており、cmd.exe はLFのみの改行を持つバッチファイルを誤って処理することがある
  • Windowsで安定して実行するには、gradlew.batCRLFでチェックアウト されていなければならない
  • Gradleプロジェクトはこの要件を .gitattributes で強制している
*.bat text eol=crlf
  • Gitでは、この挙動は標準的な流れである
    • ファイルはインデックスに正規化された形で保存される
    • eol=crlf 属性は、チェックアウト時に作業コピーへCRLFを物理化する
    • 編集内容は再保存時に再び正規化される

jj.gitattributes 未対応が生む phantom diff

  • jj.gitattributes を読まないため、ファイルごとの改行規則を適用できない
  • 関連機能は古いIssueである jj-vcs/jj#53 で追跡されており、少なくとも eol 属性のサポートが議論されている
  • 現在 jj で使える手段は、グローバル設定の working-copy.eol-conversion のみである
    • Gitの core.autocrlf のように、すべてのファイルへ一括適用される
    • .bat ファイルだけを個別指定することはできない
  • colocated repoでは問題がすぐに表面化する
    • Gitは gradlew.bat をLFで保存し、CRLFでチェックアウトする
    • jj はその属性を尊重しないため、ディスク上のCRLFをGitのLF blobに対する変更として記録する
    • 影響を受けるGradleプロジェクトでは、gradlew.bat継続的な偽の変更 が発生する
  • この問題はGradleチーム固有の事例ではない
    • ある ユーザー は、6桁のファイルでphantom changeが発生し、jj の利用を断念した

提案された回避策と限界

  • jj の作者はIssue内で、non-colocated workspaceを1つの手段として提案している
    • 例: Git作業コピーの外側で jj git init --git-repo=<path> を使う
  • Gradleエンジニアは、この方法でも問題は解決しないと見ている
    • jj が保存されたLF blobから gradlew.bat を物理化すると、ディスク上にはLFで出力される
    • LFの gradlew.bat はWindowsで実行できない
  • グローバル設定 working-copy.eol-conversion = input-output によりCRLFを強制することはできる
    • ただし、すべてのテキストファイルをチェックアウト時にCRLFへ書き換えてしまうため、別の問題になる

動作はするが脆い解決策

  • 実際に動作する方法は、gradlew.bat をリポジトリに CRLFのままコミット し、正規化をやめることだ
  • ルールは次のように変わる
    • 変更前: *.bat text eol=crlf
    • 変更後: *.bat -text
  • こうすると、Gitと jj は保存ツリーと作業コピーの両方でCRLFに一致する
    • 変換がなくなる
    • phantom diffも消える
    • Windowsでの実行性も維持される
  • その代わり、*.bat -texttext eol=crlf が提供していた 自動復旧 を放棄する
    • 以前は、誤ってLFが入ってもコミット時に静かに再正規化されていた
    • 今後はLFがそのまま残り、gradlew.bat がWindowsで静かに壊れる可能性がある
  • この変換は一度だけ行えばよく、誤った編集も通常はレビューで見つけられる
  • それでもGradleチームは、すべてのGradleプロジェクトがCRLFを直接コミットし、どのエディタもそれを密かに変更しないという前提にエコシステムを賭けるのは難しいと判断した

当面維持する代替手段: git worktree

  • jj のworkspaceは、1つのリポジトリを共有する複数の作業ディレクトリを提供し、長いCI実行や進行中の変更が次の作業を妨げないようにしてくれる
  • Gradleチームは、すでに git worktree で同じ要件を十分に満たしている
git worktree add ../gradle-feature-x feature-x
git worktree add ../gradle-hotfix    hotfix-7.6
  • この方式では、2つのチェックアウトと1つのオブジェクトストアを使う
  • stashや2つ目のcloneがなくても、並行作業と長期間維持する main チェックアウトを両立できる
  • Gradleチームはこのパターンを何年も使ってきた

jj を再検討する可能性

  • 今回の事例は、Gradleプロジェクトにおける jj 自体への最終判断ではない
  • jjoperation log だけでも、より深く検討する価値がある
  • eol サポートは解決可能な問題として扱われており、Issueでは gix-filter を通じた実装議論もある
  • そのサポートが入れば、Gradleチームは再挑戦する予定だ
  • それまでは gradlew.bat のためにGitを維持し、必要な役割は git worktree が担う

1件のコメント

 
GN⁺ 9 시간 전
Lobste.rsのコメント
  • Gitの autocrlf 設定と、それに続く .gitattributes 設定は、最初から誤りだったし、今後もそうだと思う
    バージョン管理システムが、リポジトリ内のファイルをディスク上のファイルと異なる形で保存すべきではない
    ファイル形式を強制したいなら、それはリンター、コミットフック、マージフックの仕事であって、バージョン管理システムの役割ではない

    • 最初から誤りだったという見方に近いが、今は明確に誤りだと宣言し、特に core.{eol, safecrlf, autocrlf} は廃止予定にすべきだと思う
      改行コード変換が追加されたのはWindowsのためだけで、␍専用はGit以前にすでに消滅しており、Windows以外はすべて␊専用に落ち着いた
      今では実際に␍␊を気にする主な対象は、せいぜいバッチファイルくらいだろう
      あらゆる言語が␍␊をサポートしようと苦労しているが、今ではそれを要求したり生成したりするものはほとんどない
      Notepadがいまだに␍␊をデフォルトにしていたり、それだけをサポートしていたりするなら、Notepadを使う人がその結果を受け入れるべきだ
      Windows互換性でより興味深い領域はファイルモードである
      GitはUnixのモードを取り入れており、その痕跡が残っているため、Windowsで作業していると意図しない モードエラー が発生することもある
    • むしろ逆に、バージョン管理はこうしたことを減らすのではなく、もっと行うべきだと感じる
      ツールが十分によくなり、チェックアウト時に自動で空白を使ったり、誰かが強くこだわる妙に具体的な空白数にそろえたりしてくれるなら、タブとスペースの論争をやめられるだろう
      さらに言えば、バージョン管理システムは元のテキストではなく 抽象構文木(AST) を保存すべきだ、とまで考えることもできる
      今は説得するのが難しいだろうが、こうした方式が非常に自然に感じられる別の時間軸や未来も想像できる
  • .bat ファイルをWindows以外のどこで実行するというのか分からない
    少しでも別の動作が望ましい場面がどこにあるのかも疑問だ

  • 必要な改行形式のままファイルをコミットしてはいけない、という論証には納得できない
    要点は「誰かが改行コードを壊してコミットし、コードレビューを通過して壊れたスクリプトが入ってしまう可能性がある」ということのように見える
    それは全部正しいが、だからこそ CIのリント検査 があるのではないか?
    そのファイルでCRLFではない改行をスキャンし、見つかったらCIを失敗させれば、改行を壊した変更はプルリクエストの段階を通過できない

    • この記事の目的はGradleにおけるjjだけを扱うことではなく、JVMエコシステムでjjを使おうとするすべての人とプロジェクトに、この問題が関係することを示す点にあるように思う
      「ただCIを追加すればいい」という答えでは不十分だ
      関連プロジェクトが、比較的初心者が作るMinecraftのMODかもしれず、その中の誰かが gradle.bat を壊しても気づかない可能性がある
      私自身も、初心者が始めるにはCLI Gitよりずっとよいとして jj を代替として勧めた責任がある
  • gradlew.bat から gradlew.ps1 に移る、つまり cmd.exe/バッチファイルから PowerShell へ移る選択肢はあり得るだろうか?

    • Gradleには関わっていなかったが、数年前にLeiningenでそれを試そうとしたことがあり、当時は完全にひどい状態で、まったく実用的ではなかった
      今なら、Windowsインストールベースの90%が十分に新しいPowerShellを備えていると仮定できるかもしれない
      しかし、デフォルトの実行権限を引き上げる必要がある場合はまだあり、企業環境ではロックダウンされている可能性もある
      PowerShellをどれくらい使ったことがあるかは分からないが、「bashがあるから .sh ファイルを実行すればいい」という感じではなかった
      Windowsのバージョンごとに機能セットが異なり、kshbash4 を比較しているような感じだった