はじめに
Git難しいですよね。チュートリアル通りにadd,commitしてGithubにpush。一応コマンドを打っていても、何をやっているのかよく分かりません。よく分からない原因は、Gitがユーザーのために難しい原理の部分を隠蔽しているからですが、逆にこの部分を深く理解することで、自信を持ってgitのコマンドを利用できます。
本記事では、gitの裏側の仕組みを解説しますが、Gitはファイルのバージョン管理システムだということを念頭に置いて読んでいただければと思います。
gitのデータはどこで管理しているのか
説明の前に、ターミナルでコマンドをいくつか打ってみましょう。
適当なディレクトリ を作って、移動してください
#この記事で使うディレクトリの作成・移動
mkdir git-practice && cd git-practice
#空のgit-practiceフォルダを、gitが使えるように初期化
git init
さて、ここで
open .
を実行すると、現在いるディレクトリ(ここではgit-practice)が開かれますね。
git-practiceフォルダの中身が何も見えないので、 「cmd + shift + . 」 を押してください。
.gitフォルダがうっすらと現れましたね。「.」から始まるフォルダを隠しフォルダと言い、gitが管理するために(自分で勝手に書き換えたりできないように)、デフォルトでは見えないようになっています。
フォルダを開くと中身はこのようになっています。
**gitのコマンドを実行して生成されたものは、全てこの「.gitディレクトリ 」の中で管理されています。**今までコマンドを打っていたものの実体はここにあったのですね。
gitの基本的なコマンドを実行してみる
それでは、.gitディレクトリの詳細な説明の前に、実際にgit-practiceディレクトリのなかでファイルを作成して、gitで取り扱ってみましょう。
gitでファイルを扱うのは以下のステップになります。
まずワークツリー(今回はgit-practice)の中でファイルやディレクトリを作成して、git addコマンドを実行することでステージングエリアに追加し、git commit コマンドでローカルリポジトリに追加します。
#ファイルの作成
touch first-commit.txt
#ステージングエリアに追加
git add first-commit.txt
#ローカルリポジトリに追加
git commit -m "First commit"
これで完了です。では、.gitディレクトリの中では何が起こっているのでしょうか?
.gitディレクトリの中で起こっていること
まず、ワークツリーをaddすると、ステージングエリアにいろいろなファイルができて、それらは「.git/indexフォルダ」の配下に置かれます(ここでは詳細は割愛します)
そしてそれをcommitすると、複数のオブジェクトができます。
できたオブジェクトは全て ↑の.git/objectsの中に格納されます。
gitのローカルリポジトリに保存されるオブジェクトには3種類あり
- blobオブジェクト
- treeオブジェクト
- commitオブジェクト
の3つです。
そしてそれぞれのオブジェクトは、オブジェクトの中身を関数に突っ込んで計算された、ハッシュ値という40桁の一意の値を持っています。
ハッシュ値はオブジェクトのIDみたいなものですね。それぞれ固有のIDを持った3種類のオブジェクトを使って、gitはどのように情報を管理しているのでしょうか?
そもそも、gitはファイルのバージョン管理システムです。
ファイルのバージョン管理をするとき、知りたいのは3つですよね。
- いつのバージョンのデータなのか
- そのデータはどのようなディレクトリやファイルの構成をしているのか
- 実際のファイルの中身は何なのか?
これをそれぞれ、前述のオブジェクトで表します。
- いつのバージョンのデータなのか → commitオブジェクト
- そのデータはどのようなディレクトリやファイルの構成をしているのか → treeオブジェクト
- 実際のファイルの中身は何なのか? → blobオブジェクト
これらが全て.git/objectsに入っているわけです。
僕の.git/objectsの中身は上のような感じです。(ファイルをいつ作ったかも40桁のハッシュ値に影響するので、同じコマンドを打っても、僕と皆さんのオブジェクト名は異なるはずです)
40桁の名前のオブジェクトが3つできていますが、最初の2桁がディレクトリ名に割り当てられてい流ので、実際に3つのオブジェクトができていることが確認できました。
細かくなりすぎるので、コマンドを用いて内容を確認する方法は割愛しますが、3つのオブジェクトの構造は次のようになっています。
これらが、全て./git/objectsのなかに入っていて、何度もコミットすると、前回のコミットのハッシュ値(ID)をparentを持てるので、バージョンを古いところまで辿れますね。
ちなみに、treeは、ディレクトリ構造をあらわすので、階層構造になっていた場合、treeが何層にも重なることもあります。
こうやって、それぞれのオブジェクトが別のオブジェクトを指すことでファイルとバージョンを管理しているのですね。
分かる方だけに向けてなのですが、実際には、branchはcommitを指し示しているので、何度もコミットするとこんな感じになるんですよね。
というわけで,「.gitディレクトリ」のなかにたくさんオブジェクトを作って、それに40桁の名前をつけてローカルリポジトリとして管理している、というのがgitのバージョン管理の仕組みだ、ということになります。
branchのcheckoutとかHEADの移動とかについても、また別の記事で触れたいと思います~