はじめに
Gitでmainブランチに複数のブランチをマージした際に「想定外のコミット履歴」となってしまい、「mainブランチに必要なコミットが反映されない」事象を確認しました。筆者のGitに関する知識不足が一番の原因で、Gitに詳しい人であれば(そしてよくよく考えれば)「そんなの上手く行かなくて当たり前じゃん!」という内容ですが、備忘録として本記事に書き残します。
今回ご紹介する事象の概要をお伝えすると、
「あるブランチのコミット履歴を git rebase [別のブランチ]
により(今までとは異なる)別のブランチから分岐するように更新した際、条件によってはrebase元に存在していたコミットが反映されずにスキップされる」
というものです。
以下具体的に説明していきます。
「必要なコミットが反映されない」事象発生の経緯
当初の予定
以下のようにmain
にマージするbranch1
を作成して、素直にmain
ブランチにマージする予定でした。
予定変更
ここで「branch1
のコミットBだけ先にmain
ブランチにマージしたい」事情が発生し、次のような修正を行います。
-
branch1
のコミットBをgit revert
して取り消す
git checkout branch1
git revert --no-edit [BのコミットID]
- 新たに
branch2
をmain
からの分岐で作り、コミットBをbranch2
にcherry-pickする
git checkout -b branch2 main
git cherry-pick [BのコミットID]
ここまででリポジトリは以下のような状態になっています。
この後、先にbranch2
をmain
にマージしてM2のコミットを作り、次にbranch1
をマージしてM1の状態にする予定でした。そして適用順序は変わっても、当初予定していたbranch1
のA,B,Cのコミットが全てmain
ブランチに反映される想定でした。
実際に上図のようなコミット履歴でM1にマージしたら、A,B,C全てmain
ブランチに反映されますので(おそらくですが)ご心配なく。
branch1をそのままマージできない...
ところがここで想定外の事態が発生します。
branch2
をmain
ブランチにマージした状態で、さらにbranch1
をmain
にマージするのがNGであることが判明するのです。
実際にNGだったのはリポジトリを載せていたGitLabの設定によるものだったのですが...、その説明は割愛させてください。本記事では「何らかの事情でbranch1
をmain
ブランチからrebaseせざるを得なくなった」と思って読んでいただければとm(_ _)m
そこで仕方なく「branch1
をrebaseして、M2のコミット(main
ブランチの現在位置)から分岐する」作戦を取ります。
「上図のようにrebaseしてmain
ブランチにマージしても、もともと予定していたbranch1
のA
,B
,C
のコミットが全て反映されるだろうなあ」と、この時点では当然のごとく考えていました。Bはbranch2
で一回pB
として反映されててbranch1
ではB'
とrB'
で打ち消されるから「1+(1-1)=1」で1回分のB
相当のコミットは残るよねー、的な安易な想定で。
この後「Gitのコミットは算数の加減算で簡単に理解できるようなものじゃないんだぞ!!」と思い知らされます。。。
「git rebase」で必要なコミットがスキップされる...
そしていざrebase!
git checkout branch1
git rebase main
rebase後のmain
ブランチの中身を見てみると、おかしなことに気づきます。
あれ、コミットBの中身が消えてなくないっ!?
はい、まさにそうなのです。。。ターミナルなどでgit rebase
時の出力を見ると(GitLab等のWebUI上では見れないかもしれないですが)、以下のようなwarningが出てコミットBがスキップされていることを確認できます。
$ git rebase main
warning: skipped previously applied commit [BのコミットID]
hint: use --reapply-cherry-picks to include skipped commits
hint: Disable this message with "git config advice.skippedCherryPicks false"
Successfully rebased and updated refs/heads/branch1.
筆者環境だと、hintを参考にgit rebase main --reapply-cherry-picks
してもコミットBがスキップされるのは変わりませんでした...
結果的にリポジトリのコミット履歴は下の図のような状態で表せて、2回目のBのコミットは確かにスキップされています。
回避策
当たり前のことかもしれませんが、今回の事象を回避する方法を最後に記載します。筆者が実際に試したわけではないので、もし適用する際はご自身で挙動をお確かめください。
- コミットBをrevertするコミット
rB
を最初から作らない -
rB
を既に作ってしまった場合-
branch1
に再度コミットB
をcherry-pickする -
B
(およびrB
)を抜いたブランチを再度、main
ブランチから分岐して作り直す
-
- 既に
branch1
をmain
ブランチにマージしてしまった場合-
main
ブランチにB
をcherry-pickしてコミットする(もしくは同目的のブランチをmain
ブランチから新たに作る)
-
おわりに
「git rebase
は注意が必要」と言葉では聞いていましたが、今回初めて実感しました。