git rebase の引数と動作解説
はじめに
リベースコマンド実行の大半は git rebase main
で済みますが、それはrebaseコマンドの引数省略形に過ぎません。すこし複雑なリベースをしようとすると悩んでしまうのは、引数省略しない使い方を理解していないためです。
本記事は、https://git-scm.com/docs/git-rebase に記載されている説明を、厳密な正確性よりも分かりやすさを優先して書き換えたものです。
git reabse の使い方に悩む方々の参考になればと思い公開します。
これを読めば思い通りのリベースが出来るようになるでしょう。
コマンドライン指定形式
git rebase [--onto 接続先] [切り取り開始点 [切り取り終了点]]
切り取り開始点
から切り取り終了点
までの複数コミットを、接続先
へ付け替えるのがリベースの動作であり、私たちが日頃良く使うのは --onto 接続先
と 切り取り終了点
の引数を省いた形なのです。
例えば git rebase main
は git rebase --onto main main current-branch
の省略形です。切り取り開始点
がmainで、接続先
もmainでは何も起きないのではないか。と思われるでしょうが、その疑問については 後の章 にて説明します。
先に引数を省くと何が補われるのかを説明しましょう。
引数省略時の説明
切り取り開始点(upstream)
省略時は切り取り終了点(branch)
に設定されたリモートブランチ(上流ブランチ)の最新コミットを使う。
切り取り終了点(branch)
省略時はカレントブランチの最新コミットが対象となる。
接続先(newbase)
省略時は切り取り開始点(upstream)
を使う。
動作
git rebase --onto 接続先 切り取り開始点 切り取り終了点
このgitコマンドは下記ステップで実行されます。
-
切り取り終了点
にスイッチし、それをカレントブランチとする。
git switch 切り取り終了点
-
切り取り終了点
にブランチ名を指定していない場合は、そこを最終コミットとする仮ブランチとして扱う。 - この時点で
切り取り終了点
がHEAD
になる。 -
切り取り終了点
にカレントブランチを指定した場合、この段階では何も変化しない。
-
-
切り取り終了点
から遡る全履歴コミットのうち、切り取り開始点
から遡る全履歴に含まれていないコミットのリストであるgit log 切り取り開始点..切り取り終了点
(便宜上、差分コミットと呼ぶ)を付け替え対象として退避する。-
切り取り開始点
にブランチ名を指定した場合、そのブランチの最新コミットが開始点である。
-
- カレントブランチを
接続先
に強制リセットする
git reset --hard 接続先
-
切り取り終了点
として指定したブランチの履歴はこの時点でリセットされてしまう。 -
切り取り終了点
はORIG_HEAD
に記録されるので、git reset --hard ORIG_HEAD
にて復旧可能である。
-
- カレントブランチに対して、上記2で退避した差分コミットを順番に1つづつ再コミットしてブランチを伸ばしていく。
- ただし、
git log 切り取り開始点..接続先
に含まれる各コミットと修正差分が同一のコミットはスキップされる。要するにcherry-pick
で取り込んだ接続先と重複する内容は再コミットされない。
- ただし、
- 退避した差分コミットを全て処理したら動作完了です。
ここが肝心
上記2にて両者の履歴に共通して含まれないコミットを差分コミットとし、上記4にて重複する内容を再コミットしないことが、リベース動作を正しく理解するための肝です。
先の疑問の答え
先の疑問である「git rebase --onto main main current-branch
は 切り取り開始点
がmainで接続先
もmainなので何も起きないのではないか」について答えます。
- カレントブランチから遡る全履歴コミットのうち、
切り取り開始点
に指定したmainブランチの全履歴に含まれないコミットのリストが差分コミットとなる。それはカレントブランチがmainブランチから分岐した後のコミット履歴と等しい。 -
接続先
がmainブランチなので、mainブランチの最新コミットが接続先となる。 - つまり、元のカレントブランチがmainブランチから分岐した後のコミット履歴を、mainブランチの最新コミットに接続したものが新しいカレントブランチとなるという、リベース動作になる。
豆知識
git rebase -i
にてユーザに示されるコミット履歴は、上記4にて重複をスキップした差分コミットです。
切り取り開始点(upstream)
省略についての厳密な説明(読まなくて良いです)
省略時は
切り取り終了点(branch)
に設定されたリモートブランチ(上流ブランチ)の最新コミットを使う。
という説明は厳密には違います。
正確には切り取り終了点(branch)
に git branch --set-upstream-to=
で設定された追跡ブランチが使われます。
そして追跡ブランチの設定が無い場合にはリベースは中止されます。
大半のケースでは git push -u
を実行することにより「リモートブランチ」が追跡ブランチとして設定されているか、追跡ブランチが設定されていません。よって実質的には「リモートブランチ」を使うか、リベース中止になるかの二択となります。 git branch --set-upstream-to=main mybranch
として追跡ブランチとしてローカルなmainブランチを設定すると git rebase main
のかわりに git rebase
とmainを省略することもできます。
具体例
カレントブランチを、mainの最新コミットから新規ブランチしたものとして再生成する.
git rebase main
-
main..HEAD
の差分コミットを退避し - カレントブランチをmain最新コミットに強制リセットし
- 退避した差分コミットを適用する.
main..HEAD
の差分コミットは F1,F2,C1,C2
である。
カレントブランチをmain最新コミットM4
に強制リセットし、
続いて差分コミットの内容をF1',F2',C1',C2'
として再コミットしてゆく。
コミットC1,C2
はどのブランチにも含まれていない残骸として残り、いずれは git gc
により消去される。
featXブランチを、mainの最新コミットから新規ブランチしたものとして再生成する.
git rebase main featX
- カレントブランチを featX に切り替え
-
main..featX
の差分コミットを退避し - featX をmain最新コミットに強制リセットし
- 退避した差分コミットを適用する.
featXブランチを、tag3から新規ブランチしたものとして再生成する.
git rebase --onto tag3 main featX
- カレントブランチを featX に切り替え
-
main..featX
の差分コミットを退避し - featX をtag3コミットに強制リセットし
- 退避した差分コミットを適用する.
featXブランチを、tag3から新規ブランチして featX~3..featX
の内容で再生成する.
git rebase --onto tag3 featX~3 featX
- カレントブランチを featX に切り替え
-
featX~3..featX
の差分コミットを退避し - featX をtag3コミットに強制リセットし
- 退避した差分コミットを適用する. featX~3以前のコミット
main..featX~4
は適用されない.
参考資料