DependabotのPRを何件かトリアージしてマージしていて、そういえばGitHubのマージボタンには3つの選択肢があるけど、それぞれ何がどう違うのか改めて整理したことがなかったので書いておく。PRブランチ側をrebaseかmergeで最新化する話とは別で、こちらは「PRをmainに取り込むときにどの方式を使うか」の話。
3つのマージ方法
PRブランチに A, B, C という3つのコミットがあり、それを main にマージする場面を考える。
Merge commit
PRブランチの A, B, C をそのまま残しつつ、それらをまとめる「マージコミット」を1つ追加する。
main: o---o---o---------M
\ /
feature: A---B---C
- 個々のコミットがそのまま
mainに残る - 「ここでPRがマージされた」という区切り(マージコミット)が履歴に残る
gh pr merge --mergeに対応
Squash and merge
PRブランチの A, B, C を1つのコミットにまとめて main に追加する。マージコミットは作られない。
main: o---o---o---S
- PRの中でいくつコミットを積んでいようと、
mainに残るのは1コミットだけ - レビュー対応や
fixupのような途中経過のコミットは消える gh pr merge --squashに対応
Rebase merge
PRブランチの A, B, C を個別のまま main の先端に積み直す。マージコミットは作らない。
main: o---o---o---A'---B'---C'
- 個々のコミットは残るが、コミットハッシュは変わる(rebaseなので)
mainの履歴は常に一直線になるgh pr merge --rebaseに対応
比較
| Merge commit | Squash and merge | Rebase merge | |
|---|---|---|---|
| mainに残るコミット数 | PRのコミット数+1(マージコミット) | 1個 | PRのコミット数 |
| 個々のコミットの意味 | 保たれる | 失われる(1つに統合) | 保たれる |
| mainの履歴の形 | 分岐・合流が残る | 一直線 | 一直線 |
| PR単位でrevertしやすいか | マージコミットをrevertすれば一括で戻せる | コミット1個をrevertすれば戻せる | コミットが複数残るので個別に戻す必要がある |
| ブランチ内の雑なコミット(fixupなど) | そのまま残る | 消える | そのまま残る |
どう使い分けるか
正解が1つあるわけではなく、GitHubのリポジトリ設定(Settings → Pull Requests)でどの方式を許可するか、通常はチームやプロジェクトの方針として決めておくものだと思う。判断軸としては次のあたりになりそう。
- PRの中のコミットを丁寧に作り込む文化があるか: レビュー対応のたびに
fix typoのようなコミットを積む運用なら、Squash and mergeで最終的な差分だけmainに残すのが素直 - individual commitを後から参照したいか: コミット単位でリリースノートを自動生成する(Conventional Commitsなど)場合や、機能追加とリファクタを別コミットとして残したい場合は、Merge commitかRebase mergeで個々のコミットを残す
- mainの履歴を完全に一直線にしたいか: それが重要ならRebase merge。ただしPRの数だけコミットが増えるので、雑なコミットが混ざっているとそのままmainの永続的な履歴になってしまう
このブログのリポジトリは今のところMerge commit運用にしている。DependabotのPRのようにPR=ほぼ1コミットという場合が多く、Squash and mergeとの差はほとんどないが、「PRがマージされた」という区切りが履歴に残る方が読みやすいと感じたのでこちらにしている。
PRブランチ側の更新方法との関係
ここで整理した「PRをmainに取り込む方法」と、「PRブランチを最新のmainに追従させる方法(rebase / merge)」は別の話だけど、考え方の軸は同じところに行き着く。
rebaseは対象のコミットを書き換える操作なので、「まだ誰にも共有していないコミットは自由にrebaseしてよいが、共有済みのコミットは書き換えない」という原則がある。この原則に沿うなら、
- 自分しか触っていないローカルの作業ブランチ → rebaseで整理してよい
- すでにpushされてPRになっている(=共有済みの)ブランチを最新化する → 書き換えないmergeの方が安全
という使い分けになる。実際、Dependabotが作るPRブランチはすでにpush・オープン済みの共有状態なので、これを最新化するときはrebaseではなくmergeを使うようにしている。