git rebase
コマンドの概要
コミットを並べなおす機能と、コミットをまとめる機能を有する。
機能としては、コミット間の親子関係をつなぎ直すようにみえるが、rebase
の対象とした既存のコミットを削除し新たにコミットを作成する、という動作をするので留意。
HEAD
から指定したブランチのコミットを並べたり、リモートリポジトリにプッシュする前にコミットを整理したりすることに使う。
過去のコミットを整理する機能なので、よくわからないときは使わなくても大丈夫。
プルリクエストでマージしているときにgit pull --rebase
は使った方がいいかもしれない。
コマンドの使い方
ブランチを移動させる
masterブランチからtopicブランチに分岐し、それぞれコミットを重ねた状況にある
A - B - C [HEAD:topic]
/
D - E - F - G [master]
ここで、topicブランチ(コミットC)にチェックアウトしHEAD
はtopicブランチにある状態で、git rebase master
とすると
$ git rebase master
A - B - C [HEAD:topic]
/
D - E - F - G [master]
と masterブランチ(コミットC)にコミットA,B,Cが整列する。masterブランチにチェックアウトし、ファストフォワードマージをすると
$ git switch master
$ git merge --ff topic
D - E - F - G - a - b - c [HEAD:master][topic]
とコミットが一直線に並ぶ。ただし、a,b,cはリベース前のA,B,Cと同じ内容のスナップショットだが、GitによりA,B,Cを削除し、新規作成されたもの。コミットのSHA-1ハッシュは異なる。
HEAD
から、ターゲットとするブランチ(ここではmaster)との分岐点の次のコミット(ここではA)までをターゲットとするブランチ(ここではmaster)にカット&ペーストして親子関係を書きなおしている。
コミットで指定
git rebase --onto
使うとコミット単位で親子関係を書きなおす。
A - B - C [HEAD:topic]
/
D - E - F - G [master]
のときに、古い親コミットと新しい親コミットを指定すると親を変えられる。
書式はrebase --onto < new 親 > < old 親 >
コミットAを起点に古い親コミットGから新しい親コミットFにしたいときは、
$ git rebase --onto <コミットF> <コミットG>
A - B - C [HEAD:topic]
/
D - E - F - G [master]
さらに、<コミットB>を<コミットG>の子供にしたいとき、
$ git rebase --onto <コミットG> <コミットA>
とすると
B - C [HEAD:topic]
/
D - E - F - G [master]
HEAD
からさかのぼり、コミットAを親にもつコミットBの親をコミットGにする、ということがおきる。親も子供も失ったコミットAはドロップ。
git rebase -i
-i
オプションでインタラクティブモードでリベースが可能。
git rebase -i HEAD~3
のように、リベース対象としたいコミットを指定するとエディタが立ち上がり
pick aabbcc commitA
pick ddeeff commitB
pick abcabc commitC
# Rebase 999999..001122 onto 999999 ( 3 commands)
#
# commands:
# p, pick <commit> = user commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
.
.
と、git-rebase-todo
ファイルの編集状態となる。
今回は、HEADから3つのコミットを指定したので、ファイルの1〜3行目に、コマンド、SHA-1ハッシュ、コミットメッセージが記載されている。
rebase -i
するときは、このファイルがスクリプトになっており、コマンドを上書き保存して終了するとrebaseプロセスが進む。4行目以下にコマンドのリファレンスがあるが、スクリプト上では単なるコメント。
git rebase -i
したときに編集の対象としたコミットでも、git-rebase-todo
ファイルから削除してしまえば、rebaseの対象外になる。
pick
対象のコミットに何もしないときは、デフォルトのpick。全てpick(もしくはp)とし、ファイルを保存、終了するとリベースプロセスが進む。 pick を指定して行を入れ替えるとコミットの順番が変わる。
# コミットBとコミットCの順番を入れ替える
pick aabbcc commitA
p abcabc commitC
p ddeeff commitB
reword
コミットメッセージを書き換えたいときは、reword(もしくはr)。git-rebase-todo
ファイルを保存終了するとgit commit --amend
のときのようにコミットメッセージの編集画面になるので、メッセージを変更して保存する。複数指定すると、複数回、メッセージの編集画面になる。コミットメッセージを変更保存するとrebaseプロセスは終了。
# コミットAとコミットBのコミットメッセージを変更する
rename aabbcc commitA
r ddeeff commitB
pick abcabc commitC
edit
edit
を指定するとそのコミットでrebaseプロセスが一旦中止する。ここで、追加し忘れたファイルをgit add
したりインデックスをいい感じにしてgit rebase --continue
するとrebaseプロセスが進む。
# コミットBを編集したい
pick aabbcc commitA
e ddeeff commitB
pick abcabc commitC
squash
複数のコミットをひとつにまとめるとき。squash
もしくはs
を指定したコミットの親コミット(ひとつ前のコミット)に統合される。git-rebase-todo
を保存終了するとコミットメッセージの入力画面になるので、あたらしいコミットメッセージに上書きして保存すると、rebaseプロセスが進む。
# コミットBをコミットAにまとめたい
pick aabbcc commitA
s ddeeff commitB
pick abcabc commitC
するとこんな感じでコミットメッセージの入力画面に
# This is a combination of 2 commits
# This is the 1st commit message:
CommitA
# Thisi the commit message #2:
CommitB
これを上書して新しいコミットメッセージにする。
fixup
複数のコミットをひとつにまとめるとき。fixup
もしくはf
を指定したコミットの親コミット(ひとつ前のコミットに)統合される。コミットメッセージの更新はしない。
squash
と同じだが、コミットメッセージの更新をしない。
コンフリクト
git rebase
は対象となるコミットをひとつずつ処理していくので、コンフリクトが生じると都度、rebaseプロセスが中断される。コンフリクトを解消し、git rebase --continue
でrebaseプロセスを再開、もしくは、git rebase --abort
でrebaseプロセス中止し(途中で解消したコンフリクトもgit rebase
実行前に戻る)、見直しすることになる。