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
このファイルには、いま参照しているブランチ、タグ、もしくはコミットの情報をのせたテキストファイルである。いま設定しているHEADはrefs/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

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ディレクトリが膨らんでるんだ