はじめに
.git/objects
フォルダの中に大量にできる謎のファイルについて調べてみました。
Gitをただツールとして使うだけなら必要のない知識ですが、Gitの設計を理解すると、Gitを難しいと思わなくなるかもしれません。
Gitオブジェクト
Gitのデータは、Gitオブジェクトという形で.git/objects
の中に格納されます。
例えば、以下のようにファイルを1つだけコミットしてみると、
$ git init
$ echo "hello" > sample.txt
$ git add sample.txt
$ git commit -m "Initial commit"
.git/objects
フォルダの中にファイルが3つできていることが分かります。
$ find .git/objects -type f
.git/objects/71/9448a65a4412c7dcdfa3845dbaf755f14207ae
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
.git/objects/e3/d14d7340059b5852f32f29574e98ff73eb3c47
Gitオブジェクトは40文字のハッシュが付けられ、先頭の2文字をサブフォルダ名、残りの38文字をファイル名として、1つのオブジェクトごとに1つのファイルに保存されます。
cat-fileコマンドで見てみる
git cat-file
コマンドでGitオブジェクトの内容を確認できます。
-t
オプションでオブジェクトのタイプを表示します。見たいオブジェクトのハッシュを指定しますが、40文字すべて入力する必要はなく、先頭の4文字で十分です。
$ git cat-file -t 7194
commit
$ git cat-file -t ce01
blob
$ git cat-file -t e3d1
tree
3つのオブジェクトは、それぞれcommit、blob、treeというタイプであることが分かります。
blobオブジェクト
-p
オプションでオブジェクトの中身を表示できます。
$ git cat-file -p ce01
hello
$ cat sample.txt
hello
blobオブジェクトはファイルデータそのものです。ファイル名や属性(実行可能かどうか)の情報は含まれていません。
treeオブジェクト
$ git cat-file -p e3d1
100644 blob ce013625030ba8dba906f756967f9e9ca394464a sample.txt
$ ls
sample.txt
treeオブジェクトはフォルダ構造を表します。ファイルの属性、blobオブジェクトへの参照(サブフォルダの場合はtreeオブジェクトへの参照)、ファイル名の情報を記録しています。
commitオブジェクト
$ git cat-file -p 7194
tree e3d14d7340059b5852f32f29574e98ff73eb3c47
author nkshigeru <nkshigeru@example.com> 1434542915 +0900
committer nkshigeru <nkshigeru@example.com> 1434542915 +0900
Initial commit
commitオブジェクトには、トップレベルのtreeへの参照、コミットしたユーザーの情報、タイムスタンプ、コミットメッセージが含まれます。また、これは最初のコミットなので親コミットがありませんが、2回目以降のコミットでは親コミットへの参照も含まれます。
pythonで見てみる
実はGitオブジェクトはzlibで圧縮されているだけなので、pythonや何か適当なツールでも見ることができます。
$ python
>>> f = open('.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a', 'rb')
>>> import zlib
>>> zlib.decompress(f.read())
'blob 6\x00hello\n'
つまり、blobオブジェクトは、オブジェクトタイプ'blob' + ' ' + ファイルコンテンツの長さ + null文字'\0' + ファイルコンテンツ
という構造になっていることが分かります。
さらに、Gitオブジェクトのハッシュは、圧縮されていないデータのSHA1ハッシュを計算することで求められます。
>>> import hashlib
>>> hashlib.sha1('blob 6\x00hello\n').hexdigest()
'ce013625030ba8dba906f756967f9e9ca394464a'
blobオブジェクトやtreeオブジェクトの場合は、同じ内容ならハッシュが同じになるため、ディスク容量を節約することができます。(commitオブジェクトの場合は、ユーザー情報やタイムスタンプが含まれるため、別のcommitオブジェクトと全く同じになることはたぶんないと思います。)
一方、少しでも内容が異なれば、全く異なるハッシュになるため、オブジェクトが衝突することを心配する必要はありません。また、ハッシュによってデータの破損を検出することが可能です。
まとめ
Gitのデータは、zlib圧縮とSHA1ハッシュを使って.git/objects
フォルダの中に格納されています。そのしくみはとてもシンプルですが、データを効率的に安全に扱う工夫がされています。