普段は覗かない.gitディレクトリ。どういう構造になっているのか、そしてどうやってバージョンを管理しているのか、確認してみました。動きを追っていくと、gitの仕組みが見えてきます。今回は、init~commitまでにobjectsディレクトリがどう変化するかを見てみます。
まずはinit
まずはinitします。
$ git init
Initialized empty Git repository in D:/gittest/.git/
$ tree .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
| `-- update.sample
|-- info
| `-- exclude
|-- objects
| |-- info
| `-- pack
`-- refs
|-- heads
`-- tags
8 directories, 16 files
initすると、8つのディレクトリと16のファイルが生成されました。objectsの下にはinfo/とpack/のみ存在しています。以降は.git/objects/のみを対象とします。
ファイル作成
2つのファイルを作成して、.gitフォルダの変化を確認します。
$ echo "hello qiita" > file1.txt
$ mkdir dir
$ echo "hello git" > dir/file2.txt
当然、これらのファイルはgit管理下にはないので、.git/objects/に変化はありません。
$ tree .git/objects/
.git/objects/
|-- info
`-- pack
2 directories, 0 files
git add
次にgit addします。
$ git add *
$ tree .git/objects/
.git/objects/
|-- 8d
| `-- 0e41234f24b6da002d962a26c2495ea16a425f
|-- 92
| `-- ca5c8297eb1e13c6039ae6ad11dc03f89057ed
|-- info
`-- pack
4 directories, 2 files
addすると、objects/の下に2つのディレクトリ、その下に各1つのファイルが生成されました。git addしたファイルのハッシュ値を確認すると、その正体が分かります。
$ git hash-object file1.txt
92ca5c8297eb1e13c6039ae6ad11dc03f89057ed
$ git hash-object dir/file2.txt
8d0e41234f24b6da002d962a26c2495ea16a425f
生成されたファイルの中身のハッシュ値を確認すると、先頭2桁をディレクトリ名とし、残り38桁をファイル名としていることが分かりました。objects/直下に大量のファイルが生成され、読み取り速度が低下することを防ぐために、このような設計になっているようです。
次に、git cat-fileコマンドでオブジェクトのタイプと中身を確認します。(なぜか、git cat-file -t 92/ca5c8297eb1e13c6039ae6ad11dc03f89057edとすると、エラーになる)
$ cd .git/objects/
$ git cat-file -t 92ca5c8297eb1e13c6039ae6ad11dc03f89057ed
blob
$ git cat-file -p 92ca5c8297eb1e13c6039ae6ad11dc03f89057ed
hello qiita
$ git cat-file -t 8d0e41234f24b6da002d962a26c2495ea16a425f
blob
$ git cat-file -p 8d0e41234f24b6da002d962a26c2495ea16a425f
hello git
どちらもgit addしたファイルのBlobオブジェクトです。つまり、git addされたファイル(の内容)は、objects/にBlobオブジェクトとして格納されます。(圧縮されているため、catでは内容を確認できない)
commit
commitします。どのように.git/objects/が変化するのでしょうか?
$ git commit -m "commit 1"
[master (root-commit) 98664b3] commit 1
2 files changed, 2 insertions(+)
create mode 100644 dir/file2.txt
create mode 100644 file1.txt
$ tree .git/objects/
.git/objects/
|-- 46
| `-- 660d479ecbb352fe6139074d4cca2ec9ac2f31
|-- 8d
| `-- 0e41234f24b6da002d962a26c2495ea16a425f
|-- 92
| `-- ca5c8297eb1e13c6039ae6ad11dc03f89057ed
|-- 98
| `-- 664b3fad7883101dafcb8f4891c0f7cc9f4798
|-- f9
| `-- 68a777a91646758326fe23b06633280db6bc02
|-- info
`-- pack
7 directories, 5 files
46/、98/、f9/が追加されていることが分かります。git cat-fileでそれぞれのオブジェクトタイプと内容を確認します。
$ git cat-file -t 46660d479ecbb352fe6139074d4cca2ec9ac2f31
tree
$ git cat-file -p 46660d479ecbb352fe6139074d4cca2ec9ac2f31
040000 tree f968a777a91646758326fe23b06633280db6bc02 dir
100644 blob 92ca5c8297eb1e13c6039ae6ad11dc03f89057ed file1.txt
$ git cat-file -t 98664b3fad7883101dafcb8f4891c0f7cc9f4798
commit
$ git cat-file -p 98664b3fad7883101dafcb8f4891c0f7cc9f4798
tree 46660d479ecbb352fe6139074d4cca2ec9ac2f31
author XXXX <XXXX@gmail.com> 1596887068 +0900
committer XXXX <XXXX@gmail.com> 1596887068 +0900
commit 1
$ git cat-file -t f968a777a91646758326fe23b06633280db6bc02
tree
$ git cat-file -p f968a777a91646758326fe23b06633280db6bc02
100644 blob 8d0e41234f24b6da002d962a26c2495ea16a425f file2.txt
それぞれ、1つのcommitオブジェクトと2つのtreeオブジェクトです。
commitオブジェクトはcommitの内容、およびコミットした対象(treeオブジェクト)が書かれています。
一方、treeオブジェクトはファイルのディレクトリを表しています。
再度commit
ファイルを変更して、再度commitしてみます。
ファイル更新
$ echo "hello qiita again" >> file1.txt
$ tree .git/objects/
.git/objects/
|-- 46
| `-- 660d479ecbb352fe6139074d4cca2ec9ac2f31
|-- 8d
| `-- 0e41234f24b6da002d962a26c2495ea16a425f
|-- 92
| `-- ca5c8297eb1e13c6039ae6ad11dc03f89057ed
|-- 98
| `-- 664b3fad7883101dafcb8f4891c0f7cc9f4798
|-- f9
| `-- 68a777a91646758326fe23b06633280db6bc02
|-- info
`-- pack
7 directories, 5 files
この時点では、.gitフォルダには変化はありません。オブジェクトは、addして初めて生成されるためです。
git add
$ git add file1.txt
$ tree .git/objects/
.git/objects/
|-- 23
| `-- ce4645d730951533cb1b75c03cd7e8b5d9e071
|-- 46
| `-- 660d479ecbb352fe6139074d4cca2ec9ac2f31
|-- 8d
| `-- 0e41234f24b6da002d962a26c2495ea16a425f
|-- 92
| `-- ca5c8297eb1e13c6039ae6ad11dc03f89057ed
|-- 98
| `-- 664b3fad7883101dafcb8f4891c0f7cc9f4798
|-- f9
| `-- 68a777a91646758326fe23b06633280db6bc02
|-- info
`-- pack
8 directories, 6 files
$ git cat-file -t 23ce4645d730951533cb1b75c03cd7e8b5d9e071
blob
$ git cat-file -p 23ce4645d730951533cb1b75c03cd7e8b5d9e071
hello qiita
hello qiita again
新しいBlobオブジェクトが生成されました。ファイルを更新してaddするたびに、Blobオブジェクトが新規に作成されるようです。
commit
次にcommitします。
$ git commit -m "commit 2"
[master e2e5b8a] commit 2
1 file changed, 1 insertion(+)
$ tree .git/objects/
.git/objects/
|-- 23
| `-- ce4645d730951533cb1b75c03cd7e8b5d9e071
|-- 36
| `-- b5886d72132ff023c27b2ca783eba3f3759b19
|-- 46
| `-- 660d479ecbb352fe6139074d4cca2ec9ac2f31
|-- 8d
| `-- 0e41234f24b6da002d962a26c2495ea16a425f
|-- 92
| `-- ca5c8297eb1e13c6039ae6ad11dc03f89057ed
|-- 98
| `-- 664b3fad7883101dafcb8f4891c0f7cc9f4798
|-- e2
| `-- e5b8a03da8735003da7e78cefbc84d45e524ce
|-- f9
| `-- 68a777a91646758326fe23b06633280db6bc02
|-- info
`-- pack
10 directories, 8 files
36/、e2/ディレクトリが追加されました。これまでの流れから、これらはtreeオブジェクトとcommitオブジェクトのはずです。確認してみましょう。
$ git cat-file -t 36b5886d72132ff023c27b2ca783eba3f3759b19
tree
$ git cat-file -p 36b5886d72132ff023c27b2ca783eba3f3759b19
040000 tree f968a777a91646758326fe23b06633280db6bc02 dir
100644 blob 23ce4645d730951533cb1b75c03cd7e8b5d9e071 file1.txt
「23ce46」は、今回のcommitで新しく生成されたBlobオブジェクトです。
$ git cat-file -t e2e5b8a03da8735003da7e78cefbc84d45e524ce
commit
$ git cat-file -p e2e5b8a03da8735003da7e78cefbc84d45e524ce
tree 36b5886d72132ff023c27b2ca783eba3f3759b19
parent 98664b3fad7883101dafcb8f4891c0f7cc9f4798
author XXXX <XXXX@gmail.com> 1596887500 +0900
committer XXXX <XXXX@gmail.com> 1596887500 +0900
commit 2
parentが98664bとなっており、これは1回目のcommitのオブジェクトです。
オブジェクトを図示すると、下記の様になります。
まとめ
- initすると、.gitディレクトリおよびいくつかのサブディレクトリとファイルが作成される。
- ファイルをaddすると、.git/objects/にファイルの内容のハッシュ値に基づいてディレクトリとファイル(Blobオブジェクト)が生成される。ディレクトリ名はハッシュ値の先頭2桁、ファイル名は残り38桁となる。
- commitするたびにcommitオブジェクトとtreeオブジェクトが生成される。
- commitオブジェクトはコミットの内容を表す。「parent」に親コミットのオブジェクトが記載され、コミットの親子関係を把握することができる。「tree」はコミット対象のtreeオブジェクトを表す。
- treeオブジェクトはBlobオブジェクトまたは他のtreeオブジェクトを指しており、ディレクトリを表現している。