はじめに
こんにちは!
今日はチーム開発でしばしば直面するGitの衝突問題と、それを効果的に解決するためのgit rebase --onto
について解説します。
特にSquash Mergeを使用する開発フローでの衝突回避に焦点を当てていきます。
GitHub FlowとSquash Merge
私たちのチームでは、ブランチワークとして当初は Git Flow を使用していました。
しかし、リリース頻度の向上を目標に手順の簡素化を求め、現在は GitHub Flow を採用しています。GitHub Flowのブランチワークは、レビュー後のブランチをmainにマージし即リリース可能な状態として扱います。GitHub Flowの利点としては、以下のことが挙げられます。
- シンプルさ:ブランチワークがシンプルなので、チームメンバーが容易に理解できる
- スピード:小さな変更を頻繁に統合できるため、開発サイクルが速い
- 継続的インテグレーション:変更を早く統合することで問題の早期発見が可能
次にマージ戦略としてSquash Mergeを採用しています。機能ブランチの複数のコミットを通常のマージのようにすべて履歴として残すのではなく、まとめて1つのコミットにしてmain
ブランチにマージする手法です。
特に以下の利点が挙げられます。
-
クリーンな履歴:
main
ブランチに機能単位の整理されたコミット履歴が作成される - 読みやすさ:各機能実装やバグ修正が1つのコミットとして明確に表示される
- 簡単なロールバック:問題が発生した場合、特定の機能のコミットを簡単にリバートできる
-
開発プロセスの自由:開発者は機能ブランチで自由にコミットできる(試行錯誤の過程は
main
には残らない)
頻繁にコミットを作成し履歴が多くなるようなケースでは、この戦略の恩恵にあずかれます。
Squash Mergeで発生する衝突問題
しかし、GitHub FlowとSquash Mergeを組み合わせると、特定のシナリオで衝突問題が発生します。
以下の状況を考えてみましょう:
1. branch1による作業を実施しPRを出しレビュー待ちの状態
2. レビュー待ちの間に次の作業を実施するためbranch2を作成。
3. ただし、branch1の作業がベースになるためbranch1からbranch2を作成する
4. branch2で作業を開始しコミットを作成
5. レビューが通過したため、branch1をmainにsquash merge
6. その後、branch2のPRをだしレビュー後にmainにsquash merge
7. branch2はbranch1の変更含んだ上でmainにマージするため衝突する
なぜ衝突が発生するのか?
この状況で衝突が発生する根本的な理由は以下のとおりです:
-
branch1
がSquash Mergeされると、複数のコミットが1つにまとめられ、その履歴情報は変更される -
branch2
はbranch1
から派生しており、元の詳細なコミット履歴を継承している -
branch2
をマージしようとすると、Git は同じファイルの変更が異なる履歴パスで行われていると認識する - 結果として、既に
main
に取り込まれたbranch1
の変更とbranch2
に含まれるbranch1
の変更の間で衝突が発生する
つまり、Squash Mergeにより履歴グラフが書き換えられることで、Git の変更追跡メカニズムに混乱が生じるのです。
一般的な解決方法
このような状況で一般的に行われる解決方法は次のとおりです:
-
branch1
がmain
にマージされた後、branch2
をmain
に対してリベースする:git checkout branch2 git rebase main
-
衝突が発生した場合は手動で解決する
-
衝突を解決した後、リモートの
branch2
を強制更新する:git push --force-with-lease origin branch2
この方法は有効ですが、衝突解決が複雑になることがあります。特に、branch1
とbranch2
で多くの変更が重なっている場合、同じ競合を何度も解決することになり、非効率的です。
git rebase --ontoを使用した効率的な解決方法
より効率的な解決方法がgit rebase --onto
です。このコマンドを使うと、特定のコミット範囲だけを別のベースブランチに移動させることができます。
git rebase --ontoの基本構文
git rebase --onto <新しいベース> <古いベース> <移動させたいブランチ>
先ほどの問題のケースでは、以下のように使用します:
git checkout branch2
git rebase --onto main branch1 branch2
これは「branch1
とbranch2
の間のコミット(つまりbranch2
固有の変更)のみを取り出し、main
ブランチの先端に適用する」という意味になります。
git rebase --onto の利点
-
衝突の減少:既に
main
に取り込まれたbranch1
の変更を除外できるため、衝突が大幅に減少する -
変更の明確化:
branch2
独自の変更だけを新しいベースに適用するので、PRがより明確になる - 作業効率の向上:衝突の減少により解決の手間を大幅に削減できる
実際の手順
-
branch1
がマージされた後、branch2
で以下のコマンドを実行:git checkout branch2 git rebase --onto main branch1 branch2
-
衝突があれば解決(通常は
branch1
との重複がなくなるので衝突は少なくなる) -
リモートリポジトリの
branch2
を更新:git push --force-with-lease origin branch2
-
これで
branch2
のPRにはbranch1
の変更が含まれなくなり、branch2
固有の変更のみとなる
まとめ
GitHub FlowとSquash Mergeの組み合わせは非常に効果的な開発戦略ですが、ブランチに依存関係がある場合には衝突問題を引き起こすことがあります。
git rebase --onto
を使うことで、これらの衝突を効率的に解決し、クリーンな履歴を保ちながら開発を進めることができます。
ぜひチームの開発フローにgit rebase --onto
を取り入れて、より効率的なGit運用を実現してみてください!
Schooでは一緒に働く仲間を募集しています!