誤解を恐れずに言えば、Gitとは一つの連想配列ストレージシステムです。
かっこよく言うと Key Value Storeです。
Gitがやっていることの本質は、あなたのコンテンツを圧縮してDBに保存し、あなたの要求に応じてDBから取り出して解凍する、ただそれだけです。
具体的には
KVSでのsetにあたる命令がgit add
,git commit
KVSでのgetにあたる命令がgit checkout
です。
そしてデータベースとは.git/object/ ディレクトリ配下にあるファイル群のことです。
さて今回はgit checkout
コマンドを使わずに低レイヤーなコマンドだけを使って同じことを実現してみましょう。
準備
新規レポジトリを作成して、"hello world"と書かれたファイルをコミットします。
git init dqneo
echo hello world > hello.txt
git add hello.txt
git commit -m "initial commit"
次にそのファイルを削除してコミットします。
git rm hello.txt
git commit -m "second commit"
2コミット実行した結果、いまワークツリーにはファイルが何もない状態になりました。
過去のファイルはどこへ行った?
さてhello.txt
はワークツリーから消えてしまったわけですが、いったいどこにあるのでしょうか?
git rev-parse
やgit cat-file
などの低レベルコマンドを駆使して連想配列オブジェクトを順番にたどっていけばわかります。
まず現在のHEADが指し示すコミットハッシュ値を調べます。
$ git rev-parse HEAD
3d4387e272a02ccd42d63570053ec317197c8da9
現在のHEADが指し示すコミットハッシュ値は3d4387e272a02ccd42d63570053ec317197c8da9
です。
(ちなみにこの値は、あなたの環境では違う値になるはずです。なぜならコミットした日時とauthorの名前が、私とあなたでは違うからです)
次に、このコミットの親コミットを調べます。
親コミットというのは「ひとつ前のコミット」という意味です。
$ git cat-file -p 3d4387e272a02ccd42d63570053ec317197c8da9
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
parent 3d18808630d928c2f4b826569cc92d05bc25fa73
author DQNEO <dqneoo@xxxx.xxx> 1386165103 +0900
committer DQNEO <dqneoo@xxxx.xxx> 1386165103 +0900
second commit
親コミットはparentの右に書いてある 3d18808630d928c2f4b826569cc92d05bc25fa73
であることがわかります。
親コミット(初回コミット)の中身をのぞいてみましょう。
git cat-file -p 3d18808630d928c2f4b826569cc92d05bc25fa73
tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60
author DQNEO <dqneoo@xxxx.xxx> 1386165089 +0900
committer DQNEO <dqneoo@xxxx.xxx> 1386165089 +0900
initial commit
上記のtreeのハッシュ値を使って、treeオブジェクトの中身を覗いてみると
git cat-file -p 68aba62e560c0ebc3396e8ae9335232cd93a3f60
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello.txt
あった!ありました!
hello.txt
を表すblobオブジェクトが見つかりました。
過去のファイルはここにあった!
このオブジェクトが実際に.git/objects/
内に存在することを確認しておきましょう。
ls -l .git/objects/3b
合計 4
-r--r--r-- 1 vagrant vagrant 28 12月 4 22:50 2013 18e512dba79e4c8300dd08aeb37f8e728b8dad
たしかに存在しています。
git checkoutと同じことを低レイヤーにやる
冒頭の定義によれば、git checkout
の本質はデータベースからコンテンツを取り出すことです。
具体的に言うと.git/objects/
の中にあるファイル群からそれを見つけ出してカレントディレクトリ(ワークツリーとも言う)に展開することです。
さて、ではこのハッシュ値を使ってコンテンツを取り出してみましょう。
$ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
hello world
キターーーー!!
ではこの結果をリダイレクトして、
git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad > hello.txt
はい、これでgit checkout
みたいなことができました!
(実際のgit checkout
はHEADの参照先を変更したりとかもうちょっといろいろやってる)