はじめに
「Gitってなに?」と聞かれたら皆さんは何と答えるでしょうか?
以前の私なら、
「Gitはファイルのバージョン管理システムだよ。本番のコードを変えたくなかったら、ブランチを切れば自分だけの作業環境を作ることもできるよ」
といったような説明をしていたと思います。
けれど学習するうちに様々な間違いに気づきましたのでまとめたいと思います。
非常に重い内容ですので、何回かに分割して説明しようと思います。
Git初心者が陥る勘違い
そもそもブランチとは何でしょうか?
ここの認識が誤っていると、Gitはまず理解できません。
冒頭で私は「ブランチを切れば作業環境を作れる」と説明しました。
学び初めの多くの人は私のようにブランチのことを「1つのディレクトリ」のような認識をしているのではないでしょうか?
まずはそこから正さなくてはなりません。
ブランチとはポインタである
Git初心者は多くの方がブランチを1つの作業ディレクトリ、いわゆる「面」でとらえています。
例えば新しく作業をするとき、git checkout -b edit
で新しくeditブランチを作って移動したとします。
この後作業をするときはすべてeditブランチ上、つまり、editブランチの中身を書き換えていると思っているのではないでしょうか?
これはおそらく、最初にGitを学ぶときに、「新しくブランチを作って作業しましょう」「作業が終わったら二つのブランチをマージ(統合)しましょう」と教わるためにおこる勘違いです。
では実際は何が起こっているのか、それは「.git」のディレクトリの中を見るとわかります。
コマンドラインで見ると苦手意識が生まれる人もいると思うので、今回はエクスプローラーやメモ帳で確認していきます。
以下は、git init
を行った直後の「.git」ディレクトリの中身です。
今回重要になるのは「HEAD」と「refs」です。
まずはHEADの中身を見てみましょう。
refは「参照する」という意味を持ち、HEADの中身では「refsディレクトリの中のheadsディレクトリのmainを見るよ」といっているのです。
ではHEADが見たいといっているrefs/headsのなかには何が入っているのでしょうか。
何もないです。ちなみにgit checkout -b edit
を実行したところブランチの移動自体はできましたが、相変わらずフォルダの中身は空でした。
では次に、mainブランチにコミットをしてみようと思います。
先ほどeditブランチに移動してしまったのでgit checkout -b main
でmainブランチに戻ります。ここでなぜ「-b」をつけるのかは、後ほど説明します。
echo "hello" > test1.txt
git add .
git commit "test_commit1"
先ほどまでは空だったrefs/headsの中に「main」というファイルができました。
中身には40桁の英数字が入っています。
この時できたファイルこそがブランチの正体です。
次からはもう少し深堀りして見ていきましょう。
Gitとはなんなのか
「Gitとは何か?」と聞かれれば、たいていの方が冒頭に私が答えたような『バージョン管理システム』だと答えるでしょう。間違いではないのですが、「どうやって実現されているのか」というところが最も重要です。
公式にはこのように書かれています。
Gitのコアの部分はシンプルなキー・バリュー型データストアである
キー・バリュー型データストアとは、保存したいデータに対して一意のキーを振り、セットで格納する方式です。
GitではSHA-1というハッシュ関数を用いて40桁のキー値を出力しています。
これをデータとセットで格納することで、キー値を指定するだけで任意のデータを呼び出すことができます。
では、先ほどのコミットを振り返ってみましょう。
$ git commit -m "test_commit1"
[main (root-commit) aa78566] test_commit1
1 file changed, 1 insertion(+)
create mode 100644 test1.txt
$ git log
commit aa78566285e3aa3782eaeba35fc431200252b9ef (HEAD -> main)
aa78566
というのがコミットIDの頭の文字です。
git log
コマンドで出力されているのが本来のコミットIDです。
いろんなコマンドでコミットIDを使うのですが、40桁も毎回打つのは大変なので、「これだけ打てばいいよー」とコミットするときに教えてくれています。(調べたところ、頭4桁までなら省略できるそうです。もし重複するものがあれば候補を教えてくれるようです。)
そして以下はコミット後に「.git/objects」というフォルダにできたファイルです。
今回注目していただきたいのは「aa」というフォルダです。
中身には「78566285e3aa3782eaeba35fc431200252b9ef」というファイルがあります。
ピンときたでしょうか?
実は、コミットIDの頭2文字がフォルダ名になっており、残りの38文字がファイル名になっているのです。
このファイルをたどっていくと、test1.txtの中身にたどり着くことができます。
フォルダが3つも生まれている理由などはかなり込み入った話になるので、次回の記事で触れていきます。
今回は、「Gitではコミットをするときに40桁のキー値とデータをセットで保存してるんだなあ」ということだけ覚えて頂ければ大丈夫です。
結局ブランチって何なの?
では、話を戻しましょう。
「ブランチとはポインタである」の項目で検証したように、「refs/heads」は最初は空であり、コミットをしたタイミングで「main」というファイルができていました。
改めて、「main」の中身を確認してみましょう。
見覚えがありませんか?
これは先ほどのコミットIDです。
冒頭でもお話したように、学び初めの頃はブランチを「1つのディレクトリ」のように認識しがちですが、実はブランチとはこのようにコミットIDを保存したファイルなのです。
ちなみに、このコミットIDはmainブランチでコミットをするたびに最新のものに書き換わります。
Gitではコミットの際は常に1つ前のコミットと紐付けているので、mainブランチは最新のコミットIDさえ把握していればいつでも過去のコミットをたどることができるのです。
図にするとこんな感じでしょうか。
すごろくのマスをどんどん作っているようなイメージです。
ブランチは常に先頭のマスに旗を立てているとイメージするとわかりやすいです。
コミットをする前の「refs/heads」が空だったのは、旗を持っているだけの状態で立てていなかったからだと思えば納得できますね。
HEADポインタ
ブランチの正体がつかめてきたと思います。
では最後に、「HEADポインタ」について学びたいと思います。
「ブランチとはポインタである」の項目で「HEAD」というファイルを確認したのを覚えているでしょうか?
ここまでの内容で「refs/heads」の中にはブランチが入っていることがわかりました。
今度はeditブランチにコミットして「refs/heads」の中にブランチが2つ存在する状態にしたいと思います。
今のHEADファイルはどうなっているでしょうか?
「refs/heads」の中のeditファイルを参照しています。
git checkout main
でブランチを切り替えてHEADファイルの中身を確認します。
mainに切り替わっていますね。
これが何をしたいのかというと、先ほどコミットをするとすごろくのようにマスが増えて、常に新しいマスにブランチという旗を立てるとお話しましたよね。
では旗が2つある場合はどうなるでしょうか?
どちらの旗を動かせばいいのか、そもそもどちらの旗のところに自分がいるのかわからなくなってしまいますよね。
HEADポインタとは「今この旗のところにいるよ!いつでも持って移動できるからね!」という目印です。
どのブランチを参照するかを切り替える特別なポインタで、git checkout ブランチ名
の度に参照先を書き換えることで旗のところに移動しているのです。
いわば自分の分身のようなものです。
「ブランチを切って作業する」というのは「git checkout -b ブランチ名
で新しい旗を作って持ち替えたよ!」というイメージですね。
ちなみに、コミットが1つもない状態でブランチを作っても旗を立てるところがないので「refs/heads」にファイルは増えませんが、1つでもコミットがあれば、そこに旗を立てられるので、ブランチを作った瞬間にそのコミットIDを格納したファイルが作成されます。
「ブランチとはポインタである」の項目で、editブランチに移動した後mainブランチに戻るときにgit checkout -b main
としたのは、コミットを行うまではmainの旗を立てる場所がなくて「refs/heads」が空だったので、HEADポインタが「main」という旗を見つけられなかったためです。
おまけ
これまで「ブランチ=作業ディレクトリ」と認識していたのに、急に「ただの目印だよー」なんて言われたら、「じゃあどこで作業してるんだ!」と疑問に思う方もいるでしょう。
ここでコミットするまでの流れを振り返ってみましょう。
Gitでコミットするまでには大きく3つのエリアがあります。
-
ワークツリー
作業エリアです。エリアとは言っていますが、特に意識しなくても何かしらのファイルを変更しているときは常にここにいます。 -
インデックス
コミットを準備するためのエリアです。git add
を行うとここに変更したファイルが登録されます。 -
リポジトリ
ここが「変更を管理する」エリアです。git commit
を行うとスナップショット等をここに保存します。
スナップショットとは簡単に言うとその時点での写真だと思ってもらうとイメージしやすいです。
これが存在することで、簡単に前のバージョンのファイルに戻すことができるのです。
例えばコーディネートに悩んだ時に写真を撮ってアルバムに入れておけば、「やっぱり前の方がいいかも」と思った時に写真を見れば再現できますよね。
3つのエリアをイメージするとこんな感じです。
おそらく今までのブランチのイメージはこの画像全体だったと思います。
ですが実際はアルバムに刺さっている旗(この場合は付箋の方がいいかな)だけをブランチというのです。
後でまた見たいなという写真には付箋を貼っておけば、すぐに見直すことができますね!
おわりに
どうでしょうか?実は「ブランチとは何か」というのは限定公開の記事ですでに書いており、エンジニア仲間に見てもらったのですが、全然伝わらなかったんですよね。
口頭で質問されても、頭にはイメージ図があるのにうまく言語化できなくて悔しかったので、もっと深いところまで学習した結果、「あ、まだ表面しかわかってなかったんだ」と気づくことができました。
昨日はひたすら「ブランチは面じゃない!点なんだ!」と連呼して笑われたので、この記事で伝わると良いなと思います(笑)
次回は「Gitコマンドを解剖する」というテーマで書こうと思います!
長文お付き合いありがとうございました!