git rebase -iとgit rebaseをちゃんと理解する
git rebase -i
コマンドについてのメモです。
これまで、単に直前のnコミットをくっつけたり消したりして整理できる便利コマンドとして
$ git rebase -i HEAD~n
と叩いていましたが、なぜこれがrebase
なのかを理解していませんでした。
自分のこれまでの理解が狭かったという話で、同じような状況の方がどれだけいるのかわかりませんが、もしかすると役に立つかもしれないのでメモを書いてみます。
git rebaseで何が起こるのか
まず、git rebase
すると何が起こるかを理解しました。
そのためにちょっと腰を据えて
https://git-scm.com/docs/git-rebase#_description
を読んでみると、git rebase <upstream>
は以下のことをすると書いてあります:
- 現在のブランチにはあるが、
<upstream>
には無いコミットが一時退避される <upstream>
に移動- 1.で退避したコミットを再適用
図で例を示します。
topic
ブランチ上でgit rebase master
すると
(Before) A---B---F---C topic / D---E---F---G master (After) A'--B'--C' topic / D---E---F---G master
こんな感じで、確かに
topic
にはあるが、master
には無いコミット(AとBとC)が一時退避されるmaster
に移動- 1.で退避したコミットを再適用
となっています。
なお、Fはmaster
にも含まれるので、1.の一時退避からは省かれます。
これは例えば、master
に入ったリファクタコミットをトピックブランチにもcherry-pickして取り込んだような場合にあたります。
git rebase -i との関係
準備ができたので、話をgit rebase -i
に戻します。
簡単に言えば、git rebase -i
するとエディタが立ち上がってコミットのリストが編集できますが、そこに表示されるコミットのリストが、前項の1.で一時退避されたコミット群そのものになる、ということが大事でした。
なぜこれまでこのことを意識しなかったのかな?と考えると、
git rebase -i HEAD~n
のようなHEAD
からの相対指定の場合、rebaseする範囲にブランチの分岐がなく、F
のようなコミットがありえないため、rebase感が薄かったためかな、と思います。
例で考えてみます。master
上でgit rebase -i HEAD~3
すると、
A---B---C---D master
master
にはあるが、master~3
(つまりA)には無いコミット(BとCとD)が一時退避されるmaster~3
に移動- 1.で退避したコミットを再適用
となります。
これは、-i
オプションを付けなければ、必ずBefore/Afterは同じ結果になることが想像できると思います(実際叩いてみるとわかりますが、Current branch master is up to date.
などと表示されるだけです)。
-i
オプションを付けることで、3.でにコミットログを改変する手順が追加される感じですね。
応用例・まとめ
以上を頭に入れれば、topic
ブランチ上でgit rebase -i master
と叩くことで、トピックブランチをメインブランチに追従させつつ、
- トピックブランチのコミットをまとめたりして整理
- 途中で入れた共通のcherry-pickやマージコミットを除去
という2つのことを一括して実行できる、とわかりました。 やむを得ずトピックブランチを長期間使わないといけないときに役に立ちそうです。
チーム開発でGitミスすると怖い、という気持ちは誰もが持つものだと思うので、このように仕組みからちゃんと理解することはやはり大事だなと思いました。