日本語 ▾ トピック ▾ 最新バージョン ▾ 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は同じデータを二度保存せず(同じデータには同じ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つ以上の「blob」オブジェクトを参照でき、それぞれがファイルに対応します。さらに、ツリーは他のツリーオブジェクトも参照できるため、ディレクトリ階層を作成します。ls-treeを使って任意のツリーの内容を調べることができます(SHA-1の冒頭部分が十分に長ければ機能することも覚えておいてください)。

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

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

$ 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」オブジェクトを参照し、「parent」コミットを参照してプロジェクト履歴にどのように接続されているかを示します。

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

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

  • 各ブランチのヘッドにあるコミットオブジェクトへの参照は、.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_が行ったのは、新しい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

いつでも、_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の現在の状態はインデックスファイルにキャッシュされているため、「コミットされる変更」としてリストされています。file.txtにはインデックスに反映されていない作業ディレクトリの変更があるため、「変更されたが更新されていない」とマークされています。この時点で「git commit」を実行すると、closing.txt(新しい内容を含む)を追加するコミットが作成されますが、file.txtは変更されません。

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

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

次は何?

この時点で、あらゆるGitコマンドのmanページを読むのに必要なすべてを知っているはずです。良い出発点としては、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