Gitってsvnと比べると、同じリモートリポジトリで比較しても、かなり速いですよね。
まだまだGitには知らないことも多くあるので、この際調べてみると、Gitってスナップショットで管理していることを知りました。svnのように差分を積み上げる必要がない分、Gitは速いんだと思います。
とすると、スナップショットで保持するのに、なぜGitリポジトリは肥大化しないのか?という疑問が沸いてきましたが、Gitオブジェクトの構造で工夫しているんだろうと分かったので、自分の整理のために記事にしました。
Gitオブジェクトの種類
- 4種類のオブジェクトが存在する
- SHA-1ハッシュによる識別
種類 | 内容 |
---|---|
blobオブジェクト | ファイルに相当 |
treeオブジェクト | ディレクトリに相当 |
commitオブジェクト | コミットのスナップショット |
tagオブジェクト | タグ。今回は対象外 |
blobオブジェクト
- ファイルに相当するオブジェクトで、ファイルの中身がzlibで圧縮されている。ファイル名は管理外。
- ファイルの中身に
blob <ファイルサイズ>\0
をつけたSHA-1ハッシュ値。
つまり、 ファイルの中身が同じであれば、同じオブジェクトとなる。 -
git add
したときに生成する
試してみる
- 以下4ファイルの構成で試してみます
フォルダ構成とファイルの中身
│ readme.txt ファイルの中身:hogehoge
│ sample.txt ファイルの中身:this is sample
├─subA
│ aaa.txt ファイルの中身:Hello aaa
└─subB
bbb.txt ファイルの中身:hogehoge
-
git add .
で4ファイルをaddした結果、.git/objects
は以下となりました
.git/objects以下の構成
.git/objects/48/f685c5179595c9edbcfb5ebcc1beafac716857
.git/objects/64/e3d0eb4f3c190bf547221f28a364c49b9df77e
.git/objects/6f/27b4832a80eaa3123c0d1c256c41cd1835b33d
- ここで4ファイルのハッシュ値を計算してみます。
blob <ファイルサイズ>\0 <ファイルの中身>
をsha-1計算します- blobオブジェクトのハッシュと一致していることが確認できます
- ファイルが異なっていても、中身が同じであればblobオブジェクトは共通なことが確認できます
ハッシュ計算結果
$ (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
オプションを使うと、中身が見れました。
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
は4つ増えていました
.git/objects以下の構成(増えたもののみ)
.git/objects/06/4bee06635089f2c96c653c71c24e01fe78bbf7
.git/objects/27/7f73e22786e4adfab6ec27ca1aa1baf9187e8d
.git/objects/80/dcb15ab2823857535ee6123204c0aa167ce938
.git/objects/f5/abca5b5cc24cf60e3d72ac4efeb7cb723bcf7a
- このうち3つめはcommitオブジェクトで、残りがtreeオブジェクトのようです
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オブジェクトが、ディレクトリごとに生成されていることが分かります
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オブジェクトの存在が確認できます
- コミットしたユーザー、タイムスタンプやコミットメッセージが確認できます
commitオブジェクトの中身
$ git cat-file -p 80dcb
tree 064bee06635089f2c96c653c71c24e01fe78bbf7
author hoge <hoge@hoge.com> 1660563865 +0900
committer hoge <hoge@hoge.com> 1660563865 +0900
test commit
もう少し試してみます
-
echo -n "hogehoge" > sample.txt
でsample.txtの中身を、readme.txtと合わせました
中身が同じなので、blobオブジェクトは作られないはず。実際にはこんなユースケースないと思いますが -
echo -n "abcdefg" > abc.txt
で新しいファイルを作ります -
git add .
しましたが、予想通りGitオブジェクトは1つしか増えていません
変更のないファイルはオブジェクトが作られないため、リポジトリ容量の肥大化を防いでいます
.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オブジェクトを確認します
増えたGitオブジェクト
$ git cat-file -t dda6e
blob
$ git cat-file -p dda6e
abcdefg
-
git commit -m "2nd commit"
したら、2つ増えました
.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したので、増えた1つは当然commitオブジェクトです
- もう1つはルートディレクトリにファイルが増えたので、treeオブジェクトです
増えた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
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
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で圧縮されているので、ここでも容量削減に貢献している