章 ▾ 第2版

10.3 Gitの内部構造 - Gitの参照

Git の参照

コミット `1a410e` から辿れるリポジトリの履歴を見たい場合、たとえば `git log 1a410e` のように実行すればその履歴を表示できます。しかし、`1a410e` がその履歴の開始点として使いたいコミットであることを覚えておかなければなりません。代わりに、そのSHA-1値を単純な名前で保存できるファイルがあれば、生のSHA-1値ではなくその単純な名前を使えるので、より簡単になります。

Gitでは、これらの単純な名前を“参照 (references)”または“ref”と呼びます。これらのSHA-1値を含むファイルは、`.git/refs` ディレクトリにあります。現在のプロジェクトでは、このディレクトリにファイルはありませんが、単純な構造が含まれています。

$ 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値の代わりに、今作成したヘッド参照を使うことができます。

$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 Third commit
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit

参照ファイルを直接編集することは推奨されません。代わりに、参照を更新したい場合は、Gitはより安全なコマンド `git update-ref` を提供しています。

$ 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 directory objects with branch head references included
図176. ブランチヘッド参照を含むGitディレクトリオブジェクト

`git branch ` のようなコマンドを実行すると、Gitは基本的に `update-ref` コマンドを実行して、現在いるブランチの最後のコミットのSHA-1を、新しく作成したい参照に追加します。

HEAD

さて、`git branch ` を実行するとき、Gitはどのようにして最後のコミットのSHA-1を知るのでしょうか?その答えはHEADファイルにあります。

通常、HEADファイルは現在いるブランチへのシンボリック参照です。シンボリック参照とは、通常の参照とは異なり、他の参照へのポインタを含むことを意味します。

しかし、ごく稀にHEADファイルがGitオブジェクトのSHA-1値を含む場合があります。これは、タグ、コミット、またはリモートブランチをチェックアウトするときに発生し、リポジトリが"detached HEAD"状態になります。

このファイルを見ると、通常は次のような内容が表示されます。

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

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

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

`git commit` を実行すると、コミットオブジェクトが作成され、そのコミットオブジェクトの親としてHEAD内の参照が指すSHA-1値が指定されます。

このファイルを直接編集することもできますが、ここでもより安全なコマンドが存在します: `git 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の主要な3つのオブジェクトタイプ(blobtreecommit)について説明が終わりましたが、4つ目があります。タグオブジェクトはコミットオブジェクトに非常に似ており、タグ付け者、日付、メッセージ、およびポインタを含みます。主な違いは、タグオブジェクトは通常、treeではなくコミットを指すことです。ブランチ参照に似ていますが、決して移動せず、常に同じコミットを指し、よりわかりやすい名前を付けます。

Git の基本で説明したように、タグには注釈付きタグと軽量タグの2種類があります。軽量タグは次のようにして作成できます。

$ 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値に対して `git cat-file -p` を実行してみましょう。

$ 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

オブジェクトのエントリが、タグ付けしたコミットのSHA-1値を指していることに注目してください。また、コミットを指す必要はなく、任意のGitオブジェクトにタグを付けられることにも注目してください。たとえば、Gitのソースコードでは、メンテナが自身のGPG公開鍵をblobオブジェクトとして追加し、それにタグを付けています。Gitリポジトリのクローンでこれを実行すると、公開鍵を表示できます。

$ git cat-file blob junio-gpg-pub

Linuxカーネルのリポジトリにも、コミットを指さないタグオブジェクトがあります。最初に作成されたタグは、ソースコードのインポートにおける最初のtreeを指しています。

リモート

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はそれらを、それらのブランチがそれらのサーバ上にあった最後の既知の状態へのブックマークとして管理します。

scroll-to-top