CH 26 / 75第4部 ブランチとマージ

interactive rebase で履歴を整える

このページで分かること

  • git rebase -i で開く「やることリスト」の読み方が分かる
  • pick・reword・squash・fixup・drop を使い分けられる
  • git commit --fixup と --autosquash で、修正をあとからまとめられる
  • 途中でやめる・元に戻す方法が分かる
このページの目次8 items

25で、rebase はコミットを1つずつ作り直す操作だと分かりました。作り直すついでに、まとめる・消す・メッセージを直す・順番を入れ替える、ができるのが interactive rebase(対話的な rebase)です。共有する前の作業ブランチを、読みやすい履歴に整えます。

整える前の履歴を作る#

作業中のコミットは、つい雑になりがちです。カレーのレシピを、ありがちな雑さで4回に分けて記録してみます。

text12 lines
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-curry
text4 lines
1f0d30f WIP
f8f3c22 誤字を直す
43b5885 玉ねぎ
a914e16 カレーのレシピを追加

main..recipe-curry は「recipe-curry にあって main にないコミット」という範囲の指定です。このブランチで足した4つが表示されています。

この履歴には、困ったところが3つあります。

  • 「玉ねぎ」だけでは、何をしたコミットか分かりにくい
  • 「誤字を直す」は、直前の自分の書き間違いの修正で、独立したコミットにする意味がない
  • 「WIP」(作業途中、work in progress)は、残す必要のない試し書き

これを、共有する前に整えます。

やることリストを開く#

text1 lines
git rebase -i main

-i が interactive の意味です。25と同じく main を土台に指定しますが、今回は main が進んでいないので、土台は変わりません。同じ場所で作り直しながら、整えることになります。

実行すると、エディタに次のような「やることリスト」が開きます。

text17 lines
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使うが、そこで止まって中身を直せるようにする
squash1つ上のコミットにまとめる。メッセージは両方をつないで編集する
fixup1つ上のコミットにまとめる。このコミットのメッセージは捨てる
drop捨てる(行ごと消しても同じ)

行の順番を入れ替えれば、コミットの順番も入れ替わります。

4つのコミット(カレーのレシピを追加・玉ねぎ・誤字を直す・WIP)のやることリストを、pick・reword・fixup・drop に書き換えると、カレーのレシピを追加と、誤字の修正を含むカレーに玉ねぎを追加の2つのコミットに整う図
やることリストの行頭を書き換えて、コミットの扱いを決める

書き換えて、実行する#

上の4行を、次のように書き換えて保存し、エディタを閉じます。

text4 lines
pick a914e16 # カレーのレシピを追加
reword 43b5885 # 玉ねぎ
fixup f8f3c22 # 誤字を直す
drop 1f0d30f # WIP
  • 「玉ねぎ」は reword で、メッセージを分かりやすく直す
  • 「誤字を直す」は fixup で、1つ上の「玉ねぎ」にまとめる
  • 「WIP」は drop で捨てる

閉じると Git がリストを上から実行し、reword の行でもう一度エディタが開きます。1行目の「玉ねぎ」を「カレーに玉ねぎを追加」に書き換えて保存し、閉じます。

text1 lines
Successfully rebased and updated refs/heads/recipe-curry.
text2 lines
git log --oneline main..recipe-curry
cat recipes/curry.txt
text5 lines
aeb914a カレーに玉ねぎを追加
a914e16 カレーのレシピを追加
じゃがいも 2個
にんじん 1本
玉ねぎ 1個

4つのコミットが2つに整いました。玉ねぎのコミットには誤字の修正が含まれ、最初から「1個」と正しく書いたことになっています。WIP の「あとで消す」の行もなくなりました。

最初のコミット a914e16 は ID が変わっていません。pick のままで、親も中身も変わらなかったので、Git は作り直さずにそのまま使いました。変更した2つ目以降は、25と同じく新しい ID になっています。

修正を、あとからまとめる:—fixup と —autosquash#

「このコミット、少し直したい」と気づくのが、何コミットか進んだあとのこともあります。そのときは、どのコミットの修正かを印にしてコミットしておき、あとでまとめて片付ける方法が便利です。

text4 lines
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)です。

text2 lines
sed -i.bak 's/カレールウ 1箱/カレールウ 半箱/' recipes/curry.txt && rm recipes/curry.txt.bak
git commit -a --fixup HEAD~1
text2 lines
[recipe-curry b8b59c8] fixup! ルウを追加
 1 file changed, 1 insertion(+), 1 deletion(-)

--fixup 対象 を付けると、fixup! 対象のメッセージ という名前のコミットができます。これが「ルウを追加の修正」という印です。

text1 lines
git rebase -i --autosquash main

--autosquash を付けると、やることリストが最初から次のように並べ替えられて開きます。

text5 lines
pick a914e16 # カレーのレシピを追加
pick aeb914a # カレーに玉ねぎを追加
pick 5843e96 # ルウを追加
fixup b8b59c8 # fixup! ルウを追加
pick e4ac77c # じゃがいもを3個に

fixup! のコミットが、対象の「ルウを追加」の直後に移され、命令も fixup になっています。そのまま保存して閉じるだけです。

text1 lines
git log --oneline main..recipe-curry
text4 lines
93a4042 じゃがいもを3個に
175e766 ルウを追加
aeb914a カレーに玉ねぎを追加
a914e16 カレーのレシピを追加

修正の印のコミットが消え、「ルウを追加」が最初から半箱で書いたコミットになりました。整えたブランチは main に取り込んでおきます。

text3 lines
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 に運びます。