はじめに
この記事は、ちょっとだけGitは使ったことがあるけれども、SourcetreeやGitHubやBacklogなどで出てくるGitのツリー(樹形図)の読み方がよく分からないという人に向けた記事です。なんとな~くで読んでしまっていて、実はよく分かっていないという人も、意外といるのではないでしょうか。
もしあなたが、まだGitを触ったことがなかったり、「変更のステージング」「ブランチにチェックアウトする」と言ってなんの事か分からなかったりしたら、先にこの記事 君には1時間でGitについて知ってもらう(with VSCode) を読んでくることをオススメします。(宣伝)
対象読者の想定レベル
- Gitを触ったことがあり、コミットをしたことがあるが、本格的なチームでのGitを使った開発は未経験
- mainブランチ(masterブランチ)というものがあるらしいということは知っている
- SourcetreeやGitHub等でグラフィカルに表示されるブランチの、複雑なツリーが読めない
- 何かしらのプログラムを書いた経験があり、「参照」というとなんとな〜くでいいので雰囲気が分かる
- ブランチを消すとどのコミットが消えるか分からなくて怖い
この記事のゴール
- ブランチのツリーが読めるようになる
- ブランチを作る/消すということがどういうことか分かるようになる
注意点
この記事では、人名らしきものが入ったブランチ名が例として登場しますが、人名をブランチ名として使用することを推奨しているわけではありません。実際にプロジェクトで使用するブランチ名は、そのプロジェクトに合った命名規則で運用してください。
また、この記事を書いた当時はmaster
ブランチがデフォルトのブランチ名でした。現在はmain
ブランチがデフォルトになっているため、master
に聞き馴染みの無い方もいるかもしれませんが、同じものです。気にしないでください。
突然の問題
藪から棒ですが、4問だけでいいので、ちょっと私の出題に付き合ってください。すぐ終わります。
第一問
画像を見てください。
それぞれの図形の定義は別に言っていない状態ですが、多分Gitを知っている人ならばなんとなく内容を理解できると思います。
さぁ問題です。この画像にブランチは何本あるでしょうか!
クリックして正解を見る(押すと開きます)
いや、すいません、バカにしているわけではないんですよ。少しずつ小さなステップを踏んでいくことが大切なのです。
正解は、masterブランチ 1本 だけです。
第二問
それでは次の問題です。この図にブランチは何本あるでしょうか。
なんかコミットAからコミットB, C, Dが、それぞれ生えてますね。
クリックして正解を見る
答えは 3本 です。多分皆さんそう答えると思いますし、それが正解です。
masterブランチ、takahashi_devブランチ、sato_devブランチの3本です。
sato_devブランチを消したらコミットBが消えて、takahashi_devブランチを消したらコミットCが消える……というのも、なんとな〜く直感で理解できるような気がします。
「ブランチ完全に理解した」
第三問
この図にブランチは何本あるでしょうか。
これは一体どういう状態なんだ?
おかしい。
masterブランチとtakahashi_devブランチと、sato_devブランチがありそうなのに、同じコミットに吹き出しが出ている。
何かがあったに違いない……。
そこに先輩がこう言います。
「sato_devブランチはもう要らないから消しといて」
「(ええっ、そんなことをしたら全てのコミットが消えてしまうんじゃないの! ブランチなんもわからん……。)」
クリックして正解を見る
答えは 3本 です。masterブランチとtakahashi_devブランチとsato_devブランチがあります。
それに大丈夫です、sato_devを消してもコミットが消えたりはしません。
第四問
最後の問題です。ブランチは何本あるでしょうか。
「なんか2本あるっぽいように見えるぞ……」
クリックして正解を見る
正解は 1本 です。
ここまで来たら、どうもブランチの数は枝分かれの数ではなく、「吹き出しの数」らしいという規則性は理解できたかと思います。
よくある勘違い
どうでしょうか。4問の問題を確証をもって秒殺することができたでしょうか。
もし確証アンド秒殺できなかったら、この記事はきっとあなたのお役に立つと思います。
Gitのブランチは一旦どういうものか理解してしまえば簡単です。
けれども理解したつもりになって勘違いしていると、いざというときに思わぬ挙動を見てびっくりする時が来ます。ここに、Gitのよくある 勘違い の図を書いておきます。
さて、あなたは新入社員です。先輩に 「新しくブランチを切って作業して」 と言われました。
ブランチを切る というのは、枝を切断するという意味ではなく、新しく ブランチを作る という意味です。伝票を切る、と一緒です。その時にイメージをするのは、この勘違いの図なのではないでしょうか。この青い矢印を作ってから、新しくここにコミットが乗っていくというようなイメージです。ちゃんとブランチを知っている人なら分かっている通り、このイメージは 完全な誤り です。
この図にはおかしなところがいくつかあるので、順に示していきます。
1つ目。
コミットDは最新のコミットなのに、矢印が突き抜けています。これはGitの図としておかしいです。
この矢印マークは、ただどっちが新しいかを示しているだけなので、「過去」とか「現在」とか書いてる以上矢印を書く意味もそんなにありませんが……。
なんでおかしいかはこの記事を読んだら多分わかるようになります。
2つ目。
これも1つ目と同じようなものですが、必ず矢印の先にはコミットがあるはずで、おかしいです。
それに、新しくブランチを作った瞬間には、まだ新しい矢印は発生しません。
3つ目。
矢印のことをmasterと言っていますが、これもおかしいです。ブランチは、必ずコミットに対して指し示す必要があります。
ブランチは、「複数のコミットが入っている入れ物」でも、「複数のコミットを載せている矢印」でもありません。
ブランチが何もわからなくなった? 安心して下さい。これから知っていきましょう。
ブランチの前にコミットを知ろう
それではブランチが何かを知っていきましょう……といきたいところなんですが、
それより前にコミットについて知る必要があります。
「概念としての」ブランチはコミットの連なり です。
なので、まずは コミット同士がどう繋がっているか を知るところから始めましょう。
コミットには、各々を識別するためのIDのようなものがあります。
このIDは、SHA-1というアルゴリズム1でコミット内容をHashした値です。そして各コミットは、前のコミットが生成したその値の情報を持っており、これによってコミット同士が繋がっています。
いや心配しないで下さい。大丈夫です。なんか今一瞬だけ難しげな雰囲気が出ましたが、一瞬だけですから!
簡単に言うと、
- コミット毎に、その変更内容によって振られる 謎のID がついている
- 各コミットは、自分の前のコミットのIDを知っている ため、コミット同士が繋がることができる
ということです。
図を見ればすぐ分かります。
最新のコミットe515a31
は、その一つ前のコミット2d7b999
を参照しており、更にそのコミット2d7b999
は、コミット9bf0fd4
を参照している……という具合です。このようにしてコミット同士は繋がっています。
ブランチを知ろう
それでは本題に入りましょう。ブランチです。さっきの画像にピョコンと吹き出しをつけました。
勘の良い方はもしかしたらお気づきになったかもしれませんが、ブランチというものは、コミットへの参照です。各コミットが前のコミットを参照するのと同じように、この「masterブランチ」というものの実体は、コミットe515a31
への参照です。ただそれだけなのです。なので、ブランチはとても軽く、沢山作っても職場の先輩には怒られません。……いや無意味にブランチを作りまくったら多分怒られますが、ブランチはただコミットを指し示しているだけなので、その点ではGitのタグと同じで、とても軽いものです。
けれどもブランチはタグとは違います。僕たちが日常的に「masterブランチが〜」と言ったりする時にイメージするのは、変更の枝の中の一本の線です。そして、その認識はほぼ間違っていません。ブランチの実体がただの一つのコミットe515a31
を指すだけなのに、なぜ一本の線がブランチなのでしょうか。
それは、ブランチは確かにただ一つのコミットを指しているだけなのですが、概念としてのブランチは、そのブランチの指すコミットが連なっているチェーンそのものを指すからです。
- 実体としてのmasterブランチは、ただのコミット
e515a31
への参照です。 - 実体としてのtakahashi_devブランチは、ただのコミット
6a3022c
への参照です。
ここまではいいですね。
けれども「概念としてのブランチ」では話が変わってきます。ブランチはやっぱり線だからです。
- 概念としてのmasterブランチは、
e515a31
→2d7b999
→9bf0fd4
→375f7e0
へのコミットの連なりを指します。 - 概念としてのtakahashi_devブランチは、
6a3022c
→8aad6d7
→9bf0fd4
→375f7e0
のコミットの連なりを指します。
実体としてのブランチはただ一つのコミットへの参照なのに、概念としてのブランチが成立するのは、上で説明した通りコミット自身が前のコミットへの参照を持っているからです。
だから、僕たちが普段「takahashi_devブランチが〜」と言った時に指すのは、この図の黄色になっているところなわけです。ブランチはコミットの連なりなのです。
ブランチの動きを見る
もうブランチとは何かが分かったと思うので、ここからは実際にブランチを運用していく様子を見ていきましょう。
ブランチを作って、コミットをして、削除するところまで見ていきましょう。
ブランチを作る
第一問の時の画像をそのまま引っ張ってきます。
このmasterにチェックアウトした状態で、新しく tsuchiya_devブランチを作ります 。
新しくコミットDを参照するtsuchiya_devが作られて、こうなります。
tsuchiya_devブランチを作ったのでコミットする
さっきtsuchiya_devを作ったので、そこにチェックアウトしてコミットをしましょう。
tsuchiya_devでコミットすると、こうなります。(左側のコミットA、Bは省略しました)
masterブランチにもコミットする
続いてmasterにも誰かがコミットしました。こうなります。
tsuchiya_devブランチを消す
開発が進んで下記のような感じになりました。
この状態でtsuchiya_devブランチを消すことになりました。
顧客の要望が変わり、開発していたものが完全に不要となったためです。
「やり直しは、やり始めるよりも、辛い。」 Getting Over Itの壺おじさん・ディオゲネスもそう教えてくれましたが、masterにマージ(併合)するよりも前の段階で分かっただけまだマシと割り切りましょう。
削除するとこうなります。
この、点線になっている部分が消えます。tsuchiya_devブランチは黄色になっている部分を指しますが、消えない部分もあるようです。
tsuchiya_devを消すと、tsuchiya_devによってだけ見られていた、コミットGを参照するものは何もなくなります。なのでコミットGが消えます。コミットGが消えると、コミットEを参照するものは何もなくなります。なのでコミットEも消えます。コミットDは、コミットEには参照されなくなりますが、コミットFに参照されているのでまだ消えません。コミットCは、消えないコミットDに参照されているので消えません。
補足:ブランチの削除
ブランチの削除がちゃんと理解できているかをここでもう一回確認しましょう。
下の画像は、tsuchiya_devブランチが作りたてホヤホヤだった頃の画像です。この画像の状態から、tsuchiya_devブランチを削除したらどうなるか考えてみて下さい。
答えは簡単。
ただtsuchiya_devブランチというコミットへの参照が消えるだけで、コミット自体は何も削除されません。なぜならコミットDは、まだmasterに参照されているからです。まだ何かに参照されているコミットは、消えることはありません。
マージ(merge)
上までの内容だけだと樹形図はまだ完全には読めませんが、もうあと一息です。
マージというのは、分岐した変更をがっちゃんこすることです。
通常のマージ(マージコミットを生成する)
コミットAからコミットB,Cに分岐した状態です。この状態から、masterブランチがsato_devブランチをマージします。
そうするとマージコミットが作られて、こうなります。
マージコミットの時点で、ちゃんとmasterはsato_devの変更を取り込めているということになります。
このマージコミットというやつは、今までのコミットと少しだけ違うところがあります。 前のコミットへの参照を複数持ってるという点で、これまでのコミットとは異なっています。 この場合のマージコミットは、コミットBと、コミットCという2つの参照を持っているということです。コミットへの参照は複数持てるということを覚えておきましょう。
ちなみに、この複数親を持ったときに一つめの親コミットをHEAD^
、もう一つの親コミットをHEAD^2
で指すことができます。別の方の記事ですが、この記事が詳しいです。
後はもう一点だけ。この状態からsato_devブランチに新しいコミットがあった場合どんな図になるでしょうか。考えてみてください。
正解はこうです。
クリックして正解を見る
sato_devから1コミット進むので、こうなります。sato_devの変更をmasterが取り込んでいます(マージしました)が、sato_devにとってはそんなこと知ったことではないので、追加で「sato_dev側からmasterブランチを取り込む」ことをしない限りは、sato_devはmaster側のことを何も知ることはできません。マージは、「どのブランチが」「どのブランチを」取り込んだかがとても大事です。masterはsato_devを取り込みましたが、sato_devはmasterを取り込んでいないということをちゃんと理解する必要があります。
不正解 の図は、たとえばこんな図です。
Fast-forward(早送り)マージ
変更をマージしてもマージコミットが作られない場合もあります。
下記の場合はコミットBからtsuchiya_devブランチが一方的に進んでいる状態です。
masterがtsuchiya_devをマージをすると、masterブランチがtsuchiya_devに追いつくような動きをします。
マージコミットは作られません。このようなマージをFast-forward(早送り)マージといいます。
※マージするとFast-forwardマージになる、という状態でも、マージコミットが作られる普通のマージにすることもできます。
振り返る
上記までの知識がきちんと身についたら、多分ツリーを読むことは一通りできるはずだと私は思っています。
もし、最初に4問を解けなかった人は、ここからもう一度4問を解き直してみてください。
それから、「よくある勘違い」を見て、それがなぜ勘違いかを考えてみてください。
分からなかったら完全に私の力不足でごめんなさいという感じです。
おわりに
この記事が、Gitのツリーを読めなかった人、
もしくは、ツリーが読めない人に教える立場の人にとっての一助となれば幸いです。
私がおかしなことを言っていた場合は、遠慮なくマサカリください。
どうぞよろしくお願いします。
参考
Git のブランチ機能 - ブランチとは
Gitのコミットハッシュ値は何を元にどうやって生成されているのか
【やっとわかった!】gitのHEAD^とHEAD~の違い
-
SHA-1はHashアルゴリズムとしては問題があり、米Googleが2017年、初めて衝突に成功させました。つまり、異なるパスワード(ハッシュ元)でも同じハッシュになっちゃうことを証明したような感じです。Gitではセキュリティ的な文脈でSHA-1を使っているわけではないため、あまり問題はありません。パスワードのHashなどにはSHA-1やMD5なんかは絶対使わないようにしましょう! SHA-1の衝突問題、Gitへの影響についてトーバルズ氏の見方は ↩