git rebase

コマンドの概要

コミットを並べなおす機能と、コミットをまとめる機能を有する。
機能としては、コミット間の親子関係をつなぎ直すようにみえるが、rebaseの対象とした既存のコミットを削除し新たにコミットを作成する、という動作をするので留意。
HEADから指定したブランチのコミットを並べたり、リモートリポジトリにプッシュする前にコミットを整理したりすることに使う。
プルリクエストでマージしているときにgit pull --rebaseは使った方がいいかもしれない。

コマンドの使い方

ブランチを移動させる

mainブランチからtopicブランチに分岐し、それぞれコミットを重ねた状況にある

gitGraph commit id:"D" commit id:"E" branch topic checkout topic commit id:"A" checkout main commit id:"F" checkout topic commit id:"B" checkout main commit id:"G [main]" checkout topic commit id:"C [topic][HEAD]"
ここで、topicブランチ(コミットC)にチェックアウトしHEADはtopicブランチにある状態で、git rebase mainとすると

1$ git rebase main

gitGraph commit id:"D" commit id:"E" commit id:"F" commit id:"G [main]" branch topic commit id:"A" commit id:"B" commit id:"C [topic][HEAD]"
と mainブランチ(コミットC)にコミットA,B,Cが整列する。mainブランチにチェックアウトし、ファストフォワードマージをすると

1$ git switch main
2$ git merge --ff topic

gitGraph commit id:"D" commit id:"E" commit id:"F" commit id:"G" commit id:"a" commit id:"b" commit id:"c [main][topic][HEAD]"
とコミットが一直線に並ぶ。
ただし、a,b,cはリベース前のA,B,Cと同じ内容のスナップショットだが、GitによりA,B,Cを削除し、新規作成されたもの。コミットのSHA-1ハッシュは異なる。
HEADから、ターゲットとするブランチ(ここではmain)との分岐点の次のコミット(ここではA)までをターゲットとするブランチ(ここではmain)にカット&ペーストして親子関係を書きなおしている。

コミットで指定

git rebase --ontoを使うとコミット単位で親子関係を書きなおす。 古い親コミットと新しい親コミットを指定すると親を変えられる。
書式はrebase --onto < new 親 > < old 親 >

gitGraph commit id:"D" commit id:"E" commit id:"F" commit id:"G [main]" branch topic commit id:"A" commit id:"B" commit id:"C [topic][HEAD]"
のとき
コミットAを起点に古い親コミットGから新しい親コミットFにしたいときは、

1$ git rebase --onto <コミットF> <コミットG>

gitGraph commit id:"D" commit id:"E" commit id:"F" branch topic commit id:"A" commit id:"B" commit id:"C [topic][HEAD]" checkout main commit id:"G [main]"
さらに、<コミットB>を<コミットG>の子供にしたいとき、

1$ git rebase --onto <コミットG> <コミットA>

とすると

gitGraph commit id:"D" commit id:"E" commit id:"F" commit id:"G [main]" branch topic commit id:"B" commit id:"C [topic][HEAD]" checkout main
HEADからさかのぼり、コミットAを親にもつコミットBの親をコミットGにする、ということがおきる。親も子供も失ったコミットAはドロップ。

git rebase -i

-iオプションでインタラクティブモードでリベースが可能。
git rebase -i HEAD~3のように、リベース対象としたいコミットを指定するとエディタが立ち上がり

 1pick aabbcc commitA
 2pick ddeeff commitB
 3pick abcabc commitC
 4
 5# Rebase 999999..001122 onto 999999 ( 3 commands)
 6# 
 7# commands:
 8# p, pick <commit> = user commit
 9# r, reword <commit> = use commit, but edit the commit message
10# e, edit <commit> = use commit, but stop for amending
11# s, squash <commit> = use commit, but meld into previous commit
12# f, fixup <commit> = like "squash", but discard this commit's log message
13.
14.

と、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実行前に戻る)、見直しすることになる。