グローバル設定が core.autocrlf=input のMacで、CRLF改行のPHPファイルを1か所直してコミットしようとしたら、git show --stat が5176行の変更を示しました。実際に変えたのは128行です。原因はスクリプトでファイルを書き戻したときに改行がLFに変わったことで、CRを戻してから git -c core.autocrlf=false add でステージすれば、元の改行のまま最小差分でコミットできます。
全行差分かどうかは改行だけ無視したdiffで見分ける
--stat の行数が異常に多いときは、まず改行だけを無視した差分行数と比べます。
git diff HEAD -- path/to/File.php | grep -cE '^[-+][^-+]'
git diff --ignore-space-at-eol HEAD -- path/to/File.php | grep -cE '^[-+][^-+]'
1つ目が4809、2つ目が128のように桁が違えば、中身の変更は128行ぶんで、残りは改行の違いです。元のファイルがCRLFだったかは、コミット済みの内容をそのまま出して確認します。
git show HEAD:path/to/File.php | file -
git show HEAD:path/to/File.php | grep -c $'\r$'
grep -c $'\r$' path/to/File.php
前者に with CRLF line terminators と出て、後者のCR行数が0なら、作業ツリー側でLFに変わっています。Pythonの open() や sed でファイル全体を読み書きすると、既定で \r\n が \n に変わるので、こうなります。
CRを戻し、autocrlfを切ってステージする
作業ツリーのファイルをCRLFに戻します。既にCRLFの行を二重にしないよう、\r?\n を置換対象にします。
perl -pi -e 's/\r?\n/\r\n/' path/to/File.php
grep -c $'\r$' path/to/File.php # 総行数と一致すればOK
次にステージです。core.autocrlf=input のままでは git add の時点でCRLFがLFに変換され、インデックス上は再び全行差分になります。そのコマンドだけ設定を切ります。
git -c core.autocrlf=false add path/to/File.php
git show :path/to/File.php | grep -c $'\r$' # インデックス側にCRが残っていることを確認
git commit
これで --stat は128行になりました。-c はそのコマンド1回だけに適用されるので、グローバル設定は変わりません。
「CRLF will be replaced by LF」の警告は無視してよい
この手順の途中で warning: in the working copy of 'path/to/File.php', CRLF will be replaced by LF the next time Git touches it が出ます。これは「次にGitがこのファイルを正規化する機会があれば変換する」という予告で、-c core.autocrlf=false を付けたaddでは変換されません。実際にインデックスにCRが残っているかは上の git show :path で確認できます。
リポジトリ全体をLFに揃える方針なら、この機会に正規化してしまう判断もあります。ただしそれは「改行を揃えるコミット」として分けておく方が、あとから git blame や git log -p で本当の変更を追えます。今回のように同じディレクトリでCRLFとLFのファイルが混在している(19ファイル中6ファイルがCRLF)状態では、触ったファイルだけ正規化されていくのは追いにくいので、まず元の改行を維持する方を選びました。