車輪の再発明シリーズ、今回は自作gitを作ってみましたー
gitの仕組みは公式に載っているのを参考にして実装しました
あとこちらの記事もとても詳しいです
勉強目的なので、gitの仕様を完全に踏襲する感じではなくざっくり模倣しました。
実装内容
だいたいこんなことをやってます↓
mygit initで.mygitディレクトリを作成。中身はこんな感じ↓indexファイル(ステージ用)refsディレクトリ(ブランチのreferenceを格納)objectsディレクトリ(blobやらtreeやらcommitを格納)
mygit addで.mygit/indexにファイル情報をメモしつつ、.mygit/objects/{ファイルの中身のSHA1ハッシュ}にファイルの中身を格納mygit commitで.mygit/indexのファイルをコミット.mygit/objects/{commitハッシュ}にコミットのオブジェクトを作成- 新規ディレクトリが含まれている場合はtreeオブジェクトも作成
- 既存tree内のオブジェクトの変更があればハッシュ値を更新(親treeに伝搬する)
.mygit/indexをクリア.mygit/refs/heads/masterを最新のコミットに書き換え
mygit tree {commitハッシュ}で対象コミットのファイルをツリー表示mygit diff {commitハッシュ} {commitハッシュ}で対象コミット間のdiffを表示mygit logでコミットログを簡易的に表示mygit statusで.mygit/indexの中身を良い感じに表示mygit cat-file {ハッシュ}で.mygit/objects/{ハッシュ}の中身を良い感じに表示
commitのファイル仕様
commit
{treeハッシュ}
{前のcommitハッシュ}
{author}
{message}
treeのファイル仕様
tree
{permission} {ファイル種別: file or tree} {ハッシュ} {ファイル名}
ファイルの仕様
blob
{ファイルの中身}
gitの仕様だとヘッダが {オブジェクト種別} {サイズ}\0 というようにサイズが入っていたりNULLバイトが入っていたりするのですが、面倒だったので改行と空白だけで表現しています。
所感
git addは普通にファイルのハッシュ計算してファイル作ってindexファイル更新するだけなのでそんなに難しくはなかったんですが、
git commitはtreeオブジェクトを生成、tree hashの計算、indexの更新などなどやることが結構あったので大変でした…。
ただ、 git commit まで出来てしまえばgit logはcommitオブジェクトをたどって読み込んで表示するだけだし、git diffはツリーの差分を取る処理だけ書いてあげれば良いので後のコマンドは比較的さくっと実装できました。
mygitレベルの実装であればファイル操作、ツリー操作(再帰)、ハッシュ計算、引数パースなど各言語の基本的な処理で構成されているので、 新しい言語を学ぶときの習作ツールとしても良さそうでした。
ちなみに今後はこのへんを実装する予定↓
mygit diffでファイルの中身の差分の表示できるようにmygit statusでステージしていないファイルとの差分も表示できるようにmygit branchmygit checkoutの実装