git rebase して git push -f すると何が起こるか
禁じ手の git push -f
だけど、
「コミット履歴がなんかぶっ壊れて直せなくなるから駄目」くらいの認識しかしてなかったので、
rebaseからの流れで中身がどう変化するのかちょっと調べてみた。
検証用にリポジトリを準備
cd /tmp
(mkdir repos.git && cd repos.git && git init --bare)
git clone repos.git work && cd work
ブランチ作ってみる
# masterブランチ
git commit --allow-empty -m "init"
git commit --allow-empty -m "first"
git commit --allow-empty -m "second"
git push origin master
# ブランチb1を分岐
git checkout -b b1
git commit --allow-empty -m "third"
git push origin b1
echo "(1)--------------------------------------------"
git show-branch -a --sha1-name
git merge-base master b1
echo "--------------------------------------------(1)"
みたいにやると、ブランチの履歴はこう。
(1)--------------------------------------------
* [b1] third
! [master] second
! [origin/b1] third
! [origin/master] second
----
* + [0ed34a9] third
*+++ [b06e7f4] second
b06e7f45ea0aed4650b763d7c88d6e57d1966967
--------------------------------------------(1)
b06e7f4
のコミットから分岐してるのがわかる。
親ブランチを壊す
とりあえず履歴を壊せればいいので、
紙面の都合上ここではgit rebase
じゃなくてgit reset
で。
git checkout master
git reset --hard HEAD^^
git commit --allow-empty -m "force1"
git commit --allow-empty -m "force2"
git push -f origin master
echo "(2)--------------------------------------------"
git show-branch -a --sha1-name
git merge-base master b1
echo "--------------------------------------------(2)"
すると、
(2)--------------------------------------------
! [b1] third
* [master] force2
! [origin/b1] third
! [origin/master] force2
----
* + [5031219] force2
* + [0b24ee6] force1
+ + [0ed34a9] third
+ + [b06e7f4] second
+ + [246fe02] first
+*++ [07fe638] init
07fe6387710019bcbd90b8a3c458f6a218522ede
--------------------------------------------(2)
b1ブランチの分岐元が 07fe638
まで遡る。
まぁ、そりゃそうだ。
元々のb1の分岐元のb06e7f4
がmasterブランチ上から消えてしまったのだから。
この状態で子ブランチでrebaseすると
git checkout b1
git rebase origin/master
echo "(3)--------------------------------------------"
git show-branch -a --sha1-name
git merge-base master b1
echo "--------------------------------------------(3)"
(3)--------------------------------------------
* [b1] force2
! [master] force2
! [origin/b1] third
! [origin/master] force2
----
+ [0ed34a9] third
+ [b06e7f4] second
+ [246fe02] first
*+ + [5031219] force2
*+ + [0b24ee6] force1
*+++ [07fe638] init
50312197fff7a0cd77809b56772ff66e2b14f46a
--------------------------------------------(3)
分岐元ごと壊した親の方に追従してしまう。
壊す前の親ブランチからの、一連のコミットは全て無かったことになる。
246fe02
b06e7f4
はもとより、b1ブランチでコミットした 0ed34a9
まで消えてしまう。
じゃあmergeだとどうなるか
git reset --hard ORIG_HEAD # rebase前に戻す
git merge origin/master
echo "(4)--------------------------------------------"
git show-branch -a --sha1-name
git merge-base master b1
echo "--------------------------------------------(4)"
とすると、
(4)--------------------------------------------
* [b1] Merge branch 'master' into b1
! [master] force2
! [origin/b1] third
! [origin/master] force2
----
- [6b02716] Merge branch 'master' into b1
*+ + [5031219] force2
*+ + [0b24ee6] force1
* + [0ed34a9] third
* + [b06e7f4] second
* + [246fe02] first
*+++ [07fe638] init
50312197fff7a0cd77809b56772ff66e2b14f46a
--------------------------------------------(4)
今度は消えたはずの246fe02
とb06e7f4
が復活してしまっている。
0ed34a9
は246fe02
とb06e7f4
からの流れのコミットなので、
mergeで0ed34a9
だけ取り出す、みたいなことはできない。
どうしてもやるなら、git rebase origin/master
で(3)の状態にしてから、
git cherry-pick 0ed34a9
みたいにやらないとならない。
結局
たいていこういうケースって、
「親ブランチが更新されたので、追従しておこうかな」なパターンで
git checkout 子ブランチ
git rebase 親ブランチ
とかすると思う。
で、親ブランチが壊されてた時に悲惨なことになる。
本質的には、
公開済みのブランチにforce pushするのがまずい
というより、
公開済みのブランチに対してコミット履歴書き換えることがまずい
ってこと。
まぁ、rebase した時点で fast foward じゃなくなるから、
結局 force じゃないと push できないんだけど。