枝分かれしたあとは大抵本体とマージする事が多い。その際のマージにもいろいろな方法がある。
今回はそこら辺を俯瞰して見てみようと思う。
■そもそもブランチとは
ブランチはどういう時に使うのか、という話。何度も書いてるので知ってる人は読み飛ばしてもらって構わないよ。例えば最近リリースしたばかりのプロダクトに、結果は同じだけどロジックを書き直し、パフォーマンスをアップできる要素を発見したとしよう。当然作った人間からしたら、更に良くなるならぜひ手を付けてみたいとは思うだろうね。ただ、失敗した時のことも当然考えているはずだ。
そういう時にはGitを使っているなら、迷わずブランチを切り、もう一つのパラレルワールドを作成するのが良い。なぜなら、別のブランチはあくまで別の世界。あとからくっつけることもできるけど、明示的にそうしないかぎり、関連性はほとんどない。失敗したとしても、ブランチを消せば良い話。
つまりGitの世界ではブランチを切るというコストはすごく安いわけ。だから多くの人が、なにかやるときにはブランチを切り、そこで作業をしている、というわけ。ブランチを切った先で更にブランチを切るなんてことも余裕だ。
SVNなどのVCSでは、ブランチを切る事自体が少々重い気分にさせてくれる。これはなぜかというと、Gitの様に、全てがブランチ扱いというわけではないからだ。だから本流(SVNではトランクという)とは別の、ブランチという技術が付随する形で用意されている。
Gitは本流もブランチだ。すべてがブランチという扱い。それ故に、とにかく気軽にブランチを切れる。
■ブランチを切ってみよう
それでは早速ブランチを切ってみよう。現状、デスクトップにある『mygit』はこういう状態になっているはずだ。『.git』フォルダと『.gitmodules』ファイルは、Windowsのexplorerの設定で、隠しファイルも表示する設定になって置かなければ表示されないので、そこに関しては各々方の環境によって変わってくる。
imagionフォルダ、index.htmlがある状態が前提だ。
この状態でブランチを切ろう。今いるブランチは『master』ブランチなので、この『master』ブランチを基準に、『test』ブランチを作ってみる。
コマンドは『git branch <作りたいブランチ名> <元になるブランチ名>』だ。
『git branch test master』と叩いてみよう。
画面には特に何も表示されないが、この時点で『test』ブランチが作られている。
確認してみよう。branchに何も与えないで実行すると、存在するブランチを表示してくれる。
『master』ブランチと『test』ブランチが表示されたはずだ。そして『master』ブランチが緑で、頭に『*』が付いていると思う。
この『*』が重要で、これが付いているブランチに、今自分がいるということになる。
つまり、『git branch <作りたいブランチ名> <元になるブランチ名>』でブランチを新たに切っても、自動的に作ったブランチに移動はされてないということだ。これは注意が必要。
『master』ブランチに影響与えないように作ったブランチだけど、『master』ブランチにいたまま、その作業を進めてしまうなんてことがあるかも知れない。そうなったらブランチ切った意味が全くない。気をつけよう。
一応、新ブランチを作ると同時にそのブランチに移動する方法もあるんだけど、それは後述する予定。
さて、では今『master』ブランチにいるので、『test』ブランチに移動してみようじゃないか。
ブランチ間の移動は、checkoutオプションを使う。『git checkout test』でOK。
これでtestブランチに移動ができた。試しに『git branch』で見てみよう。
今度は『test』ブランチが緑になって『*』が付いてる。これで今、『test』ブランチにいることになった。何をやっても『master』ブランチには影響がない。色々やってみよう。
『git checkout -b test master』とすると、一発で『master』ブランチを元に『test』ブランチをつくってそこにチェックアウトしてくれるけど、削除の『-d』と似ているのでおすすめしてない。
■別ブランチでの作業
index.htmlをテキストエディタで開き、以下のように編集してみよう。4行目にh2タグで『test』ブランチであることを明示する文字を追加してみた。
保存したら一旦閉じておこう。
ブラウザで開けばこのようになる。
さて、この状態で『git status』を見てみると、当然編集作業をしてからインデックスには追加してないので、赤く表示されることになる。
ここらへんは別に、別のブランチだからってmasterと違うわけではない。masterもただのブランチだ。今までどおり、ステージングのインデックスに追加し、コミットしよう。
『git add index.html』のあと、『git commit -m 'edit index.html add h2 tag'』としてみた。当然『git status』で見てみても、すでにコミット後なので何も表示されない。クリーンな状態だ。
さて、この状態でログを見てみよう。『git log』だ。
全部で3つのログができている。下から2つはブランチを切る前のログ。一番上が、今しがた行ったコミットだ。
この状態を脳内に軽く覚えておき、『master』ブランチに戻ってみようと思う。ブランチの移動は先ほどやったとおり『git checkout <移動したいブランチ名>』なので、別に『戻る』コマンドがあるってわけじゃない。移動するだけで事足りる。
『git checkout master』と叩いて、masterブランチに戻ろう。
さて、『master』ブランチに戻ったぞ。早速『git log』でログを見てみよう。
なんと、『test』ブランチで行った作業がまるごとないではないか。
index.htmlをテキストエディタで開いてみよう。
おわかりいただけただろうか。なんということでしょう!h2タグを追加した作業がまるごときえてるでおますYO!
でも大丈夫だ。先ほどのh2タグを追加した作業内容と結果は、『test』ブランチで残っている。
試しに再度、『test』ブランチに戻ってみよう。『git checkout test』だ。
そして『git log』を叩こう。
ちゃんと履歴が3つ残っているのが確認できたと思う。
というよりむしろ、これでブランチ間は完全に切り離されていることがわかったと思う。
これがgitの醍醐味、ブランチを切るということだ。楽しいし、なにより愉快。
■マージとは?
さて、一旦『master』ブランチに戻り、別の作業をしようじゃないか。貴様が別世界『test』ブランチでh2タグを追加している間、誰かが『master』ブランチで作業を進めたかもしれない。そういう場合は整合性を取るために、自分の作業と相手の作業をくっつける作業が発生する。
そうしないと、どちらかの作業しか反映されなくなるし、それじゃぁ分散管理してる意味が無いわけだ。
というわけで、誰かがやった『master』ブランチでの作業を再現する形を撮ってみようと思う。
『git checkout master』で『master』ブランチに戻ろう。
戻ったら、ファイルを1個追加しよう。『profile.html』などでOK。中身はindex.htmlをコピしてもいいし、その後でいいので、以下の様な内容にしてみよう。
さて、この状態で『git status』を見てみると、当然ステージングに上がってないので、赤い文字でprofile.htmlが表示される。
例によって『git add profile.html』した後に『git commit -m 'add file profile.html'』などと叩いておこう。
これでprofile.htmlがコミットされ、Gitの管理下に置かれることとなった。
さて、貴様は突然上司にこう言われる。『index.htmlに以下の文を追加してくれ』と。その場合、『test』ブランチから『master』ブランチに戻ることになる。
『git branch master』を叩く(手順通りなら今は『master』ブランチにいるはずなのでこの作業は省いてしてOK)。
そして知るわけだ。『え?ファイルが増えてる!』と。別にこんな驚かなくてもいい。Gitだとそういうものだし。それに貴様の作業に対しても、同じことを他のプロジェクトメンバーも感じてるはずだ。
だからここでいちいち感情的にならなくていい。
ただ、『俺の作業に、これらのファイル追加も反映させておかないとなぁ・・・』とは思うだろう。
もしくは逆に、『masterブランチに俺の作業反映させちゃおうかな』とも思うかもしれない。
しかし大丈夫だ。Gitにはマージという機能がある。ブランチとブランチをくっつけてくれるのだ。これは便利!!!
■マージしてみよう
さて、まずは『test』ブランチ側で、profile.htmlが追加されるようなマージをしてみようと思う。マージするには、『git merge <マージしたいブランチ名>』となる。この場合、『どのブランチ』を『どこのブランチ』へ、という指定にはならず、マージしたいブランチ名を、『今いるブランチ』に反映するということになる。
ということで、一旦『test』ブランチへ移動しよう。『git checkout test』だ。
このコマンドを叩くと、フォルダ内のprofile.htmlが瞬時に消えるはずだ。
なにしろ『test』ブランチにはprofile.htmlが無い。そもそもそれを反映させようとするのが今からやる作業なので。
では、早速マージだ。今『test』ブランチにいて、profile.htmlが追加された状態の『master』ブランチを反映させたいので、『git merge master』というコマンドになる。叩いてみよう。
最後の行にある『create mode 100644 profile.html』を見てもらえればわかるが、『test』ブランチでprofile.htmlが作成されたということになる。念のため、『test』ブランチにしか存在しない、index.htmlのh2タグを見てみよう。
ちゃんと残っている。
そしてmasterブランチの作業である。profile.htmlの追加も反映された。素晴らしい。
ちなみに『git log』でログを見てみよう。こんな風に、結構な量になってるはずだ。
これがマージだ。
■その他のマージ
実はマージには大きく3種類用意されている。- 直接マージ
- 圧縮コミット
- チェリーピック
どういう内容なのかは結構簡単なので、表にしてみた。
直接マージ | マージしたいブランチの最後の状態を履歴含めてマージする |
---|---|
圧縮コミット | マージしたいブランチの全コミットを1個のコミットとしてマージする |
チェリーピック | マージしたいブランチのコミットを指定してそれのみをマージする |
どうだろう、おわかりいただけたかな。
先ほど行ったマージは、直接マージだ。
図にしてみよう。
この図でいうと、先ほどの『test』ブランチ”に”『master』ブランチを反映させた処理とは逆になるが、多分そんなに複雑でもないで、簡単にご理解いただけると思う。
まず、masterブランチにbranchブランチをマージさせると考えた場合、
- (5)に(c)を反映させるのが直接マージ
- (a)(b)(c)を1個のコミットとして扱い、それを(5)に反映させるのが圧縮コミット
- (a)だけを(5)に反映させるのがチェリーピック
一番上の『直接マージ』は、実際に今やってみたのがそうだ。
だけど、『圧縮コミッ』トって何に使うの?とおもったかもしれない。これは、例えばマージしたいブランチの履歴が不要な場合など、無駄な履歴を残さないためだ。例えば何かの修正のためにブランチを切った場合、そのブランチの結果だけが重要になるはずだ。
普通はブランチの履歴は重要なんだけど、この場合は過去の履歴なんか無視して良い。そういう時は、いくつ履歴があろうが、1つにしてしまえば良い話。
ただ、修正パッチ以外ではこの圧縮コミットはあまり使われていないようなので、頭に通過させておくだけでも良いかも。こった使い方したい人は試してみてもいいかもね。
で、『チェリーピック』だけど、これはつまみ食いって意味がある。その意味の通り、ブランチ先の各コミットを、コミット名を指定して、そのコミットだけをマージ対象とするわけだ。
例えば何か機能を拡張する際、ブランチを切った後に、便利なライブラリを追加してから作業を進めたとする。機能拡張が完成する前に、最初に作ったライブラリがかなり汎用的だとわかり、本流の『master』ブランチでも使えるようにマージすることとなったが、今作り途中の機能拡張はマージ対象にしたくない、なんていう場合などにも対応できるように、コミット番号を指定して個別にマージできるようになっている。
マージも目的によっていろいろ使い分けができるので、覚えておこう。
圧縮コミット、チェリーピックは初心者向けじゃないと思うので説明しないけど、直接マージを覚えてしまえば、あとはググってすぐに習得できると思う。
■マージしたらエラーがでた
エラーというのは大抵コンフリクトだ。SVNなどのレガシーなVCSを使っていた人は、このコンフリクトを避けるため、ブランチ自体を使わないで仕事していた人もいるくらいだ。それくらい、コンフリクトの解消は、精神的にも肉体的にもコストが高い作業となる。
ただしGitではそれは大きく違う。コンフリクトの原因はわかっているし、修正後もいつもどおりコミットすれば良い話。簡単だ。
そしてまずはコンフリクト状態を自分で起こしてみようじゃないか。
『master』ブランチと『test』ブランチで、全く同じファイルの全く同じ箇所を別の内容にして、その後マージしてみる。
まずは今は『test』ブランチにいると思うので、『master』ブランチに移動しよう。
移動したら、index.htmlの4行目を以下のように編集だ。
明らかに『test』ブランチとは違う内容で、同じ行に書かれている。
編集したら『git add inde.xhtml』でステージし、『git commit -m 'edit index.html add h3 tag'』でコミットだ。
さて、この状態でマージするために、
『master』にいるので、このまま『test』の最後のコミットまでをマージする。つまり、直接マージだ。
コマンドは『git merge test』でOK。早速やってみよう。
ハイは生きました。あんまり目立つ色で書いてくれてないから気づきにくいけど、しっかりと画面に『conflict』って書いてある。注意してみないといけない。
一応俺のマシンにはTortoiseGitが入っているので、フォルダを見れば、アイコンオーバーレイで表示してくれるので、それほど目をどんぐりみたいに見開いて見てるわけじゃないんだけど、コマンドラインメインでやってる人は注意しないといけないよね。
TortoiseGitを入れてあると、警告マークが表示される。
さて、index.htmlを開き直してみよう。こんな風になってるはずだ。
これ、『<<<<<<< HEAD』が今のブランチで、『>>>>>>> test』がマージ元のブランチね。
この場合、『あんたのいるブランチではh3タグでこうなってるけど、マージしたいブランチでは別の表記になってるよ。俺にはどっちが正しいのかわからんから、あんた自信で直してくれや』と。
というわけで、h2が正しいのかh3が正しいのか当然Gitにはわからない。人間が直すことになるわけだ。早速直してみよう。h2を反映させる。
index.htmlを『test』ブランチ側を残し、『master』側を削除して保存した直後の『git status』が以下になる。
よく読むと、『コミットすればfixするよー』って書いてある。『fix conflicts and run "git commit"』だ。
早速ステージしてからコミットしようじゃないか。
『git add index.html』を叩き、『git commit』だ。
ここでは『git commit』に『-m』オプションを入れてないけど、自動的にエディタが起動し、そこにGitが自動的につけてくれラなコメントがすでに書かれているので、それを利用することにした。
ちなみにこのエディタ、保存して終了するには、『:wq』とタイプすればいい。そう、これは『viエディタ』なのだ。
■ブランチの削除
さて、これで、『test』ブランチでの作業と、『master』ブランチでの作業がおなじになった。『test』ブランチはもういらなくなったよね。というわけで、『test』ブランチは削除しよう。
『git branch -d test』というように、『-d』をつけると削除してくれる。
『git branch』で『master』ブランチにいるのを確認し、コマンドを叩いてみよう。
さて、『test』ブランチは削除された。念のため再度『git branch』コマンドを叩いてみてみることにする。
ご覧いただけただろうか。今や『master』ブランチしか無いのである。
■まとめ
『おさらい』なのに『まとめ』があるとおかしいかもしれないけど、これで一旦Gitのおさらいは完了させておこうと思う。ここで説明したコマンドは、本当に初心者向けの内容だ。
たとえば、『git add』と『git commit』を同時に行うコマンドとか、『git branch <ブランチ名>』でブランチ切った後に『git checkout <ブランチ名>』で移動するのを1つのコマンドでやるとか、そういう更に便利なコマンドも沢山ある。
しかし、そういった便利なコマンドを最初から説明してしまうと、『この「-a」ってなんだろう?』などの、余計な疑問が生まれ、無駄な消耗をすることにつながると思っている。
なので、識者がたまに俺のブログに無粋なツッコミをしているようだけど、あくまで『初心者に対しては正義の説明』として書いてるので、『多くの困難をくぐり抜けた勇者に対しての説明なんかしてねーよボケ!』と言いたい。
そういう優れた勇者は、俺なんかの記事にいちいち脊髄反射せず、てめぇ自信のブログでしょぼい知識自慢でもしていればいいと思う。
俺はいつでも初心者の味方なんだ。
初心者は毎年どんどん増えるが、勇者はそう簡単には増えない。
何が言いたいかわかるよね?
というわけで、極めてわかりやすく感情を出してみたけど、閑話休題する。
この連載を読んで、Gitという素晴らしいシステムを好きになってもらえれば、同じGitユーザとして嬉しい事この上ない。
最後まで読んでいただいて、ありがとうござる。
facebook
twitter
google+
fb share