開発環境

グローバル設定が 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 blamegit log -p で本当の変更を追えます。今回のように同じディレクトリでCRLFとLFのファイルが混在している(19ファイル中6ファイルがCRLF)状態では、触ったファイルだけ正規化されていくのは追いにくいので、まず元の改行を維持する方を選びました。