概要
branch-aからブランチを切ったbranch-bで作業する。
branch-aをsquash mergeしたあとにbranch-bをmergeしようとするとコンフリクトすることがある。
この際の対処法は以下のコマンド。
git rebase --onto origin/main branch-a branch-b
branch-bからさらにブランチを切ったbranch-cでも作業している時はbranch-bで上記コマンドを実行してしまうと(すでにrebaseしてしまっているため)コミットIDがズレるので上記のままでは使えない
その際には以下のコマンドでrebase前のコミットIDを掴んでそのIDでrebase --ontoする
git reflog show branch-b
本記事ではこれ以上の説明はありません。
以下ではこれが必要な場面の再現と対処法をなるべく詳細に解説します。
こんな時の対処法です
a.txt, b.txt, c.txtがあります。
branch-a でa.txtに修正を加えます。
まだbranch-aはmainにマージされていませんがbranch-aの修正内容を反映したまま作業したかったのでbranch-bはbranch-aからブランチを切って作業しました。
その際にbranch-aで修正されたファイル(a.txt) を一部修正しました。
両方のプルリクを作成しました。mainブランチはスッキリさせたいのでsquash mergeしましょう。
ブランチAをsquash mergeしました。この調子でブランチBもmergeしちゃいましょう。
残念、コンフリクトしました。
なぜ起こるか?どうすればよい?
squash mergeするとそのPRで作られたcommit とは別に、そのPRすべての差分をまとめた新しいコミットがmainに作られます。
結果として、main 上には branch-aで作ったコミットそのもの は存在せず、branch-b の祖先にあるコミットと main 上の コミットは異なるコミットとして扱われます。
そのため、branch-b を main に統合する際、a.txt の 同じ箇所に対する変更 が、
- branch-b 側: branch-aで作られたコミットを前提とした変更
- main 側: branch-aで作られたコミットをまとめた別の変更
というように祖先関係の切れた別変更 としてぶつかり、どちらが偉いかが判別できなくなり、コンフリクトをするのです。
本来コンフリクトしていない(squash mergeを選ばなければconflictしない)のにコンフリクト解消を頑張るのはちょっと無駄です。
そこで、rebase --ontoを使います。
rebase --ontoとは? & 解決
ひとことで言うとブランチを切った場所を変更できるコマンドです。
今の状況を図解すると以下の感じです。
o───o───o ← origin/main
\
A1───A2───A3 ← branch-a
\
B1───B2 ← branch-b (HEAD)
squash mergeするとそのPRで作られたcommit とは別に、そのPRすべての差分をまとめた新しいコミットがmainに作られるのでした。
そのため、以下のような状態になります。
これで別々のコミットが同じものを編集していることになりますから、branch-bをマージしようとするとコンフリクトするのでした。
o───o───o───S ← origin/main (S = squash(A1,A2,A3))
\
A1───A2───A3 ← branch-a
\
B1───B2 ← branch-b (HEAD)
そこで、git rebase --ontoを使います。
このコマンドの使い方は
git rebase --onto <付け替え先のブランチ> <付け替え元のブランチ> <付け替えたいブランチ>
です。今回はbranch-bを切った元のブランチをbranch-a -> origin/mainだったことに変更したいので、以下のようにすればOKです。
git rebase --onto origin/main branch-a branch-b
これで、再度pushしましょう。コミットIDが変わっているのでforce-pushします。
無事mergeできるようになりました。
さらにbranch-cがあった場合
上記の話ではbranch-bまででしたが、branch-bからさらに切ったbranch-cもあった場合はどうすれば良いでしょうか?
先程までの話だと、
git rebase --onto origin/main branch-a branch-b
して、branch-bをrebaseしたあとに、(merge, git fetchして、)
git rebase --onto origin/main branch-b branch-c
をすれば良さそうな気がします。実際にやってみます。
無事コンフリクトしました。
なぜコンフリクトするか?
これは簡単です。
答えはbranch-bをすでにrebaseしてしまっているからです。
思い出してみるとrebase --ontoは ブランチを切った場所を変更できるコマンドでした。
しかし、branch-bはすでにrebaseしてしまったため、今branch-bが持っているコミットIDはもはやbranch-cで持っているものではありません。
そのため、どこから付け替えればいいかがわからずコンフリクトしてしまうというわけです。
どうすればよい?
git reflogです。
git reflogとは、Gitで操作履歴を見ることができるコマンドです。
これは余談ですがreflogを把握しておけば大抵のことはやらかしてOKだと思っています。
今は branch-bをrebaseしたことが原因でコンフリクトが起こっているのでした。
そこでrebaseする前のコミットログを掴んでそこを基準にしてrebaseすればうまくいきそうです。
git reflog show branch-b
branch-b@{0} でrebaseされていますね。ということで直前のbranch-b@{1} をもとにrebaseしましょう。
git rebase --onto origin/main branch-b@{1} branch-c
無事にrebaseできて、マージもできるようになりました。
めでたしめでたし。
まとめ
- squash mergeのコンフリクト解消は
rebase --ontoで一発 - 複数チェーンさせているときはreflogでrebase前のコミット掴んでrebaseする










