LoginSignup
87
48

GitHubのプルリクエストでは、以下3つのマージ方法が選べます。

  • Create a merge commit: 通常のマージ
  • Squash and merge: スカッシュしてマージ
  • Rebase and merge: リベースしてマージ
    スクリーンショット 2023-07-02 17.52.20.png

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

Create a merge commit: 通常のマージ

通常のマージは簡単に言うと

  1. マージコミットを作ってマージする(マージの履歴が残る)

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

test-1ブランチにはtest-1test-2というコミットが積まれている状態です。
スクリーンショット 2023-07-02 18.20.18.png
image.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
image.png

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

Squash and merge: スカッシュしてマージ

スカッシュしてマージは簡単に言うと

  • コミットを一つにまとめて
  • マージコミットを作らずにマージする(マージの履歴が残らない)

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

squashブランチで以下のようなsquash 1squash 2というコミットをmainにスカッシュしてマージします。
スクリーンショット 2023-07-02 20.45.38.png
image.png
スクリーンショット 2023-07-02 18.36.27.png
ここで、GitHub上の画面でコミットメッセージを作成します。今回はデフォルトのままSquash (#2)でいきます。
(#2 はプルリクエストの番号です。スカッシュしてマージするときには、後でたどりやすくするため、コミットメッセージにプルリクエストの番号を入れることをおすすめします。)

すると以下のように水色のコミットが作成されます。
(見づらかったのでsquashブランチは削除しています)
スクリーンショット 2023-07-02 20.54.28.png
image.png

シンプルですね。

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

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

Rebase and merge: リベースしてマージ

リベースしてマージは簡単に言うと

  • マージコミットを作らずにマージする(マージの履歴が残らない)

です。

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

スクリーンショット 2023-07-02 21.12.20.png
image.png

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

スクリーンショット 2023-07-02 21.16.17.png
image.png

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

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

どう使い分ける?

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

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

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

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

通常のマージ

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

スカッシュしてマージ

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

リベースしてマージ

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

おわりに

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

87
48
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
87
48