CH 20 / 75第3部 Gitの内部構造 ── なぜそう動くのか

.git フォルダの中身ツアー

このページで分かること

  • .git フォルダの中身を、役割ごとに5つに分けて説明できる
  • ステージ(インデックス)の実体がどのファイルかが分かる
  • git gc で保存の形がまとめ直されることが分かる
  • .git の中を手で触ってよいもの・いけないものが分かる
このページの目次11 items

第2部の07で git init をしたとき、.git というフォルダが1つできました。第3部では、その中の HEAD・objects・refs を開けてきました。このページでは残りも含めて一通り見て回り、第2部・第3部の操作が .git のどこに書き込まれていたのかを整理します。

全体の地図#

text15 lines
cd ~/git-practice
ls -F .git

# 出力:
# COMMIT_EDITMSG
# config
# description
# HEAD
# hooks/
# index
# info/
# logs/
# objects/
# ORIG_HEAD
# refs/

末尾に / が付いているものがフォルダです。第2部の07で git init した直後と比べると、COMMIT_EDITMSG・index・logs/・ORIG_HEAD が増えています。どれも、そのあとの操作で作られたものです。

.git フォルダの中身を、記録の本体(objects)、名前(HEAD・refs・packed-refs)、ステージ(index)、操作の記録(logs・ORIG_HEAD・COMMIT_EDITMSG)、設定(config・hooks・info/exclude・description)の5つの役割に分けた図

役割ごとに、1つずつ見ていきます。

1. 記録の本体:objects/#

第3部の17で見た blob・tree・commit・tag が保存されている場所です。履歴のすべてがここにあります。 ほかのファイルは、ここに保存されたオブジェクトを「名前で呼ぶ」「今どれを見ているか覚える」ための補助です。

git gc:保存の形をまとめ直す#

text6 lines
git count-objects -v

# 出力(一部):
# count: 44
# size: 176
# in-pack: 0

今は 44 個のオブジェクトが、それぞれ1つずつのファイルとして保存されています(count)。ここで git gc を実行します。

text8 lines
git gc
git count-objects -v

# 出力(一部):
# count: 0
# size: 0
# in-pack: 44
# packs: 1

44 個のオブジェクトが、objects/pack/ の中の1つのファイルにまとめられました(in-pack)。第3部の16で触れた「似た中身どうしを差分で詰める」のは、このまとめ直しのときです。

git gc を自分で実行する必要は、ふつうはありません。オブジェクトがたまると、Git が適当なときに自動で行います。まとめ直されても、git cat-file などのコマンドからの見え方は何も変わりません。

2. 名前:HEAD・refs/・packed-refs#

第3部の18・19で見たとおり、HEAD は今いる場所を、refs/ の下のファイルはブランチとタグを表します。

git gc のあとで refs/ を見ると、様子が変わっています。

text14 lines
ls .git/refs/heads .git/refs/tags

# 出力:
# .git/refs/heads:
#
# .git/refs/tags:

cat .git/packed-refs

# 出力:
# # pack-refs with: peeled fully-peeled sorted
# b505e8e8f92abb8142a6ca486600de9100f98e42 refs/heads/main
# 98206d50345d4cba6f0ef0f0908a213aa1f6a61f refs/tags/v1.0
# ^b505e8e8f92abb8142a6ca486600de9100f98e42

refs/heads/main のファイルが消え、packed-refs という1つのファイルにまとめて書かれています。第3部の19で「ファイルがない場合がある」と書いたのは、この状態のことです。main ブランチは消えていません。次にコミットすると、Git は refs/heads/main をまた作ります。

^ で始まる行は、直前のタグ v1.0 がたどり着くコミットの ID です。タグ → tag オブジェクト → コミット、の2段を毎回たどらずに済むよう、控えておいたものです。

3. ステージ:index#

第2部の08で見たステージの実体は、index という1つのファイルです。中身は読みやすい形ではありませんが、コマンドで一覧できます。

text7 lines
git ls-files --stage

# 出力:
# 100644 64442fd642ae90b9c4be6583fa758d8b2e884f66 0 .gitignore
# 100644 c0fa007ec11499026d24c4028de6083e274444fc 0 recipes/cake.txt
# 100644 a2a4dd6683d9012c00d693af6c5b1f02f225a091 0 shopping.txt
# 100644 d3038db570e4c542737f1ab3a559741e5af3628f 0 todo.txt

ファイル名と blob の ID の一覧で、第3部の17の tree とよく似ています。ステージとは「次のコミットの tree の下書き」です。git add は、ファイルの中身を blob として objects に保存し、この下書きの ID を書き換える操作でした。git commit は、この下書きから tree を作って記録します。

4. 操作の記録:logs/・ORIG_HEAD・COMMIT_EDITMSG#

名前 中身 使われる場面
logs/ HEAD とブランチが動いた記録 第2部の15の reflog
ORIG_HEAD reset などで大きく動く直前に HEAD がいたコミットの ID 直前の操作を取り消すとき
COMMIT_EDITMSG 最後に書いたコミットメッセージ エディタでメッセージを書くとき
text4 lines
git log --oneline -n 1 ORIG_HEAD

# 出力:
# f72abeb 買い物メモにパンを追加

ORIG_HEAD は、第2部の15で reset --hard を実行する前に HEAD がいた場所を覚えています。git reset --hard ORIG_HEAD で「直前の reset をなかったことにする」使い方ができますが、reflog のほうが何回前でもたどれるので、このガイドでは reflog を使います。

5. 設定:config・hooks/・info/exclude・description#

text10 lines
cat .git/config

# 出力(例):
# [core]
#   repositoryformatversion = 0
#   filemode = true
#   bare = false
#   logallrefupdates = true
#   ignorecase = true
#   precomposeunicode = true

config は、第2部の06で --local として紹介した、このリポジトリだけの設定のファイルです。git config user.email … のように --global を付けずに設定すると、ここに書き込まれます。行の内容は OS によって少し違います。

名前 役割
hooks/ コミットの前後などに自動で処理を挟む仕掛けを置く場所。最初は .sample で終わる見本だけが入っている(第5部の35)
info/exclude このリポジトリの自分の手元だけで効く .gitignore。コミットされないので、自分だけが除外したいファイルを書く
description 一部の閲覧ツール用の説明文。ふつうは使わない

手で触ってよいもの・いけないもの#

触ってよいもの 触らないもの
info/exclude(除外したいパターンを書く) objects/・refs/・packed-refs・HEAD・index
hooks/(第5部の35の手順で) logs/
config(git config コマンド経由が安全)

右の列のファイルは、Git が互いの整合を取りながら書き換えています。手で書き換えると、Git のコマンドが前提にしている関係が崩れ、復旧が難しくなります。読むのは自由、書き換えはコマンドで、が原則です。

よくある疑問#

Q. .git フォルダをコピーすれば、リポジトリを丸ごと複製できますか?
A. できます。プロジェクトのフォルダを .git ごとコピーすれば、履歴も含めた完全な複製になります。Git のリポジトリは、このフォルダ1つで完結しているからです。別のPCとの受け渡しをコマンドで行う方法は、第6部で扱います。

まとめ#

  • .git の中身は、記録の本体・名前・ステージ・操作の記録・設定の5つに分けられる
  • 履歴のすべては objects/ にあり、ほかはそれを名前で呼ぶ・今どこにいるかを覚えるための補助
  • ステージの実体は index で、次のコミットの tree の下書きになっている
  • git gc はオブジェクトと参照を詰め直すが、コマンドからの見え方は変わらない
  • .git の中は、読むのは自由、書き換えはコマンドで行う

次のページ#

21. ブランチとは何か ── ここから第4部です。第3部で見た「ブランチは41バイトのファイル」という正体をもとに、ブランチを正しく捉え直します。