Gitの内部構造と仕組みを理解する

皆さん、こんにちは。技術開発グループのn-ozawanです。
当ブログが開設されて1年が経過しました。今後も週に一回のペースで投稿したいと思いますので、よろしくお願いいたします。

本題です。
Gitはバージョン管理システムのデファクトスタンダードとなっています。Gitに関する書籍も多く、Gitの使い方をネットで検索すれば多くのサイトがヒットします。Gitを利用した技術者も多いと思いますが、Gitの内部構造とその仕組みを知っている人は少ないと感じます。今回はGitの内部構造と仕組みについてお話しします。

Gitの内部構造と仕組み

オブジェクト

Gitはオブジェクトの集合体です。ファイルやディレクトリなどをオブジェクトで表現します。このGitオブジェクトは.git/objectsに、なんだかよく分からない英数字の羅列のファイルで格納されています。実際にメモ帳などで開いてみても中身は見れません。これはzlibで圧縮されているためです。

$ find .git/objects -type f
.git/objects/1f/06027c6ffc075fe7e0d92e2bf58694206af3f1
.git/objects/22/62de0c121f22df8e78f5a37d6e114fd322c0b0
.git/objects/34/b4cb20c2d52d10609df3a630b9c626103c19c4
.git/objects/40/42b056c7c15db8055d70feed4b977e1b5546a1
.git/objects/6c/20561d214cc99d9b2d583066d45217c5e7de2b
.git/objects/b5/c4e63d7e9e356ff41f2861e78f3fe5fa272d54
.git/objects/c2/684e0321eedff1890b7690c89726387d2af3ca
.git/objects/db/a4220318f9316310fb30b97c2fe24df05123ac
.git/objects/e4/f33342998c5fee2765d3d9e7d70c0cd2d71f47
.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
.git/objects/ec/66e8c3ceaa0ce3e44a1b8d7b5dca9d948b22c0
.git/objects/ff/107adebec950c5d63f8ac72bda3546e44c56d5
.git/objects/pack/pack-b3906d9532603a5f8969124d1615eda491b46347.idx
.git/objects/pack/pack-b3906d9532603a5f8969124d1615eda491b46347.pack

オブジェクトは「ヘッダ情報」と「内容」の2つで構成されています。「ヘッダ情報」と「内容」をハッシュ関数(SHA-1)により取得したハッシュ値がオブジェクト名となります。なんだかよく分からない英数字の羅列の正体はオブジェクト名であり、ハッシュ値です。

ヘッダ情報には、そのオブジェクトの型と、その内容の長さが格納されています。オブジェクトの型には主に、blobtreecommittagの4種類があります。また、オブジェクトの型によって内容の中身は異なります。

blob

blobはファイルを表現しています。内容にはファイルの中身が格納されます。

オブジェクトの中身はgit cat-fileで簡単に見ることが出来ます。

$ git cat-file -t 9646cac155372c3f6d8fc8db4cf72feb3a4e0e2d
blob

$ git cat-file -p 9646cac155372c3f6d8fc8db4cf72feb3a4e0e2d
# サンプル

これはサンプルのREADMEです。

tree

treeはツリー構造を表現しています。ディレクトリと言えばイメージ付くかと思います。

$ git cat-file -p f4f93a002b34e1bb0116a5393bb1c7a01ccf4fc7
100644 blob 9646cac155372c3f6d8fc8db4cf72feb3a4e0e2d    README.md
040000 tree 34b4cb20c2d52d10609df3a630b9c626103c19c4    src

treeでは、紐づくオブジェクトの型とオブジェクト名(ハッシュ値)、ファイルやディレクトリ名などで構成されています。例えば以下の場合、最初の100644は通常のファイルであることを示します。続くblobはオブジェクトの型です。9646cac1...はオブジェクト名であり、最後のREADME.mdはファイル名を示しています。

100644 blob 9646cac155372c3f6d8fc8db4cf72feb3a4e0e2d README.md

先ほど「最初の100644は通常のファイルである」と説明しましたが、他には以下があります。

種類解説
100644通常のファイル
100755通常の実行可能ファイル
120000シンボリックリンク
40000ディレクトリ
160000サブモジュール

ツリー構造

treeオブジェクトで、対象となるオブジェクト名を指定することにより、ツリー構造を形成することが出来ます。

上記の状況からREADME.mdを修正した場合、新しいオブジェクトが生成されます。もちろんファイルの中身が変わりますので、ハッシュ値も別物となります。以下はREADME.mdを修正した場合のツリー構造です。オブジェクト名が変わっていることが分かると思います。

commit

Gitはコミット履歴もオブジェクトとして管理します。先ほどのREADME.mdの修正を例にすると以下の図のようになります。

commitオブジェクト7e958f16...は、修正前のcommitオブジェクトです。7e958f16...のtreeには、修正前のtreeオブジェクトf4f93a00...が指し示されています。

README.mdを修正すると、40944655...という新しいcommitオブジェクトが生成されます。40944655...のtreeには、新しく生成されたtreeオブジェクトf53e657b...が指し示されます。また、parentには、修正元のcommitオブジェクト7e958f16...を指し示すことにより、修正を時系列で追えるようにしています。

Gitでは修正前と修正後の差分を管理していません。Gitはその時点のスナップショットを管理しており、差分はスナップショット同士を比較して表示しています。

おわりに

正直、内部構造を知らなくてもGitは扱えます。内部構造を知らなくても扱えるのは優秀なツールである証拠でもありますが、知っておくとGitを効率よく扱うことが出来るようになるかもしれません。

ではまた。

採用情報

「チームアイオス」入団者募集

〜就活で悩むアナタに来て欲しい〜