🦁

chmod +x しただけなのに Git で差分が出る理由と、core.filemode の正体

に公開

症状:chmod +x だけなのに差分扱いされる

例えば、次のような状況です。

chmod +x foo.sh   # 中身は一切変更していない

git status
# 変更あり:
#   modified: foo.sh

git diff を見ると、内容ではなく mode だけ変わっているのが分かります。

$ git diff foo.sh
diff --git a/foo.sh b/foo.sh
old mode 100644
new mode 100755

「中身は一文字も変えていないのに、なぜ差分になるのか?」というのが今回のテーマです。

Git が管理しているパーミッションは「x だけ」

Git はファイルの「パーミッション全部」を管理しているわけではありません。
read / write のビット(rw- の部分)は Git の履歴には保存されず、
「実行ビットが立っているかどうか」だけを記録します。
そのため

chmod +x foo.sh

としただけで、「中身は一文字も変えていないのに、差分があると認識されます」

.gitignore 対応できない

.gitignoreでは「x」の差分を無視するように書けません

core.filemode とは何か?

ここで関係してくるのが Git 設定項目 core.filemode です。

  • core.filemode=true(デフォルト)
    • ワークツリー上の実行ビットの変化を、変更として検出する。
    • chmod +x foo.sh → git status で差分に出る。
  • core.filemode=false
    • 実行ビットの違いだけなら、差分として扱わない。
    • 内容が変わらず、「644 ↔ 755 の違いだけ」の場合は無視される。

正しい対処法

core.filemode=true(デフォルト)
のまま、運用し。
「+x」が必要なファイルは、chmod +x をつけたうえで、コミット/プッシュし
この設定値をチーム内で共有する。

ただし、この方法は、最初にgit initしてファイルをプッシュして環境作るときに
いちいち、全部、必要なものを「+x」つけてプッシュすることになる。
その後、「+x」の付け忘れが発覚するたびに、コミット/プッシュすることになる。

非常に面倒である。

現実的な対処法

実行ビットの変更を Git で追跡したくない場合は、リポジトリ単位で

git config core.filemode false

を打ち込む
上記は、ls -aで直下に「.git」が見えるローカルのgitのルートフォルダで打ち込むこと。

その結果、

.git/config の中身の
filemode = true

filemode = false
に更新されます

内容が変わっていない
違いが「実行ビットだけ」

の変更は git status に出てこなくなります。

補足)
他の人と共有しているリポジトリでこの設定を入れると、
誰かが意図して付けた +x の差分も見落とすことになります。
チームで使う場合は方針を合わせてください。

おわり

諸事情があり、Linux環境のbashのシェルスクリプトをgitの資産として開発作業をする
機会がありました。
./foo.sh
などで打ち込んだ時に、パーミッションエラーがでて
chmod +x foo.sh
をしました。

別にfoo.shの内容を変更してない状況なのに、
git status で、foo.shが差分として表示され、不思議に思って調べたところ
当記事の内容がわかりました。

Linux環境のbashのシェルスクリプトをgitの資産として開発作業をして
「+x」を変更し、中身はまだ変更してない状況で、git statusの差分を見た

という事が、今までなかったため、知らなかった。

他のWebアプリなどでは、別にソースコードに「+x」などつける必要がない
ソースコードは基本的に読み書きするだけ。
また、
Window環境での*.batや、*.ps1の場合はLinuxでのパーミッションの概念ないです

そのため、それらをgitの資産として開発作業をしてる時は、気が付かなかった。

Discussion