- 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 で複数の作業ディレクトリを運用し、jj の eol サポートが実装されたら再挑戦する予定だ
採用を阻んだ実際の原因: gradlew.bat と .gitattributes
- Jujutsu(
jj)は、Gitバックエンドを使う際にコミットとファイルをGitに保存し、既存のGitリポジトリと共存して動作する Git互換バージョン管理システム である
jj はステージング領域をなくし、作業コピーを継続的に更新される実際のコミットとして表現する
- コミットの編集や並べ替えの後には後続コミットを自動的にリベースし、競合は作業を止めるのではなく履歴に残せて、作業ログから巻き戻すこともできる
- Gradleエンジニアが
git を jj に置き換えようとした試みは、jj のモデルではなく、Gradleプロジェクトがディスク上に配置される方式の小さな詳細に阻まれた
Windowsでは gradlew.bat にCRLFが必要
- 一般的なGradleリポジトリには、Gradle Wrapperとともに
gradlew.bat が含まれる
gradlew.bat はlabelと goto を使用しており、cmd.exe はLFのみの改行を持つバッチファイルを誤って処理することがある
- Windowsで安定して実行するには、
gradlew.bat は CRLFでチェックアウト されていなければならない
- 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 -text は text 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 自体への最終判断ではない
jj の operation log だけでも、より深く検討する価値がある
eol サポートは解決可能な問題として扱われており、Issueでは gix-filter を通じた実装議論もある
- そのサポートが入れば、Gradleチームは再挑戦する予定だ
- それまでは
gradlew.bat のためにGitを維持し、必要な役割は git worktree が担う
1件のコメント
Lobste.rsのコメント
Gitの
autocrlf設定と、それに続く.gitattributes設定は、最初から誤りだったし、今後もそうだと思うバージョン管理システムが、リポジトリ内のファイルをディスク上のファイルと異なる形で保存すべきではない
ファイル形式を強制したいなら、それはリンター、コミットフック、マージフックの仕事であって、バージョン管理システムの役割ではない
core.{eol, safecrlf, autocrlf}は廃止予定にすべきだと思う改行コード変換が追加されたのはWindowsのためだけで、␍専用はGit以前にすでに消滅しており、Windows以外はすべて␊専用に落ち着いた
今では実際に␍␊を気にする主な対象は、せいぜいバッチファイルくらいだろう
あらゆる言語が␍␊をサポートしようと苦労しているが、今ではそれを要求したり生成したりするものはほとんどない
Notepadがいまだに␍␊をデフォルトにしていたり、それだけをサポートしていたりするなら、Notepadを使う人がその結果を受け入れるべきだ
Windows互換性でより興味深い領域はファイルモードである
GitはUnixのモードを取り入れており、その痕跡が残っているため、Windowsで作業していると意図しない モードエラー が発生することもある
ツールが十分によくなり、チェックアウト時に自動で空白を使ったり、誰かが強くこだわる妙に具体的な空白数にそろえたりしてくれるなら、タブとスペースの論争をやめられるだろう
さらに言えば、バージョン管理システムは元のテキストではなく 抽象構文木(AST) を保存すべきだ、とまで考えることもできる
今は説得するのが難しいだろうが、こうした方式が非常に自然に感じられる別の時間軸や未来も想像できる
.batファイルをWindows以外のどこで実行するというのか分からない少しでも別の動作が望ましい場面がどこにあるのかも疑問だ
必要な改行形式のままファイルをコミットしてはいけない、という論証には納得できない
要点は「誰かが改行コードを壊してコミットし、コードレビューを通過して壊れたスクリプトが入ってしまう可能性がある」ということのように見える
それは全部正しいが、だからこそ CIのリント検査 があるのではないか?
そのファイルでCRLFではない改行をスキャンし、見つかったらCIを失敗させれば、改行を壊した変更はプルリクエストの段階を通過できない
「ただCIを追加すればいい」という答えでは不十分だ
関連プロジェクトが、比較的初心者が作るMinecraftのMODかもしれず、その中の誰かが
gradle.batを壊しても気づかない可能性がある私自身も、初心者が始めるにはCLI Gitよりずっとよいとして jj を代替として勧めた責任がある
gradlew.batからgradlew.ps1に移る、つまりcmd.exe/バッチファイルから PowerShell へ移る選択肢はあり得るだろうか?今なら、Windowsインストールベースの90%が十分に新しいPowerShellを備えていると仮定できるかもしれない
しかし、デフォルトの実行権限を引き上げる必要がある場合はまだあり、企業環境ではロックダウンされている可能性もある
PowerShellをどれくらい使ったことがあるかは分からないが、「bashがあるから
.shファイルを実行すればいい」という感じではなかったWindowsのバージョンごとに機能セットが異なり、
kshとbash4を比較しているような感じだった