Argano の小林です。

本記事では Git におけるファイルのパーミッションの扱い方について述べます。

Git の挙動を確かめる

はじめに Git で管理されているファイルのパーミッションを書き換えるとどうなるか見てみます。

新しく test-permisions リポジトリを作成して script.sh を追加します。

$ git init test-permissions

$ cd test-permissions

$ umask # デフォルトのパーミッションを確認
022 # ファイルについては 644 に設定される

$ echo "echo 'This is test script.''" > script.sh

$ ls -al
total 16
drwxr-xr-x 3 ryohei ryohei 4096 May 29 20:10 .
drwxr-xr-x 3 ryohei ryohei 4096 May 29 20:09 ..
drwxr-xr-x 7 ryohei ryohei 4096 May 29 20:09 .git
-rw-r--r-- 1 ryohei ryohei   29 May 29 20:10 script.sh

$ ./script.sh
zsh: permission denied: ./script.sh

この環境ではファイルの作成時に実行権限が与えられないため、script.sh の実行には失敗します。

ひとまずスクリプトに実行権限を与えていない状態で、スクリプトを git add, git commit してみます。

$ git add script.sh
$ git commit -m "add script"
[main (root-commit) 85a07d2] add script
 1 file changed, 1 insertion(+)
 create mode 100644 script.sh

script.sh のモードが 100644 の状態でコミットが作成されました。次に実行権限を与えてからコミットしてみます。

$ chmod +x script.sh

$ ls -al
total 16
drwxr-xr-x 3 ryohei ryohei 4096 May 29 20:10 .
drwxr-xr-x 3 ryohei ryohei 4096 May 29 20:09 ..
drwxr-xr-x 8 ryohei ryohei 4096 May 29 20:11 .git
-rwxr-xr-x 1 ryohei ryohei   29 May 29 20:10 script.sh

$ ./script.sh
This is test script.

$ git add script.sh
$ git commit -m "grant permissison"
[main e32e07b] grant permissison
 1 file changed, 0 insertions(+), 0 deletions(-)
 mode change 100644 => 100755 script.sh

ファイルのモードが 100644 から 100755 に変化しました。どうやら Git はファイルのパーミッションをモードという概念で管理しているようです。

結果からするとモードの下三桁とパーミッションは等しくなるように見えるので、今度はパーミッションを 444 に指定してから再び git add, git commit してみます。

$ chmod 444 script.sh

$ ls -al
total 16
drwxr-xr-x 3 ryohei ryohei 4096 May 29 20:10 .
drwxr-xr-x 3 ryohei ryohei 4096 May 29 20:09 ..
drwxr-xr-x 8 ryohei ryohei 4096 May 29 20:12 .git
-r--r--r-- 1 ryohei ryohei   29 May 29 20:10 script.sh

$ git add script.sh
$ git commit -m "change permissison"
[main 233b298] change permissison
 1 file changed, 0 insertions(+), 0 deletions(-)
 mode change 100755 => 100644 script.sh

初期状態と同じ 100644 になりました。最後にパーミッションを 555 にして同様の操作を行います。

$ chmod 555 script.sh

$ ls -al
total 16
drwxr-xr-x 3 ryohei ryohei 4096 May 29 20:10 .
drwxr-xr-x 3 ryohei ryohei 4096 May 29 20:09 ..
drwxr-xr-x 8 ryohei ryohei 4096 May 29 20:13 .git
-r-xr-xr-x 1 ryohei ryohei   29 May 29 20:10 script.sh

$ git add script.sh
$ git commit -m "change permissison"
[main b656416] change permissison
 1 file changed, 0 insertions(+), 0 deletions(-)
 mode change 100644 => 100755 script.sh

100755 になりました。これまでのファイルのパーミッションとモードを表にまとめると以下のようになります。

パーミッションモード
644100644
755100755
444100644
555100755

Git はファイルのすべてのパーミッションは管理せず、所有者の実行可能性のみを管理しているようです。ここで Git の仕様を確認してみます。

Blob object のモードについて

Git では、その管理化にあるファイルを blob オブジェクトとして保存します。

それぞれのオブジェクトには以下のような、ファイルの種別とそのアクセス権限を表すモードが割り当てられています。

モードオブジェクトの種別
100644実行不可能である通常のファイル
100755実行可能である通常のファイル
040000ディレクトリ (ツリーオブジェクト)
120000シンボリックリンク
160000Git リンク(サブモジュール)

参考: 10.2 Gitの内側 - Gitオブジェクト - Git book

先頭の 100 は通常のファイルであることを示す識別子であり、その後に続く下三桁がパーミッションを表します。

前節で試したように、通常のファイルの blob に割り当てられるモードは、実行可能を表す 100755、実行不可能を表す 100644 の 2 つに正規化されます。つまり、Git は対象のファイルのモードを決定する上で、所有者の実行可能ビットが立っているかどうかのみを評価の対象としており、それ以外のグループ権限や読み書きに関する情報は一切管理していません。

なぜこのような仕様になったのでしょうか?

実行可能性のみを管理するようになった経緯

実は Git がリリースされた直後は、ファイルのすべてのパーミッションを記録していました。しかし、開発環境の umask の設定値が開発者ごとに異なるため、ファイルを編集するだけで暗黙的にパーミッションが変更されて記録されてしまうという問題が発生しました。これにより、ソースコードの意味的な変更とは無関係なパーミッションの差分が大量に発生し、意味のある変更履歴を追跡することが非常に困難になっていたようです。

この問題を解決するため、Git のリリースの約 2 週間後に、現在のファイルの実行可能性のみを保持する仕様に変更されました。当該のコミット のメッセージでは、Git の開発者である Linus Torvalds が以下のように述べています。

Be much more liberal about the file mode bits. We only really care about the difference between a file being executable or not (by its owner). Everything else we leave for the user umask to decide.

モードを変更する方法

モードの仕様と経緯について見てきましたが、実際にモードを変更するにはどのようにすればよいでしょうか?

Linux ・ Mac では、先程のようにファイルのパーミッションを変更してから git add, git commit することでモードを変更することができます。環境の設定で core.filemodetrue になっていれば、Git は OS のシステムコールを通じてファイルの実行権限の変化を自動的に検知してくれます。

しかし、Windows でファイルシステム上に実行可能ビットという概念が存在しないため、リポジトリ作成時に core.filemode が自動的に false に設定されます。この状態では、ローカルでファイル権限を操作しても Git に変更を認識させることができません。つまり、Git が管理するインデックス上のモードを明示的に直接変更する必要があります。

これは Git バージョン 2.9.0 から導入された git add--chmod=+x, --chmod=-x オプションによって実現できます。

# Gitのインデックス上で実行権限を付与してステージングする
$ git add --chmod=+x script.sh
# Gitのインデックス上で実行権限を剥奪してステージングする
$ git add --chmod=-x script.sh

参考: git-add - Git book

おわりに

本記事では、Git がファイルのパーミッションをどのように扱っているか、その内部的な仕様と歴史的背景について解説しました。