Git
Chapters ▾ 2nd Edition

10.3 Gitの内側 - Gitの参照

Gitの参照

git log 1a410e のように実行すれば、すべての歴史に目を通すことができます。しかし、歴史を辿ってすべてのオブジェクトを探しだすには、 1a410e が最後のコミットであることを覚えていなければならないのは変わりません。 SHA-1ハッシュ値をシンプルな名前で保存できれば、生のSHA-1ハッシュ値ではなく、その名前をポインタとして使用できます。

Gitでは、これは “参照” ないしは “refs” と呼ばれます。 .git/refs ディレクトリを見ると、SHA-1ハッシュ値を含むファイルがあることが分かります。 現在のプロジェクトでは、このディレクトリにファイルはありませんが、シンプルな構成のディレクトリがあります。

$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f

参照を新しく作成して、最後のコミットがどこかを覚えやすくします。技術的には、以下のように簡単に行えます。

$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master

これで、Gitコマンドで、SHA-1ハッシュ値の代わりに、たった今作成したhead参照(ブランチ)を使えるようになりました。

$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

参照ファイルを直接変更するのは推奨されません。 その代わり、参照をより安全に更新するためのコマンド update-ref が、Gitには用意されています。

$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9

これは、Gitにおいて、基本的にブランチとは一連の作業の先頭を指す単純なポインタや参照であるということを表しています。 2つ目のコミットが先頭になるブランチを作るには、次のようにします。

$ git update-ref refs/heads/test cac0ca

作成されたブランチは、さきほど指定したコミット以前の作業のみを含むことになります。

$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

この時点で、Gitのデータベースは概念的には以下の図のように見えます。

ブランチのヘッドへの参照を含むGitディレクトリオブジェクト
Figure 151. ブランチの先頭への参照を含むGitディレクトリオブジェクト

git branch (ブランチ名) のようなコマンドを実行すると、あなたが作りたいと思っている新しい参照が何であれ、基本的にGitは update-ref コマンドを実行して、いま自分がいるブランチ上の最後のコミットのSHA-1ハッシュをその参照に追加します。

HEAD

では、git branch (ブランチ名) を実行したときに、Gitはどうやって最後のコミットのSHA-1ハッシュを知るのでしょうか? 答えは、HEADファイルです。

HEADファイルは、現在作業中のブランチに対するシンボリック参照です。 通常の参照と区別する意図でシンボリック参照と呼びますが、これには、一般的にSHA-1ハッシュ値ではなく他の参照へのポインタが格納されています。 ファイルの中身を見ると、通常は以下のようになっています。

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

git checkout test を実行すると、Git はこのようにファイルを更新します。

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

git commit を実行するとコミットオブジェクトが作られますが、そのときコミットオブジェクトの親として、HEADが指し示す参照先のSHA-1ハッシュ値が指定されます。

このファイルを直に編集することもできますが、symbolic-ref と呼ばれる、編集を安全に行うためのコマンドが存在します。 このコマンドを使ってHEADの値を読み取ることができます。

$ git symbolic-ref HEAD
refs/heads/master

HEADの値を設定することもできます。

$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test

refs の形式以外では、シンボリック参照を設定することはできません。

$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/

タグ

これで Git の主要な三つのオブジェクトを見終わったわけですが、タグという4つ目のオブジェクトがあります。 タグオブジェクトは、コミットオブジェクトに非常によく似ており、タガー、日付、メッセージ、ポインタを格納しています。 主な違いは、タグオブジェクトは通常、ツリーではなくコミットを指しているということです。 タグオブジェクトはブランチに対する参照に似ていますが、決して変動しません – 常に同じコミットを指しており、より分かりやすい名前が与えられます。

Git の基本 で述べましたが、タグには2つのタイプがあります。軽量 (lightweight) 版と注釈付き (annotated) 版です。 次のように実行すると、軽量版のタグを作成できます。

$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d

これが軽量版のタグのすべてです。つまり決して変動しない参照なのです。 一方、注釈付き版のタグはもっと複雑です。 注釈付き版のタグを作ろうとすると、Gitはタグオブジェクトを作った上で、コミットを直接指す参照ではなく、そのタグを指す参照を書き込みます。 注釈付き版のタグを作ると、これが分かります( -a オプションで、注釈付き版のタグを作るよう指定しています)。

$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'

作られたオブジェクトのSHA-1ハッシュ値はこうなります。

$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2

ここで、そのSHA-1ハッシュ値に対して cat-file コマンドを実行します。

$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
object 1a410efbd13591db07496601ebc7a059dd55cfe9
type commit
tag v1.1
tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48:58 2009 -0700

test tag

object の項目が、上でタグ付けしたコミットのSHA-1ハッシュ値を指していることに注意してください。 また、この項目が必ずしもコミットだけをポイントするものではないことも覚えておいてください。あらゆるGitオブジェクトに対してタグを付けることができます。 例えば Git のソースコードリポジトリでは、メンテナが自分のGPG公開鍵をブロブオブジェクトとして追加し、そのオブジェクトにタグを付けています。 Gitリポジトリのクローン上で、以下のコマンドを実行すると公開鍵を閲覧できます。

$ git cat-file blob junio-gpg-pub

Linux カーネルのリポジトリもまた、object 項目でコミット以外を指しているタグオブジェクトを持っています。 これは最初のタグオブジェクトであり、最初にソースコードをインポートしたときの初期ツリーオブジェクトを指しています。

リモート

これから見ていく3つ目の参照のタイプはリモート参照です。 リモートを追加してそこにプッシュすると、Gitはそのリモートへ最後にプッシュした値を、ブランチ毎に refs/remotes へ格納します。 例えば、 origin というリモートを追加して、そこに master ブランチをプッシュしたとします。

$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.git
  a11bef0..ca82a6d  master -> master

ここで refs/remotes/origin/master ファイルの中身を確認してみてください。最後にサーバーと通信したときに origin リモートの master ブランチが何であったかがわかるはずです。

$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949

リモート参照は、特に読み取り専用とみなされる点において、ブランチ(refs/heads にある参照)とは異なります。 リモート参照に対して git checkout を行うことはできますが、GitはHEADの参照先をそのリモートにすることはなく、したがって commit コマンドでリモートを更新することもできません。 Gitはリモート参照を一種のブックマークとして管理します。つまり、最後に通信したとき、向こうのサーバー上でリモートブランチが置かれていた状態を指し示すブックマークということです。