Gitのオブジェクトモデル(The Git Object Model翻訳)

Git Community Book から、1章2節の The Git Object Model を翻訳。ライセンスは、 GPLv2

多くのGit解説本と違い、まずGitの内部モデル解説から入るという、おもしろい構成の本です。人によっては、このほうがわかり易いかもしれません。Gitは差分データを管理していると誤解されることがたまにありますが、それは誤りであるということがこれを読めばわかります。

SHA

プロジェクトの履歴を表すのに必要な情報は、いずれも、40桁の「オブジェクト名」で参照されるファイルに格納されている。オブジェクト名は、このような形をしている:

6ff87c4664981e4397625791c8ea3bbb5f2279a3

このような40文字の文字列は、Gitのあらゆる場面で見られる。どの場面で出てくるものであれ、その名前は、オブジェクトの内容のSHA1ハッシュを取ることで求められる。SHA1ハッシュは、暗号学的なハッシュ関数である。この意味は、異なるオブジェクトで、同じ名前を持つものを見付けるのは、ほぼ不可能ということである。これにはいくつもの利点がある。とくに重要なのは:

オブジェクト

どのオブジェクトも、3つのものから構成される。 サイズコンテンツ である。サイズは、たんにコンテンツのサイズで、コンテンツはオブジェクトの型が何であるかに依存し、そして、4つの異なるオブジェクトの型がある: 「ブロブ」、「ツリー」、「コミット」、そして「タグ」である。

Gitのすべては、この4つの異なるオブジェクト型からなる構造を操作することにあると言っても過言ではない。これは、ある種、小規模な独自のファイルシステムである。このファイルシステムは、マシンのファイルシステムそのものの上に構築される。

SVNとの違い

重要なのは、これが、読者の慣れ親しんでいるかもしれない、ほとんどのSCMとは異なるということである。Subversion、CVS、Perface、Mercurialのような差分ストレージを使うすべてのシステムが該当する。これらは、あるコミットと次のコミットとの間の差分を格納する。Gitでは、このようなことはしない。Gitは、プロジェクト内のすべてのファイルの見たままの形のスナップショットを、コミットをするたびに、上記のツリー構造に格納する。これは、とても重要な考えかたで、Gitを使うときには理解すべきことだ。

ブロブオブジェクト

ブロブは、一般的にファイルの内容を格納する。

Object blob

git show を使えば、ブロブの内容を確認できる。ブロブのSHAがあるとして、このようにすればコンテンツを確認できる。

$ git show 6ff87c4664

 Note that the only valid version of the GPL as far as this project
 is concerned is _this_ particular version of the license (ie v2, not
 v2.2 or v3.x or whatever), unless explicitly otherwise stated.
...

「ブロブ」オブジェクトは、バイナリデータのチャンク以外のなにものでもない。それ以外のものにはなにも言及しておらず、どんな属性も持っていない(ファイル名さえも)。

ブロブは、完全にデータによって定義されるので、ディレクトリツリーの中に、同一の内容を持つ2つのファイルがあれば(あるいはリポジトリの異なるバージョンでもいい)、それらは同じブロブオブジェクトを共有する。ブロブオブジェクトは、ディレクトリツリー内の位置とはまったく関係がなく、ファイル名を変更したとしても、ファイルが関連付けられたオブジェクトは変わらない。

ツリーオブジェクト

ツリーは、シンプルなオブジェクトで、ブロブや他のツリーへのポインタを束ねたものだ。一般的に、ディレクトリやサブディレクトリの内容を表している。

Tree Object

非常に多彩な機能を持つ git show を使って、ツリーオブジェクトの内容を確認することももちろんできるが、 git ls-tree ならもっと詳しいことがわかる。ツリーのSHAがあるとすると、このように内容を確認できる:

$ git ls-tree fb3a8bdd0ce
100644 blob 63c918c667fa005ff12ad89437f2fdc80926e21c    .gitignore
100644 blob 5529b198e8d14decbe4ad99db3f7fb632de0439d    .mailmap
100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3    COPYING
040000 tree 2fb783e477100ce076f6bf57e4a6f026013dc745    Documentation
100755 blob 3c0032cec592a765692234f1cba47dfdcc3a9200    GIT-VERSION-GEN
100644 blob 289b046a443c0647624607d471289b2c7dcd470b    INSTALL
100644 blob 4eb463797adc693dc168b926b6932ff53f17d0b1    Makefile
100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52    README
...

見てわかるように、ツリーオブジェクトは、エントリーのリストを含んでおり、それぞれにモード、オブジェクト型、SHA1名、ファイル名があって、ファイル名でソートされている。これは、ひとつのディレクトリツリーの内容を表している。

ツリーから参照されるオブジェクトは、ブロブ(ファイルの内容を表す)か、または他のツリー(サブディレクトリの内容を表す)かもしれない。ツリーとブロブは、他のすべてのオブジェクトと同様に、それらの内容のSHA1ハッシュで参照される。2つのツリーが(再帰的にすべてのサブディレクトリについても)同一の内容を持つならば、かつその場合に限り、それらは同じSHA1名を持つ。これにより、Gitは、関連した2つのツリーオブジェクトの間の違いをすばやく判定することができる。オブジェクト名が同一のエントリーは無視できるためだ。

(注意: サブモジュールが存在する場合には、ツリーには、コミットもエントリーとして含まれるかもれない。 サブモジュール の節を見よ。)

すべてのファイルは、644か755のモードとなることに注意: Gitは、実際には、実行ビットしか見ない。

コミットオブジェクト

「コミット」オブジェクトは、ツリーの物理的な状態と、そこにどうやって辿りつくのかの記述、及びその理由を結びつける。

Commit Object

--pretty=rawオプションを git show または git log に与えて、好きなコミットの内容を見ることができる。

$ git show -s --pretty=raw 2be7fcb476
commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
parent 257a84d9d02e90447b149af58b271c19405edb6a
author Dave Watson <dwatson@mimvista.com> 1187576872 -0400
committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700

    Fix misspelling of 'suppress' in docs

    Signed-off-by: Junio C Hamano <gitster@pobox.com>

ここから、コミットの定義がわかる:

コミット自体は、実際になにが変更されたのかについての情報をまったく含んでいないことに注意。すべての変更は、コミットから参照されるツリーの内容と親に関連付けられたツリーを比較することで計算される。とくに、Gitは、ファイル名の変更を明示的には記録しない。にも関わらず、同じファイルデータのパス変更があるときにはそれを検出して、リネームを提案する。(例えば、 git diff の-Mオプションを見よ)

コミットは、通常、 git commit によって作られる。これは、通常、現在のHEADを親とするコミットを作成し、そのツリーは、現在インデックスに格納されている内容から取得される。

オブジェクトモデル

これまで3つの主要なオブジェクト型を見てきた(ブロブ、ツリー、コミット)ので、これらがどのようにまとめられるのか簡単に見てみよう。

次のようなディレクトリ構造を持つシンプルなプロジェクトがあるとする。

$>tree
.
|-- README
`-- lib
    |-- inc
    |   `-- tricks.rb
    `-- mylib.rb

2 directories, 3 files

そして、これをGitリポジトリにコミットしたとすると、このように表される。

Objects structure

(ルートを含めて)ディレクトリー毎に ツリー オブジェクトが、ファイル毎に ブロブ オブジェクトができたことがわかる。それから、 ルートを指している コミット オブジェクトがあるので、コミットされた時点でのプロジェクトのあるがままの形を追跡することができる。

タグオブジェクト

Tag Object

タグオブジェクトは、オブジェクトの名前(単に「オブジェクト」と呼ばれる)、オブジェクトの型、タグ名、タグを作成した人の名前(タガー)、そしてメッセージが含まれる。メッセージにはシグネチャが含まれることもある。これは git cat-file を使えば見られる:

$ git cat-file tag v1.5.0
object 437b1b20df4b356c9342dac8d38849f24ef44f27
type commit
tag v1.5.0
tagger Junio C Hamano <junkio@cox.net> 1171411200 +0000

GIT 1.5.0
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)

iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui
nLE/L9aUXdWeTFPron96DLA=
=2E+0
-----END PGP SIGNATURE-----

git tag コマンドを見て、タグオブジェクトの作成と検証方法を学ぶこと。(git tag は、「軽量タグ」を作成するためにも使われることに注意。これはタグオブジェクトとはぜんぜん違うもので、たんに"refs/tags/"ではじまる名前のものを参照するだけだ。)