【git】マージを理解する - ブランチのお供に!!

はじめに

本日は、マージの種類・方法についてまとめます。

先日、【git】"git-flow"を齧る - ドラムと筋肉とプログラミングという記事を書かせていてだきましたが、gitで複数のブランチを管理していくとなると「ブランチを切る・マージする」といった作業が必要になります。


マージの方法というのはいくつかありまして、マージに関して検索すると、「rebase」、「merge」、「fast-foward」といった用語が登場します。

それぞれどういった特徴なのか理解していないと、なんとなーく使用してしまいます。そしてコミットログが汚くなります。

コミットログが汚くなるのを避けるために、git-flowなどを用いてブランチを管理するためにも、マージの方法について理解していきます。

mergeとは?

概念

あるブランチの作業履歴(コミット)を、自分のブランチに取り込むこと

種類

  • Fast-fowardマージ
  • 3wayマージ(Non-Fast-fowardマージ)
  • rebase

いきなり「マージ」とは関係のなさそうな単語が登場しましたね・・・
しかし、一つ一つ見ていけば決して理解し難いものではないです。それぞれ説明していきます。

Fast-fowardマージ

概念

ブランチの「ポインタ」を"前に進める事"によるマージ

解説

概念だけ聞かされると何を言ってるかイメージしづらいと思います。
実際に作業するシーンも踏まえて説明します。


1. 新たなブランチの導入
 masterブランチで「バグ」が見つかったため、ブランチを切り(hotfix)修正した(C4コミットを切った)
https://git-scm.com/book/en/v2/images/basic-branching-4.png

2. masterブランチへの反映
 hotfixブランチでの修正作業が完了したため、masterブランチをhotfixブランチと同じ状況にしたい

// mergeコマンドの実行
$ git checkout master
$ git merge hotfix

Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

3. 結果
 masterブランチのポインタ、すなわち「コミットの位置」がhotfixと同じ場所に移動しました。
https://git-scm.com/book/en/v2/images/basic-branching-5.png

補足

上記のような例を出しましたが、言い換えると派生元のブランチが「更新されることがないことを見込める」の時に使用可能です。
上記の例で、hotfixで修正中にmasterブランチでも別途コミットを切ってしまった場合は、以下の3wayマージを使用する必要があります。

3wayマージ(Non-Fast-forwardマージ)

概念

3wayは3つのコミットを表しています。

①マージ元ブランチのポインタに該当するコミット
②マージ先ブランチのポインタに該当するコミット
③①、②の共通祖先となるコミット

「①と③の差分」と、「②と③の差分」をマージ元ブランチに取り込む方法を「3wayマージ」と言います。

解説

こちらも実際に作業するシーンも踏まえて説明します。


1. 新たなブランチの導入

  • masterブランチでバグが見つかったので修正用のブランチを切った(iss53)
  • iss53ブランチにて修正作業を実施した(C3, C5コミットを切った)
  • masterブランチで非バグの修正を行った(C4コミットを切った)。

https://git-scm.com/book/en/v2/images/basic-merging-1.png


2. masterブランチへの反映
 iss53ブランチでの修正作業が完了したので、masterブランチにマージをします。
 コマンドは「fast-fowardマージ」の時と同様ですが、ブランチを切ったコミット位置(C2)からマージ元ブランチ(このケースだとmaster)のコミットが進んでいます。

 そのため、masterブランチの差分(C2-C4の差分)とiss53ブランチの差分(C2-C5の差分)をそれぞれ抽出し、結合したものをmasterの新しいコミットとします(3.結果のC6)。

// mergeコマンドの実行
$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

3. 結果
3wayマージをした結果になります。
iss53ブランチのコミット位置は変わりませんが、masterブランチはiss53ブランチのコミットを取り込んだ結果として、C6コミットが新しく生成されました。

このC6は3wayマージにより自動的に生成されるコミットでして、「マージコミット」といいます。

https://git-scm.com/book/en/v2/images/basic-merging-2.png

補足

コンフリクトについて

3wayマージなどで、それぞれのブランチの「共通の親」からの差分の箇所が同一となる場合、コンフリクトが発生します。

この場で詳細な修正手順は省きますが、
競合した該当ファイルはマージ元、マージ先の差分のどちらか(両方も可能)をファイルに取り込むよう修正して、コミットしてあげる必要があります。
先ほど、3wayマージでは「マージコミット」が自動的に生成されると話しましたが、コンフリクトした場合は手動で「マージコミット」を切る必要がある、ということがポイントです。

リベース

概念

基本は、マージと同じ考えです。

あるブランチの作業履歴(コミット)を、自分のブランチに取り込むこと

解説

こちらも実際に作業するシーンも踏まえて説明します。


1. 新たなブランチの導入

  • masterブランチで新たな機能を追加したいため、ブランチを切った(experiment)
  • iss53ブランチにて新規機能を追加した(C4コミットを切った)
  • masterブランチでバグが見つかったので修正した(C3コミットを切った)

https://git-scm.com/book/en/v2/images/basic-rebase-1.png


2. masterブランチの取り込み
 masterブランチでのバグ修正作業が終わったため、experimentブランチにマージします。

// rebaseコマンドの実行
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command


3. 結果
リベースした結果になります。

https://git-scm.com/book/en/v2/images/basic-rebase-3.png

experimentブランチのC4コミットは、C4'コミットという形でmasterブランチの先頭に切られました。

experimentブランチ上にmasterブランチが存在するような形になりましたので、「Fast-fowardマージ」により「masterブランチ」を「experimentブランチ」に移動することが可能となりました。

3wayマージと違いコミットが一直線となっているため見た目も綺麗です。

補足

リベースの多用はNG

一度リモートリポジトリにプッシュしたコミットに対してリベースは絶対に実施してはならないです!!

3.結果で少し触れましたが、リベースによって複数のコミットログをまとめた場合でも、それがプッシュ済みのコミットである場合、後で復活してしまう恐れがあります(要はコミットログがめちゃくちゃになって履歴の確認が困難になってしまう)。

そのほかにも、リベースを使用してはいけないと感じたケースがありますが、それはまた次の機会にご紹介したいと思います。

さいごに

git-flowなどのブランチ戦略を導入するとなると、「ブランチを切る」、「マージする」という作業がいやというほど登場してきます。

マージするときに、どのマージ手法を選択すれば良いか?その都度迷わないように、ブランチを運用する前に上記の内容をしっかり抑えておくと良いと思います。