.gitフォルダについて
GitHubなどからリポジトリをローカルにCloneしてくると必ず存在しているこのフォルダ。
中身を見てみるとGitの仕組みが見えてきました。

まずは用語定義
Gitを理解するうえでデータを管理する領域の用語定義と、各領域の役割を説明します。
.gitフォルダ構成
.gitフォルダ内にあるフォルダ、ファイルの概要は以下の通りです。
今回掘り下げる対象
Gitのメインのサービスに関係が深いファイルについて掘り下げます。
- HEAD
- index
- objects/
HEAD
現在チェックアウトしているブランチ(またはコミット)を示すポインタです。
1行だけ 記載されているテキストファイルとなっています。
fa96ccd4cf97bcc73fb8593af0cb0176ec5ad357
ref: refs/heads/test-branch
index
ステージングエリア(インデックス)の内容を管理するファイルです。
中身の大半はバイナリになっているため、テキストエディタで確認することはできません。
代わりに以下のコマンドを実行することで、このファイルの一部を確認することができます。
git ls-files --stage
100644 8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b91 0 .gitattributes
100644 b2c3d4e5f6a7b8c9d0e1f234567890abcdef1234 0 README.md
100644 9e8d7c6b5a4f3e2d1c0b9a8e7d6c5b4a3210fedc 0 hello.txt
100644 1a23c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1 0 src/app.py
| 項目名 | 値 | 備考 |
|---|---|---|
| ファイルモード | 100644 | 実行権限やファイル区分 (通常ファイル、シンボリックリンクなど) |
| 【重要】blob ID | adb287dbf7e698243d0e492b44920afffa1c3244 | ファイルの 内容 そのものに付与されたID |
| stage番号 | 0 | 基本0が設定される コンフリクト時には同じファイルパスに対して別の番号が振られる |
| ファイルパス | memo.md | - |
objects
このフォルダ配下には Gitオブジェクト と呼ばれるファイルが配置されます。
Gitオブジェクトは以下の4種類に細分化されます。
Gitオブジェクトの保存形式は looseオブジェクト と packedオブジェクト の2種類存在しています。
looseオブジェクト :各オブジェクトファイルを個別で管理する状態
packedオブジェクト :複数のlooseオブジェクトを集約・圧縮し容量を削減した状態
ローカルの作業で生成されるGitオブジェクトは基本的にlooseオブジェクトで管理され、特定のタイミングでpackedオブジェクトに集約されます。
(チェックアウトしてきた時点ではpackedオブジェクトになっていることが多い)
■ Gitオブジェクトについて
それぞれのオブジェクトファイル名は、オブジェクトの情報をハッシュ化することで生成したIDが使用されます。
通常のリポジトリでは SHA-1(40桁) を使用してハッシュ化することが一般的ですが、SHA-256(64桁) を使用するケースもあります。
オブジェクトID = SHA-1 or SHA-256(
オブジェクト種別 + " " + サイズ + "\0" + 実データ
)
オブジェクトIDの構成要素にファイル名は含まれません。
そのためblobオブジェクトを生成する際、ファイルの中身が同じであればファイル名が異なっていても同じblobオブジェクトを参照します。
各オブジェクトファイルの実態は.git/objectsにて、各オブジェクトIDの
・先頭2桁:フォルダ名
・3桁目以降:ファイル名(拡張子なし)
のように管理されます。
【補足】先頭2桁でフォルダを作成する理由
1つのディレクトリに大量のファイルが作成されることを避けるためです。
リポジトリが大きくなるほど、Gitオブジェクトはどんどん増えていきます。
SHA-1で生成された文字列はランダム性が強いため、文字列の一部をそのままフォルダ名に流用することで格納場所の偏りを防いでいます。
※「数学的に必ず均等に散らばる」というわけではなく、あくまで偏りにくくなるというレベルです
SHA-1によって生成される文字列に使用されるのは0~fまでの16種類となっており、2桁にすることで最大256個のフォルダに分散することができます。
これを3桁にすると4096通りになってしまうため、フォルダが多すぎてしまいます。
各Gitオブジェクトの中身について
ファイルの中身はzlib圧縮されているバイナリデータのため、テキストエディタから直接中身を確認することはできません。
commitオブジェクトの中身
commit 189
tree cdef1234567890abcdef1234567890abcdef1234
parent 1111222233334444555566667777888899990000
author Alice <alice@example.com> 1715000000 +0900
committer Alice <alice@example.com> 1715000000 +0900
docs: add README and initial files
| 項目 | 値 | 意味 |
|---|---|---|
| オブジェクト種別 | commit |
コミットオブジェクト |
| サイズ | 189 |
本文部分のバイト数 |
| tree ID | cdef1234567890abcdef1234567890abcdef1234 |
このコミット時点のディレクトリ・ファイル構成 |
| parentID | 1111222233334444555566667777888899990000 |
直前のコミットID 初回コミットの場合は項目自体存在しない マージコミットの場合は項目が複数存在する |
| author | Alice <alice@example.com> |
変更を作成した人 |
| committer | Alice <alice@example.com> |
コミットとして記録した人 |
| 日時 | 1715000000 +0900 |
コミット日時 |
| コミットメッセージ | add README and initial files |
コミット内容の説明 |
treeオブジェクトの中身
tree 96
100644 blob 8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b91 .gitattributes
100644 blob b2c3d4e5f6a7b8c9d0e1f234567890abcdef1234 README.md
100644 blob 9e8d7c6b5a4f3e2d1c0b9a8e7d6c5b4a3210fedc hello.txt
040000 tree 1a23c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1 src
| 項目 | 値 | 意味 |
|---|---|---|
| オブジェクト種別 | tree |
Treeオブジェクト |
| サイズ | 96 |
本文部分のバイト数 |
| ファイルモード |
100644 , 040000
|
100644:通常ファイル040000:ディレクトリ |
| ファイル名 | .gitattributes |
ファイル名 |
| blobID |
8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b91 など |
blobオブジェクトへの参照用ID |
| treeID | 1a23c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1 |
treeオブジェクトへの参照用treeID |
blobオブジェクトの中身
blob 58
# Project
このプロジェクトのサンプルリポジトリです。
Gitオブジェクトの構造を説明するためのサンプルです。
| 項目 | 値 | 意味 |
|---|---|---|
| オブジェクト種別 | blob |
blobオブジェクト |
| サイズ | 58 |
本文部分のバイト数 |
| ファイルの中身 | # Project このプロジェクトのサンプルリポジトリです。 Gitオブジェクトの構造を説明するためのサンプルです。 |
ファイルの中身 |
先述のHEADから現在チェックアウト済みのコミットの全ファイル情報を取得する場合は以下の手順で取得します。
-
commitIDからcommitオブジェクトを取得 -
commitオブジェクトに記載されているtreeIDからtreeオブジェクトを取得 -
treeオブジェクトに記載されている各ファイルのblob IDからblobオブジェクトを取得
ステージングエリア内の情報を確認したい場合は、先述の.git/indexに各ファイルの blob ID が記載されているため、直接 blobオブジェクト を参照できます。
■ packedオブジェクトについて
.git/objects には、到達可能な過去コミットの情報も全て管理されています。
到達可能な過去コミットとは
以下のような参照から辿ることのできるコミットを指しています。
| 参照元 | 例 |
|---|---|
| ブランチ | .git/refs/heads/main |
| タグ | .git/refs/tags/v1.0.0 |
| リモート追跡ブランチ | .git/refs/remotes/origin/main |
| HEAD | .git/HEAD |
| reflog |
.git/logs/HEAD, .git/logs/refs/heads/main
|
| stash | refs/stash |
逆に到達不可能なコミットとは、以下のような場合を指します。
- ブランチを削除した後、その履歴が他から参照されていない場合
-
reset --hardにて、ブランチの先端を戻した場合 -
rebaseにて、古いコミット列が置き換わった場合
など
到達不可能になった場合もすぐに削除されるわけではなく、reflog から辿ることができる間は削除されません。
つまり、到達可能な過去コミットであれば常に堆積し続けるということになります。
そんなことを続けていれば.git/objects内は大量のファイルであふれかえってしまいますね。
なので適切なタイミングで複数のlooseオブジェクトを統合・圧縮し、packedオブジェクトを生成することで容量の削減などを行っています。
差分だけ抽出して情報を削減(delta化)する場合もある
packfileを作成する際に似ているオブジェクト同士を探し、
片方を 基準オブジェクト、
もう片方を 差分オブジェクト
として保存することがあります。
ただし、これは「コミット日時が古い順に並べて、古いファイルから順番に差分を作る」という単純な仕組みではありません。
Gitはオブジェクトの種類、サイズ、ファイル名・パス名などをもとに候補を並べ、
近い候補同士を比較して、差分圧縮した方が小さくなる場合にdelta化します。
ソースコードなどのテキストファイルであればこの恩恵を十分に受けることができますが、バイナリデータの場合は基本的にはこの恩恵は十分に発揮できません。
なので、容量の大きいバイナリデータをGitで管理しようとしてしまうと.git/objectsの肥大化につながってしまいます。
この作業は git gc コマンドなどで明示的に実行することも可能ですが、一定以上情報が堆積した場合などにGit側が自動実行し、整理してくれるようになっています。
.git/objects
├─01
├─04
├─05
├─06
├─07
├─0b
├─11
├─12
├─13
├─14
├─15
~省略~
├─info
└─pack
pack-02b57f67f34247954badfdd69cba86b221008c65.idx
pack-02b57f67f34247954badfdd69cba86b221008c65.pack
pack-02b57f67f34247954badfdd69cba86b221008c65.rev
.git/objects
├─info
│ commit-graph
│ packs
│
└─pack
pack-4afd2f323c41e8e3f4802cdeac6c39c4c8bd1ccb.idx
pack-4afd2f323c41e8e3f4802cdeac6c39c4c8bd1ccb.pack
pack-4afd2f323c41e8e3f4802cdeac6c39c4c8bd1ccb.rev
さいごに
今回まとめた内容だけでは「実用性」という観点では非常に低いと思っています。
ただし、Gitの各コマンドが内部でどのようなことをやっているのかについて、
ある程度ホワイトボックス的に理解できれば、メリット・デメリットを正しく判断し、
意図したとおりに使いこなせるのではないかと思っています。
今回の内容を踏まえたうえで、Gitの各コマンドが内部でどんなことをやっているのかについて深堀する記事を投稿出来たらと目論んでいるので…がんばりまーす




