GitHubの新バージョンでは、merge、Squash and merge、Rebase and mergeの3種類のマージに対応するようになりました。マージ方式によってコミット履歴が異なるため、どのような場合にどのマージを使用するのがよいか、共有したいと思います。
グラフで表すと
各マージをグラフで表してみます。
- merge(a、b、cをreferするmコミットノード作成、mはparentにInit、cを持つ)
- Squash and merge(a、b、cを合わせて新しいコミットを作り、ターゲットブランチに追加。’ a,b,c ‘コミットはparentをInitだけ持つ)
- Rebase and merge(a、b、cをシームレスにマージ対象ブランチに追加。各コミットはすべてparentを1つずつ持つ)
メインブランチの観点から
メインブランチのコミットでマージされたコミットがどのような形状を持つか比べてみました。
- merge:コミットmから後ろへ戻りながら親をすべて検索し、ブランチを構成する。コミットmは親としてc、Initを持っており、cはb、bはa、aはInitを再び親として持つ。この形状をすべてバックトレースして、Init -> a -> b -> c -> mという構造を作成し、履歴として残す。
- Squash and merge:コミット ‘a,b,c’ はInitだけ親に持つ単一コミット。作業したブランチのa、b、cコミットはマージ後のメインブランチコミットInit、’a,b,c’ とは何の関連も持たない。
- Rebase and merge:コミットa、b、cの関係を維持したまま、メインブランチに追加する。コミットaは親としてコミットeを持つ。Rebase and merge作業後は、作業したブランチのa、b、cコミットは、マージ後、メインブランチのInit、d、e、a、b、cコミットと関連を持たない。
使用例
Git Flowに従うとき、以下のように整理できます。
- develop – featureブランチ間マージ:Squash and mergeが便利。featureの複雑で面倒なコミット履歴をすべてまとめ、完全に新しいコミットでdevelopブランチに加え、developブランチで独自に管理できる。一般的に、マージ後にfeatureブランチを削除してしまうことを考えると、featureブランチのコミット履歴をすべてdevelopブランチに関連付けて残す必要がない。
- master – developブランチ間マージ:Rebase and mergeが便利。developの内容をmasterに追加するとき、別途新しいコミットを作成する理由がないため。
- hotfix – develop、hotfix – masterブランチ間マージ:mergeまたはSquash and mergeが便利。TOPに合わせて選択して使うとよいだろう。hotfixブランチ作業の各コミット履歴をすべて残す必要がある場合はmerge、不要な場合はSquash and mergeを使用すればよい。
おまけ
2つのブランチ間のRebaseは上記のような観点で使用できますが、cliのinteractive rebase(ex. git rebase -i HEAD~5)を使うと、単一ブランチ内でrebaseを使ってコミット履歴を整理できます。
- rewordキーワードを使うと、コミットメッセージを変更できる
- squashキーワードを使うと、さまざまなコミットを1つにまとめられる
たとえば、以下のようにcliで作業対象ブランチにgit rebase -i HEAD~5
を入力して、reword、squashを適切に使用すると
グラフで表すと以下のようになります。
既存のコミット履歴は
以下のように変更されます。(reword、squashすべてコミットメッセージを任意に変更できます。)
つまり、rebaseを他のブランチ間のマージで使用する以外にも、単一ブランチの取りまとめに使用できます。