Git Objectの話
これは Harekaze Advent Calendar 2017 23日目の記事です。
git objectを皆さんはご存知ですか。そもそもとしてgitをご存知であるか問題があるわけですが、まぁ親の顔よりgitのdiffをみた人もいるんと思うので知ってることにして話を進めます。
で、git objectって何よって話をすると皆さんがgit init
をしてホゲホゲすると生まれる.git/objects
を指しており、gitのコアの部分のデータストアのことを指します。
さて、このオブジェクトは大きく分けて三つの構成がされています。
- blob
- ファイルデータを表現する
- tree
- フォルダ構造を表現する
- commit
- treeへの参照、コミットしたユーザーやタイムスタンプなどのスナップショットの表現
それをラップしているのが僕らが普段扱うコマンドたち。
そのコマンドも大きく分けると二つで、
- 磁器コマンド(Porcelain command)
- 僕らが普段使うような
branch
とかユーザーフレンドリーなコマンドたち
- 僕らが普段使うような
- 配管コマンド(plumbing command)
- 普段のgitフロー開発では普通全く使う必要がないgit objectを直接触るような
hash-object
などのコマンド
- 普段のgitフロー開発では普通全く使う必要がないgit objectを直接触るような
後者の方はあまり聞きませんが、今回はこれも用いて行なっていきます。
何が何だかわからんって人もいるかもしれませんが、git使ったことあるならばなんとなく分かるんじゃないかなと思うのでつらつらと書いて行きます。
ではサックっと見ていきましょう
.git/objects
gitのデータは前述の通り、gitオブジェクトという形で.git/objects
の中に格納される。
実際にコピってやってみよう。(以下よ#〜は結果)
git init echo "hello" > piyo.txt git add piyo.txt git commit -m "init commit" find .git/objects -type f #.git/objects/73/0c1bf1546ce745073e50572b64d951427b868c #.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a #.git/objects/2e/1206f177f168e9654bd0f803c3b79100308516
中身を見てみると.git/objects
フォルダの中にファイルが3つできているのがわかると思う。
この不規則に見えるgit objectの名前は40文字のhashがつけられていて、先頭の2文字がサブフォルダ名、残りの38文字をファイル名として1つのオブジェクトにつき一つのファイルに保存される。ちなみにこのobjectの名前はsha1で生成される。
さて、これではそれぞれどんなオブジェクトなのかわからない。そこでgit cat-file
というコマンドを用いる
git cat-file -t 2e12 #tree git cat-file -t 730c #commit git cat-file -t ce01 #blob
よって以上のobjectはtree
,commit
,blob
と言うことがわかった
blob object
-p
オプションでオブジェクトの中身を表示ができる。
git cat-file -p ce01 #hello cat piyo.txt #hello
blob objectはファイルデータのスナップショットで、ファイル名などのメタ情報は含まれてない。
tree object
git cat-file -p 2e12 #100644 blob ce013625030ba8dba906f756967f9e9ca394464a piyo.txt ls # piyo.txt
tree objectはfile構造を指していて、file属性やblob objectへの参照、filenameを記録している。 なお、サブフォルダの場合はtree objectへの参照になる。
commit object
git cat-file -p 730c #tree 2e1206f177f168e9654bd0f803c3b79100308516 #author take <****@gmail.com> 1513838255 +0900 #committer take <****@gmail.com> 1513838255 +0900 #init commit //流石にメールはぼかしましたw
commit objectには、トップレベルのtreeへの参照、commitしたユーザー情報、タイムスタンプ、commitメッセージが含まれる。
ちなみに最初のcommitなので親commitがないが、2回目以降のコミットでは親commitへの参照も含まれる。 ほんまか?って思うかもしれないので試しに見てみる。
ではまずは新しいファイルで新たにオブジェクトが生成されるか確認する。
echo "world" > fuga.txt git add fuga.txt git commit -m "second commit" ls #fuga.txt piyo.txt find .git/objects -type f #.git/objects/da/22367adec08f45e1f4c368361f7582159c9012 #.git/objects/b4/7b491cb488426c0ea4c0b04bc0ac9609fb6cc0 #.git/objects/73/0c1bf1546ce745073e50572b64d951427b868c #.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a #.git/objects/83/c7a922210527a3543ce0cfa9f1f63d9dce921b #.git/objects/2e/1206f177f168e9654bd0f803c3b79100308516
確かに、以下のobjectが増えていることがわかる。
.git/objects/da/22367adec08f45e1f4c368361f7582159c9012 .git/objects/b4/7b491cb488426c0ea4c0b04bc0ac9609fb6cc0 .git/objects/83/c7a922210527a3543ce0cfa9f1f63d9dce921b
ではcommit objectをgit cat-file
をする。
git cat-file -p 83c7 #tree da22367adec08f45e1f4c368361f7582159c9012 #parent 730c1bf1546ce745073e50572b64d951427b868c #author take <hayatake396@gmail.com> 1513954425 +0900 #committer take <hayatake396@gmail.com> 1513954425 +0900 #second commit
この場合、確かにparent
と言う親commitが出来ていることがわかる。
折角なので、他に増えたobjectたちも確認してみよう。
git cat-file -p da22 #100644 blob b47b491cb488426c0ea4c0b04bc0ac9609fb6cc0 fuga.txt #100644 blob ce013625030ba8dba906f756967f9e9ca394464a piyo.txt
treeなのでファイル自体がやはり増えている。
git cat-file -p b47b #world
blobなのでファイルの中身が確かに出てきている。素晴らしい。とりあえずgitobjectが頑張ってることはわかった。
ここで思うだが、git objectを配管コマンドから直接いじって見たい気持ちになったりするわけである。
と言うわけでこれってもしかして全くファイル作らずにgit commitができるのではと言うことが容易に思えるので検証していく
配管コマンドを用いたファイルを一度も作らずに行うcommit
初めはまずはgit init
をして確かに初めにはないもないことを確認する。
mkdir fugafuga cd fugafuga git init #Initialized empty Git repository in /Users/Home/Desktop/fugafuga/.git/ find .git/objects #.git/objects/ #.git/objects/pack #.git/objects/info
さて、使うコマンドはgit hash-object
と言う物を使う。これはobjectIDを計算してファイルからblobを作成するコマンドである。
オプションがいくつか設定する必要があって、今回使うのは-w
,--stdin
と言う実際にobjectデータベースに書き込むオプションと標準入力からobjectを読み込むと言うものを指定する。
ちなみに-w
をつけないとただ単にIDを吐くだけになる。blobは出来ない。
echo 'create data' | git hash-object -w --stdin #070f3bd01632c945394b3aac7187a9d91ca4816a ls #(何もない) git cat-file -p 070f #create data
これで確かにblob objectだけができた。
次にこれを格納するtree objectを作る。
さて、下準備をする必要がある。
まず、使うコマンドはgit update-index
と言うコマンドを使う。これはworking treeのファイル内容をindexに登録するコマンドである。
これにもオプションをつける必要があり、Stageingしてるファイルに対してこれは実行されるので(と言うかファイルがないのでStageingする場所もない)--add
オプションをつけて追加すること必要ある。
また、このindexに追加するものはファイルではなくてobjectデータベースにあるので--cacheinfo
オプションをつけて指定された情報をindexに直接挿入してやることになります。このオプションは引数を3つ取ることになって--cacheinfo <mode> <object> <path>
と記述する必要がある。
ちなみにだがこのmodeとはchmodなどと同じパーミッションの数値の値を指しています。今回の100644
はchmod 644
と同じですので通常の一般的な権限のファイルであることを意味します。逆に実行可能ファイルとかならば100755
になります。
git update-index --add --cacheinfo 100644 8b137891791fe96927ad78e64b0aad7bded08bdc test.txt
さて、これ無事indexにぶち込んだのでStageingエリアをtree objectに書き出すことができるようなった。
これをどう書き出すかと言うとgit write-tree
コマンドで行う。これはまだtreeがないときに自動でindexからtree objectを作る。
git write-tree #c47b19c8b7a3b7724138f73e4cd53efa0f1e9595 git cat-file -p c47b #100644 blob 070f3bd01632c945394b3aac7187a9d91ca4816a test.txt
これによって作ることができた。
さて、次はcommit objectです。ここまでやれば簡単で、
echo 'first commit' | git commit-tree c47b #5cb61261e3c4d2d15d09583c107577564cc6335e git cat-file -p 5cb6 #tree c47b19c8b7a3b7724138f73e4cd53efa0f1e9595 #author take <*****@gmail.com> 1514031034 +0900 #committer take <****@gmail.com> 1514031034 +0900 #first commit
確かにcommitが出来ました。やったぜ!
終わりに
私は今回NICT主催のSecHack365というのに参加しているんですが、計画性のなさが露呈して、開催期間中に記事を書く羽目になりました。ツライ....
次は@Knom10さんです。お楽しみに!