Gitオブジェクトってなにもの？

Gitってsvnと比べると、同じリモートリポジトリで比較しても、かなり速いですよね。
まだまだGitには知らないことも多くあるので、この際調べてみると、Gitってスナップショットで管理していることを知りました。svnのように差分を積み上げる必要がない分、Gitは速いんだと思います。

とすると、スナップショットで保持するのに、なぜGitリポジトリは肥大化しないのか？という疑問が沸いてきましたが、Gitオブジェクトの構造で工夫しているんだろうと分かったので、自分の整理のために記事にしました。


# Gitオブジェクトの種類
- 4種類のオブジェクトが存在する
- SHA-1ハッシュによる識別

|種類|内容|
|-|-|
|blobオブジェクト|ファイルに相当
|treeオブジェクト|ディレクトリに相当
|commitオブジェクト|コミットのスナップショット
|tagオブジェクト|タグ。今回は対象外

# blobオブジェクト
- ファイルに相当するオブジェクトで、ファイルの中身がzlibで圧縮されている。ファイル名は管理外。
- ファイルの中身に`blob <ファイルサイズ>\0`をつけたSHA-1ハッシュ値。
つまり、 **ファイルの中身が同じであれば、同じオブジェクトとなる。**
- `git add`したときに生成する

### 試してみる
- 以下４ファイルの構成で試してみます
```console:フォルダ構成とファイルの中身
│  readme.txt      ファイルの中身：hogehoge
│  sample.txt      ファイルの中身：this is sample
├─subA
│     aaa.txt      ファイルの中身：Hello aaa
└─subB
      bbb.txt      ファイルの中身：hogehoge
```

- `git add .`で４ファイルをaddした結果、`.git/objects`は以下となりました
```console:.git/objects以下の構成
.git/objects/48/f685c5179595c9edbcfb5ebcc1beafac716857
.git/objects/64/e3d0eb4f3c190bf547221f28a364c49b9df77e
.git/objects/6f/27b4832a80eaa3123c0d1c256c41cd1835b33d
```

- ここで４ファイルのハッシュ値を計算してみます。
`blob <ファイルサイズ>\0 <ファイルの中身>`をsha-1計算します
  - blobオブジェクトのハッシュと一致していることが確認できます
  - ファイルが異なっていても、中身が同じであればblobオブジェクトは共通なことが確認できます
```console:ハッシュ計算結果
$ (echo -en 'blob 8\0';cat readme.txt) | shasum
48f685c5179595c9edbcfb5ebcc1beafac716857 *-

$ (echo -en 'blob 14\0';cat sample.txt) | shasum
64e3d0eb4f3c190bf547221f28a364c49b9df77e *-

$ (echo -en 'blob 9\0';cat subA/aaa.txt) | shasum
6f27b4832a80eaa3123c0d1c256c41cd1835b33d *-

$ (echo -en 'blob 8\0';cat subB/bbb.txt) | shasum
48f685c5179595c9edbcfb5ebcc1beafac716857 *-
```

- 次にこのblobオブジェクトを確認します。`git cat-file <オプション> <gitオブジェクト>`
`-p`オプションを使うと、中身が見れました。
```console:blobオブジェクトの確認
$ git cat-file -p 48f68
hogehoge

$ git cat-file -p 64e3d
this is sample

$ git cat-file -p 6f27b
Hello aaa
``` 

# treeオブジェクトとcommitオブジェクト
- treeオブジェクト
  - `git commit`したときに生成される
  - ディレクトリに相当
  - ディレクトリごとに生成される
- commitオブジェクト
  - `git commit`したときに生成される
  - 親のcommitを参照している
  - 作成者や日付、コミットメッセージが格納されている

### 試してみる
- `git commit -m "test commit"`した結果、`.git/objects`は４つ増えていました
```console:.git/objects以下の構成（増えたもののみ）
.git/objects/06/4bee06635089f2c96c653c71c24e01fe78bbf7
.git/objects/27/7f73e22786e4adfab6ec27ca1aa1baf9187e8d
.git/objects/80/dcb15ab2823857535ee6123204c0aa167ce938
.git/objects/f5/abca5b5cc24cf60e3d72ac4efeb7cb723bcf7a
```

- このうち３つめはcommitオブジェクトで、残りがtreeオブジェクトのようです
```console:gitオブジェクトのタイプ
 $ git cat-file -t 064be
tree

$ git cat-file -t 277f7
tree

$ git cat-file -t 80dcb
commit

$ git cat-file -t f5abc
tree
```

- treeオブジェクトの中身を見てみます
treeオブジェクトが、ディレクトリごとに生成されていることが分かります
```console:treeオブジェクトの中身
$ git cat-file -p 064be
100644 blob 48f685c5179595c9edbcfb5ebcc1beafac716857    readme.txt
100644 blob 64e3d0eb4f3c190bf547221f28a364c49b9df77e    sample.txt
040000 tree 277f73e22786e4adfab6ec27ca1aa1baf9187e8d    subA
040000 tree f5abca5b5cc24cf60e3d72ac4efeb7cb723bcf7a    subB

$ git cat-file -p 277f7
100644 blob 6f27b4832a80eaa3123c0d1c256c41cd1835b33d    aaa.txt

$ git cat-file -p f5abc
100644 blob 48f685c5179595c9edbcfb5ebcc1beafac716857    bbb.txt
```

- commitオブジェクトの中身を見ます
  - ルートのtreeオブジェクトの存在が確認できます
  - コミットしたユーザー、タイムスタンプやコミットメッセージが確認できます
```console:commitオブジェクトの中身
$ git cat-file -p 80dcb
tree 064bee06635089f2c96c653c71c24e01fe78bbf7
author hoge <hoge@hoge.com> 1660563865 +0900
committer hoge <hoge@hoge.com> 1660563865 +0900

test commit
```

- 図で表すとこんな感じ
![commit_80dcb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674934/fa00a693-0b10-2c36-d8e0-f131988bddd1.png)


### もう少し試してみます
- `echo -n "hogehoge" > sample.txt`でsample.txtの中身を、readme.txtと合わせました
中身が同じなので、blobオブジェクトは作られないはず。実際にはこんなユースケースないと思いますが
- `echo -n "abcdefg" > abc.txt`で新しいファイルを作ります
- `git add .`しましたが、予想通りGitオブジェクトは１つしか増えていません
**変更のないファイルはオブジェクトが作られないため、リポジトリ容量の肥大化を防いでいます**
```console:.git/objects以下の構成
.git/objects/06/4bee06635089f2c96c653c71c24e01fe78bbf7
.git/objects/27/7f73e22786e4adfab6ec27ca1aa1baf9187e8d
.git/objects/48/f685c5179595c9edbcfb5ebcc1beafac716857
.git/objects/64/e3d0eb4f3c190bf547221f28a364c49b9df77e
.git/objects/6f/27b4832a80eaa3123c0d1c256c41cd1835b33d
.git/objects/80/dcb15ab2823857535ee6123204c0aa167ce938
.git/objects/dd/a6eb4b7b86af8aea969498003937b7c8aa46be  これが増えました
.git/objects/f5/abca5b5cc24cf60e3d72ac4efeb7cb723bcf7a
```
- 増えたGitオブジェクトを確認します
```console:増えたGitオブジェクト
$ git cat-file -t dda6e
blob

$ git cat-file -p dda6e
abcdefg
```

- `git commit -m "2nd commit"`したら、2つ増えました
```console:.git/objects以下の構成
.git/objects/06/4bee06635089f2c96c653c71c24e01fe78bbf7
.git/objects/27/7f73e22786e4adfab6ec27ca1aa1baf9187e8d
.git/objects/48/f685c5179595c9edbcfb5ebcc1beafac716857
.git/objects/64/e3d0eb4f3c190bf547221f28a364c49b9df77e
.git/objects/6f/27b4832a80eaa3123c0d1c256c41cd1835b33d
.git/objects/72/d0fc47ed60e0f2abdfef26c9d48c2a0b56e3b0  これが増えた
.git/objects/80/dcb15ab2823857535ee6123204c0aa167ce938
.git/objects/a0/4d10c2f7ebd3490d126d88e5979b0c4913b865  これも増えた
.git/objects/dd/a6eb4b7b86af8aea969498003937b7c8aa46be
.git/objects/f5/abca5b5cc24cf60e3d72ac4efeb7cb723bcf7a
```

- 増えたGitオブジェクトを確認します
  - commitしたので、増えた１つは当然commitオブジェクトです
  - もう１つはルートディレクトリにファイルが増えたので、treeオブジェクトです
```console:増えたGitオブジェクト
$ git cat-file -t 72d0f
commit

$ git cat-file -p 72d0f
tree a04d10c2f7ebd3490d126d88e5979b0c4913b865
parent 80dcb15ab2823857535ee6123204c0aa167ce938
author hoge <hoge@hoge.com> 1660572580 +0900
committer hoge <hoge@hoge.com> 1660572580 +0900

2nd commit

$ git cat-file -t a04d1
tree

$ git cat-file -p a04d1
100644 blob dda6eb4b7b86af8aea969498003937b7c8aa46be    abc.txt
100644 blob 48f685c5179595c9edbcfb5ebcc1beafac716857    readme.txt
100644 blob 48f685c5179595c9edbcfb5ebcc1beafac716857    sample.txt
040000 tree 277f73e22786e4adfab6ec27ca1aa1baf9187e8d    subA
040000 tree f5abca5b5cc24cf60e3d72ac4efeb7cb723bcf7a    subB
```

- 図で表すとこんな感じ
  - 赤の２つが増えて、黄色のハッシュが変わっています。
![commit_72d0f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674934/72ae43e6-082d-7a5c-eda3-09dc3ed93f19.png)

- こんなこともできます
```console:HEADを使ってcommitオブジェクトの確認
$ git cat-file -p HEAD
tree a04d10c2f7ebd3490d126d88e5979b0c4913b865
parent 80dcb15ab2823857535ee6123204c0aa167ce938
author hoge <hoge@hoge.com> 1660572580 +0900
committer hoge <hoge@hoge.com> 1660572580 +0900

2nd commit

$ git cat-file -p HEAD~
tree 064bee06635089f2c96c653c71c24e01fe78bbf7
author hoge <hoge@hoge.com> 1660563865 +0900
committer korena hoge <hoge@hoge.com> 1660563865 +0900

test commit
```
```console:HEADのtreeを確認
$ git cat-file -p HEAD^{tree}
100644 blob dda6eb4b7b86af8aea969498003937b7c8aa46be    abc.txt
100644 blob 48f685c5179595c9edbcfb5ebcc1beafac716857    readme.txt
100644 blob 48f685c5179595c9edbcfb5ebcc1beafac716857    sample.txt
040000 tree 277f73e22786e4adfab6ec27ca1aa1baf9187e8d    subA
040000 tree f5abca5b5cc24cf60e3d72ac4efeb7cb723bcf7a    subB

$ git ls-tree -r HEAD
100644 blob dda6eb4b7b86af8aea969498003937b7c8aa46be    abc.txt
100644 blob 48f685c5179595c9edbcfb5ebcc1beafac716857    readme.txt
100644 blob 48f685c5179595c9edbcfb5ebcc1beafac716857    sample.txt
100644 blob 6f27b4832a80eaa3123c0d1c256c41cd1835b33d    subA/aaa.txt
100644 blob 48f685c5179595c9edbcfb5ebcc1beafac716857    subB/bbb.txt
```


# まとめ
こんな感じになるのかなぁと思ってます。間違っていたらご指摘ください
- Gitはスナップショットで管理しているため、チェンジセット管理のsvnよりも速い
- 変更がないファイルのblobオブジェクトは作られないため、リポジトリ容量の肥大化を防いでいる
- blobファイルは、zlibで圧縮されているので、ここでも容量削減に貢献している