GitHub のマージ方法:Merge commit、Squash and merge、Rebase and merge の違い

GitHub のマージ方法:Merge commit、Squash and merge、Rebase and merge の違い

GitHub でプルリクエストをマージする際には下記の通り3つの方法があります。

  1. Merge commit: All commits from this branch wil be added to the base branch via a merge commit.
  2. Squash and merge: The n commits from this branch will be combined into one single commit in the base branch.
  3. Rebase and merge: The n commits from this branch will be rebased and added to the base branch.

それぞれの挙動 #

では、以下のケースを想定して、それぞれのマージ方法でどのようなコミット履歴になるかを見てみましょう。

  1. ベースブランチ(main ブランチ)で A, B, C のコミットがありました。
  2. C のあとでトピックブランチ(feature ブランチ)を切り出して D, E のコミットをしました。
  3. ベースブランチ(main ブランチ)に PR を出してマージします。

Merge commit(マージコミット) #

マージを実行すると、main ブランチにマージコミット(X)が作成されます。

# マージ実行直前
main   : A---B---C
                  \
feature:           D---E

# マージ実行後
main   : A---B---C---X--->
                    /
               D---E

Squash and merge(スカッシュマージ) #

マージを実行すると、main ブランチに D と E をまとめた新しいコミット(X)が作成されます。

# マージ実行直前
main   : A---B---C
                  \
feature:           D---E

# マージ実行後
main   : A---B---C---X--->

Rebase and merge(リベースマージ) #

マージを実行すると、D と E が main ブランチの最新のコミットの後ろに追加されます。

# マージ実行直前
main   : A---B---C
                  \
feature:           D---E

# マージ実行後
main   : A---B---C---D'--E'-->

補足

ここで、GitHub でのリベースとマージの動作は、通常の git rebase コマンドの動作とは少し異なっていることには注意が必要です。通常の git rebase では、Fast-forward マージが可能な場合コミット情報(D, E)は変更されませんが、GitHub でのリベースマージでは新しいコミット SHA(D', E')で書き直します。

GitHub でのリベースとマージの動作は、git rebase とは少し異なっています。 GitHub でリベースしてマージすると、常にコミッター情報が更新され、新しいコミット SHA が作成されますが、GitHub 外の git rebase は、先祖コミットの上でリベースが発生した場合、コミッター情報は変更されません。

https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/about-merge-methods-on-github#rebasing-and-merging-your-commits

# マージ実行直前
main   : A---B---C
                  \
feature:           D---E

# 通常の git rebase でのマージ実行後
main   : A---B---C---D---E--->

# GitHub でのリベースマージ実行後
main   : A---B---C---D'--E'-->

どれを使うのがよい? #

ベースブランチ(main ブランチ)にどのように履歴を残すか、という観点で選択することになります。

GitHub の初期状態のマージ方法は「Merge commit(マージコミット)」です。

わたしは「Squash and merge(スカッシュマージ)」をおすすめします。Squash and merge では、ベースブランチに残る履歴はプルリクエストと1対1の関係になり、履歴が整理されてトレーサビリティが向上します。

マージコミットをまとめる

よりスムーズなGitの履歴をリポジトリに作り出すために利用できます。 作業途中でのコミットは、フィーチャブランチで作業しているときには役立ちますが、必ずしもGitの履歴に残すほど重要とはかぎりません。 既定のブランチにマージするときにこれらのコミットを 1 つのコミットに squash すると、変更が統合され、Git の履歴がわかりやすくなります。

https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/about-merge-methods-on-github#squashing-your-merge-commits

また、GitHub ではマージコミットのコミットメッセージに、プルリクエストのタイトルや説明文を自動的に反映させることができます。Squash and merge を使用しつつ、PR タイトル = コミットヘッダー、PR の説明文 = コミットメッセージのボディとして自動反映する設定を有効にしておけば、ベースブランチに残る履歴がプルリクエストの内容と対応するようになり、より分かりやすくなります。

なお「Rebase and merge(リベースマージ)」については使用を避けたほうが良い考えています。その変更がどの PR で入った、などが容易には追えなくなり、チェリーピックなどが困難になる可能性があります。また、マージ後にトピックブランチ(feature ブランチ)を削除した場合、PR に積んだコミットのハッシュが無効になります。そのため、履歴管理としてはリベースを行わず、常にマージコミットを生成することが望ましいと考えています。

参考 #