Git のコミット履歴を大胆に書き換えるなら git rebase -i がオススメ


Git の超強力なコマンドと言っていい, git rebase -i を使うと, コミット履歴をこれでもかと言うくらい自由自在に書き換える事ができてしまいます.

Git のバージョンは 2.18.0 を使用しています.

コマンドを単体で使ってみる

git rebase -ipick, reword, edit, squash, fixup, exec, drop という基本的な 7 つのコマンドがありますので, それぞれを単体で使ってみようと思います.

実演用のコミット履歴 A を作る

そのためにまず, 次のような実演用のコミット履歴 A を作りたいと思います:

  A --- B --- C --- D <- master <- HEAD

A, B, C, D を次の手順で作ります:

# Commit 'A'
echo 'Alfa' > example.txt
git add example.txt
git commit -m 'A'

# Commit 'B'
echo 'Bravo' >> example.txt
git commit -am 'B'

# Commit 'C'
echo 'Charlie' >> example.txt
git commit -am 'C'

# Commit 'D'
echo 'Delta' >> example.txt
git commit -am 'D'

そして git log -p で表示される内容は次のようになります:

commit 604f0fcb23314c966fed841843c0ebb69a016e7e (HEAD -> master)
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:54:01 2018 +0900

    D

diff --git a/example.txt b/example.txt
index 04e2eea..0cd0e4f 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,4 @@
 Alfa
 Bravo
 Charlie
+Delta

commit 7bd8be6bfc16ab1611aeae5afb9963f53110127f
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:48 2018 +0900

    C

diff --git a/example.txt b/example.txt
index 90f9b64..04e2eea 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,3 @@
 Alfa
 Bravo
+Charlie

commit dca5a5aefdd92b90c4347b8e0813f6620a941133
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:43 2018 +0900

    B

diff --git a/example.txt b/example.txt
index 8a5239e..90f9b64 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 Alfa
+Bravo

commit d874f81af3706d9a0980bbb59d99fa15be36f6cf
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:35 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

git rebase -i HEAD~3 を実行する

git rebase -i の基本的な使い方として git rebase -i <after-this-commit> という構文があり, <after-this-commit> に指定されたコミットから <after-this-commit> は含まない HEAD までのコミットをリベースするというものがあります.

その基本的な構文を使って, 今作ったコミット履歴 A を, それぞれのコマンド毎にリベースしてみようと思います.

なので B, C, D のコミットをリベースしてみようと思いますので, 次のコマンドを入力します:

git rebase -i HEAD~3

ちなみに, どうして最初のコミット A を含める事ができないのかというのは, リベースとはその言葉通り, ベースになるコミットを変えるという意味になります.

なので, この場合 A, B, C, D というコミット履歴で少なくとも A をベースにする必要があるので, A をリベースの対象に含める事ができないというわけです.

HEAD~3 とすることによって HEAD の三番目の親 A を指定することになるので, A を含まない HEAD までのコミット, B, C, D がリベースの対象となります.

すると次のような .git/rebase-merge/git-rebase-todo というファイルが開かれます:

pick dca5a5a B
pick 7bd8be6 C
pick 604f0fc D

# Rebase d874f81..604f0fc onto d874f81 (3 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 <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
#   However, if you remove everything, the rebase will be aborted.
#
#   
# Note that empty commits are commented out

このようなファイルは Git の core.editor の値に設定されたエディタで開かれます.

僕はその値を nvim と設定しているので, Neovim によって開かれます.

その開かれたファイルを見ると, 何やら色々書かれていますが, コメントアウトされていない最初の三行が, コミットメッセージを書くときのように実質的な意味を持つ行になります.

その三行は単なるメッセージではなく, どのように B, C, D をリベースさせるかという言わばプログラミング言語のシンプルなコードのようなものになります.

一行目, 二行目, 三行目の pick がプログラミングで例えるなら pick という関数の呼び出しで dca5a5a, 7bd8be6, 604f0fc というショート SHA1 がそれぞれの関数の第一引数と捉えることもできるかと思います.

コメントアウトされている Commands: の下の内容を見てもらうと, pick, reword, edit, squash, fixup, exec, drop, etc とあります.

つまりはそれらのコマンドを使って B, C, D をあれやこれやとリベースすることができます.

ではその実演用のコミット履歴 A を使って, pick, reword, edit, squash, fixup, exec, drop という 7 つのコマンドを単体でそれぞれ使ってみようと思います.

どのコマンドを使うとどのようなリベースになるのか, どのようにコミット履歴を書き換える事ができるのかという参考にしていただければと思います.

pick を使う場合

pick (拾う) を使うと, 指定のコミットを使うという意味になります.

なので次のように何もいじらないで B, C, Dpick としたままにすると, ベースである AB, C, D の順序でコミットを適用させていきます.

pick dca5a5a B
pick 7bd8be6 C
pick 604f0fc D

つまりは, リベース後もリベース前と変わらずそのままのコミット履歴となります.

僕は Neovim を使っているので, :wq コマンドで保存 & 終了させると, その形だけのリベースが完了します:

Successfully rebased and updated refs/heads/master.

あくまで pick は, このコミットは使います, 残しますという意味になります.

なので git log -p で履歴を見てみると, 何も変更されていないことを確認できます:

commit 604f0fcb23314c966fed841843c0ebb69a016e7e (HEAD -> master)
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:54:01 2018 +0900

    D

diff --git a/example.txt b/example.txt
index 04e2eea..0cd0e4f 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,4 @@
 Alfa
 Bravo
 Charlie
+Delta

commit 7bd8be6bfc16ab1611aeae5afb9963f53110127f
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:48 2018 +0900

    C

diff --git a/example.txt b/example.txt
index 90f9b64..04e2eea 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,3 @@
 Alfa
 Bravo
+Charlie

commit dca5a5aefdd92b90c4347b8e0813f6620a941133
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:43 2018 +0900

    B

diff --git a/example.txt b/example.txt
index 8a5239e..90f9b64 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 Alfa
+Bravo

commit d874f81af3706d9a0980bbb59d99fa15be36f6cf
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:35 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

reword を使う場合

reword (言い換える) を使うと, 指定のコミットのコミットメッセージを書き換えることができます.

B のコミットメッセージを書き換える場合, 次のようにします:

reword dca5a5a B
pick 7bd8be6 C
pick 604f0fc D

そして :wq すると, B のコミットメッセージの編集画面が表示され:

B

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 03:53:43 2018 +0900
#
# interactive rebase in progress; onto d874f81
# Last command done (1 command done):
#    reword dca5a5a B
# Next commands to do (2 remaining commands):
#    pick 7bd8be6 C
#    pick 604f0fc D
# You are currently editing a commit while rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

このように好きなコミットメッセージに書き換え:

B

This commit appends the word 'Bravo' to example.txt.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 03:53:43 2018 +0900
#
# interactive rebase in progress; onto d874f81
# Last command done (1 command done):
#    reword dca5a5a B
# Next commands to do (2 remaining commands):
#    pick 7bd8be6 C
#    pick 604f0fc D
# You are currently editing a commit while rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

:wq して, B のコミットメッセージの書き換えが完了します:

[detached HEAD 84ea262] B
 Date: Sat Aug 25 03:53:43 2018 +0900
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

git show HEAD~2B を見てみると, B のコミットメッセージが書き換わっているのを確認できます:

commit 84ea262ea4f722759419b336f2810cdc129068a6
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:43 2018 +0900

    B

    This commit appends the word 'Bravo' to example.txt.

diff --git a/example.txt b/example.txt
index 8a5239e..90f9b64 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 Alfa
+Bravo

edit を使う場合

edit (編集する) を使うと, 指定したコミットでリベース操作が一時止まり, そのコミットをアメンドしたり, 新たなコミットを作ったりする機会が与えられます.

B のコミットをアメンドする場合, 次のようになります:

edit dca5a5a B
pick 7bd8be6 C
pick 604f0fc D

そして :wq すると, B で止まるので:

Stopped at dca5a5a...  B
You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

example.txt を次のように編集し:

Alfa
Bravo edited by Git's rebase command

git add example.txt でステージし, git commit --amendB のコミットをアメンドします:

B

This commit has been amended.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 03:53:43 2018 +0900
#
# interactive rebase in progress; onto d874f81
# Last command done (1 command done):
#    edit dca5a5a B
# Next commands to do (2 remaining commands):
#    pick 7bd8be6 C
#    pick 604f0fc D
# You are currently editing a commit while rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

このように B のコミットメッセージもついでに編集し, :wq して:

[detached HEAD 19e35b4] B
 Date: Sat Aug 25 03:53:43 2018 +0900
 1 file changed, 1 insertion(+)

git commit --amend が完了したので, git rebase --continue でリベースを続けると, C の適用がマージコンフリクトのため失敗するので:

Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
error: could not apply 7bd8be6... C

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

Could not apply 7bd8be6... C

Cexample.txt のマージコンフリクトを:

Alfa
<<<<<<< HEAD
Bravo edited by Git's rebase command
||||||| parent of 7bd8be6... C
Bravo
=======
Bravo
Charlie
>>>>>>> 7bd8be6... C

このように解決し:

Alfa
Bravo edited by Git's rebase command
Charlie

:wq して, git add example.txt でステージし, git rebase --continue でリベースを続けると:

C

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto d874f81
# Last commands done (2 commands done):
#    edit dca5a5a B
#    pick 7bd8be6 C
# Next command to do (1 remaining command):
#    pick 604f0fc D
# You are currently rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

C のマージコンフリクトが解決したので, C のコミットメッセージを書き換える機会が与えられますが, 特に変更せずそのまま :wq してリベースが完了します:

[detached HEAD 25d3b1f] C
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

git log -pA から D までのコミットを見てみると:

commit 417b2fed6dbd4a1bdbd656de0bd6757761ec37cd (HEAD -> master)
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:54:01 2018 +0900

    D

diff --git a/example.txt b/example.txt
index 52005b4..5700538 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,4 @@
 Alfa
 Bravo edited by Git's rebase command
 Charlie
+Delta

commit 25d3b1fda15bba2e8690957ff3608344c691f7e3
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:48 2018 +0900

    C

diff --git a/example.txt b/example.txt
index 8a851c6..52005b4 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,3 @@
 Alfa
 Bravo edited by Git's rebase command
+Charlie

commit 19e35b47a3d649302c8ec1eac6064143c23c45e0
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:43 2018 +0900

    B

    This commit has been amended.

diff --git a/example.txt b/example.txt
index 8a5239e..8a851c6 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 Alfa
+Bravo edited by Git's rebase command

commit d874f81af3706d9a0980bbb59d99fa15be36f6cf
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:35 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

Bexample.txtBravo から Bravo edited by Git's rebase command に書き換えられた一行が, そのあとの C, Dexample.txt にも反映されているのを確認できます.

また B のコミットメッセージも書き換えられているのを確認できます.

squash を使う場合

squash (押しつぶす) を使うと, 指定のコミットをその親コミットに取り込んで, そのコミットの存在をなかったことにできます. また取り込まれるコミットのコミットメッセージの一行目もその親コミットのコミットメッセージに取り込まれます.

なので squash を使う場合, 取り込む先となる親コミットが必要となるので (リベースのベースとなるコミットは除く) pick, reword, edit といったコミットが存在する前提のコマンドより後に使われる必要があります.

C を親の B に取り込んで, C を削除する場合, 次のようになります:

pick dca5a5a B
squash 7bd8be6 C
pick 604f0fc D

:wq すると, B のコミットメッセージを確定させる画面が表示されます:

# This is a combination of 2 commits.
# This is the 1st commit message:

B

# This is the commit message #1:

C

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 03:53:43 2018 +0900
#
# interactive rebase in progress; onto d874f81
# Last commands done (2 commands done):
#    pick dca5a5a B
#    squash 7bd8be6 C
# Next command to do (1 remaining command):
#    pick 604f0fc D
# You are currently rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

このように C のコミットメッセージが取り込まれているのを確認できます.

:wq で確定させ:

[detached HEAD 5e865fb] B
 Date: Sat Aug 25 03:53:43 2018 +0900
 1 file changed, 2 insertions(+)
Successfully rebased and updated refs/heads/master.

git log -p でコミット履歴を見てみると, コミットメッセージに加えて Cexample.txt の内容が Bexample.txt の内容に取り込まれ, C が削除されているのを確認できます:

commit 7d781b3739496289dbf33ad6bd5877ea28413ba7 (HEAD -> master)
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:54:01 2018 +0900

    D

diff --git a/example.txt b/example.txt
index 04e2eea..0cd0e4f 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,4 @@
 Alfa
 Bravo
 Charlie
+Delta

commit 5e865fb1a09b4e323b68d9b14333672fd4529a13
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:43 2018 +0900

    B

    C

diff --git a/example.txt b/example.txt
index 8a5239e..04e2eea 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,3 @@
 Alfa
+Bravo
+Charlie

commit d874f81af3706d9a0980bbb59d99fa15be36f6cf
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:35 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

fixup を使う場合

fixup (直し切る) を使うと, squash と同じ使われ方をします. ただ squash と違うのは, 取り込みれるコミットのコミットメッセージの一行目は, その親コミットのコミットメッセージに取り込まれません.

C を親の B に取り込んで, C を削除する場合, 次のようになります:

pick dca5a5a B
fixup 7bd8be6 C
pick 604f0fc D

:wq すると, そのままリベースが完了します:

Successfully rebased and updated refs/heads/master.

C のコミットメッセージを B のコミットメッセージに取り込まないので, squash と違い B のコミットメッセージの編集画面が表示されません.

git log -p で確認してみると, Cexample.txt の内容が, Bexample.txt の内容に取り込まれて, C が削除されているのを確認できます:

commit 487fa2b76145f282d4df5ea412fa71a776324865 (HEAD -> master)
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:54:01 2018 +0900

    D

diff --git a/example.txt b/example.txt
index 04e2eea..0cd0e4f 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,4 @@
 Alfa
 Bravo
 Charlie
+Delta

commit 7c9cb17cdd2713cffc1c91637c015f8b29642cc0
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:43 2018 +0900

    B

diff --git a/example.txt b/example.txt
index 8a5239e..04e2eea 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,3 @@
 Alfa
+Bravo
+Charlie

commit d874f81af3706d9a0980bbb59d99fa15be36f6cf
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:35 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

exec を使う場合

exec (実行する) を使うと, exec の上に書かれたコマンドが完了次第, exec に書かれたコマンドが実行されます.

例えば Bexample.txt の内容が "Alfa\nBravo\n" という文字列と一致するかどうか Ruby でテストする場合, 次のようになります:

pick dca5a5a B
exec ruby -e '$<.read == "Alfa\nBravo\n" ? exit(0) : exit(1)' < example.txt
pick 7bd8be6 C
pick 604f0fc D

そして :wq すると:

Executing: ruby -e '$<.read == "Alfa\nBravo\n" ? exit(0) : exit(1)' < example.txt
Successfully rebased and updated refs/heads/master.

となり, example.txt の内容が "Alfa\nBravo\n" という文字列と一致したため, テストが通り, リベースが完了しました.

もし Bpick から edit にして

edit dca5a5a B
exec ruby -e '$<.read == "Alfa\nBravo\n" ? exit(0) : exit(1)' < example.txt
pick 7bd8be6 C
pick 604f0fc D

:wq して, example.txt を次のように編集して:

Alfa
BRAVO

git add example.txt して git commit --amend して:

B

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 03:53:43 2018 +0900
#
# interactive rebase in progress; onto d874f81
# Last command done (1 command done):
#    edit dca5a5a B
# Next commands to do (3 remaining commands):
#    exec ruby -e '$<.read == "Alfa\nBravo\n" ? exit(0) : exit(1)' < example.txt
#    pick 7bd8be6 C
# You are currently editing a commit while rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

:wq して B のコミットをアメンドした上で:

[detached HEAD 8b3c9fc] B
 Date: Sat Aug 25 03:53:43 2018 +0900
 1 file changed, 1 insertion(+)

git rebase --continue すると:

Executing: ruby -e '$<.read == "Alfa\nBravo\n" ? exit(0) : exit(1)' < example.txt
warning: execution failed: ruby -e '$<.read == "Alfa\nBravo\n" ? exit(0) : exit(1)' < example.txt
You can fix the problem, and then run

  git rebase --continue

となり, example.txt の内容が, 先ほどの Ruby のテストを通らないため, リベースが失敗します.

このように git rebase -iexec コマンドは特定のコミットに対してテストを走らせるという用途で使えたりします.

drop を使う場合

drop (使わない) を使うと, 指定したコミットを削除できます.

B を削除する場合, 次のようになります:

drop dca5a5a B
pick 7bd8be6 C
pick 604f0fc D

もしくは B の行を削除しても B を削除することになります:

pick 7bd8be6 C
pick 604f0fc D

:wq すると, Cexample.txt にマージコンフリクトが発生するので:

Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
error: could not apply 7bd8be6... C

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

Could not apply 7bd8be6... C

この example.txt のマージコンフリクトを:

Alfa
<<<<<<< HEAD
||||||| parent of 7bd8be6... C
Bravo
=======
Bravo
Charlie
>>>>>>> 7bd8be6... C

このように解決して:

Alfa
Charlie

git add example.txt して, git rebase --continue すると:

C

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto d874f81
# Last commands done (2 commands done):
#    drop dca5a5a B
#    pick 7bd8be6 C
# Next command to do (1 remaining command):
#    pick 604f0fc D
# You are currently rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

C のコミットメッセージ編集画面が表示されるので, そのまま :wq して:

[detached HEAD a5205dc] C
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

リベースが完了します.

git log -p で見てみると, BB の内容が削除されていることを確認できます:

commit 422d8cdde60419ba2d4d30e5cc0087704cf76489 (HEAD -> master)
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:54:01 2018 +0900

    D

diff --git a/example.txt b/example.txt
index 7e9063b..279e3cf 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,3 @@
 Alfa
 Charlie
+Delta

commit a5205dcdaedfc4424db41b8aa66ca1695bf6a780
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:48 2018 +0900

    C

diff --git a/example.txt b/example.txt
index 8a5239e..7e9063b 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 Alfa
+Charlie

commit d874f81af3706d9a0980bbb59d99fa15be36f6cf
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:35 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

コマンドを組み合わせて使ってみる

pick, reword, edit, squash, fixup, exec, drop というそれぞれのコマンドを実際に使ってみましたが, 今度は単体ではなく, それぞれを組み合わせて使ってみるとどうなるのか, ということを見てみようと思います.

実演用のコミット履歴 B を作る

7 つのコマンドを組み合わせて使うため, 追加で次のような実演用のコミット履歴 B を作りたいと思います:

  A --- B --- C --- D --- E --- F --- G<- master <- HEAD

作る手順は, 実演用のコミット履歴 A の時と同じ要領で次のようになります:

# Commit 'A'
echo 'Alfa' > example.txt
git add example.txt
git commit -m 'A'

# Commit 'B'
echo 'Bravo' >> example.txt
git commit -am 'B'

# Commit 'C'
echo 'Charlie' >> example.txt
git commit -am 'C'

# Commit 'D'
echo 'Delta' >> example.txt
git commit -am 'D'

# Commit 'E'
echo 'Echo' >> example.txt
git commit -am 'E'

# Commit 'F'
echo 'Foxtrot' >> example.txt
git commit -am 'F'

# Commit 'G'
echo 'Golf' >> example.txt
git commit -am 'G'

git log -p で表示されるコミット履歴は次のようになります:

commit 6c537a96a3f51b9b3f0b85fef89225c740ca5744
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:33:46 2018 +0900

    G

diff --git a/example.txt b/example.txt
index c88de65..148633b 100644
--- a/example.txt
+++ b/example.txt
@@ -4,3 +4,4 @@ Charlie
 Delta
 Echo
 Foxtrot
+Golf

commit a30e0eb85af6b2074c69b276a3b99b3809ddcc69
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:32:03 2018 +0900

    F

diff --git a/example.txt b/example.txt
index f07efbf..c88de65 100644
--- a/example.txt
+++ b/example.txt
@@ -3,3 +3,4 @@ Bravo
 Charlie
 Delta
 Echo
+Foxtrot

commit 52c8264c022f6a358896727971c59b72178c46b8
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:31:54 2018 +0900

    E

diff --git a/example.txt b/example.txt
index 0cd0e4f..f07efbf 100644
--- a/example.txt
+++ b/example.txt
@@ -2,3 +2,4 @@ Alfa
 Bravo
 Charlie
 Delta
+Echo

commit 0f3c05f81b46298367647fae5de2374204f660fc
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:30:57 2018 +0900

    D

diff --git a/example.txt b/example.txt
index 04e2eea..0cd0e4f 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,4 @@
 Alfa
 Bravo
 Charlie
+Delta

commit 7c219962a34d94b1c0b8335e7bd92fb9ce44ab91
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:30:43 2018 +0900

    C

diff --git a/example.txt b/example.txt
index 90f9b64..04e2eea 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,3 @@
 Alfa
 Bravo
+Charlie

commit 05cd616f2d14fcc62e632d11d82d4b4a109a53d1
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:30:36 2018 +0900

    B

diff --git a/example.txt b/example.txt
index 8a5239e..90f9b64 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 Alfa
+Bravo

commit 17cbaede18306a91f9bea2715576ea2ea45953ce
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:30:29 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

このコミット履歴でそれぞれ 7 つのコマンドを組み合わせて使ってみたいと思います.

git rebase -i HEAD~6 を実行する

早速, git rebase -i HEAD~6 で, それら 7 つのコマンドを使ってみたいと思いますが, 流れとしては Bpick し, Creword でコミットメッセージを編集し, Dedit し, D で止めて D をアメンドし, Esquash して D に取り込み, FfixupD に取り込み, exec で Ruby のテストを Dexample.txt に対して実行し, Gdrop するという感じになります.

詳しい流れを見てみたいと思います.

まず git rebase -i HEAD~6 で, 今作ったコミット履歴 B の B, C, D, E, F, G のコミットを A にリベースするようにします:

pick 05cd616 B
pick 7c21996 C
pick 0f3c05f D
pick 52c8264 E
pick a30e0eb F
pick 6c537a9 G

# Rebase 17cbaed..6c537a9 onto 17cbaed (6 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 <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
#   However, if you remove everything, the rebase will be aborted.
#
#   
# Note that empty commits are commented out

そして, このように変更して, pick, reword, edit, squash, fixup, exec, drop 7 つのコマンドを順々に使うようにします:

pick 05cd616 B
reword 7c21996 C
edit 0f3c05f D
squash 52c8264 E
fixup a30e0eb F
exec ruby -e "$<.read == \"Alfa\nBravo\nCharlie\nDelta edited by Git's rebase command\nEcho\nFoxtrot\n\" ? exit(0) : exit(1)" < example.txt
drop 6c537a9 G

:wq すると, まず rewordC のコミットメッセージの編集画面が表示されるので:

C

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 05:30:43 2018 +0900
#
# interactive rebase in progress; onto 17cbaed
# Last commands done (2 commands done):
#    pick 05cd616 B
#    reword 7c21996 C
# Next commands to do (5 remaining commands):
#    edit 0f3c05f D
#    squash 52c8264 E
# You are currently editing a commit while rebasing branch 'master' on '17cbaed'.
#
# Changes to be committed:
#   modified:   example.txt
#

次のようにメッセージを書き加えて:

C

This commit appends the word 'Charlie' to example.txt.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 05:30:43 2018 +0900
#
# interactive rebase in progress; onto 17cbaed
# Last commands done (2 commands done):
#    pick 05cd616 B
#    reword 7c21996 C
# Next commands to do (5 remaining commands):
#    edit 0f3c05f D
#    squash 52c8264 E
# You are currently editing a commit while rebasing branch 'master' on '17cbaed'.
#
# Changes to be committed:
#   modified:   example.txt
#

:wq すると, 今度は editD で止まるので:

[detached HEAD a978895] C
 Date: Sat Aug 25 05:30:43 2018 +0900
 1 file changed, 1 insertion(+)
Stopped at 0f3c05f...  D
You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

example.txt をテキストエディタで開き, 次のように編集し:

Alfa
Bravo
Charlie
Delta edited by Git's rebase command

git add example.txt して, git commit --amend して:

D

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 05:30:57 2018 +0900
#
# interactive rebase in progress; onto 17cbaed
# Last commands done (3 commands done):
#    reword 7c21996 C
#    edit 0f3c05f D
# Next commands to do (4 remaining commands):
#    squash 52c8264 E
#    fixup a30e0eb F
# You are currently editing a commit while rebasing branch 'master' on '17cbaed'.
#
# Changes to be committed:
#   modified:   example.txt
#

このようにコミットメッセージも書き加えて, :wq して:

D

This commit has been amended.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 05:30:57 2018 +0900
#
# interactive rebase in progress; onto 17cbaed
# Last commands done (3 commands done):
#    reword 7c21996 C
#    edit 0f3c05f D
# Next commands to do (4 remaining commands):
#    squash 52c8264 E
#    fixup a30e0eb F
# You are currently editing a commit while rebasing branch 'master' on '17cbaed'.
#
# Changes to be committed:
#   modified:   example.txt
#

D の編集が完了するので:

[detached HEAD 191e5ae] D
 Date: Sat Aug 25 05:30:57 2018 +0900
 1 file changed, 1 insertion(+)

git rebase --continue すると:

Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
error: could not apply 52c8264... E

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

Could not apply 52c8264... E

Esquash によって, example.txt にマージコンフリクトが発生するので:

Alfa
Bravo
Charlie
<<<<<<< HEAD
Delta edited by Git's rebase command
||||||| parent of 52c8264... E
Delta
=======
Delta
Echo
>>>>>>> 52c8264... E

このように解決し:

Alfa
Bravo
Charlie
Delta edited by Git's rebase command
Echo

git add example.txt して git clone --continue すると:

# This is a combination of 2 commits.
# This is the 1st commit message:

D

This commit has been amended.

# This is the commit message #1:

E

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 05:30:57 2018 +0900
#
# interactive rebase in progress; onto 17cbaed
# Last commands done (4 commands done):
#    edit 0f3c05f D
#    squash 52c8264 E
# Next commands to do (3 remaining commands):
#    fixup a30e0eb F
#    exec ruby -e "$<.read == \"Alfa\nBravo\nCharlie\nDelta edited by Git's rebase command\nEcho\nFoxtrot\n\" ? exit(0) : exit(1)" < example.txt
# You are currently rebasing branch 'master' on '17cbaed'.
#
# Changes to be committed:
#   modified:   example.txt
#

squash された E のコミットメッセージが取り込まれた D のコミットメッセージの編集画面が表示されるので, そのまま :wq すると:

[detached HEAD 98f1d05] D
 Date: Sat Aug 25 05:30:57 2018 +0900
 1 file changed, 2 insertions(+)
Executing: ruby -e "$<.read == \"Alfa\nBravo\nCharlie\nDelta edited by Git's rebase command\nEcho\nFoxtrot\n\" ? exit(0) : exit(1)" < example.txt
Successfully rebased and updated refs/heads/master.

fixupFD に取り込まれ, exec の Ruby のテストが通り, dropG が削除され, リベースが完了します.

git log -p で見てみると, 今までの一連のリベースを確認できます:

commit 93aa6b067c54c6c71b7f9963c29461e0acd600ae
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:30:57 2018 +0900

    D

    This commit has been amended.

    E

diff --git a/example.txt b/example.txt
index 04e2eea..62a7882 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,6 @@
 Alfa
 Bravo
 Charlie
+Delta edited by Git's rebase command
+Echo
+Foxtrot

commit a97889590d932955e47e8bf70b8b788057e933d0
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:30:43 2018 +0900

    C

    This commit appends the word 'Charlie' to example.txt.

diff --git a/example.txt b/example.txt
index 90f9b64..04e2eea 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,3 @@
 Alfa
 Bravo
+Charlie

commit 05cd616f2d14fcc62e632d11d82d4b4a109a53d1
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:30:36 2018 +0900

    B

diff --git a/example.txt b/example.txt
index 8a5239e..90f9b64 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 Alfa
+Bravo

commit 17cbaede18306a91f9bea2715576ea2ea45953ce
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 05:30:29 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

順序を変えてみる

今度はコミットの順序を変えてリベースしてみようと思います.

git rebase -i はそんなこともできるんです.

ということで, 最初の A, B, C, D のコミット履歴 A で試してみようと思います.

A, B, C, D というコミット履歴を B, C の順序を入れ替えて A, C, B, D としてみたいと思います.

流れとしては, git rebase -i HEAD~3 して, BC の行を入れ替えて, AC を, CB を, BD を適用する時に発生するそれぞれのマージコンフリクトを解決して, リベースが完了するという感じです.

その流れを詳しく見てみたいと思います.

git rebase -i HEAD~3 して:

pick dca5a5a B
pick 7bd8be6 C
pick 604f0fc D

# Rebase d874f81..604f0fc onto d874f81 (3 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 <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
#   However, if you remove everything, the rebase will be aborted.
#
#   
# Note that empty commits are commented out

BC の行を入れ替えて:

pick 7bd8be6 C
pick dca5a5a B
pick 604f0fc D

:wq すると:

Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
error: could not apply 7bd8be6... C

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

Could not apply 7bd8be6... C

AC を適用する時に example.txt にマージコンフリクトが発生するので:

Alfa
<<<<<<< HEAD
||||||| parent of 7bd8be6... C
Bravo
=======
Bravo
Charlie
>>>>>>> 7bd8be6... C

次のように解決し:

Alfa
Charlie

:wq し, git add example.txt し, git rebase --continue すると, C のコミットメッセージの編集画面が表示されるので:

C

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto d874f81
# Last command done (1 command done):
#    pick 7bd8be6 C
# Next commands to do (2 remaining commands):
#    pick dca5a5a B
#    pick 604f0fc D
# You are currently rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

:wq すると:

[detached HEAD b107ed5] C
 1 file changed, 1 insertion(+)
Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
error: could not apply dca5a5a... B

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

Could not apply dca5a5a... B

CB を適用させる時に example.txt にマージコンフリクトが発生するので:

Alfa
<<<<<<< HEAD
Charlie
||||||| parent of dca5a5a... B
=======
Bravo
>>>>>>> dca5a5a... B

次のように解決し:

Alfa
Charlie
Bravo

:wq し, git add example.txt し, git rebase --continue すると, B のコミットメッセージの編集画面が表示されるので:

B

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto d874f81
# Last commands done (2 commands done):
#    pick 7bd8be6 C
#    pick dca5a5a B
# Next command to do (1 remaining command):
#    pick 604f0fc D
# You are currently rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

:wq すると:

[detached HEAD 02670d8] B
 1 file changed, 1 insertion(+)
Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
error: could not apply 604f0fc... D

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

Could not apply 604f0fc... D

BD を適用する時に example.txt にマージコンフリクトが発生するので:

Alfa
Charlie
<<<<<<< HEAD
Bravo
||||||| parent of 604f0fc... D
=======
Delta
>>>>>>> 604f0fc... D

次のように解決し:

Alfa
Charlie
Bravo
Delta

:wq し, git add example.txt し, git rebase --continue すると, D のコミットメッセージの編集画面が表示されるので:

D

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto d874f81
# Last commands done (3 commands done):
#    pick dca5a5a B
#    pick 604f0fc D
# No commands remaining.
# You are currently rebasing branch 'master' on 'd874f81'.
#
# Changes to be committed:
#   modified:   example.txt
#

:wq すると:

[detached HEAD ae06793] D
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

リベースが完了します.

git log -p で見てみると, BC が入れ替わっているのを確認できます:

commit ae06793dfe4b087e812b020cc6eaeffc13ee423c
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:54:01 2018 +0900

    D

diff --git a/example.txt b/example.txt
index c1d9c97..af7dd98 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,4 @@
 Alfa
 Charlie
 Bravo
+Delta

commit 02670d825fc9e25ba6cf89f735e5a4a05ce1cf83
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:43 2018 +0900

    B

diff --git a/example.txt b/example.txt
index 7e9063b..c1d9c97 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,3 @@
 Alfa
 Charlie
+Bravo

commit b107ed558d34f4f40f66615756ed94303c4312f9
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:48 2018 +0900

    C

diff --git a/example.txt b/example.txt
index 8a5239e..7e9063b 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 Alfa
+Charlie

commit d874f81af3706d9a0980bbb59d99fa15be36f6cf
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:35 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

コミットを分割してみる

git rebase -i を使って, 1 つのコミットを 2 つに分割することもできちゃいます.

先ほどの A, B, C, D のコミット履歴 A で, C のコミットを C1C2 に分割してみたいと思います.

流れとしては git rebase -i HEAD~2C のコマンドを pick から edit に変更して, C で止めて, example.txt を編集して, git add example.txt し, git commit --amend して CC1 に修正し, example.txt を編集して, git add example.txt し, git commit して C2 を新たに作り, git rebase --continueCC1C2 に分割するリベースが完了するという感じです.

その流れを詳しく見ていきたいと思います.

まず git rebase -i HEAD~2 で, リベース対象に C を含ませ:

pick 7bd8be6 C
pick 604f0fc D

# Rebase dca5a5a..604f0fc onto dca5a5a (2 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 <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
#   However, if you remove everything, the rebase will be aborted.
#
#   
# Note that empty commits are commented out

C のコマンドを pick から edit に変更し:

edit 7bd8be6 C
pick 604f0fc D

:wq して:

Stopped at 7bd8be6...  C
You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

C で止まるので, example.txt の内容を次のように編集し:

Alfa
Bravo
Char

git add example.txt し, git commit --amend して:

C1

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sat Aug 25 03:53:48 2018 +0900
#
# interactive rebase in progress; onto dca5a5a
# Last command done (1 command done):
#    edit 7bd8be6 C
# Next command to do (1 remaining command):
#    pick 604f0fc D
# You are currently editing a commit while rebasing branch 'master' on 'dca5a5a'.
#
# Changes to be committed:
#   modified:   example.txt
#

と編集し, :wq して:

[detached HEAD 0ff0d50] C1
 Date: Sat Aug 25 03:53:48 2018 +0900
 1 file changed, 1 insertion(+)

今度は example.txt を次のように編集し:

Alfa
Bravo
Charlie

git add example.txt し, git commit して:

C2

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto dca5a5a
# Last command done (1 command done):
#    edit 7bd8be6 C
# Next command to do (1 remaining command):
#    pick 604f0fc D
# You are currently editing a commit while rebasing branch 'master' on 'dca5a5a'.
#
# Changes to be committed:
#   modified:   example.txt
#

このようなコミットメッセージで :wq で確定させ:

[detached HEAD b202c5f] C2
 1 file changed, 1 insertion(+), 1 deletion(-)

git rebase --continue すると:

Successfully rebased and updated refs/heads/master.

リベースが完了します.

git log -p で見てみると, CC1C2 に分割されているのを確認できます:

commit 5189b114058c0f5ffd8b0af59b9be53fd7fca25d
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:54:01 2018 +0900

    D

diff --git a/example.txt b/example.txt
index 04e2eea..0cd0e4f 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,4 @@
 Alfa
 Bravo
 Charlie
+Delta

commit b202c5fd014832912caad81c590181d44e37e640
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 20:50:02 2018 +0900

    C2

diff --git a/example.txt b/example.txt
index 4b3fd7e..04e2eea 100644
--- a/example.txt
+++ b/example.txt
@@ -1,3 +1,3 @@
 Alfa
 Bravo
-Char
+Charlie

commit 0ff0d508106045de31bce12fc07b83c61b82b8ac
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:48 2018 +0900

    C1

diff --git a/example.txt b/example.txt
index 90f9b64..4b3fd7e 100644
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,3 @@
 Alfa
 Bravo
+Char

commit dca5a5aefdd92b90c4347b8e0813f6620a941133
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:43 2018 +0900

    B

diff --git a/example.txt b/example.txt
index 8a5239e..90f9b64 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1,2 @@
 Alfa
+Bravo

commit d874f81af3706d9a0980bbb59d99fa15be36f6cf
Author: 濱田悠 <yu@yu8mada.com>
Date:   Sat Aug 25 03:53:35 2018 +0900

    A

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..8a5239e
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Alfa

まとめ

コミット履歴を少し修正する場合から大胆に抜本的に書き換えてしまう場合まで git rebase -i <after-this-commit> はカバーしてくれます.

僕はこのコマンドを知ってから, 一度作られたコミットって必ずしも未来永劫変更することのできない不変のものでは全然ないのねと, こんなにも自由に書き換えようと思えば書き換えることができてしまうのねと軽い衝撃を受けました.

(git commit --amend で先端のコミットを修正することができると知っていましたが, まさか先端よりも前のコミットも修正する事ができるという事は知りませんでした)

思えば, git rebase <branch> といった普通のリベースも, <branch> と HEAD の共通祖先から, 共通祖先を含まない HEAD までのコミットのベースを, <branch> に挿げ替えるので, それらのコミットを書き換えているといえば書き換えているんですよね.

そこで, ベースを挿げ替えるだけでは少し物足りないという事で, より粒度の細かいリベースを可能にしてくれるものとして git rebase -i というインタラクティブなリベースがあるのでしょうね.

ただ普通のリベース同様, 既存のコミット履歴を書き換えてしまうため, 他の人と共有しているリモートのコミット履歴では, 例外的な状況を除いて使わないように注意する必要があります.

もしリモートのコミット履歴をリベースしてしまうとカタストロフィックな事態を招きかねませんので collision

リモートにプッシュする前のローカルのコミット履歴をやっぱりこうしたいと修正する時に使えますね blush

参考資料