git のブランチについてわかりやすく説明してみる


ありがちなイメージ

 最初にブランチというものをイメージするとき、GUI なツールを使ってブランチツリー図を見た感じから、普通はブランチはこんな風に考える方が多いと思います。

 ブランチを作成した所から新しい枝が伸び、この枝の部分がブランチとなっています。
 Mercurial なんかでは、確かにこのようなイメージで問題ありません。ですから、そちら方面から git に入った人はこういうイメージを持つ方が多いのではないかと思います。
 ですが、git のブランチは、このようにイメージしているとかなりの混乱を招くことになります。

git のブランチのイメージ

 git のブランチは、以下のようにイメージすると理解しやすいと思います。

 git でブランチを作成すると、ツリーの根元から現在の位置までが作成したブランチとしてマークされます。上の例で言うと、すべてのコミットが Branch-A であり、Branch-B でもあるという状態になります。そしてコミットすると、新しいコミットも自動的に現在のブランチとしてマークされます。
 つまり、ツリーの枝というものは単にコミットの履歴でしかなく、ブランチとは関係ありません。そしてブランチは、個々のコミットに対してマークをつけているだけなのです。
 ですので、git はブランチを削除したりすることもできます。

この時、削除するのはブランチだけなので、コミット自体は消えずに残ることになります。

コミット時、ブランチのマークがつかない場合がある

 git はコミットしたとき、いずれかのブランチに所属している場合のみ、そのコミットに自動的にブランチのマークをつけます。
 もし、それ以外の状態でコミットしてしまうと、そのコミットにはブランチのマークがつきません。つまり、そのコミットはどのブランチにも所属していないものとして扱われます。
 さらに git の GUI ツールはたいていブランチに含まれないものを表示しないため、コミットが消えてしまったように見えます。(たちの悪いことに、コミットした後 checkout コマンド等でブランチを切り替えるまでは、ログは正常に見えています。)
 では、どんな場合にブランチに所属していない状態になるのか。例をあげると次のような場合に、どのブランチにも所属していない状態となります。

  • origin/master 等、origin/ のついたブランチ、つまりリモートブランチを checkout した場合。
  • ハッシュ値を使用して checkout した場合。(GUI ツールを使用して、ブランチの途中を checkout した場合もこれに含まれます。)

 逆に言うと、「git checkout ローカルブランチ名」として、ローカルブランチの先頭をチェックアウトしたとき以外は、どのブランチにも所属していない状態になっていると言えます。
 わかりにくいかもしれませんが、こうしておかないとブランチの先頭が複数個存在するケースが発生してしまい、例えば「git checkout ブランチ名」とした場合に、どこへ移動すべきかわからなくなってしまいます。

使いこなせば便利になる

 push してしまうとサーバ上のものと矛盾ができてしまうので駄目ですが、push する前なら取り消し等様々な操作が可能です。
 例えば、あるブランチとしてコミットしてしまった後で別ブランチ化する、なんてこともできてしまいます。

こんな風に、ブランチがただのマークであるということさえわかっていれば、いろいろ応用が利くということもわかっていただけるのではないかと思います。

最後に

 これでだいたいイメージしていただけたでしょうか?
 僕や僕の周りの人間も、導入の段階で躓いて、いろいろ苦労しました。まあ、理解してしまえば、簡単なことなんですが。
 ただ、なかなか普及させるのは難しく、特にプログラマ以外の人間にこれを理解させるのは無理な気がしています。プログラマ以外はブランチやマージなんて使うことがまずないので、svn でいいじゃん、とかになってしまうんですよね。プログラマからすると、svn ではもうやっていけなくなってるんですが。