SYNOPSIS

git *

DESCRIPTION

このGit入門パート2を読む前に、 gittutorial(7) (Git入門)を読んで下さい。

この入門の目的は、Gitのアーキテクチャの2つの基本的な部分(オブジェクトデータベースとインデックスファイル)を紹介し、残りのGit文書を理解するために必要なすべてのものを読者に提供することです。

The Git object database

新しいプロジェクトを開始して、履歴を少々作成しましょう:

$ 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 file changed, 1 insertion(+)
 create mode 100644 file.txt
$ echo 'hello world!' >file.txt
$ git commit -a -m "add emphasis"
[master c4d59f3] add emphasis
 1 file changed, 1 insertion(+), 1 deletion(-)

さて、ここで、Gitがコミットで返してきた7桁の16進数(訳注: 54196cc とか c4d59f3)は一体何でしょうか?

入門のパート1で、コミットには名前が付いていることがわかりました。Git履歴内のすべてのオブジェクトは、40桁の16進名で保存されていることがわかりました。その名前は、オブジェクトの内容のSHA-1ハッシュです。特に、これにより、Gitが同じデータを2回保存することはなく(同一のデータには同一のSHA-1名が付けられるため)、Gitオブジェクトの内容が変更されることはありません(オブジェクトの名前も変更されるため)。ここでの7文字の16進文字列は、このような40文字の長さの文字列の略語です。 略語は、明確である限り(訳注:その環境で一意になる限り)、40文字の16進文字列を使用できるすべての場所で使用できます。

上記の例に従って作成したコミットオブジェクトのコンテンツは、コミットオブジェクトが作成された時刻とコミットを実行した人の名前を記録するため、上記とは異なるSHA-1ハッシュを生成することが期待されます。

cat-file コマンドを使用して、 この特定のオブジェクトについてGitに問い合わせることができます。 上記の例から40桁の16進数をコピペするのではなく、 あなた自身が実行したバージョンの16進数を使用してください。 40桁すべての16進数を入力する手間を省くために、数文字に短縮できることに注目です:

$ git cat-file -t 54196cc2
commit
$ git cat-file commit 54196cc2
tree 92b8b694ffb1675e5975148e1121810081dbdffe
author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500

initial commit

ツリーは、それぞれがファイルに対応する、1つ以上のブロブ(blob)オブジェクトを参照できます。さらに、ツリーは他のツリーオブジェクトを参照することもできるため、ディレクトリ階層が作成されます。 ls-tree を使用して任意のツリーの内容を調べることができます(その際、SHA-1の最初の部分を指定するだけで十分なことを忘れないでください)。

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

これで、このツリーには1つのファイルが含まれていることがわかります。SHA-1ハッシュは、そのファイルのデータへの参照(reference)です:

$ git cat-file -t 3b18e512
blob

「blob」は単なるファイルデータであり、cat-fileで調べることもできます:

$ git cat-file blob 3b18e512
hello world

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

これらのオブジェクトはすべて、Gitディレクトリ内部にSHA-1名で保存されます:

$ 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 のいずれかです。

見つけるのが最も簡単なコミットはHEADコミットで、これは .git/HEAD から見つけることができます:

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

ご覧のとおり、これにより、現在どのブランチを使用しているかがわかります。 .git ディレクトリの下にあるファイルに名前を付ける事でこれを私達に知らせます。このディレクトリ自体には、コミットオブジェクトを参照するSHA-1名が含まれていて、 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 <bfields@puzzle.fieldses.org> 1143418702 -0500
committer J. Bruce Fields <bfields@puzzle.fieldses.org> 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 <bfields@puzzle.fieldses.org> 1143414668 -0500
committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500

initial commit

このツリー・オブジェクトは最初に調べたツリーであり、 このコミットには親がないという点でレアです。

多くのコミットでは親が1つしかないですが、 複数の親を持つコミットも割とあります。 その場合、コミットはマージを表し、 親参照はマージされたブランチのヘッドを指します。

ブロブ(blob)とツリー(tree)とコミット(commit)以外に残っているオブジェクトのタイプは「タグ」(tag)だけです。これについてはここでは説明しません。 詳細については、 git-tag(1) を参照してください。

これで、Gitがオブジェクトデータベースを使用してプロジェクトの履歴を表す方法がわかりました:

  • 「コミット」(commit)オブジェクトは、履歴の特定のポイントでのディレクトリ・ツリーのスナップショットを表す「ツリー」オブジェクトを指し、「親」コミットを、プロジェクト履歴にどのように接続されているかを示すために参照します。

  • 「ツリー」(tree)オブジェクトは単一のディレクトリの状態を表し、ディレクトリ名をファイルデータを含む「ブロブ」オブジェクトとサブディレクトリ情報を含む「ツリー」オブジェクトに関連付けます。

  • 「ブロブ」(blob)オブジェクトには、 他の構造を持たないファイルデータが含まれています。

  • 各ブランチのヘッドにあるコミットオブジェクトへの参照は、 .git/refs/heads/ の下のファイルに保存されます。

  • 現在のブランチの名前は .git/HEAD に保存されます。

なお、 多くのコマンドがツリーを引数として受け取ります。 しかし、 上記でわかるように、 ツリーはさまざまな方法で参照できます。 ツリーの SHA-1 名や、 ツリーを参照するコミットの名前や、 ツリーを参照するブランチのヘッドの名前などです。 そして、 ほとんどのそのようなコマンドはこれらのいずれかを受け入れることができます。

コマンドの概要では、「tree-ish」(ツリーっぽい)という単語がそのような引数を示すために使用されることがあります。

The index file

コミットを作成するために使用している主なツールは git-commit -a です。これは、作業ツリーに加えたすべての変更を含むコミットを作成します。しかし、特定のファイルの変更のみをコミットしたい場合はどうでしょうか? または、特定のファイルの特定(一部の)の変更のみをコミットしたい場合はどうでしょうか?

コミット作成の秘密を知れば、 コミットを作成する、 より柔軟な方法があることがわかります。

我々のテストプロジェクトを続けて、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

ここで、 最後の git diff の結果は空です。 しかし、 これは、 まだ新しいコミットは行われておらず、 ヘッドにはまだ新しい行が含まれていないからです:

$ 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 は(作業ツリーを)ヘッド以外の何かと比較しています。 比較しているのは、 実際にはインデックス・ファイルです。 これはバイナリ形式で .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」が行ったことは、 新しいブロブを格納し、 それへの参照をインデックス・ファイルに配置することでした。 ファイルを再度変更すると、 新しい変更が「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

あなたは、 以下のようにして、 いつでも git commit ( -a オプションなし)を使用して新しいコミットを作成することができ、 コミットされた状態にはインデックス・ファイルに保存されている変更のみが含まれ、 作業ツリーにのみ残っている追加の変更が含まれてい無いことを確認できます:

$ 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 の効果は、 以下のようにインデックス・ファイルに1つのエントリを追加することでした:

$ 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
ブランチ master
コミット予定の変更点:
  (use "git restore --staged <file>..." to unstage)

        new file:   closing.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)

        modified:   file.txt

closeing.txtの現在の状態はインデックスファイルにキャッシュされているため、「コミット予定の変更点」(Changes to be committed)としてリストされます。 file.txtには、インデックスに反映されていない作業ディレクトリの変更があるため、「Changes not staged for commit」とマークされています。 この時点で、「git commit」を実行すると、(新しいコンテンツを含む)closeing.txtが追加されたコミットが作成されますが、file.txtは変更されませんでした。

また、裸の git diff はfile.txtへの変更を示しますが、closeing.txtの追加は示しません。これは、インデックスファイルのcloseing.txtのバージョンが作業ディレクトリのバージョンと同じであるためです。

インデックスファイルは、新しいコミットのステージング領域であることに加えて、ブランチをチェックアウトするときにオブジェクトデータベースからも入力され、マージ操作に関係するツリーを保持するためにも使用されます。詳細については、 gitcore-tutorial(7) および関連するマニュアルページを参照してください。

What next?

この時点で、 あなたは任意の Git コマンドのマニュアルページを読むために必要な知識はすべて身についているはずです。 その良い出発点は giteveryday(7) で紹介されているコマンドから始めることです。 あなたが知らない専門用語は gitglossary(7) で調べる事もできます。

Git User’s Manual は、Gitのより包括的な紹介を提供します。

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

Gitの使用に関する興味深い例については、 howtos を参照してください。

Git開発者向けに、 gitcore-tutorial(7) で、新しいコミットの作成などに関連する低レベルのGitメカニズムについて詳しく説明しています。

SEE ALSO

GIT

Part of the git(1) suite