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

Last modified 2020.05.29