🌲

autocrlf変更後のGit作業ツリー

2022/07/31に公開

概要

git config でcore.autocrlfの設定を変更しても、チェックアウト済みの作業ツリーの内容は勝手には追従してくれない。autocrlf有効で使っていたローカルリポジトリでautocrlfを無効化した直後、git statusgit diff は差分なしと判定するが実際には改行コードの差異がある。作業ツリーをそのままにしておくと以後の変更でCRLFがリポジトリに混入する恐れがあるので、autocrlf設定を変えたら作業ツリーを設定に整合させておこう。

autocrlf有効なリポジトリと作業ツリー

autocrlfが有効になっていると、チェックアウトの際にCRの付与、コミットの際にCRの除去が行われる。

$ git config core.autocrlf
true
$ git ls-files
readme.txt

作業ツリー上のファイルの改行コードはCRLFとしつつ、

$ file readme.txt
readme.txt: ASCII text, with CRLF line terminators

そのファイルに対応するリポジトリ内のBLOBオブジェクトの改行コードはLFとして扱う。

$ git cat-file -t $(git hash-object readme.txt)
blob
$ git cat-file -p $(git hash-object readme.txt) | file -
/dev/stdin: ASCII text

autocrlfを無効に変える

autocrlf有効で運用していたリポジトリでautocrlfを無効化したとする。

$ git config core.autocrlf false
$ git config core.autocrlf
false

この時、作業ツリーはそのまま維持されるので、ファイルの改行コードはCRLFのままである。

$ file readme.txt
readme.txt: ASCII text, with CRLF line terminators

autocrlf無効の場合、コミット時のCR除去は行われないので、この状態でファイルを編集してコミットすると、リポジトリ内のBLOBの改行コードもCRLFになってしまう。

差分が見えない

上で述べたように、autocrlfを有効から無効に変えた時点でリポジトリ内のBLOB(改行コードLF)と作業ツリー上のファイル(改行コードCRLF)という差分が出ているのだが、git statusgit diff はこの時点では差分を検出しないというわかりにくい状況が発生している。

$ git status
On branch master
nothing to commit, working tree clean
$ git --no-pager diff
$ 

この状況では git reset すら無力である

$ git reset --hard
HEAD is now at ee07037 init
$ file readme.txt
readme.txt: ASCII text, with CRLF line terminators

このような状況になっている理由は、indexファイル[1]に stat(2) の結果(ファイルのタイムスタンプやサイズ)が保存されていて、それが作業ツリー上のファイルのものと同じであれば、ファイルの内容を見ずに差分なしと判定するため。

変更をコミットすると起きる問題

stat(2)の結果がindexファイル記載のものから変化すれば、差分がないという誤判定は解消する。例えばファイルのタイムスタンプを更新すると、差分が見える状態になる。

$ touch readme.txt
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")

autocrlf無効であり、コミット時に改行コードは変換されないので、これをコミットするとBLOBオブジェクトの改行コードはCRLFになる。実際、以下のように確認できる(ここではコミットまではしない)。

$ git add readme.txt
$ git cat-file -p $(git hash-object readme.txt) | file -
/dev/stdin: ASCII text, with CRLF line terminators

今の場合には実質的な変更を加えていないので、改行コードをCRLFにする変更のみがコミットされるが、ファイルを編集して実質的な変更を加えてコミットした場合には、正味の差分と(おそらく意図しないものである)改行コードをCRLFにする変更が混じってややこしい状況になってしまう。

対処

ややこしい状況に陥らないために、どうすれば良いか。core.autocrlfを変更したら、作業ツリーのファイルをautocrlf設定と整合するように更新しておけば良い。

対処1: cloneし直す

ローカルリポジトリを破棄してリモートから clone しなおせる場合はそうするのが簡単。

$ git clone -c core.autocrlf=false リポジトリURL

対処2: 作業ツリーを整合させる

対処1が実施できない場合に、ローカルリポジトリを維持しつつの対処方法。

autocrlfの設定を変更する前に、未コミットの変更がないのを確認しておく(あれば先にコミットしておく)。

$ git config core.autocrlf
true
$ git status
On branch master
nothing to commit, working tree clean

autocrlfの設定を変更する。

$ git config core.autocrlf false
$ git config core.autocrlf
false

git read-treeするとindex中のファイルタイムスタンプ(ctime, mtime)がゼロクリアされ、続けてgit reset --hardすれば作業ツリーをインデックスと同期できるので、作業ツリーの状態がautocrlfの設定に整合したものになる。

$ git read-tree HEAD
$ git reset --hard
HEAD is now at ee07037 init
$ git status
On branch master
nothing to commit, working tree clean
$ file readme.txt
readme.txt: ASCII text

まとめ

autocrlf設定を変更した時、git statusgit diffで差分なしとなっていてもそれは嘘。後でややこしい状況にならないよう、作業ツリーを整合させておこう。

脚注
  1. .git/index のこと。ファイルフォーマットは https://git-scm.com/docs/gitformat-index の通り ↩︎

Discussion