0
1

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 解剖

Last updated at Posted at 2025-11-27

はじめに

gitを使い倒している!addしてcommitしてmergeして...という人でも、その仕組みを知らないことも多い。今回はみんなお世話になっているgitの正体を暴いていく...

まずは、そのメカニズムを理解するのに必要なGit Objectについて調べ、その後コマンドがどのようにオブジェクトを操作しているのかを見ていく。

gitとは

言うまでもないだろうが、プログラムのソースコードなどの変更履歴を記録・追跡するための分散型バージョン管理システムである。

GitはOSのファイルシステムを活かして実装されたオブジェクトデータベースです。データベース内に作成される無数のコミットオブジェクトは、自分以外のコミットオブジェクトを参照していて、それを再帰的に辿ることで長い履歴のチェーンが構成されます。いわゆるハッシュツリーの一種。
(Git完全に理解してるひと向けコミットツリーこねこねコマンド一覧)

Git objects

gitは3つの主要なオブジェクトタイプ(blob, tree, commit)で構成され、オブジェクトチェーニングを用いてオブジェクトを連結する。また、committreeにリンクされ、treeはさらにblobにリンクされる。1つずつみていこう。

Blobオブジェクト

この節は、以下の記事をまとめたものである

それは何?

Blobは、サイズやオブジェクトタイプ(ここではblob)と共に保存される、対象ファイルのバイナリデータのことで、簡単に言えば、ファイルの内容をGitの内部表現に直したもの。

ここでいう「サイズ」は単にファイルサイズを指すものではない!
下の「フォーマット」の節で解説。

パス

.git/objects/

フォーマット

以下のようになっている

blob <size-of-blob-in-bytes>\0<file-binary-data>

このフォーマットは、gitオブジェクトタイプから始まる。今回で言えば、blobである。次に来るのが、blobのバイトサイズ(<size-of-blob-in-bytes>)で、これはフォーマット中のオブジェクトタイプやバイトサイズ、区切り文字、バイナリコンテンツを含めて計算される。そして次は\0に代表される, NULバイトが来る。これは、単に空のバイナリ00000000であり、区切り文字としてもふるまう。最後にファイルのバイナリコンテントが格納される。

blobが生成されるとき

gitは次のようなとき、新たなblobを生成する

  • git addによって非追跡対象ファイルをステージングしたとき
  • git addによって修正された、追跡対象のファイルをステージングしたとき
  • 既存ファイルの内容の変更をマージしたとき(すべてのマージが該当するわけではない)

git rebaseのような他のコマンドでも、既存の追跡対象ファイルを変更したときに限って、同様にblobの新規生成が行われる可能性がある。

blobの保存方法

まず、gitは未追跡ファイルをblobに変換し、サイズを計算しながら前節「フォーマット」で示した形式を生成する。
そうしたら、OpenSSL SHA Libraryを使ってSHA-1でblobをハッシュ化する。(これは、the blob hashやもっと一般的にはobject IDと呼ばれる。)
次に、Zlib Libraryを用いてblobを圧縮・デフレートする。
最後に、圧縮されたblobはgitのオブジェクトストア内の新しいファイルに書き込まれ、計算したSHA-1ハッシュを使用して名前が付けられる。

blobは削除されるのか

gitは、そのblobがリポジトリ内の他のすべてのオブジェクトから到達不能にならない限り、削除することはない。

Treeオブジェクト

この節は、以下の記事を参考にしている

それは何?

Treeは、blobを実際のファイルパス/名前および権限に関連付けるものであり、簡単に言うと複数のblobへの参照に名前をつけてまとめて管理するためのものである。
このtreeが無いと、gitはどの追跡対象ファイルがどのblobに対応しているか判別できない。というのも、先述した通りblobは純粋なファイルのバイナリであるので、ファイルシステムでのパスや名前に直接紐づけるための情報を含まないからである。
また、ツリーはディレクトリを表すものとも考えられる。これは、ファイルシステム上の特定のcommit済みフォルダを直接追跡するからではなく、特定の時点におけるファイルblobのリストとそれに対応するパス/名前および権限を紐づけるからである。

パス

.git/objects/

フォーマット

treeはメモリバッファ内でgitによって以下の形式で構築される

tree <size-of-tree-in-bytes>\0
<file-1-mode> <file-1-path>\0<file-1-blob-hash>
<file-2-mode> <file-2-path>\0<file-2-blob-hash>
...
<file-n-mode> <file-n-path>\0<file-n-blob-hash>

このフォーマットは、gitオブジェクトタイプから始まる。今回で言えば、treeである。次いでサイズ(<size-of-tree-in-bytes>)と区切り文字が来る。

次に、以下の3つの要素からなる、一連のcache entriesが来る。

1. ファイルモード(権限)
2. ファイルパス/名前
3. そのファイルのblobのSHA-1ハッシュ

これにより、gitは特定のblobとファイル名を紐づけることができ、treeは作業ディレクトリのサイズに応じて必要な数のblobのマッピングを保持できるようになった。

treeが生成されるとき

git addコマンドを用いて新規ファイルを追跡対象にするときや変更したファイルをステージングするとき、gitはまずblobを生成してローカルリポジトリに配置する。その後、gitはcache entry(実際には、.git/indexというごく普通のファイルだが)を生成する。これらには、ファイル名/パスや権限、blobのハッシュが格納される。

git commitを実行したとき、indexファイルのcache entriesからメモリ上に適切なtreeが構築される。これは、新しいcommitオブジェクトからルートツリーとして参照されることになり、各コミットはその時点での作業ディレクトリの内容のスナップショットであるルートツリーオブジェクトを指すことになる。

treeの保存方法

メモリ上に適切なtreeが構築されると、gitはOpenSSL SHA Libraryを使用してそのツリーのSHA-1ハッシュを計算する。
その後、gitはZlibを用いてツリーを圧縮し、loose objectとして保存する。(そのtreeは自身のSHA-1ハッシュを用いて命名される。)

treeは削除されるのか

そのツリーに関連付けられたコミットが孤立した場合に限り、ガベージコレクタによって削除される可能性がある。

Commitオブジェクト

この節は、以下の記事を参考にしている

それは何?

Commitは単一のtreeを指し、プロジェクトがその時点でどのような状態だったかをマークするものであり、タイムスタンプ、最後のcommitからの変更の作者、前のcommitへのポインタなどのメタ情報を含む。簡単に言うと、プロジェクトの特定時点における完全なスナップショットを表すオブジェクトである。

commitオブジェクトは、スナップショットを保存した人、いつ保存されたか、なぜ保存されたかという基本的な情報を保存する。

ここでいう「スナップショット」は、Gitが他のSCMシステム(Subversion、CVS、Perforce、Mercurialなど)とは異なり、Delta Storage(差分保存)ではなく、commitのたびにプロジェクトのすべてのファイルがどのように見えるかの完全なスナップショットをtree構造で保存することを指す。

パス

.git/objects/

フォーマット

commitオブジェクトの形式は単純で、その時点でのプロジェクトスナップショットのトップレベルtreeを指定し、author/committer情報(user.nameとuser.email設定とタイムスタンプを使用)、空行、そしてcommitメッセージが含まれる:

commit <size-of-commit-in-bytes>\0
tree <tree-sha>
parent <parent-commit-sha>
author <author-name> <author-email> <author-date-seconds> <author-timezone>
committer <committer-name> <committer-email> <committer-date-seconds> <committer-timezone>

<commit-message>

このフォーマットは、gitオブジェクトタイプから始まる。今回で言えば、commitである。次いでサイズ(<size-of-commit-in-bytes>)と区切り文字(NUL)が来る。

その後、以下の要素が含まれる:

1. treeハッシュ: そのcommitが表すルートtreeオブジェクトのSHA-1ハッシュ
2. parentハッシュ: 親commitのSHA-1ハッシュ(複数ある場合は複数行、初回commitの場合は無し)
3. author情報: 作者の名前、メールアドレス、タイムスタンプ、タイムゾーン
4. committer情報: commitした人の名前、メールアドレス、タイムスタンプ、タイムゾーン
5. 空行: メタデータとcommitメッセージの区切り
6. commitメッセージ: 実際のcommitメッセージ

commitが生成されるとき

commitは通常git commitによって作成され、親は通常現在のHEADで、treeは現在indexに保存されているコンテンツから取得される。

これは本質的にgit addgit commitコマンドを実行したときにGitが行うことで、変更されたファイルのblobを保存し、indexを更新し、treeを書き出し、トップレベルtreeとその直前のcommitを参照するcommitオブジェクトを書き込む。

具体的なcommit生成プロセス:

  1. Indexから tree 作成: ステージングエリアの状態からtreeオブジェクトを構築
  2. 親commitの特定: 現在のHEADが指すcommitを親として設定
  3. メタデータ収集: author/committer情報とタイムスタンプを取得
  4. commitオブジェクト作成: 上記の情報とcommitメッセージを組み合わせてcommitオブジェクトを生成

commitの保存方法

treeオブジェクトと同様に、commitオブジェクトもメモリ上で構築された後、以下の手順で保存される:

  1. commit-treeを呼び出して単一のtree SHA-1と、もしあれば直接的に先行するcommitオブジェクトを指定してcommitオブジェクトを作成する
  2. OpenSSL SHA Libraryを使用してcommitのSHA-1ハッシュを計算
  3. Zlibを用いてcommitオブジェクトを圧縮
  4. 計算したSHA-1ハッシュを用いて.git/objects/内にloose objectとして保存

commitは削除されるのか

commitオブジェクト自体は、そのcommitがbranchやtagなどの参照から到達不可能になった場合に限り、ガベージコレクションによって削除される可能性がある。ただし、commitは通常長期間保持され、git reflogなどの仕組みによって一定期間は参照可能な状態が保たれる。

Git Command Mechanism

Gitのオブジェクト(blobtreecommit)の仕組みを理解した上で、実際のコマンドがどのようにこれらのオブジェクトを操作しているかを見ていく。

1. git init コマンド(リポジトリ初期化)

git initとは

git initはGitリポジトリを初期化するコマンドで、新しいプロジェクトでGitによるバージョン管理を開始するときに最初に実行する。

内部動作

git initを実行すると、以下のディレクトリ構造が作成される:

.git/
├── HEAD
├── config
├── description
├── hooks/
├── info/
├── objects/
└── refs/
    ├── heads/
    └── tags/

各要素の役割

  • .git/objects/: 全てのGitオブジェクト(blob、tree、commit)が保存されるディレクトリ
  • .git/refs/: ブランチやタグなどの参照が保存されるディレクトリ
  • .git/HEAD: 現在チェックアウトしているブランチを指すポインタ
  • .git/config: プロジェクト固有の設定ファイル
  • .git/index: ステージングエリアの情報(初期状態では存在しない)

初期状態の確認

# リポジトリの初期化
git init

# .gitディレクトリの確認
ls -la .git/

# HEADファイルの内容確認
cat .git/HEAD
# 出力: ref: refs/heads/master(または main)

2. git add (ステージング)

ステージングエリア(Index)とは

ステージングエリアはラフドラフトスペースのようなもので、次のcommitで保存したいファイルのバージョンや複数のファイルをgit addできる場所。git addコマンドが実際に行うのは、ワーキングディレクトリからステージングエリアへのファイルのバージョンのコピー。

git addの内部動作

先述の通り、blobを作成するプロセスは通常、ステージングエリアに何かを追加するとき、つまりgit addを使用するときに発生する。

具体的な手順
  1. ファイル内容の読み取り

    • ワーキングディレクトリのファイル内容を読み取る
    • ファイル内容のSHA-1ハッシュを計算
  2. blobオブジェクトの作成

    • git addコマンドを実行すると、gitはファイルの内容が前回のcommitから変更されていなければ新しいblobを作成せず、代わりに既存のblobを次のcommitのためにステージするだけ。これはgitがスペースを節約する方法の一つである。
  3. blobの保存

    • zlibで圧縮してから.git/objects/に保存
    • ハッシュの最初の2文字をディレクトリ名、残りをファイル名として使用
  4. Indexファイルの更新

    • .git/indexファイルにファイルパス、権限、blobハッシュを記録
    • git addコマンドによってファイルの変更がStagingIndexに昇格され、オブジェクトSHAが更新される

ステージングの確認

# ステージングエリアの状態確認
git ls-files -s

# 出力例:
# 100644 bab2a0adb8921f504cb0521bc00b8dde22ee92a4 0 myfile.txt

ステージングエリアの利点

ステージングは大きな変更を複数のcommitに分割することを支援し、一度にすべてを巨大なcommitにするのではなく、コードの変更の各側面に焦点を当てたクリーンなcommitに適切に分割できる。

3. git commit コマンド(コミット作成)

git commitの内部動作

ステージングされた情報からtreeオブジェクトを書き出し、そのトップレベルtreeと直前のcommitを参照する新しいcommitオブジェクトを作成する。

具体的な手順
  1. ステージングエリア(Index)からTreeオブジェクトを作成

    • .git/indexファイルに記録されているステージング済みのファイル情報を読み取る
    • この情報(ファイルパス、権限、blobハッシュ)を使ってtreeオブジェクトを構築
    • ディレクトリ構造に応じて、必要な数だけtreeオブジェクトを作成(サブディレクトリがあれば子treeも作成)
  2. ルートTreeオブジェクトの決定

    • 作成したtreeオブジェクトの中で、プロジェクトのルートディレクトリを表すtreeを特定
    • このルートtreeがcommitオブジェクトから参照される「トップレベルtree」になる
  3. Commitオブジェクトの作成

    • ルートtreeのハッシュを参照として含める
    • 現在のHEADが指すcommitを「親commit」として参照に含める
    • author/committer情報、タイムスタンプ、commitメッセージを追加
  4. 参照の更新

    • 新しく作成したcommitオブジェクトのハッシュで現在のブランチを更新
    • HEADが指す参照(通常はrefs/heads/<branch-name>)を新しいcommitに移動

実際のcommit例

# ファイル作成とステージング
echo "Hello World" > hello.txt
git add hello.txt

# commitの実行
git commit -m "Initial commit"

# 作成されたオブジェクトの確認
find .git/objects -type f
# 出力例:
# .git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238  # blob
# .git/objects/b2/5c15b81fae06e1c55946ac6270bfdb293870e8  # tree  
# .git/objects/f7/c3bc1d808e04732adf8be7c0ab7c8e2b13bfff  # commit

低レベルコマンドでの操作

git commit-treeの低レベルコマンドでは、単一のtreeオブジェクトをcommitできるが、git commitが行うような後続の参照やHEADの作業は実行しない。つまりgit commit-treeコマンドを手動で実行する場合、後続の参照作業なしでは、到達不可能なtreeを簡単に作成してしまい、gitガベージクリーナーによって削除される可能性がある。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?