.gitフォルダをチラ見(gitの実装)

gitがいつも偉そうに見えてるが、中身は相当単純なものだ

ここ便利のため、自分のリポジトリを例としてgitの実現を解析しようと思う

もちろん興味があれば自分のリポジトリで同じような命令を実行しても構わないよ

まずはリポジトリをクローンしよう

mkdir badappe_oscilloscope     
git clone --mirror https://github.com/yeonzi/badappe_oscilloscope badappe_oscilloscope/.git
cd badappe_oscilloscope
git config --bool core.bare false
git checkout master

ここは普通のcloneとちょっと違ってるみたいね?

今回は中身を探求するため、.gitに入ろう

cd .git

ここでlsしたら

> ls -l
合計 24
-rw-r--r-- 1 yeonji yeonji   23  6月  4 14:15 HEAD
drwxr-xr-x 2 yeonji yeonji    6  6月  4 14:14 branches
-rw-r--r-- 1 yeonji yeonji  178  6月  4 14:15 config
-rw-r--r-- 1 yeonji yeonji   73  6月  4 14:14 description
drwxr-xr-x 2 yeonji yeonji 4096  6月  4 14:14 hooks
-rw-r--r-- 1 yeonji yeonji 2239  6月  4 14:15 index
drwxr-xr-x 2 yeonji yeonji   21  6月  4 14:14 info
drwxr-xr-x 2 yeonji yeonji   18  6月  4 14:15 logs
drwxr-xr-x 4 yeonji yeonji   30  6月  4 14:14 objects
-rw-r--r-- 1 yeonji yeonji  288  6月  4 14:14 packed-refs
drwxr-xr-x 4 yeonji yeonji   31  6月  4 14:14 refs

ファイルがいっぱい出てる。

まず、唯一の大文字ファイル、HEADを確認しよう

> cat HEAD
ref: refs/heads/master

このファイルには、いま参照しているブランチ、タグ、もしくはコミットの情報をのせたテキストファイルである。いま設定しているHEADrefs/heads/masterへの参照である。

さて、refs/heads/masterを見に行こう

> cat refs/heads/master
cat: refs/heads/master: そのようなファイルやディレクトリはありません

おや、ファイルが存在しないって?

packed-refsっていうファイルもあるみたい、見に行こう

> cat packed-refs 
# pack-refs with: peeled fully-peeled sorted 
df6530526ee3e791d5e0875264181202535f2d97 refs/heads/lctf2018
9e14e9a2f28ae20ab526c716649afa5b7aae94ef refs/heads/master
6e28df7aab4624b5b2d8cbbd0eab50c01f2cac73 refs/heads/nen9ma0
a814574a1029b1f40b1727f1ba4130facb6cf338 refs/tags/v0.1_alpha

成歩堂🤔?アクセス効率のためパックされたんだ

これでrefs/heads/masterが対応しているIDは9e14e9a2f28ae20ab526c716649afa5b7aae94efであるこどがわかった

さて、このIDを見に行こう…

> ls objects
info pack

e.jpg

GitHub様感謝します、色々な最適化をしてますね。。。

一旦もどって、

> cd ..
> mv .git/objects/pack ./
> git unpack-objects < pack/*.pack  
Unpacking objects: 100% (173/173), 5.77 MiB | 12.49 MiB/s, done.
> rm -rf pack
> cd .git

これで全てパックされたオブジェクトを取り出そう

今度もう一回IDを見に行こう

> ls objects
01  17  2f  3b  53  5d  66  78  84  92  a3  b1  cd  dc  e9  fb
02  18  31  3c  54  5e  69  79  85  94  a4  b4  ce  dd  ea  fc
03  1a  32  3e  55  5f  6c  7a  87  96  a5  b6  d0  de  eb  fe
06  22  33  43  56  61  6d  7b  88  98  a6  bb  d1  df  ec  info
0c  26  34  44  58  62  6e  7d  8a  99  a8  c2  d2  e0  ee
0f  28  37  47  59  63  71  7e  8c  9c  a9  c5  d3  e2  f4
12  2a  38  4e  5b  64  72  81  8d  9d  ac  c8  d7  e3  f6
13  2e  39  50  5c  65  73  82  90  9e  ae  cb  d9  e8  f9

色々出てるね。ちなみにディレクトリで分けた理由は、ファイルシステムの実装がダサいからx

ほぼのファイルシステムはB木を基づいて実装してた。一つのディレクトリにファイルがいっぱい入ると、フォルダの中でファイル探すとき、多くのデイスクブロックの読み取ることになって、アクセス効率が悪くなる一方だ。

このはなし一旦置いといて、オブジェクトIDは9e14e9a2f28ae20ab526c716649afa5b7aae94efだったっけ?最初の2文字でディレクトリを探しに行こう

> ls objects/9e
14e9a2f28ae20ab526c716649afa5b7aae94ef

あった

中身も確認しよう

> cat objects/9e/14e9a2f28ae20ab526c716649afa5b7aae94ef
x��K
�0Eg���埀��p���H�H������Á7����R��؈��,Hi#(�uҁ�:��� CJ���F���8��D��f�6"��J� &��?+��g<����W��Z�ݗK�ro7.�V`�3��!���������%�io�}DD#%

あっ、言い忘れた、これらのファイルは圧縮されてるんだ。

> cat objects/9e/14e9a2f28ae20ab526c716649afa5b7aae94ef | openssl zlib -d
commit 216tree 8dc1e3451eb9184912704c4a5e0d2179e8029bb5
parent 5dd57dcd71c8aef7d6ad03f62230ab64d30a8f38
author Yeonji <[email protected]> 1543063755 +0900
committer Yeonji <[email protected]> 1543063755 +0900

gitlab-ci.yml

出った!コミットオブジェクトだ

このコミットに対応しているディレクトリ情報は8dc1e3451eb9184912704c4a5e0d2179e8029bb5で、前回のコミットは5dd57dcd71c8aef7d6ad03f62230ab64d30a8f38だった。作者はYeonji <[email protected]>で、Yeonji <[email protected]>という人物がこのコミットをリポジトリに追加した、そしてコミットメッセージはgitlab-ci.ymlだった

ちなみに、この9e14e9a2f28ae20ab526c716649afa5b7aae94efの計算方については、

cat objects/9e/14e9a2f28ae20ab526c716649afa5b7aae94ef | openssl zlib -d > commit.txt

まずこのコマンドで中身をcommit.txtに保存して

> sha1sum commit.txt
9e14e9a2f28ae20ab526c716649afa5b7aae94ef  commit.txt

出った、単に圧縮前のファイルのSHA1ハッシュだ。

ちなみに、git自身でもこれらのファイルを確認する命令が提供したんだ

git cat-file commit 9e14e9a2f28ae20ab526c716649afa5b7aae94ef

でマジック抜きのオブジェクトファイルを確認できたんだ。

この機能をいったん置いといて、ディレクトリ情報8dc1e3451eb9184912704c4a5e0d2179e8029bb5を見に行こう

> cat objects/8d/c1e3451eb9184912704c4a5e0d2179e8029bb5 | openssl zlib -d
tree 215100644 .gitlab-ci.yml�'l�ߪ�4���;�^�I100644 LICENSE;�Oc��`����=qa�100644 Makefile9��l���U��*A��ה�
                                                                                                        100644 README.md��b��
                                                                                                                             څC�
                                                                                                                                ��Q����100644 arch.md�Y�G�
                                                                                                                                                          �=�Ε�?E�

                                                                                                                                                                  ��40000 src�ۼ�0��O�HҀߺ^*�w��%

git treeオブジェクトバイナリで保存してるんだ。

まずは、ヘッダー部分を取り出そう

> cat objects/8d/c1e3451eb9184912704c4a5e0d2179e8029bb5 |\
>> openssl zlib -d |\
>> sed -e 's/\x00/\n/'
tree 215
100644 .gitlab-ci.yml�'l�ߪ�4���;�^�I100644 LICENSE;�Oc��`����=qa�100644 Makefile9��l���U��*A��ה�
                                                                                                100644 README.md��b��
                                                                                                                     څC�
                                                                                                                        ��Q����100644 arch.md�Y�G�
                                                                                                                                                  �=�Ε�?E�

                                                                                                                                                          ��40000 src�ۼ�0��O�HҀߺ^*�w��%

ちょっと明らかになってるでしょう?

このファイルの構造このようだ

tree [content size]\0[Entries having references to other trees and blobs]

ヘッダー部分を削除して

> cat objects/8d/c1e3451eb9184912704c4a5e0d2179e8029bb5 |\
>> openssl zlib -d |\
>> sed -r 's/^tree [0-9]*\x00//'
100644 .gitlab-ci.yml�'l�ߪ�4���;�^�I100644 LICENSE;�Oc��`����=qa�100644 Makefile9��l���U��*A��ה�
                                                                                                100644 README.md��b��
                                                                                                                     څC�
                                                                                                                        ��Q����100644 arch.md�Y�G�
                                                                                                                                                  �=�Ε�?E�

                                                                                                                                                          ��40000 src�ۼ�0��O�HҀߺ^*�w��%

次の部分を分割しようとしたら、ちょっとしたバイナリの操作が必要でsedだけだと難しそうなので、xxdで前処理をしようと

> cat objects/8d/c1e3451eb9184912704c4a5e0d2179e8029bb5 |\
>> openssl zlib -d |\
>> sed -r 's/^tree [0-9]*\x00//' |\
>> xxd -p - | tr -d '\n'
313030363434202e6769746c61622d63692e796d6c00f6276c8cdfaa8b1734bc8fc13b0712d35ea51f49313030363434204c4943454e5345003b7f8e4f63b5ae6093e7f1180e9a3d71150f61f3313030363434204d616b6566696c650039c80fc56cb4e01fe155ad982a41f59cd794c30b31303036343420524541444d452e6d6400eecf6289880bda8543f80cc0cd517fc8caf48cf931303036343420617263682e6d6400dc598a47bb0b903d9ace95cd3f45910b0ce8ea0e34303030302073726300b6dbbc9630868c4fc648d280dfba5e2acd77fff9

そして、分割処理しよう(正規表現むずっ)

> cat objects/8d/c1e3451eb9184912704c4a5e0d2179e8029bb5 |\
>> openssl zlib -d | sed -r 's/^tree [0-9]*\x00//' |\
>> xxd -p - | tr -d '\n' |\
>> perl -p -e 's/((?:3[0-9]{1})*)20((?:(?!00).)*)00(.{40})/\3 \1 \2\n/g'
f6276c8cdfaa8b1734bc8fc13b0712d35ea51f49 313030363434 2e6769746c61622d63692e796d6c
3b7f8e4f63b5ae6093e7f1180e9a3d71150f61f3 313030363434 4c4943454e5345
39c80fc56cb4e01fe155ad982a41f59cd794c30b 313030363434 4d616b6566696c65
eecf6289880bda8543f80cc0cd517fc8caf48cf9 313030363434 524541444d452e6d64
dc598a47bb0b903d9ace95cd3f45910b0ce8ea0e 313030363434 617263682e6d64
b6dbbc9630868c4fc648d280dfba5e2acd77fff9 3430303030 737263

ハッシュから文字列への変換はだるいからやってなかった

このヘッダ抜いたツリーオブジェクトの中身は

[mode] [file/folder name]\0[SHA-1 of referencing blob or tree]

の形でかくファイルのハッシュを保存している

どうしてもファイル名を文字列に変換したいならどうぞ

> echo 4c4943454e5345 | xxd -r -p
LICENSE

LICENSEファイルを例として

3b7f8e4f63b5ae6093e7f1180e9a3d71150f61f3 313030363434 4c4943454e5345

ハッシュは3b7f8e4f63b5ae6093e7f1180e9a3d71150f61f3である

まえと同じように、catでよんで、opensslで解凍したら

> cat objects/3b/7f8e4f63b5ae6093e7f1180e9a3d71150f61f3 | openssl zlib -d
blob 477           DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
                   Version 2, December 2004
 
Copyright (C) 2017-2018 Yeonji <[email protected]>

Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
 
           DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
 0. You just DO WHAT THE FUCK YOU WANT TO.

うむむむむ、ヘッダーが邪魔なんで、前と同じsedで削除しよう

> cat objects/3b/7f8e4f63b5ae6093e7f1180e9a3d71150f61f3 |\
>> openssl zlib -d |\
>> sed -r 's/^blob [0-9]*\x00//'
           DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
                   Version 2, December 2004
 
Copyright (C) 2017-2018 Yeonji <[email protected]>

Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
 
           DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
 0. You just DO WHAT THE FUCK YOU WANT TO.

これでこのファイルをまるごとに出してた

まとめ

簡単でいうと

  • gitには4種類のオブジェクトがある
    • commit
    • tag
    • tree
    • blob
  • すべてのオブジェクトは.git/objectsの中に保存されてる。(たまにパックされてる)
  • すべてのオブジェクトは最初に[type]\x20[contents size]\x00のようなヘッダーが持つ、このヘッダーでオブジェクトの類型を調べられる。
  • オブジェクトはzlibによって圧縮されていることがある、オブジェクトの名前は圧縮されるまえのSHA1である。
  • commitオブジェクトは、現在のtreeオブジェクト、前のコミット、コミット情報を持つ、署名がある場合は、最後にシグネチャーもある。(ブロックチェーンと同じ構造)
  • treeオブジェクトには、あるディレクトリ情報を記録するオブジェクトである。かくエンティティに、名前、権限、ハッシュ(オブジェクトID)の形で格納している。
  • treeオブジェクトの中にもtreeオブジェクトを含むことがある。(入れ子ディレクトリ)
  • blobオブジェクトはファイルの前にヘッダーを追加して保存するものである。

これはgitバージョン管理ツール全ての奥義だ。

ちなみに、logsのところには、refsと同じ、前のcommitハッシュへの記録がある。

git GCはここにも参照している。なので、ファイルを削除してもここに参照がのこって、GC動作しなく、.gitディレクトリが膨らんでるんだ