背景

git は差分管理ツールというイメージが強いですが、どうやってディレクトリ情報を保存しているかについてふと疑問に思いました。
ディレクトリ情報も差分で管理している?差分ならどこから適用している?.gitの中に実は入っている?
少し考えても手段は分からなかったので調べて見ました。

sha1(前提知識)

今回調べた結果として git で使用されている sha1 というハッシュ技術に触れないわけにはいかないので、前提知識として少し説明します。といっても大した内容じゃないですが…

git では sha1 と呼ばれるハッシュ技術を使用しております。みなさんが git のハッシュと聞いて最初に思い浮かべるのは commit ハッシュだと思います。現在はこの sh1 はセキュリティ上の脆弱性などが見つかり sh2 系統が暗号化技術では主流になっています。まぁ、git の使い方では気にするようなものでもない気がしますが、どうなんですかね。面倒でよく調べていません。 まぁ、git の根幹とも言える技術であり、こういうハッシュ技術をいろんな所に使っているよっていうのが今回のオチだったりします。

どうやってディレクトリ情報を保存しているか

それではどうやって保存しているかを見ていきます。 まず、以下のようにして適当なレポジトリをコピーして、ディレクトリ内に入ります。

$ git clone git@github.com:komem3/go-diary.git
$ cd go-diary

ログなどを確認して適当な commit ハッシュを取ってきます。

$ git log | grep commit | head -n3
commit 14bde74b0c41c5fa48313ea78185e77668b97f20 (HEAD -> master, origin/master, origin/HEAD)
commit 352908fdb0ea4b5a9e611c060eeb496d61d248f4
commit b2175aadf10b51307c33d931840241d0d57bc7e5

この取得したコミットハッシュをデコードしていきます。git には ハッシュに関する操作を行うコマンドがあるため、そのコマンドを使用します。 以下のように入力するとコミットの内容が見れます。コミットハッシュはコミットの内容から作っているってことがよく分かります。

$ git cat-file -p 352908fdb0ea4b5a9e611c060eeb496d61d248f4
tree 30e8473323d6fc439711f2814cb433c7bb408e3b
parent b2175aadf10b51307c33d931840241d0d57bc7e5
author dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1596432759 +0000
committer GitHub <noreply@github.com> 1596432759 +0000
...

上から見ていくと、tree という項目と parent という項目があるのが分かると思います。parentはこのコミットの前のコミットに当たります。こうやって前のコミット情報を保存しているということが分かります。
では、treeとは何でしょうか?これが今回の目的のディレクトリ情報です。
試しに出力された tree のハッシュ値をデコードしてみます。

$ git cat-file -p 30e8473323d6fc439711f2814cb433c7bb408e3b

そうすると、以下のようにディレクトリ情報が出力されることが分かります。すごい!

$ git cat-file -p 30e8473323d6fc439711f2814cb433c7bb408e3b
040000 tree ef24d35be196813c0aea4ab8ffa911947741ab83	.circleci
040000 tree 9b65cf3b799fe9861bd209f7329dbb3dfe165393	.github
100644 blob 9dfdfdb57d914ec5af4b96a265d782c27e2cc359	.gitignore
100644 blob 932a7ebed933a1a9a9c8ec5f54cec24c844d8b0f	.markdownlint.json
...

この出力における treeはサブディレクトリとなっています。ハッシュ値をデコードしてみると、サブディレクトリの似たような出力を見ることが出来ます。では、blob は何なのか?こちらも出力してみます。

$ git cat-file -p 9dfdfdb57d914ec5af4b96a265d782c27e2cc359
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so

ファイル情報が入っていました。なるほど − って感じです。つまり commit ハッシュの中に「差分情報」「前のコミット」「その時のディレクトリ情報」が入っているということです。ハッシュ技術の凄さを感じます。 図にしてみる以下のようなイメージです。

commitハッシュの図

まとめ

git はそれぞれのコミットに「前のコミット」「差分情報」「ディレクトリ情報」を保存しているということが分かりました。20 バイトとかにこれだけの情報が詰まっているというのはハッシュ技術の面白さを感じます。git のハッシュについてもう少し詳しく知りたいかたは下の参考文献を呼んでみると良いかもしれないです。

参考文献

https://git-scm.com/book/ja/v2/Gitの内側-Gitオブジェクト