SYNOPSIS

git *

解説

注意:このチュートリアルを読む前に gittutorial(7) を学習してください。

このチュートリアルの目的は、git のアーキテクチャである2つの 基本部品(オブジェクトデータベース、索引ファイル)を紹介することと、 git のドキュメントの残りの部分を理解する為に必要な全てを読者に 伝えることです。

git のオブジェクトデータベース

新規プロジェクトを開始し、少し履歴を作成しましょう。

$ mkdir test-project
$ cd test-project
$ git init
Initialized empty Git repository in .git/
$ echo 'hello world' > file.txt
$ git add .
$ git commit -a -m "initial commit"
[master (root-commit) 54196cc] initial commit
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 file.txt
$ echo 'hello world!' >file.txt
$ git commit -a -m "add emphasis"
[master c4d59f3] add emphasis
 1 files changed, 1 insertions(+), 1 deletions(-)

コミット時のメッセージに含まれる 7桁 の16進数は何でしょうか?

我々はチュートリアルのパート1で、コミットがこのような名前を持つことを見てきました。 これは、git の履歴内にある全てのオブジェクトが 40桁の16進数の名前で 格納されていることを示しています。 この名前はオブジェクトのコンテンツの SHA1 ハッシュ値です; それはつまり、git が同じデータを2回記録することが決してないことを 保障しています(同一のデータは同じ SHA1 名となるからです)。 そして、git オブジェクトのコンテンツは、決して変わりません (オブジェクトが変更された場合、その名前も同じように変更される為です)。 ここにある7桁の16進数の名前は40桁の16進数の名前をあいまいにならない範囲で 省略した物です。

上記例に従ってあなたが作成したコミットオブジェクトのコンテンツは、 上記に表示されたものと異なる SHA1 ハッシュ値を生成するでしょう。 なぜなら、コミットオブジェクトは、作成日時とコミットした人の名前を 記録するからです。

git の cat-file コマンドを用いるとこの特定のオブジェクトについて調べることができます。 この例の 40桁の 16進数をコピーせず、あなた自身のバージョンの名前を 使用してください。40桁全てではなく、最初の数桁をタイプするだけで 良い点に注意してください。

$ git cat-file -t 54196cc2
commit
$ git cat-file commit 54196cc2
tree 92b8b694ffb1675e5975148e1121810081dbdffe
author J. Bruce Fields <[email protected]> 1143414668 -0500
committer J. Bruce Fields <[email protected]> 1143414668 -0500

initial commit

ツリー(tree)は 1つないし複数の "blob" オブジェクトを参照することができます。 各 blob オブジェクトは1つのファイルに対応しています。 さらに、tree は 他の tree オブジェクトを参照しています。 このようにしてディレクトリ階層を作成しています。 ls-tree を使用すると任意のツリーのコンテンツを試すことができます。 (SHA1値の最初の数文字を使用すれば十分なことを思い出してください)

$ git ls-tree 92b8b694
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt

このように、この tree は1つのファイルを持っていることがわかります。 SHA1 ハッシュ値はそのファイルのデータを参照しています。

$ git cat-file -t 3b18e512
blob

"blob" は単なるファイルデータです。 cat-file を使用することでそのことがわかります。

$ git cat-file blob 3b18e512
hello world

これは過去のファイルデータであることに注意してください。; つまり git は最初のツリーに対する自身の応答により名前をつけたオブジェクトは、 最初のコミットによって記録されたディレクトリ状態のスナップショットを 持ったツリーになっています。

これらオブジェクトの全ては、git ディレクトリ内の SHA1値 に従い記録されています。

$ find .git/objects/
.git/objects/
.git/objects/pack
.git/objects/info
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/92
.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
.git/objects/54
.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
.git/objects/a0
.git/objects/a0/423896973644771497bdc03eb99d5281615b51
.git/objects/d0
.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
.git/objects/c4
.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241

そして、これらファイルの中身は、圧縮されたデータと、それらの長さと種別を 特定するヘッダで構成されています。 種別には blob, tree, commit, tag があります。

見つけることのできる最も単純な commit は HEAD の commit です。 それは、.git/HEAD で確認できます:

$ cat .git/HEAD
ref: refs/heads/master

既に見たように、これは、現在どのブランチにいるかを教えてくれています。 そして、ディレクトリ .git 配下のファイルに名前を付けることでこのことを教えてくれます。 それ自身 commit オブジェクトを参照する SHA1 名を含んでおり、 cat-file により確認できます。

$ cat .git/refs/heads/master
c4d59f390b9cfd4318117afde11d601c1085f241
$ git cat-file -t c4d59f39
commit
$ git cat-file commit c4d59f39
tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
author J. Bruce Fields <[email protected]> 1143418702 -0500
committer J. Bruce Fields <[email protected]> 1143418702 -0500

add emphasis

この "tree" オブジェクトはツリーの新しい状態を参照しています:

$ git ls-tree d0492b36
100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt
$ git cat-file blob a0423896
hello world!

そして、"parent" オブジェクトは前のコミットを参照しています:

$ git cat-file commit 54196cc2
tree 92b8b694ffb1675e5975148e1121810081dbdffe
author J. Bruce Fields <[email protected]> 1143414668 -0500
committer J. Bruce Fields <[email protected]> 1143414668 -0500

initial commit

この tree オブジェクトは最初に確認した tree です。 そして、このコミットは親が無く、通常と違っています。

大抵のコミットは1つだけ親を持ちますが、複数の親を持つコミットもまた 一般に存在します。そのようなケースではコミットはマージを表現しており、 マージしたブランチの先頭を親として参照しています。

blob, tree, commit の他に、説明していないオブジェクトは "tag" オブジェクト だけです。 tag オブジェクトはここでは議論しません; 詳細は git-tag(1) を参照してください。

さて、ここまでで、git がプロジェクトの履歴を表現するために どのようにオブジェクトデータベースを使用するかを見てきました。

注意:ところで、多くのコマンドが tree を引数にとることに注意してください。 しかし、上記で見たように、tree は様々な方法で参照されています。 — tree の SHA1名や、tree を参照する commit 名や、head が参照している tree のブランチ名など。-- そして、多くのそのようなコマンドは これらの名前を受け付けることができます。

コマンドの使用法では、"tree-ish" という言葉で、そのような引数を 表すことがあります。

索引ファイル

コミットをする時に使用する主要なツールは git-commit-a です。 このコマンドは、作業ツリーにて行った全ての変更を含んだコミットを作成します。 しかし、特定のファイルの変更だけをコミットに含めたい場合はありませんか? もしくは、特定のファイルの特定の変更だけ?

コミットが作成される手順を以下でみてゆくと、 コミットを作成するより柔軟な手順があることがわかります。

先程の test-project に対して、再度 file.txt を修正しましょう:

$ echo "hello world, again" >>file.txt

しかし、今回はすぐにはコミットせず、中間的段階をとりましょう。 何がおきているかを追跡する為に diff をとります:

$ git diff
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again
$ git add file.txt
$ git diff

最後の diff は空ですが、まだ新しいコミットは作成されていません。 head には 新しい行は含まれていません:

$ git diff HEAD
diff --git a/file.txt b/file.txt
index a042389..513feba 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again

従って git-diff は head 以外の何かと比較していることになります。 何に対して比較しているかというと、実のところ索引ファイルと比較しているのです。 索引ファイルは .git/index にバイナリ形式で記録されていて、その中身は ls-files を用いて確認できます:

$ git ls-files --stage
100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
$ git cat-file -t 513feba2
blob
$ git cat-file blob 513feba2
hello world!
hello world, again

従って git-add をすると、新しい blob が記録され、 索引ファイルにその参照が記録されます。 そのファイルが再び変更されたならば、その新しい変更は git-diff の 出力に反映されます:

$ echo 'again?' >>file.txt
$ git diff
index 513feba..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,3 @@
 hello world!
 hello world, again
+again?

正しい引数を用いると、git-diff は作業ディレクトリと最後のコミットとの 差分や、索引と最後のコミットとの差分を表示することができます:

$ git diff HEAD
diff --git a/file.txt b/file.txt
index a042389..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,3 @@
 hello world!
+hello world, again
+again?
$ git diff --cached
diff --git a/file.txt b/file.txt
index a042389..513feba 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again

任意の時点で ("-a" オプションを使用せずに) git-commit を使用して 新しいコミットを作成できます。 そして、その状態は索引ファイルに記録された変更だけを含んでいて、 作業ディレクトリでだけ行われている追加の変更は含んでいないことを確認できます:

$ git commit -m "repeat"
$ git diff HEAD
diff --git a/file.txt b/file.txt
index 513feba..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,3 @@
 hello world!
 hello world, again
+again?

従ってデフォルトでは git-commit はコミットを作成する際に索引を使用していて 作業ツリーは使用していないことがわかります;コミット時の "-a" オプションは 最初に作業ツリー内の全ての変更を含むように索引を更新することを表しています。

最後に、索引ファイルにおける git-add の効果について見ておきましょう:

$ echo "goodbye, world" >closing.txt
$ git add closing.txt

git-add の効果は、索引ファイルにひとつのエントリを追加することです:

$ git ls-files --stage
100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt
100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt

そして、cat-file を用いてこれを確認できます。 この新しいエントリは、現在のファイルのコンテンツを参照しています:

$ git cat-file blob 8b9743b2
goodbye, world

"status" コマンドは状態の概要を迅速に取得する簡単な方法です:

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file: closing.txt
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#       modified: file.txt
#

closing.txt の現在の状態は索引ファイルにキャッシュされている為、 "Changes to be committed"(コミットされるべき変更)としてリストされます。 file.txt は作業ディレクトリ内で変更されているが索引ファイルに反映されていない為、 "changed but not updated"(変更されているが更新されていない) と印付けされています。 この観点から "git commit" を実行すると、closing.txt (新しいコンテンツを含む) が追加された、 コミットが作成され、file.txt の変更は含みないことがわかります。

また、裸の git diff は file.txt の変更を表示するが、追加した closing.txt は 含まないことに注意してください。何故なら索引ファイル内の closing.txt のバージョンは 作業ディレクトリ内のものと同一だからです。

新しいコミットの為に終結した領域であるということに加えて、 索引ファイルはブランチを作成する時にオブジェクトデータベースから利用されます。 そして、マージ操作によって引き起こされる tree を保持するためにも使用されます。 詳細は gitcore-tutorial(7) と 関連する man ページを 参照してください。

その次は何?

この時点で、git コマンドの man ページを読む為に必要な全てを理解したはずです; もうひとつの利用開始の為の良い場所は 毎日の git 内の コマンドに対する説明です。また、全ての知らない専門用語は gitglossary(7) 内に 見つけることができるでしょう。

gitcvs-migration(7) のドキュメントは CVS リポジトリを git にインポートする方法を説明しており、 CVS のような方法で git を使用する方法を示しています。

面白い git 使用例を知りたい方は、howtos を参照してください。

git 開発者は gitcore-tutorial(7) が (例えば新しいコミットの作成のような)下位レベルの git メカニズムの 詳細を説明しているので、そちらに進んでください。

参考

gittutorial(7), gitcvs-migration(7), gitcore-tutorial(7), gitglossary(7), git-help(1), Everyday git, The Git User's Manual

GIT

git(1) スイートの一部