2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gitの内部構造を理解する — コミット・ツリー・blobオブジェクトの仕組み

2
Posted at

この記事で伝えること

git commitgit log は毎日使うのに、「Gitの中で何が起きているか」はあまり意識したことがない、という方は多いと思います。
この記事では、Gitが内部でどのようにデータを管理しているかを解説します。内部構造を知ることで、リベース・マージ・チェリーピックの動作が直感的に理解でき、トラブル時の対処もしやすくなります。

Gitの基本 — 「差分」ではなく「スナップショット」

まず大前提として、GitはSVNのような差分管理ツールではありません。コミットのたびに「その時点のファイルツリー全体のスナップショット」を記録しています(同一内容のファイルは共有して重複しない)。

この設計が、ブランチの切り替えや履歴の巻き戻しを高速にしている理由です。

Gitオブジェクトの4種類

Gitは内部で .git/objects/ ディレクトリにオブジェクトをハッシュ(SHA-1)で保存します。種類は4つです。

オブジェクト 役割
blob ファイルの内容そのもの
tree ディレクトリ構造(blobやtreeへの参照)
commit スナップショット(treeへの参照 + メタ情報)
tag 注釈付きタグ(commitへの参照)

具体的な構造を見てみる

# 試しに新規リポジトリを作って1ファイルをコミット
mkdir git-internal-demo && cd git-internal-demo
git init
echo "Hello, Git!" > hello.txt
git add hello.txt
git commit -m "first commit"

この時点で .git/objects/ に何があるか確認します。

find .git/objects -type f
# 例:
# .git/objects/8a/b686eafeb1f44702738c8b0f24f2567c36da6d  ← blob
# .git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904  ← tree
# .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391  ← commit

blobオブジェクト

blobはファイルの「中身」だけを持ちます。ファイル名すら持ちません。

git cat-file -t 8ab686ea   # → "blob"
git cat-file -p 8ab686ea   # → "Hello, Git!"

同じ内容のファイルは、名前が違っても同一blobを参照します。 これが重複排除の仕組みです。

treeオブジェクト

treeはディレクトリを表し、「ファイル名 + パーミッション + blobへのハッシュ参照」を持ちます。

git cat-file -p HEAD^{tree}
# 100644 blob 8ab686ea...  hello.txt

サブディレクトリがある場合は、treeが別のtreeを参照します。これがファイルシステムのディレクトリ構造に対応しています。

commitオブジェクト

commitは以下の情報を持ちます:

git cat-file -p HEAD
# tree 4b825dc6...        ← そのコミット時点のルートtree
# parent e6c3a7f9...      ← 1つ前のcommitのハッシュ
# author  ...
# committer ...
# 
# first commit

parent が前のコミットを指すことで、コミット履歴はリンクリスト(連鎖)になっています

ブランチとは何か

多くの人が「ブランチ=コピー」とイメージしますが、実際は違います。
ブランチは特定のcommitハッシュを指す「ポインタ(テキストファイル)」に過ぎません。

cat .git/refs/heads/main
# e3f0a1c2...  ← mainブランチが指すcommitのSHA-1

git checkout -b feature をしても、新しいcommitは作られません。同じコミットを指す新しいポインタが追加されるだけです。だからブランチの作成が一瞬で終わります。

HEADとは何か

HEAD は「今いるブランチ(またはコミット)」を指す特別なポインタです。

cat .git/HEAD
# ref: refs/heads/main   ← mainブランチを経由してcommitを参照

git checkout でブランチを切り替えると、この HEAD ファイルの内容が変わります。detached HEAD 状態はブランチを経由せず直接commitハッシュを書いている状態です。

コミットをやり直す仕組み(resetとrebase)

内部構造を知ると、git reset も怖くなくなります。

# HEAD~1 まで戻る(1つ前のcommitをHEADにする)
git reset --soft HEAD~1

これは「HEAD が指すcommitを1つ前に変える」だけです。ファイルは変わりません(--softの場合)。

git rebase は「別のcommitのツリーを親としてコピーを作り直す」操作です。commitハッシュが変わるのはそのためです。

筆者の考え・所感

Gitを使い始めた頃、私は「コミット=差分の記録」だと思っていました。
差分管理なら何十回もコミットすると遅くなりそう……と漠然と不安だったのですが、スナップショット方式と知って腑に落ちました。内容が同じファイルはblobを共有するので、軽量なのです。

ブランチが「ポインタ」だと知ったときの衝撃は大きかったです。「git branch feature で何十MBもコピーされる」と思っていたのが、実際にはたった数十バイトのファイルが増えるだけだったとわかったとき、Gitへの信頼感がぐっと上がりました。

内部構造を知ると、git reflog(削除されたcommitのハッシュを探すコマンド)が「失われたcommitを救える理由」も直感的にわかります。オブジェクトはGCが走るまで .git/objects/ に残っているからです。

まとめ

  • Gitは差分でなくスナップショットを保存する
  • データは blob(ファイル内容)・tree(ディレクトリ)・commit(スナップショット) の3種のオブジェクトで表現される
  • ブランチは「commitハッシュを指すポインタファイル」に過ぎず、切り替えコストはほぼゼロ
  • 内部構造を知ることで、resetrebase の動作が直感的に理解できるようになる
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?