リベースの使い方とマージとの違いについてまとめました【Git/rebase】

Linux

リベースは、主に作業ブランチにmasterの内容を取り込む際に使用しています。
今回、使い方とマージとの違いをまとめたのでメモ。

仮にこのようなツリーがあったとします。
※見やすいようにGitクライアントの『GitKraken』の画面をキャプチャしました。

開発用のdevelopブランチとmasterブランチがあり、それぞれコミットがあります。
この状態で、最終的にmasterブランチにdevelopブランチで作業した内容を取り込むことを想定しています。

マージする場合の流れ

masterブランチにdevelopブランチの内容を取り込むには、一旦、masterブランチにチェックアウトします。この状態でdevelopブランチをマージします。コマンドだと次のようになります。

# masterブランチにチェックアウト
$ git checkout master
Switched to branch 'master'

# masterブランチにdevelopブランチの内容を取り込む(マージ)
$ git merge develop
Merge made by the 'recursive' strategy.
 index.html | 1 +
 post.html  | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 post.html

ツリーは次のような状態になります。ふたつに枝分かれしていたブランチが統合されました。
統合される際、マージコミット「Merge branch ‘develop’」が作成されます。
※赤で囲んだところがマージコミットです。

これでマージが完了です。masterブランチにdevelopブランチの内容を取り込むことができました。
最後に不要なブランチは削除しておきます。削除するコマンドは次の通りです。

# マージしたブランチを確認
# ここではmasterにチェックアウトしており、developが表示されていればOK
$ git branch --merged master
  develop
* master

# developブランチを削除する
$ git branch -d develop

最終的にツリーはこのような状態になります。

リベースする場合の流れ

developブランチにチェックアウトした状態でmasterブランチの内容をリベースで取り込みます。
※リベースは履歴を改変するので、基本的にはmasterブランチで作業しません。

コマンドは次の通りになります。

# developブランチにチェックアウト
$ git checkout develop
Switched to branch 'develop'

# リベースしてmasterの内容を取り込む
$ git rebase master
Successfully rebased and updated refs/heads/develop.

ツリーは次のような状態になります。
先程のマージと異なり、枝分かれしていたブランチが1本になっています。masterブランチの後ろにdevelopブランチが結合されているような状態です。マージコミットも作成されません。

リベースは完了しましたが、developブランチとmasterブランチの位置が異なっています。
最後に、masterブランチの位置をdevelopブランチに合わせておきます。
具体的には、developブランチにmasterブランチをfast-forwardでマージします。

First Forwardとは
一方のブランチのみ変更がある状態でマージすること。
結果的には、変更箇所を取り込むだけになる。マージコミットが作成されない。

masterブランチにチェックアウトした状態でdevelopブランチの内容をマージで取り込みます。
コマンドは次の通りになります。

# masterブランチにチェックアウト
$ git checkout master
Switched to branch 'master'

# masterブランチにdevelopブランチの内容を取り込む(Fast-forwardでマージ)
$ git merge develop
Updating 79e1643..62be7c6
Fast-forward # First-forwardでマージされる
 index.html | 1 +
 post.htm   | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 post.htm

結果を確認すると、Fast-forwardでマージしたことがわかります。
最終的にツリーはこのような状態になります。マージするよりスッキリした履歴になりました!

リベースする際の注意点

一言で表すと、一度リモートにプッシュしたコミットに対してリベースをしない。です。
具体的な例として、先程のブランチをリベースする前の状態から説明します。

developブランチにチェックアウトした状態で、masterブランチをリベースすると、ツリーは次のようになります。

masterブランチの後ろにdevelopブランチが結合され、一見問題ないように見えます。
ですが、コミットIDを確認するとdevelopブランチのコミットIDが異なります。

リベース前後のコミットIDを確認してみます。
※コミットIDを見やすくする為、コマンドのlogで確認しています。

リベース前の履歴

行先頭の「740e97a」「cbb452e」がコミットID

リベース後の履歴

行先頭の「b3ba7c4」「f7aa246」がコミットID。リベース前とコミットIDが異なる

コミットIDが異なるので、リベース前のdevelopブランチのコミットは削除され、同じコミットメッセージで新しくコミットが作成されたことになります。

リモートにプッシュしたコミットは、他の誰かがプルして作業している可能性があります。
この状態だと中央リポジトリに齟齬が発生しトラブルの原因となります。

その為、リベースは自分しか作業しないブランチで行うのが良いと思います。

コンフリクト(衝突)が発生した場合

リベースでも当然コンフリクトが発生しますが、通常のコンフリクトと同じ対応です。
コンフリクトが発生した際に表示されるメッセージに沿って対応します。
次の3通りから1つ対応してくれとメッセージにあります。

Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
error: could not apply 2337e00... change index
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue". # リベースを再開
You can instead skip this commit: run "git rebase --skip". # リベースをスキップ
To abort and get back to the state before "git rebase", run "git rebase --abort". #リベースする前の状態に戻す
Could not apply 2337e00... change index

コマンドの詳細は次の通りです。大抵はコンフリクトが発生したらリベースする前に戻して普通にマージします。。

git rebase –continue # リベースを再開する(コンフリクトを解決したら実行)
git rebase –skip # リベースをスキップする(それぞれのコミットとして履歴が残る)
git rebase –abort #リベースする前の状態に戻す

まとめ

コミットの履歴が綺麗になるリベースですが、今のところ自分しか作業しないブランチで使用しています。
チームによってはプルの時、マージではなくリベースを使う場合もあるそうです。
他にもコミットをまとめる機能があるそうですが、機会があったら覚えたいと思います。


コメント