GitHubプルリクエストのマージを理解する

GitHubのプルリクエストでは、以下3つのマージ方法が選べます。
- `Create a merge commit`: 通常のマージ
- `Squash and merge`: スカッシュしてマージ
- `Rebase and merge`: リベースしてマージ
![スクリーンショット 2023-07-02 17.52.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/5575201f-dc2b-84f0-0c1f-5457ab350a6e.png)

これらはどのように違うのでしょう？
実際にやってみて違いを見てみたいと思います。

# `Create a merge commit`: 通常のマージ
通常のマージは簡単に言うと
1. マージコミットを作ってマージする(**マージの履歴が残る**)

です。実際にやってみましょう。

`test-1`ブランチには`test-1`と`test-2`というコミットが積まれている状態です。
![スクリーンショット 2023-07-02 18.20.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/e98dddec-2404-700b-15e1-86c82623dc41.png)
<img width="400px" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/fa0e0a41-9106-612b-6815-b1056ff73a1b.png">

> 余談ですが、このGitのログは`git log --graph --all --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative`というコマンドで表示しています。
> 長いのでgitコマンドのエイリアスに登録することをおすすめします。

ではこの`test-1`ブランチを`main`ブランチに`Create a merge commit`でマージします。
すると、水色枠で示したような**マージコミット**が作成されます。
![スクリーンショット 2023-07-02 18.20.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/0fee668c-6d1f-4b99-a7a6-f25bc8bee464.png)
<img width="400px" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/aa655a0e-3200-27e4-f346-9f719ff5eba9.png" >

マージコミットの特徴として、**親が2つ**ある事が挙げられます。
つまり、上図でいうと緑枠で示した、マージ元の最後のコミット(`test-2`)と、マージ先の`main`ブランチの最後のコミット(`Initial commit`)です。
こうすることで、**どのマージでどのコミットをマージしたか**が後々も簡単にわかります。
もっと言うと、**Git自身も**これまでのマージでどのコミットをマージしたかを辿ることができます。
これが通常マージの特徴です。

# `Squash and merge`: スカッシュしてマージ
スカッシュしてマージは簡単に言うと
- コミットを一つにまとめて
- マージコミットを作らずにマージする(**マージの履歴が残らない**)

です。実際にやってみましょう。

`squash`ブランチで以下のような`squash 1`と`squash 2`というコミットを`main`にスカッシュしてマージします。
![スクリーンショット 2023-07-02 20.45.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/14f24c4f-420b-a7a2-1bcf-7a45d21b232a.png)
<img width="400px" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/152be0b2-7b08-ff1a-e45a-518281f91363.png" >
![スクリーンショット 2023-07-02 18.36.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/715d711f-a955-6497-0ed8-0c11e79d216b.png)
ここで、GitHub上の画面でコミットメッセージを作成します。今回はデフォルトのまま`Squash (#2)`でいきます。
(#2 はプルリクエストの番号です。スカッシュしてマージするときには、後でたどりやすくするため、コミットメッセージにプルリクエストの番号を入れることをおすすめします。)

すると以下のように水色のコミットが作成されます。
(見づらかったので`squash`ブランチは削除しています)
![スクリーンショット 2023-07-02 20.54.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/ee8bb4d5-2518-88f4-c3f5-6cef63eedd7f.png)
<img width="400px" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/a1f07f65-6b6d-8c56-8d46-88ea4b463bb5.png" >

シンプルですね。

`squash 1`や`squash 2`というコミットは跡形もなく消えて、`Squash (#2)`というコミットだけが、元の`main`ブランチに積まれています。
この`Squash (#2)`というコミットには`squash 1`と`squash 2`の両方の変更が含まれています。

マージコミットは作らないので、**Git自身はどのマージでどのコミットがマージされたか辿れません。**
ただし上記のように**コミットメッセージにプルリクエストの番号を入れる**ことで、人間はどのプルリクエストでどのコミットがマージされたか辿ることはできます。

# `Rebase and merge`: リベースしてマージ
リベースしてマージは簡単に言うと
- マージコミットを作らずにマージする(マージの履歴が残らない)

です。

`Squash (#2)`のコミットが最後である`main`から`rebase-and-merge`ブランチを作成し、下記のように`rebase 1`と`rebase 2`というコミットを積みます。

![スクリーンショット 2023-07-02 21.12.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/a499458b-c58a-0803-dac1-75ba26d2bc96.png)
<img width="400px" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/bae17518-5c52-6a38-bee2-2e74c6bd7322.png" >

これをリベースしてマージをした後が以下です。
(見やすくするため`rebase-and-merge`ブランチはマージ後に削除しています)

![スクリーンショット 2023-07-02 21.16.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/4cefca38-10ca-11cc-259f-9b382a3af744.png)
<img width="400px" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/6f709026-599d-baae-e127-565fd7ecd26e.png" >

ぱっと見ほとんど変わりませんね。
よく見ると、`rebase-and-merge`ブランチでの変更が、そのまま`main`ブランチに取り込まれていることがわかります。
このように、リベースしてマージは、マージ元(ここでは`rebase-and-merge`ブランチ)のコミットをそのままマージ先(ここでは`main`ブランチ)のブランチ積み上げます。

スカッシュしてマージと同じく、マージコミットは作らないので、**Git自身がどのマージでどのコミットがマージされたかは辿れません。**
また、新たにコミットを作るわけでもないので、**Gitの履歴からどのコミットがどのプルリクエストと関連づいているのか、人間が辿ることも難しい**です。

# どう使い分ける？
ここまでで、それぞれの違いがなんとなく理解できたかと思いますが、**結局どう使い分けるの？** という疑問が残りますね。
このあたりは運用の考え方で次第で様々に使い分けがされると思います。
簡単にメリット・デメリットをまとめると以下のようになると思います。
`* PR: プルリクエスト`

| マージ方法 | メリット | デメリット |
| --- | --- | --- |
| 通常のマージ | どのPRでどのコミットがマージされたかわかりやすい。マージの履歴が残る。 | マージ元のコミットが残るので多少見づらい。 |
| スカッシュしてマージ | コミットがPRごとに1つにまとまるので見やすい。新たにコミットを作るので、コミットメッセージにPR番号を入れればPRとの関連付けもできる。 | Git上でマージの履歴が残るわけではないので、過去にマージしたことのあるブランチに新たにコミットを積んで再度マージしようとすると、マージ済みのコミットも全てマージしようとするため、大抵マージコンフリクトが発生する。 |
| リベースしてマージ | スカッシュしてマージとは違い、コミットが全て残る。 | 上記に加えて、PRとの関連付けができない。(どのPRでどのコミットがマージされたかがわからない) |

簡単にと言いましたが、抽象的でよくわかりませんね…
このあたりを深掘りすると長くなってしまいそうなので、こちらは別記事で書いていきたいと思います。

簡単に、使い分けの例として以下が挙げられると思います。

**通常のマージ**
- マージ元ブランチをマージ後も消さずに使い続ける場合
    - 例えばGit Flowにおける`develop`ブランチを`main`ブランチにマージするときなど
    - これはマージ履歴が残ることに関係します
- ベースブランチ(`main`や`develop`)へのマージ
    - 注意すればスカッシュしてマージでも対応可能
- 変更が巨大など、マージ履歴を残しつつマージ後もコミット履歴を残したい場合

**スカッシュしてマージ**
- マージ元ブランチはマージ後に消してしまう場合
    - `feature`ブランチからさらに派生したブランチを`feature`ブランチにマージする場合
    - Git Flowにおける`feature`ブランチを`develop`にマージする場合
        - ただしマージする際に最新の`develop`にリベース済みである必要がある
- コミットを1つにまとめても変更が巨大でない場合

**リベースしてマージ**
- コミット履歴をそのまま、マージ先ブランチに取り込みたい場合
    - `feature`ブランチからさらに派生したブランチを`feature`ブランチにマージする場合

# おわりに
以上、簡単にGitHubのプルリクエストのマージ方法を整理しました。
それぞれの特徴を理解して使い分ける必要がありそうです。
わからなければ、無難に通常マージをするのが良さそうです。