Gitのデータ管理の補足
Gitのデータの管理の仕方について補足します。
ここまでの解説では正確性よりイメージを掴んでいただくことを優先していました。
ここではより正確に、どのようにGitがデータを管理しているのか見ていきます。
イメージがわかっていれば大丈夫だよという場合は飛ばしてください。
Gitオブジェクト
「git add 」や「git commit」した時、「圧縮ファイル」「ツリーファイル」「コミットファイル」が作成されることは前の解説で見てきました。
Gitではこれらのファイルを「Gitオブジェクト」と呼んでいます。
Gitオブジェクトは「.git/objects」ディレクトリの下に保存されます。
この3つのGitオブジェクトに関して、より詳しく解説します。
圧縮ファイル
圧縮ファイルはファイルの中身そのものを圧縮したものです。
正確には「blob(ブロブ)オブジェクト」と言います。
blobというのは「カタマリ」という意味です。
ファイルの中身を圧縮しただけのカタマリということになります。
圧縮ファイルのファイル名は、前章では「圧縮ファイルA」と書いていましたが、実際はハッシュIDになります。
ハッシュIDというのは、ヘッダー(ファイル内容の文字数など、ファイルのメタ情報)とファイル内容を、SHA-1というハッシュ関数で40文字の英数字に変換したものです。
ハッシュIDのうち、先頭2文字をディレクトリ名に、残り38文字をファイル名にして保存します。
実際にどのようなファイル名になるのか、確認してみましょう。
# 新しいディレクトリを作成します $ mkdir sample # そのディレクトリに移動します $ cd sample # Gitを初期化します。ここまでは前準備です $ git init # ファイルの中身が「Hello, world!」というgreetingというファイルを作成します $ echo 'Hello, world!' > greeting # greetingのハッシュIDを表示します $ git hash-object greeting af5626b4a114abcb82d63db7c8082c3c4756e51b
このようにハッシュIDは、「af5626b4a114abcb82d63db7c8082c3c4756e51b」という40文字の英数字になります。
では次に git add して圧縮ファイルを作成してみましょう。
# git add することで圧縮ファイルを作成します $ git add greeting # .git以下のファイル構造を表示します。以下は今回関係している部分だけを抜粋 $ tree .git .git ├─ objects ├─ af └─ 5626b4a114abcb82d63db7c8082c3c4756e51b
(*)treeコマンドのインストール方法はこの記事の末尾に記載しております。
圧縮ファイルは「.git/objects/af/5626b4a114abcb82d63db7c8082c3c4756e51b」として保存されています。
ここで重要なことは、ハッシュIDというのは、ファイルの中身に対して一意になるということです。
中身が同じファイルであれば必ず同じハッシュIDになります。
そのため、ファイルの中身が同じであれば「git add」しても追加で圧縮ファイルが作られることはありませんし、ファイルの中身に変更があれば「git add」すると別の圧縮ファイルが作成されます。
ツリーファイル
圧縮ファイルは、ファイルの中身を圧縮したものを保存していて、圧縮ファイルのファイル名もファイルの中身をベースにハッシュ関数で作成されたものです。
つまり、圧縮ファイルにはもともとのファイル名の情報がどこにも残っていないことになります。
そこで、ファイル名とファイルの中身の組み合わせ(ファイル構造)を保存するためにあるのがツリーファイルです。
コミットをするとツリーファイルが作成されます。ツリーファイルは「treeオブジェクト」と言います。
ツリーファイルは前の記事で説明したことと実際のファイル構造に違いがあるため注意してください。
前の記事ではファイル名と圧縮ファイル名の組み合わせを保存したものとして説明しました。
実際は、ディレクトリの一つの階層ごとに一つのツリーファイルになっていて、ツリーファイルには圧縮ファイルだけでなくツリーファイルも保存されています。
言葉ではわかりにくいので、具体的に見てみましょう。
なお、Gitオブジェクトの中身を確認するには「git cat-file -p <オブジェクト名>」(オブジェクト名はGitオブジェクトのハッシュIDやブランチ名など。詳細は公式ドキュメントのSPECIFYING REVISIONSを参照)コマンドを使用します。
# コミットしてツリーファイルを作成します # -m オプションを付けることでエディタを立ち上げずにコミットできます $ git commit -m 'add greeting' [master (root-commit) ae682f6] add greeting 1 file changed, 1 insertion(+) create mode 100644 greeting # master ブランチ上での最後のコミットが指しているツリーファイルの中身を表示します $ git cat-file -p main^{tree} 100644 blob af5626b4a114abcb82d63db7c8082c3c4756e51b greeting
最後のコミットが指しているtreeには、blobオブジェクト「af5626b4a114abcb82d63db7c8082c3c4756e51b」が greeting というファイル名だ、ということが保存されています。
ここまでは前の解説の通りです。
ではここで、ディレクトリを追加してコミットすると何が起こるでしょうか。
$ mkdir subdir # subdir ディレクトリの下に goodmorning というファイルを作成します $ echo 'Goodmorning!' > subdir/goodmorning $ git add subdir $ git commit -m 'add subdir' [master 75458c8] add subdir 1 file changed, 1 insertion(+) create mode 100644 subdir/goodmorning # ツリーファイルのIDを取得するために、最後のコミットの中身を表示します # git cat-file -p main^{tree} コマンドでも大丈夫です $ git cat-file -p HEAD tree acd75d1289b95787ecaab96c73fe1f3dbfa9cf67 parent ae682f61f39b5c364781cb179035ae534c56a326 author kiyodori <メールアドレス> 1493763216 +0900 committer kiyodori <メールアドレス> 1493763216 +0900 add subdir # ツリーファイルの先頭の文字を指定して、ツリーファイルの中身を表示します $ git cat-file -p acd75d 100644 blob af5626b4a114abcb82d63db7c8082c3c4756e51b greeting 040000 tree 60ac1b2d01e7f0c21178dcc2e767fb9a24d97124 subdir
blogオブジェクトに関しては先ほどと同じです。
そこに、treeオブジェクト「60ac1b2d01e7f0c21178dcc2e767fb9a24d97124」のツリー名は subdir だよ、というのが追加されています。
ここが注目ポイントで、ツリーファイルの中にツリーファイルが含まれています。
このように、ツリーファイルは一つのディレクトリに対応していて、ツリーファイルの中にツリーファイルと圧縮ファイルが含まれるようになっています。
一応 subdir のツリーファイルの中身も確認しておきましょう。
# ツリーファイルの先頭の文字を指定して、ツリーファイルの中身を表示します $ git cat-file -p 60ac1b 100644 blob fa476f276a6fa984a789416f63f925e999834081 goodmorning
subdir ディレクトリには blobオブジェクト「fa476f276a6fa984a789416f63f925e999834081」がgoodmorning というファイル名で保存されています。
ここまでを振り替えると、一つのファイルに一つの圧縮ファイルが対応していて(※)、一つのディレクトリに一つのツリーファイルが対応していることがわかります。
ツリーファイルは構造や名前を持たない圧縮ファイルに構造を与えるためのもので、圧縮ファイルやツリーファイルを保存しているのです。
※ ファイルの中身が同じでファイル名が違う場合、圧縮ファイルはファイルの中身をベースに作成されるため、圧縮ファイルは同じものになります。
コミットファイル
ツリーファイルが作成されたことで、ファイルの構造がわかるようになりました。
しかしまだ、いつ、誰が、何を、何のために変更したのかということがわかりません。
そこで、その情報を保存するためにあるのがコミットファイルでした。コミットファイルは正確には「commitオブジェクト」と言います。
早速コミットファイルの中身を確認してみましょう。
# 最新のコミットファイルの中身を表示します $ git cat-file -p HEAD tree acd75d1289b95787ecaab96c73fe1f3dbfa9cf67 parent ae682f61f39b5c364781cb179035ae534c56a326 author kiyodori <メールアドレス> 1493763216 +0900 committer kiyodori <メールアドレス> 1493763216 +0900 add subdir
まず、コミットした時点のtree「acd75d1289b95787ecaab96c73fe1f3dbfa9cf67」が保存されています。
これはこのプロジェクトの一番上のディレクトリのツリーファイルになります。
一番上の階層のツリーをコミットファイルに保存することで、コミットした時点でのスナップショットを記録しています。
次がparent、親コミットを保存しています。
親コミットは「ae682f61f39b5c364781cb179035ae534c56a326」です。
Gitはこのように親コミットを保存することでコミットの履歴を辿れるようにしています。
あとは作成者の名前とメールアドレス、改行、コミットメッセージと続きます。
これで、変更者と変更理由がわかります。
まとめ
Gitは変更履歴を保存する時、「圧縮ファイル」「ツリーファイル」「コミットファイル」という形でスナップショットを記録しています。
Gitの実体は基本的にはこれだけです。
Gitのコマンドは、この3つのGitオブジェクトに対して何らかの操作をしているだけです。
これから色々なコマンドを学んでいきます。
その時、コマンドを闇雲に覚えるのではなく、このデータ構造に対してどういう操作をしているコマンドなのかということをイメージしてもらえれば、Gitが実際どのようなことをしているかがわかると思います。
次回の解説からはGitの具体的なコマンド、操作に進んでいきましょう。
treeコマンドのインストール方法
Macの場合
Homebrewを使ってインストールします。ターミナルで下記コマンドを実行してください。
$ brew install tree
以上でtreeコマンドが使えるようになります。
もしHomebrewを使われていない場合は、下記URLのインストールのスクリプトをターミナルに貼って実行してください。
Windowsの場合
ホームディレクトリ直下に .bashrc ファイルを作成します (~/.bashrc )
そのファイルに以下を記載します。
alias tree='cmd ¥¥c tree ¥¥A ¥¥F'
- Git Bashを再起動します。
※ 初回起動時にエラー文が表示されますが、問題ないため無視して大丈夫です。
これでtreeコマンドを使用できるようになります。
参考図書
独学で挫折しそうになったら、オンラインプログラミングスクール