Gitの裏側の仕組み


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