参照(refs)とブランチの実体
このページで分かること
- ブランチの実体が、コミットの ID を書いた41バイトのファイルだと分かる
- ブランチを作る・進めるときに、ファイルの上で何が起きているか説明できる
- HEAD・ブランチ・タグが、どう指し合っているかが分かる
- refs のファイルを手で書き換えてはいけない理由が分かる
このページの目次10 items
第3部の18で、HEAD の中身が ref: refs/heads/main だと見ました。このページでは、その先にある refs/heads/main を開けます。ここが分かると、第4部の21でブランチを「コミットを指す名前付きのポインタ」と説明する意味が、そのまま見えるようになります。
参照(refs)とは#
コミットの ID は40桁あり、人が覚えて使うには向きません。そこで Git は、ID に名前を付けて呼べるようにしています。この名前の仕組みを参照(reference、略して ref)と呼び、.git/refs フォルダにまとめて置いています。

refs/heads/ の下にあるのがブランチ、refs/tags/ の下にあるのがタグです。どちらも、中身はIDを1行書いただけのファイルです。
ブランチは41バイトのファイル#
cd ~/git-practice
cat .git/refs/heads/main
# 出力:
# b505e8e8f92abb8142a6ca486600de9100f98e42
wc -c .git/refs/heads/main
# 出力:
# 41 .git/refs/heads/mainwc -c はファイルの大きさをバイト数で数えるコマンドです。40桁の ID と、行の終わりを表す改行1文字で、ちょうど41バイト。main ブランチの正体は、これで全部です。
ブランチの中にファイルや履歴が入っているわけではありません。ブランチは「最新のコミットはこれ」と1つの ID を指しているだけで、それより前の履歴は、第3部の17で見たとおりコミットどうしが parent でつながっています。
ブランチを作る = ファイルを1つ作る#
試しにブランチを1つ作ってみます(ブランチの操作は第4部の22で詳しく扱います)。
git branch experiment
ls .git/refs/heads
# 出力:
# experiment
# main
cat .git/refs/heads/experiment
# 出力:
# b505e8e8f92abb8142a6ca486600de9100f98e42experiment という41バイトのファイルが1つ増え、中身は main と同じ ID です。ファイルは1つもコピーされていません。 ブランチをいくつ作っても一瞬で終わり、容量もほとんど増えないのはこのためです。
確かめたら消しておきます。
git branch -d experiment
# 出力:
# Deleted branch experiment (was b505e8e).ブランチが進む = ファイルの中身が書き換わる#
コミットすると、HEAD が指すブランチのファイルが、新しいコミットの ID に書き換わります。その記録は .git/logs/ に残っていて、第2部の15の reflog はここを読んでいます。
tail -n 1 .git/logs/refs/heads/main
# 出力:
# 29c43994091deca933abfcfca9795596789675ee b505e8e8f92abb8142a6ca486600de9100f98e42 Taro Yamada <taro@example.com> 1792630800 +0900 commit: ケーキのレシピを追加左が書き換える前の ID(第2部の15の Revert)、その次が書き換えたあとの ID(第3部の17のケーキのレシピ)です。「main の中身を 29c4399… から b505e8e… に書き換えた」という1行が、コミットの正体の半分です。残りの半分は、第3部の17で見たオブジェクトを保存することです。
タグも同じ仕組み#
cat .git/refs/tags/v1.0
# 出力:
# 98206d50345d4cba6f0ef0f0908a213aa1f6a61f
git cat-file -t 98206d5
# 出力:
# tag第3部の17で作ったタグ v1.0 のファイルは、tag オブジェクトの ID を持っています。tag オブジェクトがさらにコミットを指す、という2段です。
ブランチとタグの違いは、動くかどうかです。ブランチはコミットのたびに先へ進みますが、タグは作ったときのコミットを指したまま動きません。だからタグは「v1.0 を出したときの状態」のような目印に使われます(第5部の30)。
一覧で確かめる:git show-ref#
git show-ref
# 出力:
# b505e8e8f92abb8142a6ca486600de9100f98e42 refs/heads/main
# 98206d50345d4cba6f0ef0f0908a213aa1f6a61f refs/tags/v1.0すべての参照と、それぞれが指す ID が一覧で表示されます。第6部では、ここに refs/remotes/ で始まる参照が加わります。
手で書き換えてはいけない理由#
仕組みが分かると、ファイルを直接書き換えたくなるかもしれません。ですが、refs のファイルは Git のコマンドで操作してください。
- ファイルがない場合がある:参照が増えると、Git は
.git/packed-refsという1つのファイルにまとめて保存します(第3部の20)。そのときrefs/heads/mainのファイルは消えますが、main ブランチはなくなっていません - 記録が残らない:手で書き換えると
.git/logs/に記録されず、reflog で戻れなくなります - 保存の形式が変わる予定がある:将来の Git 3.0 では、新しく作るリポジトリの参照の保存形式が reftable という別の形式に変わる予定です。その場合、ここで見たような1つずつのファイルにはなりません(出典:Git の BreakingChanges)
保存の形が変わっても、「参照は ID に付けた名前で、ブランチは最新のコミットを指す名前」という考え方は変わりません。このページで確かめたのは、その考え方のほうです。
よくある疑問#
Q. refs/heads/feature/login のように、スラッシュを含むブランチ名はどう保存されますか?
A. フォルダとして保存されます。.git/refs/heads/feature/ フォルダの中に login というファイルができます。ブランチ名をスラッシュで分類できるのは、この仕組みのおかげです。
まとめ#
- 参照(ref)は、コミットの ID に付けた名前。
.git/refsに置かれる - ブランチの実体は、コミットの ID を1行書いた41バイトのファイル
- ブランチを作るとファイルが1つ増え、コミットするとその中身が書き換わる
- タグは作ったときのコミットを指したまま動かない。ブランチは進む
- 参照は保存の形式が変わることがあるので、ファイルを手で触らずコマンドで操作する
次のページ#
20. .git フォルダの中身ツアー ── ここまでに開けた HEAD・objects・refs 以外も含めて、.git の中を一通り見て回ります。