Gitでは、2つのブランチの履歴を統合する方法として merge
と rebase
の2つが用意されています。私自身、rebase
についてよくわかっておらず、「どんな時でも merge!」派だった過去があるのですが、 rebase
を理解できてからは merge
と rebase
を使い分けられるようになりました。
今日は、merge
と rebase
の違いについてまとめていきます。
#確認環境
- Mac
- git version 2.30.0
#前提
- 各コマンドの使い方やオプションについての説明はここでは省略させていただきます
- featureブランチにdevelopブランチを統合する、という操作をそれぞれのコマンドで実行していきます
-
git log
コマンドに使用しているオプションについてはこちら git-log からご確認ください
#merge
以下のような履歴を持つリポジトリを使います。
% git log --graph --pretty=format:"%h %s" develop-merge feature-merge
* 49ea0ef develop-merge first commit
| * deefe27 feature-merge first commit
|/
* 09c96b7 first commit
develop-mergeブランチ、feature-mergeブランチ、それぞれに変更が入っています。featureブランチにdevelopブランチをmergeしてみます。すると、以下のようになります。
% git switch feature-merge
Already on 'feature-merge'
% git merge develop-merge
# コンフリクトが発生しました。解消は割愛させていただきます。
[feature-merge 8b3ec25] Merge branch 'develop-merge' into feature-merge
% git log --graph --pretty=format:"%h %s" develop-merge feature-merge
* 8b3ec25 Merge branch 'develop-merge' into feature-merge
|\
| * 49ea0ef develop-merge first commit
* | deefe27 feature-merge first commit
|/
* 09c96b7 first commit
8b3ec25
という新しいマージコミットが生まれています。また、マージコミットの親となるコミットが2つ(49ea0ef と deefe27)になり、枝分かれしています。このようにmergeコマンドを使用すると、今までの履歴に影響を与えずに新しいコミットを生み出し2つのブランチの履歴を統合します。
※FastForward状態になるとマージコミットが生まれません。ここでは説明を割愛します。
#rebase
mergeの例と同様、以下のような履歴を持つリポジトリを使います。
% git log --graph --pretty=format:"%h %s" develop-rebase feature-rebase
* 49ea0ef develop-merge first commit
| * deefe27 feature-merge first commit
|/
* 09c96b7 first commit
develop-rebaseブランチ、feature-rebaseブランチ、それぞれに変更が入っています。featureブランチにdevelopブランチをrebaseしてみます。すると、以下のようになります。
% git switch feature-rebase
Switched to branch 'feature-rebase'
% git rebase develop-rebase
# コンフリクトが発生しました。解消は割愛させていただきます。
[detached HEAD 9159f39] feature-merge first commit
1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/feature-rebase.
% git log --graph --pretty=format:"%h %s" develop-rebase feature-rebase
* 9159f39 feature-merge first commit
* 49ea0ef develop-merge first commit
* 09c96b7 first commit
先ほどのmergeの例とは違って、履歴が一直線になりました。また、新たなコミットも生まれていません。
ですが、よくみてみると、 feature-merge first commit
というメッセージのコミットのcommitIDが変わっています。
- rebase前
-
deefe27
feature-merge first commit - rebase後
-
9159f39
feature-merge first commit
rebaseコマンドを実行すると、rebase先のブランチの先頭コミット(今回はdevelop-rebaseの49ea0ef
)に対して、現在チェックアウトしているブランチの新しい変更(今回はfeature-rebaseのdeefe27
)を適応する、といったことが行われます。このような挙動によって、mergeの時とは違って履歴が一直線になります。またdeefe27
コミットはIDが変わります。
#merge と rebase
しばしば、「rebaseを使うとコミットの履歴が綺麗になる」と言われますが、上記のように、一直線の履歴になる、マージコミットがない、といったことがそのように言われる理由にだと思います。
しかし、rebaseはmergeと違ってコミット自体を書き換えてしまいます。そのため、メンバーと共有しているリポジトリ内でrebaseを使うと他のメンバーがpullできない、pushできない、といった事態も起こりかねません。このあたりはチームによって運用が異なると思います。
###使い分けの一案
私が実際に行っている使い分けです。あくまで一案になります。
- 開発の最新となるdevelopブランチからfeatureブランチをきる
- featureブランチで作業を行い、コミットする
- featureブランチにdevelopブランチをrebaseする
- developブランチに対してfeatureブランチをプルリクする
- developブランチにfeatureブランチをmergeする(実際はプルリクエストのマージ)
上記のように作業を行うことで、featureブランチの履歴は一直線になり、マージコミットなども入らずに、レビューがしやすいです。一方、開発の中心となるdevelopブランチにはmergeを行います。こうすることで、developブランチの履歴にはmergeコミットが残り、どのタイミングでどのプルリクエストがマージされたのかが分かりやすくなります。