Gitの仕組みを理解するために、gitの操作に応じて.gitディレクトリの中身がどのように変化するかをまとめました。
Gitの用語や基本操作を理解していることは前提。
git init
git init
を実行すると実行したディレクトリ内のファイルがGitの追跡状態になり、.gitディレクトリが作られる。
.gitディレクトリ
.gitディレクトリの中身は以下のようになっている。
.git
├── HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
主なディレクトリの役割
- HEAD - 現在のブランチの参照を表すファイル
- config - リモートブランチなどの情報が書かれたファイル
- objects - コミットなどのオブジェクトを保存するディレクトリ
- refs- ブランチの情報を保存するディレクトリ
- heads - ブランチの情報を保存する
- tag - タグの情報を保存する
ステージに追加
git add
で変更をステージに追加する。
ステージに追加したときの.gitディレクトリの中身
.git
├── HEAD
├── config
├── description
├── hooks
│ ├── 省略
├── index
├── info
│ └── exclude
├── objects
│ ├── e6
│ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
- index - インデックスの情報を保存するファイル
ステージに追加するとindex
ファイルとobjects
ディレクトリの中にファイルが作られている。
objects
の中に作られたe6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
というファイルは変更対象の圧縮ファイルであり、blobオブジェクトという。blobとはかたまりという意味。
圧縮ファイルのファイル名はファイル内容などをハッシュ関数で40文字の英数字に変換したハッシュIDであり、先頭の2文字をディレクトリ名にして、残り38文字をファイル名として保存される。
このハッシュIDは一意のものであり、ファイルの内容が同じなら全く同じものとなる。
index
ファイルには、変更したファイルのblobオブジェクトと対応するファイルの名前が記録されている。
コミット
ステージに追加した変更をコミットする。
コミット時の.gitディレクトリの中身は以下のようになる。
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│ ├── 省略
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 09
│ │ └── b5607d0c6afa2cc0067699e6b25807c7c8659c
│ ├── e6
│ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── f9
│ │ └── 3e3a1a1525fb5b91020da86e44810c87a2d7bc
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
COMMIT_EDITMSG
、logs
、refs/heads/master
とobjects
の中にいくつかのディレクトリとファイルが作られている。
重要なのがobjects
の中の3つのファイルである。1つはステージに追加した時の圧縮ファイルであり、残りがツリーファイルとコミットファイルである。
ツリーファイル
ツリーファイルはファイル名とファイル構造を保存するファイルで、treeオブジェクトという。
圧縮ファイルにはファイル名が保存されていないので、ツリーファイルによって圧縮ファイルとファイル名を対応させる。
コミットファイル
コミットファイルはいつ、誰が、何を、何のために変更したかが保存されるファイルで、commitオブジェクトという。
コミットファイルにはtree、parent、author、committer、コミットメッセージの5つの情報が保存される。
treeはコミットした時点のプロジェクトの一番上のディレクトリのツリーファイルが保存される。このツリーファイルを保存することでスナップショットを記録している。
parentは親のコミット、つまり1つ前のコミットを保存している。これによってコミットの履歴を辿ることができる。一番最初のコミットには親のコミットを持たないのでparentは保存されない。
authorとcommitterはコミットした人のユーザー名とメールアドレスが保存される。
プッシュ
リモートリポジトリにローカルの内容を保存するためにgit push
を行う。git push
を行うとremotes
ディレクトリが作られる。このremotes
ディレクトリの直下にあるディレクトリがリモート追跡ブランチになる。
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│ ├── 省略
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 09
│ │ └── b5607d0c6afa2cc0067699e6b25807c7c8659c
│ ├── e6
│ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── f9
│ │ └── 3e3a1a1525fb5b91020da86e44810c87a2d7bc
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
├── remotes
│ └── origin
│ └── main
└── tags
refs/remotes/origin/main
の中身をcat
コマンドで確認すると、プッシュした時点での最新のコミットのハッシュが保存されている。
09b5607d0c6afa2cc0067699e6b25807c7c8659c
ブランチを作成
git branch
でブランチを作成すると、refs/heads
の中に作成したブランチ名のファイルが作られ、その中身には最新のコミットのハッシュが記述されている。
git branch feature
を実行する。
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│ ├── 省略
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 09
│ │ └── b5607d0c6afa2cc0067699e6b25807c7c8659c
│ ├── e6
│ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── f9
│ │ └── 3e3a1a1525fb5b91020da86e44810c87a2d7bc
│ ├── info
│ └── pack
└── refs
├── heads
│ ├── feature
│ └── master
└── tags
作られたrefs/heads/feature
の中身をcat .git/refs/heads/feature
で確認する。
09b5607d0c6afa2cc0067699e6b25807c7c8659c
これによってブランチは最新のコミットを参照していることがわかる。
ブランチの切り替え
git checkout
でブランチを切り替えると、HEADファイルの中身に切り替えたブランチ名が記述される。
masterブランチにいる状態でgit checkout feature
を実行し、cat .git/HEAD
を実行して中身を確認する。
ref: refs/heads/feature
これによってHEADが今いるブランチを指していて、そのブランチは最新のコミットを指しているということがわかる。
タグ
git tag [タグ名]
でタグを作成すると、refs/tags
の中に作成したタグ名のファイルが作られ、
ここではgit tag v1.0
と実行する。
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│ ├── 省略
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ ├── feature
│ └── master
├── objects
│ ├── 09
│ │ └── b5607d0c6afa2cc0067699e6b25807c7c8659c
│ ├── e6
│ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── f9
│ │ └── 3e3a1a1525fb5b91020da86e44810c87a2d7bc
│ ├── info
│ └── pack
└── refs
├── heads
│ ├── feature
│ └── master
└── tags
└── v1.0
cat .git/refs/tags/v1.0
で中身を確認する。
09b5607d0c6afa2cc0067699e6b25807c7c8659c
これによって作成したタグがコミットを指していることがわかる。
まとめ
-
git init
でGitの初期化を行うと、.gitディレクトリが作られる -
git add
で変更をステージに追加すると、ファイルの内容を圧縮した圧縮ファイルが作られる - 圧縮ファイルには一意の名前を設定するためにハッシュIDが使われる
-
git commit
で変更をコミットすると、ファイル名とファイル構造を保存するツリーファイルと、いつ、誰が、何を、何のために変更したかを保存するコミットファイルが作られる
参考
https://github.com/kaityo256/github/blob/main/internals/README.md
https://zenn.dev/kaityo256/articles/inside_the_index