言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

ファイルシステムとしての Git

Git のコマンド体系は全く歴史に学ばず後世に禍根を残す酷いデザインだが、どういうわけか内部構造は大変素晴らしい。特にファイル構造を一旦キーバリュー式データストアに保存するというのは是非参考にしたいアイデアなので調べてみました。

Git 内部データストアの基本機能は、ファイル名を使わず中身だけを保存する事です。ファイル名が無くて後からどうやって保存した中身を取り出すかというと、保存時に SHA-1 という文字列が発行されるのでそれを鍵に取り出します。それでは試しにやってみます。まず準備として新しい Git レポジトリを作ります。

$ mkdir test
$ cd test
$ git init
Initialized empty Git repository in /Users/takashi/tmp/test/.git/

blob

次に、適当な文字列を保存します。

$ echo '適当な文字列' | git hash-object -w --stdin
6c85caf5f36c9f3722c6d1f2f7cc6183b6514855

ここで出て来た 6c85caf5f36c9f3722c6d1f2f7cc6183b6514855 が SHA-1 と呼ばれる物です。これは内容によって一意に決まる文字列で、私のパソコンでもあなたのパソコンでも「適当な文字列」という文字列を Git に入れれば必ずこの文字列が発行されます。また、ほとんど同じ長い文章やプログラムでも一文字違うだけで別の SHA-1 発行されます。こういうのをハッシュ関数と呼び、この Git 内部データストアの肝です。SHA-1 については後でもう一度触れます。

発行された SHA-1 を使って保存した文字列を取り出します。

$ git cat-file -p 6c85caf5f36c9f3722c6d1f2f7cc6183b6514855
適当な文字列

このような普通のデータを blob と呼びます。

tree

実際にこのデータストアを使ってリビジョン管理を実装するには、ファイル名とデータを対応させる必要があります。このために使われるオブジェクトが tree です。tree を作るには、直接作る git mk-tree というコマンドもありますが、ここでは通常の git の操作の通りにまず staging area にファイルを登録する事から始めます。

staging area というのは、他のリビジョン管理ツールから移った人には大変難しい概念で、ローカルファイルと、コミットされたファイルの途中にもう一段「これからコミットしたいファイル」を置くための物です。先ほど保存した適当な文字列をstagging area にtekitou.txt という名前で登録しましょう。これは git add に相当します。

$ git update-index --add --cacheinfo 10064 6c85caf5f36c9f3722c6d1f2f7cc6183b6514855 tekitou.txt

staging area の実体は .git/index ファイルです。staging area の内容を表示する普通の方法は git status する事ですが、他にも低レベルの git ls-files があります。

$ git ls-files
tekitou.txt

次に staging area にある情報から tree を作ります。出来た tree オブジェクトの SHA-1 が返ってきます。

$ git write-tree
dad00c62f3d92c5ad894851a0e01f272f7401bd9

この SHA-1 を git cat-file -p で確認します。先ほどもこのコマンドを使いましたが、-p オプションは整形表示の意味で、blob でも tree でも見やすい形で表示されます。

$ git cat-file -p dad00c62f3d92c5ad894851a0e01f272f7401bd9
100644 blob 6c85caf5f36c9f3722c6d1f2f7cc6183b6514855    tekitou.txt

commit

tree が出来たら今度は commit です。commit というのは、tree のリストにログ情報等を付けた物です。git commit-tree を使うと tree から commit を作成出来ます。

$ echo 'my first low level commit' | git commit-tree dad00c62f3d92c5ad894851a0e01f272f7401bd9
26fda4e87fbe9c03e12b7f650e81bf0208053c6c

$ git cat-file -p 26fda4e87fbe9c03e12b7f650e81bf0208053c6c
tree dad00c62f3d92c5ad894851a0e01f272f7401bd9
author Takashi Yamamiya <tak@metatoys.org> 1294211792 -0800
committer Takashi Yamamiya <tak@metatoys.org> 1294211792 -0800

my first low level commit

refs

出来上がった commit に名前を付けてブランチにしてみます。SHA-1 が書かれたファイルを .git/refs/heads/ に作成するとその名前のブランチを作成出来ます。

$ echo 26fda4e87fbe9c03e12b7f650e81bf0208053c6c > .git/refs/heads/tekitou

$ git checkout tekitou .
$ cat tekitou.txt 
適当な文字列

手で echo しなくても安全にブランチを作る update-ref というコマンドがあります。

$ git update-ref refs/heads/tekitou 26fda4e87fbe9c03e12b7f650e81bf0208053c6c

SHA-1

なお、SHA-1 は Git に限らずこの業界でよく使われる規格ですが、どうも単にデータの SHA-1 を計算しているだけじゃ無いみたいです。ドキュメントによると、データの先頭にタグとサイズから成るヘッダを付けて、zlib で圧縮したあと SHA-1 すると書いてあります。これもコマンドラインで実験してみようと思ったけど、コマンドラインから生の zlib を使う方法が思い付かなかったので省略します。

参考