Git備忘録：あるブランチの特定コミットを別ブランチに付け換え後、マージしても反映されない

# はじめに

Gitでmainブランチに複数のブランチをマージした際に「想定外のコミット履歴」となってしまい、「mainブランチに必要なコミットが反映されない」事象を確認しました。筆者のGitに関する知識不足が一番の原因で、Gitに詳しい人であれば（そしてよくよく考えれば）「そんなの上手く行かなくて当たり前じゃん！」という内容ですが、備忘録として本記事に書き残します。

今回ご紹介する事象の概要をお伝えすると、

「あるブランチのコミット履歴を ``git rebase [別のブランチ]``により（今までとは異なる）別のブランチから分岐するように更新した際、条件によってはrebase元に存在していたコミットが反映されずにスキップされる」

というものです。

以下具体的に説明していきます。

# 「必要なコミットが反映されない」事象発生の経緯

## 当初の予定

以下のように``main``にマージする``branch1``を作成して、素直に``main``ブランチにマージする予定でした。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/6e59b5de-53b0-631f-5f30-757faea3bdcf.png">

## 予定変更

ここで「``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]
```

ここまででリポジトリは以下のような状態になっています。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/cf48995a-4414-6aad-e372-9cbd8aa469fa.png">
この後、先に``branch2``を``main``にマージしてM2のコミットを作り、次に``branch1``をマージしてM1の状態にする予定でした。そして適用順序は変わっても、当初予定していた``branch1``のA,B,Cのコミットが全て``main``ブランチに反映される想定でした。

:::note info
実際に上図のようなコミット履歴でM1にマージしたら、A,B,C全て``main``ブランチに反映されますので（おそらくですが）ご心配なく。
:::

## branch1をそのままマージできない...

ところがここで想定外の事態が発生します。
``branch2``を``main``ブランチにマージした状態で、さらに``branch1``を``main``にマージするのがNGであることが判明するのです。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/2c2bd59a-a3ca-7701-fe21-6cdac562a774.png">

:::note info
実際にNGだったのはリポジトリを載せていたGitLabの設定によるものだったのですが...、その説明は割愛させてください。本記事では「何らかの事情で``branch1``を``main``ブランチからrebaseせざるを得なくなった」と思って読んでいただければとm(_ _)m
:::

そこで仕方なく「``branch1``をrebaseして、M2のコミット（``main``ブランチの現在位置）から分岐する」作戦を取ります。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/bbfbefc4-a251-fda4-ee77-d7759eee28c6.png">

「上図のように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``ブランチの中身を見てみると、おかしなことに気づきます。

<font color="red">**あれ、コミットBの中身が消えてなくないっ！？**</font>

はい、まさにそうなのです。。。ターミナルなどで``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.
```

:::note warn
筆者環境だと、hintを参考に``git rebase main --reapply-cherry-picks``してもコミットBがスキップされるのは変わりませんでした...
:::

結果的にリポジトリのコミット履歴は下の図のような状態で表せて、2回目のBのコミットは確かにスキップされています。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/866e2266-37df-7e82-99ff-83f1228909d7.png">

# 回避策

当たり前のことかもしれませんが、今回の事象を回避する方法を最後に記載します。筆者が実際に試したわけではないので、もし適用する際はご自身で挙動をお確かめください。

* コミットBをrevertするコミット``rB``を最初から作らない
* ``rB``を既に作ってしまった場合
    * ``branch1``に再度コミット``B``をcherry-pickする
    * ``B``（および``rB``）を抜いたブランチを再度、``main``ブランチから分岐して作り直す
* 既に``branch1``を``main``ブランチにマージしてしまった場合
    * ``main``ブランチに``B``をcherry-pickしてコミットする（もしくは同目的のブランチを``main``ブランチから新たに作る）

# おわりに

「``git rebase``は注意が必要」と言葉では聞いていましたが、今回初めて実感しました。