Edited at

GitHubのマージとリベースについて

絶賛 Git 勉強中でして、自分向けのメモ書きとしてマージとリベースの違いを調べて整理してみました。

GitHubにはほかのブランチの更新分を自分のブランチに取り込む際、git merge/git rebaseの二つのコマンドがありますが、その違いを整理してみました。


やったこと

devブランチから派生した dev_#50, dev_#60があって並行で開発していたとして、dev_#60 がさきにdevにマージされたとします。dev_#50の開発者は、書き換えられたdevの更新分を取り込んだうえで、さらにdevへ自分の更新分をマージしてもらいたいわけですが、devの更新分を「マージ」で取り込んだ場合と「リベース」で取り込んだ場合で、どうなるかって比較をしてみました。

臨場感(?)を出すため、マージする時に修正をコンフリクトするようにしてあります。


Calc.java

public class Calc{

public double execute(int source){
return source ;
}

}


こんなソースに、

dev_#60で修正


Calc.java

public class Calc{

public double execute(int source){
// 消費税対応1.05倍 #60 2016/12/26
return source * 1.05;
}

}


こんな修正や

dev_#50で修正


Calc.java

public class Calc{

public double execute(int source){
/* 消費税対応1.08倍 #50 2016/12/26 */
return source * 1.08;
}

}


こんな修正を入れてコンフリクトさせます。

繰り返しですが、dev_#60分はdevに取込済みで、それをふまえてdev_#50の更新をdevへ取り込むケースを考えます。


初期設定

mkdir sample && cd $_

git init

Calc.javaを作成し、コミットします

git add Calc.java  && git commit -m 'initial'

利用するブランチを作成します。

git checkout -b dev && git checkout -b dev_#50 && git checkout -b dev_#60

まずdev_#60でソースを修正し、コミットとdevへのマージを行います

git commit -a -m '消費税対応1.05倍 #60 2016/12/26'

git checkout dev
git merge --no-ff dev_#60

つづいて、dev_#50でソースを修正し、コミットまでしておきます。

git checkout dev_#50

ここでコード修正。
git commit -a -m '消費税対応1.08倍 #50 2016/12/26'

ここまでで、dev,dev_#60,dev_#50 の状態は下記のようになりました。

before.png


マージ

まずは、普通にマージします。

$ git checkout dev_#50

$ git merge dev

このとき、競合が起きますが、落ち着いて修正を行い、

$ git add Calc.java

$ git commit -m 'merge commit'

でマージ完了です。さらに、dev側にマージします

$ git checkout dev

$ git merge --no-ff dev_#50

さて、このマージが完了したdevですが、

$ git checkout dev_#50

$ git merge dev

このマージはすでにdev_#50で修正されたところに dev分(すなわちdev_#60の修正)をマージしています。 そしてそれがdevへマージされているのがわかります。

merge.png

これはこれでOKなんですが、devからみると、dev_#60でのマージ履歴はいらない情報だったりしますね。


リベース

そこでリベースです。

$ git checkout dev_#50

$ git rebase dev

このとき競合がでてなんかごっちゃごちゃ言われますが、落ち着いて修正を行い、

$ git add Calc.java

$ git rebase --continue

ってやってリベースのcontinueを選びます。これでリベース完了です。リベースの考えかたに従い、まずdev_#50のコミットがいったん待避され、devの修正が取り込まれて、さらにdev_#50の新規コミットが行われます。当然、もとのdev_#50のコミットとは別のコミットですね。

ちなみに、rebase の競合をキレイにするのっていろいろめんどくさく、その手順は

git rebaseでのブランチ融合でコンフリクト解消

に丁寧にまとめてありました。感謝します!

さて、さらにdev側にマージします

$ git checkout dev

$ git merge --no-ff dev_#50

今回はマージでなくてリベースを行ったので、devに取り込んだもの(すなわちdev_#60の修正)のつぎに、dev_#50の修正がマージされました。dev_#50が「dev_#60を取り込む前のdev」からブランチした事実は'なかったこと'になってます。

rebase.png


まとめ

マージは、文字通りのマージで、devの修正をdev_#50の「直近状態」にマージしました。最終的には、その事実も含めてdevにマージされていきました。

リベースは文字通り、re-baseすることで、dev_#60を取り込んだdevをベースに、dev_#50の更新分がコミットされ、そしてdevにマージされていきました。

今回みたいなdevを中心にチケット毎のブランチで開発するようなケースだと、devでの更新分を取り込むようなケースでは、リベースをするのが正しそうですね。。。


関連リンク