あのコミットをなかった事に。git rebase -i の使い方

2014年6月21日

git-rebase-i-usage

以前、Gitの使い方、よく使うGitコマンド という記事を書きましたが、git rebase -i の項目に書き足したいことが増えてきたので別エントリに切り出し、内容を見直しました。

git rebase -i を使うと、最新のコミットから指定したコミットまでの歴史を対話式に改変することができます。具体的には以下のことができます。

  • コミットメッセージを変更する
  • コミット内容を修正する
  • コミットを分割する
  • コミットをまとめる
  • コミットを削除する

私個人の利用シーンとしては、開発ブランチを master にマージする前(プルリクを送る前)にコミットの整理に使うことが多いです。一発できれいなコミット履歴を作るのは難しいので、散らかったコミットを後から整理するのによく使います。Git って便利だなあと思う瞬間です。

目次

  1. rebase -i の使い方
  2. reword コミットメッセージを変更する
  3. edit コミットを修正する
  4. scuash コミットをまとめる
  5. fixup コミットをまとめる。コミットメッセージはそのまま
  6. コミットの順番を入れ替える
  7. コミットを削除する
  8. 初回コミットの rebase -i をするには
  9. rebase 中に止まってしまったときは
  10. rebase -i を取り消すには
  11. リモートの共有リポジトリに 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 のコミットメッセージ 33A に変更したい場合は、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 addgit 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 330ff21a 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 330ff21a 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 330ff21a 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 したい時は事前に開発チームに相談するようにしましょう。

-技術ブログ
-