日本語 ▾ トピック ▾ 最新バージョン ▾ gittutorial-2 は 2.23.0 で最終更新されました

名前

gittutorial-2 - Gitのチュートリアル入門: パート2

概要

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 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進数は何でしたか?

チュートリアルのパート1で、コミットがこのような名前を持つことを見ました。Git履歴のすべてのオブジェクトは、40桁の16進数名で保存されています。その名前は、オブジェクトの内容のSHA-1ハッシュです。これにより、Gitは同じデータを2度保存することはありません(同一のデータには同一のSHA-1名が与えられるため)、またGitオブジェクトの内容が変更されることもありません(変更するとオブジェクトの名前も変わるため)ことが保証されます。ここにある7文字の16進数文字列は、そのような40文字の長い文字列の単なる略語です。略語は、曖昧でなければ、40文字の文字列が使用できるすべての場所で使用できます。

上記の例に従って作成したコミットオブジェクトの内容は、作成された時刻とコミットを実行した人物の名前を記録するため、上記に示されているものとは異なるSHA-1ハッシュを生成することが予想されます。

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

$ 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つ以上の「ブロブ」オブジェクトを参照できます。それぞれが1つのファイルに対応します。さらに、ツリーは他のツリーオブジェクトも参照でき、これによりディレクトリ階層を作成します。ls-treeを使用して任意のツリーの内容を調べることができます(SHA-1の十分長い最初の部分でも機能することを忘れないでください)。

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

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

$ git cat-file -t 3b18e512
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

そして、これらのファイルの内容は、圧縮されたデータと、その長さとタイプを識別するヘッダーのみです。タイプは、ブロブ、ツリー、コミット、またはタグのいずれかです。

最も見つけやすいコミットは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

ここでの「ツリー」オブジェクトは、ツリーの新しい状態を指します。

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

そして「親」オブジェクトは、前のコミットを指します。

$ 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つだけ持ちますが、複数の親を持つコミットも一般的です。その場合、コミットはマージを表し、親参照はマージされたブランチのヘッドを指します。

ブロブ、ツリー、コミット以外に、残りのオブジェクトタイプは「タグ」のみですが、ここでは説明しません。詳細はgit-tag[1]を参照してください。

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

  • 「コミット」オブジェクトは、履歴の特定の時点におけるディレクトリツリーのスナップショットを表す「ツリー」オブジェクトを参照し、プロジェクト履歴への接続方法を示すために「親」コミットを参照します。

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

  • 「ブロブ」オブジェクトは、他の構造を持たないファイルデータを含んでいます。

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

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

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

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

インデックスファイル

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

コミットがどのように作成されるかを見ると、より柔軟なコミット作成方法があることがわかります。

引き続きテストプロジェクトで、file.txt をもう一度変更してみましょう。

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

ただし今回は、すぐにコミットするのではなく、中間ステップを踏み、何が起こっているかを追跡するために、途中で差分を要求してみましょう。

$ git diff
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again
$ git add file.txt
$ 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
On branch master
Changes to be committed:
  (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

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 のバージョンが、作業ディレクトリ内のものと同一であるためです。

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

次は何を?

この時点で、Gitコマンドのマニュアルページを読むのに必要なすべてを理解しているはずです。最初に読むのに良い場所は、giteveryday[7] で言及されているコマンドでしょう。gitglossary[7] で、知らない専門用語を見つけることができるはずです。

Gitユーザーマニュアル は、Gitへのより包括的な入門書です。

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

Gitの使用例については、ハウツー を参照してください。

Git開発者向けには、gitcore-tutorial[7] が、たとえば新しいコミットの作成など、下位レベルのGitメカニズムについて詳しく説明しています。

GIT

git[1]スイートの一部

scroll-to-top