Claude Media
Claude Codeでgitattributesを整える — 改行コード混在の全行差分を止める

Claude Codeでgitattributesを整える — 改行コード混在の全行差分を止める

Windowsとmacで改行コードが混ざり、1行直しただけで全行が差分になる。text=autoとgit add --renormalizeで止める手順と、Claude Codeに書き換えさせた後の確かめ方をまとめます。

Claude Codeに3行だけ直させたのに、git diffがファイル全体の書き換えを示す。原因の多くは、リポジトリ内の改行コードがLFとCRLFで混ざっていることです。止める手段は.gitattributesに* text=autoを1行書き、git add --renormalize .で既存ファイルを一度そろえることです。

この記事では、全行差分が出る仕組みを手元のGitで再現し、text=autoとeolの決め方、正規化コミットの作り方、Claude Codeに触らせるときの運用までを順に説明します。

全行差分は「インデックスと作業ツリーの改行が違う」ときに出る

Gitがファイルを比べるとき、見ているのは行の中身と行末の改行です。LFで保存された行とCRLFの行は、見た目が同じでも別の行として扱われます。

.gitattributesのtext属性が有効なファイルでは、git addのときに改行がLFにそろえられてインデックスへ入ります。作業ツリーに取り出すときは、eol属性やGit設定、プラットフォームに応じてLFがCRLFへ変換されます。この往復が決まっていないリポジトリでは、人やツールによってCRLFのファイルがそのままコミットされます。

手元のGit 2.50.1(Apple Git-155)で、状況を再現しました。グローバル設定は読み込まない空の環境で、CRLFのファイルとLFのファイルを1つずつコミットしてあります。

git config core.autocrlf false
git ls-files --eol

出力は次のとおりです。

i/crlf  w/crlf  attr/                 	crlf.txt
i/lf    w/lf    attr/                 	lf.txt

i/がインデックス、w/が作業ツリーの改行です。attr/が空なので、改行について何も指定されていません。crlf.txtはCRLFのままリポジトリに入っています。

次にLFのファイルを、別のツールがCRLFで書き直した状況を作ります。

sed -i '' 's/$/\r/' lf.txt
git diff --stat
git diff --stat --ignore-cr-at-eol

3行しかないファイルでも、1回目は3 insertions(+), 3 deletions(-)、つまり全行が変更扱いです。2回目の--ignore-cr-at-eol付きは何も出力しません。中身は同じで、行末のCRだけが違うことが分かります。

git ls-files --eolは、この切り分けの最初の一手です。i/lf w/crlfと出れば、リポジトリ側はLFで作業ツリーだけがCRLFです。i/crlfと出るファイルは、リポジトリ自体にCRLFが入っています。

text=autoでチーム全員の改行をそろえる

.gitattributesに次の1行を書くと、Gitが各ファイルをテキストかバイナリか自分で判定します。

* text=auto

text=autoはテキストと判定したファイルだけ、チェックイン時とチェックアウト時に改行を変換します。ただしすでにCRLFでGitに入っているファイルは変換の対象外です。そのため、この1行を足しただけでは既存のCRLFは直りません。

既存ファイルを正規化するコミットを作る

既存ファイルを正規化するには、変更のない作業ツリーから次の手順を踏みます。

手順

改行を正規化するコミットの作り方

  1. 1

    作業ツリーを空にする

    未コミットの変更を先にコミットするか退避します。正規化の差分に他の変更を混ぜないためです。

  2. 2

    .gitattributesを書く

    * text=autoを1行書きます。バイナリが誤判定されるなら*.jpg -textのように個別に外します。git statusに正規化したくないファイルが出た場合も同じで、git addをやり直す前にtext属性を外します。

  3. 3

    renormalizeで入れ直す

    git add --renormalize .を実行します。追跡中の全ファイルに改めてcleanの処理をかけ、インデックスへ入れ直します。

  4. 4

    差分を見てコミットする

    git statusで正規化されるファイルを確かめ、問題がなければ専用のコミットにします。メッセージはIntroduce end-of-line normalizationのように、改行の正規化だけだと分かる一文にしておくと、履歴をたどる人が迷いません。

--renormalizeは、core.autocrlfやtext属性を変えたあとに、誤ったCRLF/LFで追加されたファイルを直すオプションです。-uを含みます。

手元の再現では、この手順を実行した結果は次のとおりでした。

echo '* text=auto' > .gitattributes
git add --renormalize .
git status --short
git ls-files --eol
M  crlf.txt
?? .gitattributes
i/lf    w/crlf  attr/text=auto        	crlf.txt
i/-text w/-text attr/text=auto        	img.png
i/lf    w/lf    attr/text=auto        	lf.txt

crlf.txtだけがステージされ、インデックス側はi/lfに変わりました。NUL文字を含むPNGは-textと判定され、触られていません。コミットの統計は3 3 crlf.txtで、改行の置き換えだけです。

注目点が1つあります。作業ツリー側はw/crlfのまま残っています。この状態でgit statusやgit diffを実行すると、CRLF will be replaced by LF the next time Git touches itという警告が出る一方、git diff --statは空になりました。次にGitがそのファイルを触るときにLFへ変換される、という予告です。作業ツリーも整えたいときは、コミット後にファイルを再チェックアウトします。

eolはファイルの種類ごとに決める

text=autoだけでも、チェックアウト時の改行は環境次第です。eolが未指定のとき、作業ツリーの改行はcore.autocrlfまたはcore.eolで決まります。どちらも未設定なら、WindowsではCRLF、それ以外ではLFが既定です。同じファイルでもWindowsの人とmacの人で作業ツリーの中身が違う、ということです。

特定の拡張子だけ固定したいときは、eolを書きます。

* text=auto
*.sh   text eol=lf
*.bat  text eol=crlf
*.ps1  text eol=crlf
*.jpg  -text

textだけを書いた拡張子は、自動判定を挟まずテキストとして正規化されます。判定に任せたくない拡張子にはこちらを使います。eol=lfは作業ツリーでもインデックスと同じLFを使い、eol=crlfは取り出すときにCRLFへ変換します。eolはtextかtext=autoが有効なときだけ効きますが、eolを書くとtextが未指定なら自動で有効になります。

ファイル推奨される指定理由
シェルスクリプト推奨される指定text eol=lf理由Gitの例が*.shをLF固定にしている
Visual Studioのプロジェクト、バッチ推奨される指定text eol=crlf理由Gitの例が*.vcprojをCRLF固定にしている
画像など推奨される指定-text理由Gitの例が*.jpgを変換対象から外している
その他のソース推奨される指定text=autoに任せる理由作業ツリーはOS既定に従う

手元で*.bat text eol=crlfと*.sh text eol=lfを書いて、ファイルを消してからgit checkoutで取り出すと、結果はa.batがw/crlf、b.shがw/lfでした。インデックス側はどちらもi/lfです。

Claude Codeに触らせるときの運用

過去のバージョンでは、Claude Codeの書き換えで改行コードが変わる不具合がありました。changelogには、WriteツールがCRLFのファイルを上書きするときに改行を黙って変換していた不具合(v2.1.77)や、EditとWriteがWindowsでCRLFを二重にしていた不具合(v2.1.89)の修正が載っています。VS Code拡張でCRLFのファイルの編集を承認するとString not found in fileで失敗する不具合も、v2.1.267で直されています。

これらはすべて修正済みです。いま改行が変わる経路として考えやすいのは、外部のスクリプトやエディタが作業ツリーのファイルをCRLFに書き換えるケースです。.gitattributesを整えておけば、そうした書き換えがコミット時にLFへ戻されます。

CLAUDE.mdに書く規約

次のような断片をCLAUDE.mdに置くと、Claudeが改行の問題を自分で切り分けられます。例として挙げる文面であり、プロジェクトに合わせて直してください。

## 改行コード
- 改行の扱いはリポジトリ直下の.gitattributesが決める
- ファイルを書き換えたら`git ls-files --eol <path>`を実行し、
  `i/`と`w/`が食い違っていないか確かめる
- 全行が差分に見えるときは、まず`git diff --stat --ignore-cr-at-eol`で
  改行だけの差か確かめる
- .gitattributesは勝手に変更せず、変更案を提示する

「.gitattributesは変更しない」を入れる理由は、正規化がリポジトリ全体に及ぶからです。属性を1行変えると、次のgit add --renormalizeで全ファイルのインデックスが変わります。本来は専用のコミットに分けたい作業が、機能追加のコミットに混ざります。

全行差分の調査を任せる

調査そのものはClaudeに任せられます。コマンドの出力を読ませる形にすると、推測で答えさせずに済みます。

git diff --stat の差分が大きすぎる。次を実行して、原因を改行コードの食い違い
かどうか判断して。
1. git ls-files --eol で i/ と w/ が違うファイルを列挙
2. git diff --stat --ignore-cr-at-eol と比較
3. 改行だけの差なら、.gitattributes の案と正規化手順を提案(実行はしない)

差分の大きさを--ignore-cr-at-eolの有無で比べれば、改行だけの差なのか中身の変更なのかを機械的に判断できます。結果を表にまとめさせると、レビューがしやすくなります。

hookで書き換え直後に整える

Claude CodeのhooksにあるFileChangedは、監視中のファイルがディスク上で変わると動きます。ファイルシステムの変更を検知する仕組みで、EditやWriteだけでなく、Bashで動かしたスクリプトや外部プロセスによる書き換えも対象です。data.csvの改行を整えるhookが次の形で紹介されています。

#!/bin/bash
FILE=$(jq -r .file_path)
if grep -q $'\r$' "$FILE"; then
  perl -pi -e 's/\r$//' "$FILE"
fi

行末のCRがあるときだけperlで削る構成です。ガードを緩めると、perl -iが置換なしでもファイルを書き直すので、hookが際限なく再実行されます。この例はデータファイル単位の対処です。リポジトリ全体は.gitattributesで整える方が筋が通ります。hookの仕組みはClaude Codeのhooksでlintを自動修正する手順にもあります。

つまずきやすい点

正規化後に既存ブランチをマージすると衝突が増える

属性を足してリポジトリ上の正規形が変わると、属性のないブランチとのマージで不要な衝突が起きることがあります。Gitにはmerge.renormalizeという設定があり、有効にすると三者マージの前に仮想的なチェックアウトとチェックインをかけて、改行変換による差を衝突から外します。正規化コミットを入れたあとは、長く生きているブランチの持ち主に知らせておきます。

正規化コミットの差分はレビューしにくい

全ファイルが変わるので、機能の変更とは別のPRに分けるのが無難です。専用のブランチで.gitattributesと正規化だけを1コミットにまとめると、あとから履歴を調べるときにも切り分けやすくなります。履歴の調べ方はgit履歴から経緯を調べる手順が参考になります。

変換できないと警告や拒否が出る

core.safecrlfをtrueかwarnにすると、Gitは改行変換が元に戻せるかを確かめます。trueでは元に戻せない変換を拒否し、warnでは警告を出して続行します。警告が出たファイルは、先に改行をそろえてから正規化します。

バイナリが壊れる

text=autoは誤判定することがあります。git statusに画像やアーカイブが出たら、そのパターンに-textを付けてからgit add --renormalizeをやり直します。Gitがテキストと見なさなかったファイルを手動で変換させたいときは、逆にtextを付けます。

判断の目安

直す場所はgit ls-files --eolの出力で決まります。i/crlfならリポジトリ側に正規化コミットが要り、i/lf w/crlfなら作業ツリーを書き換えたツール側の問題です。Claude Codeには、この確認コマンドと規約だけを渡し、.gitattributesの変更は提案にとどめておくと、正規化が機能追加に混ざりません。

改行の扱いを.gitattributesに書いてあれば、環境ごとの違いはインデックスに入る前にそろいます。レビューで見る差分は、中身が変わった行に絞れます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. git-scm.com/docs/gitattributes
  2. git-scm.com/docs/git-add
  3. code.claude.com/docs/en/hooks
  4. code.claude.com/docs/en/changelog
この記事を共有:XはてブLinkedIn