【Git】mergeとrebase

Gitでは、2つのブランチの履歴を統合する方法として `merge` と `rebase` の2つが用意されています。私自身、`rebase` についてよくわかっておらず、「どんな時でも　merge！」派だった過去があるのですが、　`rebase` を理解できてからは `merge` と `rebase` を使い分けられるようになりました。
今日は、`merge` と `rebase`の違いについてまとめていきます。

#確認環境
- Mac
- git version 2.30.0

#前提
- 各コマンドの使い方やオプションについての説明はここでは省略させていただきます
- featureブランチにdevelopブランチを統合する、という操作をそれぞれのコマンドで実行していきます
- `git log` コマンドに使用しているオプションについてはこちら [git-log](https://git-scm.com/docs/git-log) からご確認ください

#merge
以下のような履歴を持つリポジトリを使います。

```bash
% 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してみます。すると、以下のようになります。

```bash
% 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の例と同様、以下のような履歴を持つリポジトリを使います。

```bash
% 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してみます。すると、以下のようになります。

```bash
% 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できない、といった事態も起こりかねません。このあたりはチームによって運用が異なると思います。

###使い分けの一案
私が実際に行っている使い分けです。あくまで一案になります。

1. 開発の最新となるdevelopブランチからfeatureブランチをきる
1. featureブランチで作業を行い、コミットする
1. featureブランチにdevelopブランチを**rebase**する
1. developブランチに対してfeatureブランチをプルリクする
1. developブランチにfeatureブランチを**merge**する（実際はプルリクエストのマージ）

上記のように作業を行うことで、featureブランチの履歴は一直線になり、マージコミットなども入らずに、レビューがしやすいです。一方、開発の中心となるdevelopブランチにはmergeを行います。こうすることで、developブランチの履歴にはmergeコミットが残り、どのタイミングでどのプルリクエストがマージされたのかが分かりやすくなります。