Gitの参照についてまとめました。また別記事にてGitの内側について記載しています。
Gitの内側についておさらい
- 「git init」すると「.git」ディレクトリができるがその中についてがテーマ
- 普段は使わない配管コマンドと呼ばれる下位レベルのコマンドがある
- 4つのオブジェクトがある
- blobオブジェクト(ファイルに相当)
- treeオブジェクト(ディレクトリに相当)
- commitオブジェクト(commit情報を保持)
- tagオブジェクト(Tagを保持)
Gitの参照って?
ここで取り上げるGitの参照は「HEAD」ファイルと「.git/refs」配下のことを示します。「git init」した直後「HEAD」ファイルは「refs/heads/master」を参照し(初期化直後はなにも存在しない)、「.git/refs」配下には、headsとtagsディレクトリがあります。ブランチにcommitするとheads配下にブランチ名のファイルができ、HEADファイルはheads配下のブランチ名を参照します。
# masterにcommit後 # HEADファイル ref: refs/heads/master # refs配下の構造 └── refs ├── heads │ └── master └── tags
Gitの参照の種類
Gitの参照の種類は次の通りです。
- heads:作業中ブランチのcommitを参照
- tags:commitをわかりやすくする為の名前付け用
- remotes:リモートブランチのcommitを参照
- HEAD:作業中のブランチに対するシンボリック参照。HEADは基本的にheadsを参照しているので、他の参照と区別する為シンボリック参照と呼ばれます
動作確認の為の事前準備
次のような3つのcommitがあるリポジトリを用意しremoteへpushしておきます。
% git log --pretty=oneline master 1f36229bc26825222b191dcd63929392d9d8e2fd third commit 543ee947f5a865e1a998f793120667c3bf89fc80 second commit 23391397b4316d334e9293c4f7d1d601c1f24c4c first commit % git remote add origin git@github.com:taisa007/internal-git.git
次からはこのリポジトリを利用します。クローンする場合はこちらを利用してください。
構成
構成は次の通りです。「.git/refs」ディレクトリ配下にそれぞれheads, remotes, tagsというディレクトリが作られています。
% tree .git └── refs ├── heads │ └── master ├── remotes │ └── origin │ └── master └── tags
Gitの参照の解説
「refs」配下にあるファイルはそれぞれ最後のcommitをファイルに記録しています。例えば現在masterは「third commit」にいますが「git log --pretty=oneline master」とすると次のようなcommit履歴が確認できます。
% git log --pretty=oneline master 1f36229bc26825222b191dcd63929392d9d8e2fd third commit 543ee947f5a865e1a998f793120667c3bf89fc80 second commit 23391397b4316d334e9293c4f7d1d601c1f24c4c first commit # このコマンドでも上記と同じ結果となる % git log 1f36229bc26825222b191dcd63929392d9d8e2fd 1f36229bc26825222b191dcd63929392d9d8e2fd third commit 543ee947f5a865e1a998f793120667c3bf89fc80 second commit 23391397b4316d334e9293c4f7d1d601c1f24c4c first commit # masterファイルの中を確認 % cat .git/refs/heads/master 17fb14ac7c9493751cff838ba0c22897582406a5 # 現在の位置のハッシュ値が書かれている
これはハッシュを指定した次のコマンド「git log --pretty=oneline 17fb14ac7c9493751cff838ba0c22897582406a5」と同じ結果です。「.git/refs/heads/master」ファイルにcommit情報が記述されている為同じ結果となります。
上記のことから「.git/refs/heads/master」にcommitのハッシュ値を指定することでmasterのheadが変更可能です。ただし直接操作は非推奨(通常操作することはありませんが)なので参照を変更する時は次のコマンドを使います。
# 参照を変更 % git update-ref refs/heads/master 543ee947f5a865e1a998f793120667c3bf89fc80 # headが変わった % git log --pretty=oneline master 543ee947f5a865e1a998f793120667c3bf89fc80 second commit 23391397b4316d334e9293c4f7d1d601c1f24c4c first commit % cat .git/refs/heads/master 543ee947f5a865e1a998f793120667c3bf89fc80
「second commit」に変わりました。また次のコマンドはそこからブランチを切ることができます。
% git update-ref refs/heads/test 543ee947f5a865e1a998f793120667c3bf89fc80 # testブランチができた % git branch master test
「.git/refs」ディレクトリの構成を見ていきましたので、次は「HEAD」ファイルと「.git/refs」を構成する各参照について解説します。
HEAD
HEADファイルは現在作業中のブランチを参照しています。「git branch (ブランチ名)」を実行したときは、HEADからブランチが切られます。HEADファイルは 「.git」直下に配置されます。
.git ├── COMMIT_EDITMSG ├── FETCH_HEAD ├── HEAD ← ココ ├── ORIG_HEAD ├── config ├── description ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── prepare-commit-msg.sample │ └── update.sample ・ ・ ・ ## HEADファイルの中を確認 % cat .git/HEAD ref: refs/heads/master ← refs/heads配下を参照している
「git checkout test」を実行した内容は次の通りです。
% git checkout test cat .git/HEAD ref: refs/heads/master
このHEADファイルを確認したり変更するには次のコマンドを使います。
# 確認 % git symbolic-ref HEAD refs/heads/test # 変更 % git symbolic-ref HEAD refs/heads/master % git symbolic-ref HEAD refs/heads/master
tags
tagはGitのオブジェクトでもあります。tagオブジェクトはcommitオブジェクトに似ていますが、treeではなくcommitを指し示します。また、tagには軽量 (lightweight) 版と注釈付き (annotated) 版の2つのタイプがあります。
軽量 (lightweight) 版
- tagをつけられる
- commitオブジェクトにtagがつく
- tagオブジェクトではない
# tagを付ける % git update-ref refs/tags/v1.0 543ee947f5a865e1a998f793120667c3bf89fc80 # 確認 % git tag -n v1.0 second commit
注釈付き (annotated)版
- tagとコメントがつけられる
- tagオブジェクトになる
# tagとコメント付き % git tag -a v1.1 23391397b4316d334e9293c4f7d1d601c1f24c4c -m 'test tag' % git tag -n v1.0 second commit v1.1 test tag ← コメントが付く % cat .git/refs/tags/v1.0 543ee947f5a865e1a998f793120667c3bf89fc80 ← second commitのハッシュ % cat .git/refs/tags/v1.1 0d69685716366ce02ea4452c8e47c64e779a40d8 ← 新しいハッシュ % git cat-file -p d4f4075dfccb64a4c1b1966b6c7aa853f526c33a object 23391397b4316d334e9293c4f7d1d601c1f24c4c ← first commitのハッシュ type commit tag v1.1 tagger sato.masaki <sato.masaki@aainc.co.jp> 1435421112 +0900 test tag
軽量版は「second commit」のハッシュ値になのに対し、注釈付きは別のハッシュの値となっています。これはcommitに対する直接的な参照ではなく、tagオブジェクトがつくられた為です。また、refsの中は下記の通りです。
└── refs ├── heads │ ├── master │ └── test ├── remotes │ └── origin │ └── master └── tags ├── v1.0 ← タグ追加 └── v1.1 ← タグ追加
remotes
remoteもほぼ上記内容と同様の仕組みとなっています。では試しにtestブランチをremoteにプッシュしてみます。するとさっきまでなかった「.git/refs/remotes/origin/test」が追加されます。またその中身はtestブランチのHEADのハッシュです。これはつまりremoteを確認することで最後にoriginと通信したときの情報を取得できるということです。
└── refs ├── heads │ ├── master │ └── test ├── remotes │ └── origin │ ├── master │ └── test ← 追加 └── tags ├── v1.0 └── v1.1 % cat .git/refs/remotes/origin/test 543ee947f5a865e1a998f793120667c3bf89fc80 % cat .git/refs/heads/test 543ee947f5a865e1a998f793120667c3bf89fc80 # 同じハッシュとなる
まとめ
- 参照にはheadとremoteとtagがある
- HEADファイルは参照を参照し現在のブランチのシンボリックリンクを参照している
- tagには軽量版と注釈付き版があり注釈付きの方がtagオブジェクトとなる
- remoteは最後にサーバと通信したときのハッシュを保持している
Gitの参照と参照系の下位コマンドが確認できました。普段何気なく使っているコマンドの内側で何が行なわれてるかが分かっておもしろいですね。
参考
- 10.3 Git Internals - Git References
- 10.3 Gitの内側 - Gitの参照日本語翻訳 (v2の方も翻訳が進んでました)
- こせきの技術日記