みなさんこんにちは!
フリーランスプログラマーのsatoです。gitを使い始めた時、最初につまづく点の一つがmerge(マージ)だと思います。
今回はこのマージについて徹底解説していきますね。そもそもmergeがなんなのかわからない人も、もっと使いこなしたい人も必見の内容になっていますよ。コンフリクトの解消手段もまとめていますのでぜひ読んでおいてください!
- [基本]mergeとは?
- [基本]mergeの使い方
- [基本]コンフリクトとは
- [応用]mergeには二種類ある!
まずは基本的な使い方を一通り勉強しましょう。次にmergeの一番の難所である、コンフリクトの解消方法も見ていきましょうか。
続けて「ここを理解していないとうまく扱えない」とも言える、マージの2つの種類について学びましょう。それでは宜しくお願いします。
mergeとは?
まず早速ですが、マージを一言で説明すると…
「現在のブランチ(HEADの指している場所)へ、他のブランチの更新を取り込む処理」と言えるでしょう。言葉だけではわからないと思うので図にして見てみましょうか。現在の開発状況が、以下の図のようだったとしましょう。
開始地点や丸はコミットです。Aさんの使っているブランチがブランチA。Bさんの使っているブランチがブランチBだったとしましょう。この状況で、AさんがBさんの更新を取り込みたくなったとしましょう。そこで登場するのがマージです。
マージを使用すると…
Bさんの更新を取り込み、取り込んだ後のマージコミットが一つ作成されました。マージコミットには、ちゃんとBさんの更新が含まれています。つまり自分のブランチへ他のブランチの更新を取り込んだということですね!
mergeの使い方
ここまででマージの概念は理解できたかと思います。
では次にコマンドを使用して、実際にマージを行ってみましょう。コマンドは非常に簡単です。取り込みを行いたい場所に居た上で、以下のコマンドを投げましょう。
git merge 取り込みたいブランチ
これだけです。
少し実例を見てみましょう。例えば先ほどの、AさんがBさんのブランチを取り込む例を見てみましょう。
この画像の状況で、Aさんは最新の「ブランチA」に居たとします。その状況で以下のコマンドを実行しましょう。
git merge ブランチB
すると…
このように、Bさんの更新を取り込めるわけですね!
コンフリクトとは
マージの基本的な使い方を理解できましたでしょうか?
しかし、理解が進んでくると、一つ疑問が湧いてくるとおもます。
実はこれがあるからマージは難しいのです。例えば「test.txt」というファイルをgitで管理していたとしましょう。これを同時に同じ箇所を修正してしまったとします。
この状況でマージを行うと、コンフリクト(衝突)が発生します。
どちらの処理を優先すればいいのか、gitが判別できないからです。これに関してはユーザーが手動で対処する必要があります。
ではコンフリクトが起きた時にはどう対処すれば良いかの? それを次に見ていきましょう。
コンフリクトの解消の仕方(手動)
ここでは実例をベースに解消方法を見ていきましょう。
例えばtest.txtの中身が以下のように変更されている状況でマージを行い、コンフリクトが起きた場合を見ていきましょう。
元々:
AAA
ブランチAでの変更:
BBB
ブランチBでの変更:
CCC
コンフリクトの場所を確認
コンフリクトが発生した際、以下のようなエラー文章が流れます。
CONFLICT (content): Merge conflict in test.txt
Automatic merge failed; fix conflicts and then commit the result.
これは読めば分かると思いますが「test.txtで衝突が発生しています」というエラー文です。衝突が起きた場合は、まずエラーをみて場所を判断しましょう!
コンフリクトを解消
そしてそれを解消するためにtext.txtの中身を覗きます。すると以下のようになっているはずです。
<<<<<<< HEAD BBB ======= CCC >>>>>>> branchB
これは、現在いるブランチ(ブランチA)は「BBB」に変更しているけど、ブランチBは「CCC」に変更しているよ!という意味です。優先したい方だけ残すように修正しましょう。
BBB
今回はブランチAのみを残すことにします。
解消し終わったらコミット!
修正が終わったら、必ず変更箇所に対してaddとコミットを行いましょう。そうすることで、マージコミットが生成されます。
コンフリクトの解消の仕方(片方を破棄する方法)
片方の更新のみ優先したい場合ならば、もっと簡単な方法があります。checkoutコマンドを利用して、指定したブランチ側のファイルを落とし直す方法です。
ブランチA側(現在いるブランチ)を優先したい:
git checkout --ours text.txt
ブランチB側(取り込むブランチ)を優先したい:
git checkout --theirs text.txt
これを使用すると、任意のブランチの内容のみを残すことができます。またこちらの手段を用いた場合も、addとcommitを最後に行い、マージコミットを作りましょう。
mergeには二種類ある!
ここまでで、マージについて一通り学ぶことができたと思います。しかしもう一点、重要な要素があるので勉強しておきましょう。実はマージには2種類のマージがあるんです。
その2種類とは?
- fast-forward(早送りマージとも呼ばれる)
- no-fast-forward
この二つです。
どう違うのかというと、マージコミットが作られないのがfast-forwardのマージ。マージコミットが作られるのが、no-fast-forwardのマージです。
fast-forward
fasta-forwardはマージコミットが作られません。
例えばこのような状況の時は、単純にブランチAに、ブランチBの更新が反映されていないだけです。そのためマージしても、マージコミットが作られません。
このように、ブランチの位置が最新の位置に移動されるだけです。
no-fast-forward
逆に最初見たように、ブランチAとブランチBで、それぞれ別々の開発が進んでしまっている場合、マージコミットが作られます。このようにマージコミットが作られるコミットが、no-fast-forwardと呼ばれるコミットです。
–no-ffオプションで必ずマージコミットを作る!
ここまで読んで…
と初心者の方は思うかもしれませんが、これが結構重要な話なのです。それは運用ルールによりはしますが、マージコミットが必要ない場合でも、あえてマージコミットを残しておいた方が良いケースも多いからです。
それを実現するために、mergeには–no-ffオプションが存在します。
git merge --no-ff 取り込みたいブランチ名
このオプションをつけると、強制的にマージコミットが作られますので、ぜひ覚えておきましょう!
まとめ
今回はマージについて見てきました。
マージは他人の修正と自分の修正をつなげるために使います。そこでミスが発生すると、自分にも相手にも損害が発生するため注意しましょう。ぜひマスターして、スムーズに開発が行えると良いですね!
またコンフリクトを多発させないコツは、定期的なマージです。長期に渡りマージをしないと、後で大きなコンフリクトが起きて苦労することになります。ぜひ定期的にマージを心がけましょう!