車輪の再発明シリーズ、今回は自作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 branch
mygit checkout
の実装