以前、Gitの使い方、よく使うGitコマンド という記事を書きましたが、git rebase -i
の項目に書き足したいことが増えてきたので別エントリに切り出し、内容を見直しました。
git rebase -i
を使うと、最新のコミットから指定したコミットまでの歴史を対話式に改変することができます。具体的には以下のことができます。
- コミットメッセージを変更する
- コミット内容を修正する
- コミットを分割する
- コミットをまとめる
- コミットを削除する
私個人の利用シーンとしては、開発ブランチを master にマージする前(プルリクを送る前)にコミットの整理に使うことが多いです。一発できれいなコミット履歴を作るのは難しいので、散らかったコミットを後から整理するのによく使います。Git って便利だなあと思う瞬間です。
目次
- rebase -i の使い方
- reword コミットメッセージを変更する
- edit コミットを修正する
- scuash コミットをまとめる
- fixup コミットをまとめる。コミットメッセージはそのまま
- コミットの順番を入れ替える
- コミットを削除する
- 初回コミットの rebase -i をするには
- rebase 中に止まってしまったときは
- rebase -i を取り消すには
- リモートの共有リポジトリに push したコミットを rebase してはいけない
rebase -i の使い方
以下のようなコミット履歴があるとします。
$ git log --oneline
5ce576e 5
30ff21a 4
95eaecd 3
adb04d8 2
29102b1 1
3個前のコミット 95eaecd 3
を修正したい場合は、git rebase -i adb04d8
もしくは git rebase -i HEAD~3
と入力します。入力するとエディタが立ち上がり、次のように表示されます。
$ git rebase -i adb04d8
pick 95eaecd 3
pick 30ff21a 4
pick 5ce576e 5
# Rebase 828e3dd..6249a6b onto 828e3dd
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
先頭行を見ると pick 95eaecd 3
と表示されています。コミット番号の前にある pick
はコマンドで、このコマンドを変更することで対象のコミットを変更できます。
コマンドの説明は以下の通り
pick
コミットをそのまま使う。内容を変更しない。
reword
コミットメッセージを変更する。コミット内容は変更しない。
edit
コミットを修正する。
squash
ひとつ前のコミットにまとめる。コミットメッセージを書き直す。
fixup
ひとつ前のコミットにまとめる。コミットメッセージをそのまま使う。
exec
shell でコマンドを実行する
各コマンドの使い方は、この後順番に解説します。
reword コミットメッセージを変更する
コミットメッセージを変更したいときは reword
コマンドを使います。
たとえば 95eaecd 3
のコミットメッセージ 3
を 3A
に変更したい場合は、95eaecd
のコマンドを reword
に変更して保存終了します。
reword 95eaecd 3
pick 30ff21a 4
pick 5ce576e 5
保存終了すると再びエディタが立ち上がり、コミットメッセージの変更画面が表示されます。コメントを 3A
に変更して保存終了するとコメントが書きかえられます。
3A
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto adb04d8
# You are currently editing a commit while rebasing branch 'master' on 'adb04d8'.
#
# Changes to be committed:
# modified: README.md
#
ログを確認します。コメントが書きかえられていますね。
$ git log --oneline
67d8c2b 5
0305dca 4
e48f27c 3A
adb04d8 2
29102b1 1
edit コミットを修正する
あるコミットを編集したいときは edit
コマンドを使います。ちょっとした typo を修正したり、コミットを分割したりできます。
たとえば 95eaecd 3
のコミットを編集したい場合は edit
コマンドに変更します。
edit 95eaecd 3
pick 30ff21a 4
pick 5ce576e 5
エディタを保存終了すると、指定したコミットの直後の状態に戻れるので、コードを編集して git add
し git commit --amend
などでコミット内容を変更します。がっつり変更する場合は git reset HEAD^
するといいでしょう。
$ git reset HEAD^
$ git add .
$ git commit --amend
修正が終わったら git rebase --continue
で rebase を実行します。
$ git rebase --continue
途中で rebase をやめたくなったら、git rebase --abort
で rebase を中止してもとの状態に戻せます。
scuash コミットをまとめる
2つのコミットを1つにまとめたい場合は、squash
コマンドを使います。
たとえば 95eaecd 3
と 30ff21a 4
のコミットを1つにまとめたい場合は次のように入力します。
pick 95eaecd 3
squash 30ff21a 4
pick 5ce576e 5
エディタを保存終了すると再びエディタが立ち上がり、コミットメッセージの変更画面が表示されます。
# This is a combination of 2 commits.
# The first commit's message is:
3
# This is the 2nd commit message:
4
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto adb04d8
# You are currently editing a commit while rebasing branch 'master' on 'adb04d8'.
まとめた後のコミットメッセージを入力して保存終了します。
3 and 4
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto adb04d8
# You are currently editing a commit while rebasing branch 'master' on 'adb04d8'.
squash 後のコミットログはこんな感じ。コミットがまとまり、コメントも新しくなっていますね。
$ git log --oneline
24454fb 5
99f98c8 3 and 4
adb04d8 2
29102b1 1
fixup コミットをまとめる。コミットメッセージはそのまま
2つのコミットを1つにまとめ、コミットメッセージを変更しなくてよい場合は fixup
コマンドを使います。
たとえば 95eaecd 3
と 30ff21a 4
のコミットを1つにまとめたい場合は次のように入力します。
pick 95eaecd 3
fixup 30ff21a 4
pick 5ce576e 5
エディタを保存終了すると rebase が実行されます。実行後のログは次の通り。fixup のひとつ上のコミットメッセージ 3
が適用されています。
$ git log --oneline
43f5048 5
09d24cf 3
adb04d8 2
29102b1 1
コミットの順番を入れ替える
コミットの順番を入れ替えたいときは、pick コマンドを指定して順番を並び替えます。
たとえば 95eaecd 3
と 30ff21a 4
のコミットを並び替えたい場合は次のように並び替えます。
pick 30ff21a 4
pick 95eaecd 3
pick 5ce576e 5
エディタを保存終了してログを確認します。コミット 3
4
の順番が入れ替わります。
$ git log --oneline
f34ae4d 5
4f595f3 3
50dbee5 4
adb04d8 2
29102b1 1
コミットを削除する
コミットを削除したいときは、該当のコミットを削除して保存終了します。
たとえば 30ff21a 4
のコミットを削除したいときは pick 30ff21a 4
の行を削除します。
pick 95eaecd 3
-pick 30ff21a 4 ←行削除
pick 5ce576e 5
エディタを保存終了してログを確認します。「4」のコミットが削除されています。
$ git log --oneline
0c75026 5
95eaecd 3
adb04d8 2
29102b1 1
初回コミットの rebase -i をするには
初回コミットの rebase -i
をするには --root
オプションを指定します。
$ git rebase -i --root
pick 29102b1 1
pick adb04d8 2
pick 95eaecd 3
pick 30ff21a 4
pick 5ce576e 5
rebase 中に止まってしまったときは
rebase 中に conflict して途中で止まると以下のように表示されます。
$ git rebase -i HEAD~3
error: could not apply 30ff21a... 4
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply 30ff21af4a430a3f577a7db1941c8efcfe55e6f5... 4
以下のコマンドで rebase を先に進めたり中断したりできます。
git rebase --abort
rebase 中止。変更を元に戻す。
git rebase --continue
conflict を解決して、rebase を続行する。
上記のコマンドは edit コマンドで作業している時にも使えます。
rebase -i を取り消すには
ORIG_HEAD 編
rebase -i
で操作ミスして、意図せぬ rebase が走ってしまうことがあります。rebase 前のコミットは ORIG_HEAD
という名前で残っています。以下コマンドで rebase 前の状態に戻せます。
$ git reset --hard ORIG_HEAD
reflog 編
rebase 取り消しその2。reflog を使います。reflog は git のすべての操作ログです。rebase だけでなく何でも元に戻せます。
以下は、reword コミットメッセージを変更する で git rebase -i
を実行した直後の git reflog
です。
$ git reflog
67d8c2b HEAD@{0}: rebase -i (finish): returning to refs/heads/master
67d8c2b HEAD@{1}: rebase -i (pick): 5
0305dca HEAD@{2}: rebase -i (pick): 4
e48f27c HEAD@{3}: rebase -i (reword): 3 -> 3A
95eaecd HEAD@{4}: cherry-pick: fast-forward
adb04d8 HEAD@{5}: rebase -i (start): checkout adb04d8
5ce576e HEAD@{6}: rebase -i (finish): returning to refs/heads/master
5ce576e HEAD@{7}: rebase -i (pick): 5
HEAD@{0} が一番新しい git の操作ログで、数字が増えるごとに古くなります。
上から見ていくと、HEAD@{5}
から HEAD@{0}
までが rebase -i
の操作ログです。rebase 前の状態に戻すには、HEAD@{6}
まで戻せば OK です。
git reset
コマンドで rebase 前の状態に戻します。
$ git reset HEAD@{6}
ログを確認します。rebase 前の状態に戻りましたね。
$ git log --oneline
5ce576e 5
30ff21a 4
95eaecd 3
adb04d8 2
29102b1 1
再度 reflog を確認してみます。git reset
の操作ログも記録されています。
$ git reflog
5ce576e HEAD@{0}: reset: moving to HEAD@{6}
67d8c2b HEAD@{1}: rebase -i (finish): returning to refs/heads/master
67d8c2b HEAD@{2}: rebase -i (pick): 5
0305dca HEAD@{3}: rebase -i (pick): 4
e48f27c HEAD@{4}: rebase -i (reword): 3 -> 3A
95eaecd HEAD@{5}: cherry-pick: fast-forward
adb04d8 HEAD@{6}: rebase -i (start): checkout adb04d8
5ce576e HEAD@{7}: rebase -i (finish): returning to refs/heads/master
リモートの共有リポジトリに push したコミットを rebase してはいけない
リモートの共有リポジトリをみんなで使っている場合、rebase するとコミット番号が変わり conflict を引き起こします。push 後の変更は revert を使用するのが正しい git の使い方です。
とはいえ、コミットを取り消したくなることはありますので(パスワード付きのコミットを github に push してしまったり)、push 後に rebase したい時は事前に開発チームに相談するようにしましょう。