git rebase -iで選択できるコマンドのまとめ

# はじめに
git rebase -iで選べるコマンドが多すぎて、どんな操作を行うのかいまいち分かっていませんでした。
そのため、これらの操作コマンドが何を意味するのか、どんな挙動になるのかをまとめました。

# git rebase -iでできること
[Gitによるバージョン管理](https://www.amazon.co.jp/Git%E3%81%AB%E3%82%88%E3%82%8B%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%AE%A1%E7%90%86-%E5%B2%A9%E6%9D%BE-%E4%BF%A1%E6%B4%8B/dp/4274068641)から抜粋します。
> git rebase コマンドの-iオプションを使うとリベースするパッチを操作できる

とあります。
なお、リベースとは
>- リベース：チェックアウトしているブランチのコミットを、指定したブランチの最新コミットに対してパッチとして適用する。

を意味しています。
そのため、過去のコミットに対して様々なコマンドで操作することができます。

# 操作コマンド
## 前提
ログメッセージにそれぞれA~Dを記載して、コミットしている状態とします。

```
$ git log --stat
commit 6cf1859519381aef6744fa8e0f2209257bc1ac14 (HEAD -> master)
Author: xxxxx
Date:   Thu Apr 30 22:52:02 2020 +0900
    D
 D.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

commit f5451ab6b5d2cf33239540f50298308f97a346ed
Author: xxxxx
Date:   Thu Apr 30 22:51:52 2020 +0900
    C
 C.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

commit 43427fcc39abd45930dc8a7654e545e84e7810ab
Author: xxxxx
Date:   Thu Apr 30 22:51:42 2020 +0900
    B
 B.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

commit eb4cdce998b38fbb7edaa28a5eb25c642ad4bcb1
Author: xxxxx
Date:   Thu Apr 30 22:51:23 2020 +0900
    A
 A.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
```

## 一覧
git rebase -iを行うと、各コミットに対するコマンドが選択できるようになります。

```
$ git rebase -i HEAD~4
```

```
pick 34b1f88 A
pick e467a4c B
pick cfed787 C
pick 5d27bf5 D

# Rebase fa693a7..5d27bf5 onto 5d27bf5 (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 <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# 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.
#
~ 
```

1個ずつコマンドを見ていきます。
なおここではlabel、reset、mergeは対象外とします。

## 挙動
### pick
対象のコミットを変更なくそのまま使用します。
git rebase -iでは、各コミットのデフォルトがpickです。
git rebase -i後、何もせずにエディタを閉じても、gitログは何も変わりません。

### reword
対象のコミットを使用します。
rebase画面を閉じた後、gitコマンドを打つことなく、rewordとなったコミットのコミットメッセージ編集ができます。


例：Dをrewordする
Dのコミットをpickからrewordに変更して保存します。

```
pick eb4cdce A
pick 43427fc B
pick f5451ab C
reword 6cf1859 D
```

コミットメッセージの編集エディタが開くので、適当なメッセージを入れて保存します。

```
D reword

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
・・・
```

ログを確認すると、変更したメッセージで表示されます。

```
$ git log --oneline 
80b4a08 (HEAD -> master) D reword
f5451ab C
43427fc B
eb4cdce A
```

### edit
rewordと違って、gitコマンドを実行してメッセージを編集し、修正完了していく方式です。

例：Dをeditする
Dのコミットをpickからeditに変更して保存します。

```
pick eb4cdce A
pick 43427fc B
pick f5451ab C
edit 6cf1859 D
```

コマンド選択画面が閉じられ、次に実施するコマンドが表示されます。

```
$ git rebase -i HEAD~4
Stopped at 6cf1859...  D
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue
```

上記コマンドの実行、コミットメッセージを変更("D edit"に変更)後、ログを確認すると、変更したメッセージで表示されます。

```
>git log --oneline    
2ec9da8 (HEAD -> master) D edit
f5451ab C
43427fc B
eb4cdce A
```
補足：--amendオプションについてはこちらの記事を参考にしました。
[Git：過去のコミットコメントを修正する方法](http://www-creators.com/archives/5238)

なお、複数のコミットに対してeditを指定した場合、以下の例のようなコミット修正、完了を繰り返していく流れになります。

例： B、C、Dをpickからeditに変更する

```
pick eb4cdce A
edit 43427fc B
edit f5451ab C
edit 6cf1859 D
```

このときの挙動は、

1. git commit --amendを実行
1. Bのコミットメッセージを修正
1. git rebase --continueを実行
1. 1~3をC、Dに対して順に実行

となります。

### squash
squash対象のコミットでの、ファイル変更履歴、コミットメッセージが、対象コミットの前のコミットにまとめられます。
なおコミットメッセージは、まとめられる前に編集可能です。

例：Dをsquashする
Dのコミットをpickからsquashに変更して保存します。

```
pick eb4cdce A
pick 43427fc B
pick f5451ab C
squash 6cf1859 D
```

コミットメッセージの編集エディタが開きます。
ここでコミットメッセージの編集を行うことも可能です。
今回はそのままで保存します。

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

C

# This is the commit message #2:

D

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
・・・
```

ログを確認すると、CとDのコミット履歴とコミットメッセージがまとめられて表示されます。

```
$ git log --stat      
commit f0ff490bc8d4a867d586e8384c6e404f30d3a318 (HEAD -> master)
Author: xxxxx
Date:   Thu Apr 30 22:51:52 2020 +0900
    C
    
    D
 C.txt | 0
 D.txt | 0
 2 files changed, 0 insertions(+), 0 deletions(-)

commit 43427fcc39abd45930dc8a7654e545e84e7810ab
Author: xxxxx
Date:   Thu Apr 30 22:51:42 2020 +0900
    B
 B.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

commit eb4cdce998b38fbb7edaa28a5eb25c642ad4bcb1
Author: xxxxx
Date:   Thu Apr 30 22:51:23 2020 +0900
    A
 A.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
```

### fixup
squashと似たような挙動になりますが、異なる点は、対象のコミットメッセージが破棄されることです。


例：Dをfixupする
Dのコミットをpickからfixupに変更して保存します。

```
pick eb4cdce A
pick 43427fc B
pick f5451ab C
fixup 6cf1859 D
```

コマンド選択画面が閉じられ、修正が完了します。
ログを確認すると、Dのコミットメッセージがなくなり、Dの変更履歴がCのコミットにまとめられて表示されます。

```
$ git log --stat
commit 622f58620405e65468b4a44ac218cdaa353faed8 (HEAD -> master)
Author: xxxxx
Date:   Thu Apr 30 22:51:52 2020 +0900
    C
 C.txt | 0
 D.txt | 0
 2 files changed, 0 insertions(+), 0 deletions(-)

commit 43427fcc39abd45930dc8a7654e545e84e7810ab
Author: xxxxx
Date:   Thu Apr 30 22:51:42 2020 +0900
    B
 B.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

commit eb4cdce998b38fbb7edaa28a5eb25c642ad4bcb1
Author: xxxxx
Date:   Thu Apr 30 22:51:23 2020 +0900
    A
 A.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
```


### exec
[git-rebaseのexecを使ってstashした内容を履歴に埋め込む](https://qiita.com/yuku_t/items/fed530acb97385ecbeee)
こちらの記事にあるように、コミットの合間にexec コマンドを追記することで、記載したコマンドを実行できます。
また、セミコロンでつなげることで複数のコマンドを実行できます。

例：CとDの間でexecする
execでpwdとlsのコマンドを実行してみます。

```
pick eb4cdce A
pick 43427fc B
pick f5451ab C
exec pwd;ls
pick 6cf1859 D
```

コマンド選択画面を閉じると、pwdの結果とlsの結果が表示されます。

```
$ git rebase -i HEAD~4
Executing: pwd;ls
/Users/xxxx
A.txt	B.txt	C.txt
Successfully rebased and updated refs/heads/master.
```

よって記事にあるように、execとgitコマンドを組み合わせることで、任意の場所に現在に変更を含めたりできそうです。


### break
breakを記載した場所でgitログのコミットが停止します。
その後git rebase --continueすることで、続きのコミットが行われます。

例：CとDの間でbreakする

```
pick eb4cdce A
pick 43427fc B
pick f5451ab C
break
pick 6cf1859 D
```

コマンド選択画面を閉じてログを確認すると、Cで止まっていることが分かります。

```
$ git rebase -i HEAD~4 
Stopped at f5451ab... C

$ git log --oneline   
f5451ab (HEAD) C
43427fc B
eb4cdce A
```

何もコマンドを行わずにgit rebase --continueを行うと、Dまでコミットが行われます。

```
$ git rebase --continue
Successfully rebased and updated refs/heads/master.

$ git log --oneline    
6cf1859 (HEAD -> master) D
f5451ab C
43427fc B
eb4cdce A
```

### drop
dropの対象コミットを削除します。

例：Dをdropする
Dのコミットをpickからdropに変更して保存します。

```
pick eb4cdce A
pick 43427fc B
pick f5451ab C
drop 6cf1859 D
```

コマンド選択画面を閉じてログを確認すると、Dのコミットが削除されています。

```
$ git log --oneline   
f5451ab (HEAD -> master) C
43427fc B
eb4cdce A
```

# まとめ
gitログに対して、様々なコマンドを実際に試すことで、どんな挙動になるのか理解することができました。
必要に応じて各コマンドを使い分けていきましょう。