interactive rebase で履歴を整える
このページで分かること
git rebase -iで開く「やることリスト」の読み方が分かる- pick・reword・squash・fixup・drop を使い分けられる
git commit --fixupと--autosquashで、修正をあとからまとめられる- 途中でやめる・元に戻す方法が分かる
このページの目次8 items
25で、rebase はコミットを1つずつ作り直す操作だと分かりました。作り直すついでに、まとめる・消す・メッセージを直す・順番を入れ替える、ができるのが interactive rebase(対話的な rebase)です。共有する前の作業ブランチを、読みやすい履歴に整えます。
整える前の履歴を作る#
作業中のコミットは、つい雑になりがちです。カレーのレシピを、ありがちな雑さで4回に分けて記録してみます。
cd ~/git-practice
git switch -c recipe-curry
printf 'じゃがいも 2個\nにんじん 1本\n' > recipes/curry.txt
git add recipes/curry.txt
git commit -m "カレーのレシピを追加"
echo "玉ねぎ 1子" >> recipes/curry.txt
git commit -am "玉ねぎ"
sed -i.bak 's/1子/1個/' recipes/curry.txt && rm recipes/curry.txt.bak
git commit -am "誤字を直す"
echo "あとで消す" >> recipes/curry.txt
git commit -am "WIP"
git log --oneline main..recipe-curry1f0d30f WIP
f8f3c22 誤字を直す
43b5885 玉ねぎ
a914e16 カレーのレシピを追加main..recipe-curry は「recipe-curry にあって main にないコミット」という範囲の指定です。このブランチで足した4つが表示されています。
この履歴には、困ったところが3つあります。
- 「玉ねぎ」だけでは、何をしたコミットか分かりにくい
- 「誤字を直す」は、直前の自分の書き間違いの修正で、独立したコミットにする意味がない
- 「WIP」(作業途中、work in progress)は、残す必要のない試し書き
これを、共有する前に整えます。
やることリストを開く#
git rebase -i main-i が interactive の意味です。25と同じく main を土台に指定しますが、今回は main が進んでいないので、土台は変わりません。同じ場所で作り直しながら、整えることになります。
実行すると、エディタに次のような「やることリスト」が開きます。
pick a914e16 # カレーのレシピを追加
pick 43b5885 # 玉ねぎ
pick f8f3c22 # 誤字を直す
pick 1f0d30f # WIP
# Rebase 7137480..1f0d30f onto 7137480 (4 commands)
#
# Commands:
# p, pick <commit> = use 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 [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# (以下略。drop などの説明が続く)上の4行が、作り直すコミットの一覧です。git log とは逆に、古いものが上に並んでいます。Git はこのリストを上から1行ずつ実行していきます。# から始まる行は説明で、実行には関係しません。
行頭の pick が「このコミットをそのまま使う」という命令です。これを書き換えることで、各コミットの扱いを決めます。
| 命令 | 動き |
|---|---|
pick | そのまま使う |
reword | 使うが、メッセージを書き直す |
edit | 使うが、そこで止まって中身を直せるようにする |
squash | 1つ上のコミットにまとめる。メッセージは両方をつないで編集する |
fixup | 1つ上のコミットにまとめる。このコミットのメッセージは捨てる |
drop | 捨てる(行ごと消しても同じ) |
行の順番を入れ替えれば、コミットの順番も入れ替わります。
書き換えて、実行する#
上の4行を、次のように書き換えて保存し、エディタを閉じます。
pick a914e16 # カレーのレシピを追加
reword 43b5885 # 玉ねぎ
fixup f8f3c22 # 誤字を直す
drop 1f0d30f # WIP- 「玉ねぎ」は
rewordで、メッセージを分かりやすく直す - 「誤字を直す」は
fixupで、1つ上の「玉ねぎ」にまとめる - 「WIP」は
dropで捨てる
閉じると Git がリストを上から実行し、reword の行でもう一度エディタが開きます。1行目の「玉ねぎ」を「カレーに玉ねぎを追加」に書き換えて保存し、閉じます。
Successfully rebased and updated refs/heads/recipe-curry.git log --oneline main..recipe-curry
cat recipes/curry.txtaeb914a カレーに玉ねぎを追加
a914e16 カレーのレシピを追加
じゃがいも 2個
にんじん 1本
玉ねぎ 1個4つのコミットが2つに整いました。玉ねぎのコミットには誤字の修正が含まれ、最初から「1個」と正しく書いたことになっています。WIP の「あとで消す」の行もなくなりました。
最初のコミット a914e16 は ID が変わっていません。pick のままで、親も中身も変わらなかったので、Git は作り直さずにそのまま使いました。変更した2つ目以降は、25と同じく新しい ID になっています。
修正を、あとからまとめる:—fixup と —autosquash#
「このコミット、少し直したい」と気づくのが、何コミットか進んだあとのこともあります。そのときは、どのコミットの修正かを印にしてコミットしておき、あとでまとめて片付ける方法が便利です。
echo "カレールウ 1箱" >> recipes/curry.txt
git commit -am "ルウを追加"
sed -i.bak 's/じゃがいも 2個/じゃがいも 3個/' recipes/curry.txt && rm recipes/curry.txt.bak
git commit -am "じゃがいもを3個に"ここで、ルウは1箱ではなく半箱だったと気づいたとします。直したいのは1つ前のコミット(HEAD~1)です。
sed -i.bak 's/カレールウ 1箱/カレールウ 半箱/' recipes/curry.txt && rm recipes/curry.txt.bak
git commit -a --fixup HEAD~1[recipe-curry b8b59c8] fixup! ルウを追加
1 file changed, 1 insertion(+), 1 deletion(-)--fixup 対象 を付けると、fixup! 対象のメッセージ という名前のコミットができます。これが「ルウを追加の修正」という印です。
git rebase -i --autosquash main--autosquash を付けると、やることリストが最初から次のように並べ替えられて開きます。
pick a914e16 # カレーのレシピを追加
pick aeb914a # カレーに玉ねぎを追加
pick 5843e96 # ルウを追加
fixup b8b59c8 # fixup! ルウを追加
pick e4ac77c # じゃがいもを3個にfixup! のコミットが、対象の「ルウを追加」の直後に移され、命令も fixup になっています。そのまま保存して閉じるだけです。
git log --oneline main..recipe-curry93a4042 じゃがいもを3個に
175e766 ルウを追加
aeb914a カレーに玉ねぎを追加
a914e16 カレーのレシピを追加修正の印のコミットが消え、「ルウを追加」が最初から半箱で書いたコミットになりました。整えたブランチは main に取り込んでおきます。
git switch main
git merge recipe-curry
git branch -d recipe-curryうまくいかなかったとき#
interactive rebase も25の rebase と同じしくみなので、困ったときの対処も同じです。
| 状況 | 対処 |
|---|---|
| やることリストを開いたが、やめたい | リストの行をすべて消して保存する(However, if you remove everything, the rebase will be aborted.) |
| 途中でコンフリクトして止まった | 直して git add → git rebase --continue。やめるなら git rebase --abort |
| 終わったあとで、整える前に戻したい | git reflog で rebase 前の ID を探し、git reset --hard ID |
よくある疑問#
Q. squash と fixup は、どう使い分けますか?
A. まとめる両方のコミットのメッセージに残したい内容があるときは squash(エディタでメッセージをつないで編集できる)、片方が「誤字を直す」のように残す必要のないメッセージなら fixup です。迷ったら squash にして、エディタで不要な行を消せば同じ結果になります。
Q. ブランチの最初のコミットまで整えたいときは?
A. 土台の指定を変えます。git rebase -i main なら main から先のすべてが対象です。直近の3つだけなら git rebase -i HEAD~3 のように数えて指定することもできます。
まとめ#
git rebase -i 土台で、土台より先のコミットの「やることリスト」が開く。古いものが上- 行頭の命令で扱いを決める:
pick(そのまま)・reword(メッセージ)・squash/fixup(まとめる)・drop(捨てる)。行の順番を変えれば順番も変わる git commit --fixup 対象で修正の印を付け、git rebase -i --autosquashでまとめて片付けられる- やめるなら
--abort、終わったあとなら reflog から戻せる - 整えるのは、共有する前の自分の作業ブランチだけ
次のページ#
27. cherry-pick で特定のコミットだけ運ぶ ── ブランチを丸ごと取り込むのではなく、必要なコミットを1つだけ選んで main に運びます。