git rebaseのinteractiveモードを活用して2個以上前のコミットを修正する

# 前置き
業務ではこまめにコミットをするようにしているのですが
「あの修正入れ忘れた！」
とか
「あ！コミットメッセージが誤字ってる！！」
とかありますよね（私はよくあります）

それを修正するためのコマンドを毎回調べるのも面倒なので備忘録のためまとめました
この記事が私と同じおっちょこちょいな人の手助けになれば幸いです🙇‍♂️

:::note alert
前提として本記事は**リモートリポジトリにpushしてない状態**での説明となります

push済みだと`push -f`を使わないと修正出来ないのでオススメしません
素直に新しくコミットを作ってpushしましょう
:::

2個以上前のコミットの修正の説明をより理解しやすくするために
先に1個前のコミットの修正を説明しようと思います

そんなん知ってるわ！早く教えてくれーって方は
[こちら](#2個以上前のコミットの修正)をクリックでジャンプ出来ます
（そんな人はこんな記事見ないと思いますが…😅ｗ）

# 1個前のコミットの修正
これはとてもシンプル
`git commit --amend`を使います

どうやって使うか具体例で見てみましょう
↓
### e.g.) さっきのコミットにBの変更を入れ忘れた
```:terminal
$ git log -1 --name-only
commit 509bab5e2596d88b469bb692b9dc23bdbc1287e7 (HEAD -> xxx, origin/xxx
Author: xxxx <xxx@test.com>
Date:   Thu Mar 10 10:02:00 2022 +0900

    Aを追加

app/views/A.html

$ git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   app/views/B.html

no changes added to commit (use "git add" and/or "git commit -a")
```
「あ！！今のコミットにBの変更を入れ忘れた！！」
落ち着いてください
直前のコミットの変更は簡単ですよ

このBの変更をさっきのコミットに滑り込ませるには
```:tarminal
$ git add app/views/B.html
$ git commit --amend
```
これだけ！

:::note info
他にも変更を入れたいなら普通に作業して`git add`すればOKです
:::

`git commit --amend`を実行すると
vimが立ち上がりコミットメッセージの修正画面が出ます
オプションなしで`git commit`を実行したときの画面ですね

ちょっと違うのは**1個前のコミットメッセージが表示**されています
つまりコミットメッセージの修正もここで出来るということです

今回は
- 入れ忘れたBの修正を追加
- 追加でCの修正も追加
- コミットメッセージを「AとBとCを追加した」に修正

これらの変更をしました

```:tarminal
$ git add app/views/B.html app/views/C.html
$ git commit --amend
```

:::note info
コミットメッセージの変更をしない場合はそのまま`:q`で終了でOKです
:::

コミットメッセージ修正画面を終了するとちゃんとコミットが追加されています
確認してみましょう

```:tarminal
$ git log -1 --name-only
commit 509bab5e2596d88b469bb692b9dc23bdbc1287e7 (HEAD -> xxx, origin/xxx
Author: xxxx <xxx@test.com>
Date:   Thu Mar 10 10:47:22 2022 +0900

    AとBとCを追加した

app/views/A.html
app/views/B.html
app.views/C.html

```
ちゃんと追加出来ました👏


# 2個以上前のコミットの修正
いよいよ表題の`git rebase -i`を使います

ちょっとだけ手順が増えるので順を追って具体例で見てみましょう
↓
### e.g.) 3個前のコミットにDの変更を入れ忘れた
先ほどの修正から少し開発も進みコミットも何個か積んできました
ふと思いつきます

「あの時のコミットにDの変更含めておけば良かった…」
```:terminal
$ git log --oneline
7d6fd8904 (HEAD -> xxx, origin/xxx 1個前のコミット
55074c0d9 2個前のコミット
509bab5e2 3個前のコミット <- これに含め忘れたい
.
.
```
なんと2個以上前のコミットの修正には`git commit --amend`は使えません

「でもまた新たにコミットを作ると履歴が汚くなるし嫌だな…」

大丈夫です
ここではgit rebase -i（interactive mode）を使って
N個前のコミットを取得してから編集するという流れで修正できるので説明します

:::note info
`-iオプション`は`git rebase`をinteractiveモードで起動できるオプション
interactiveは**対話**とか**対話式**と訳されたりするようです
編集だけでなくコミットをまとめたり削除したりも出来ます

Rubyのコンソールであるirbもinteractive Rubyの略
たしかにコンソールってinteractiveですよね💡
:::

#### 1. 対象のコミットを取得
`git rebase -i HEAD~N`(NにはN個前のコミットまで対象にするかで適宜数値を入れてください)

```:terminal
$ git rebase -i HEAD~3
```
を実行するとvimが立ち上がり

```bash:vim
pick 0d2711298 3個前のコミット
pick 55074c0d9 2個前のコミット
pick 7d6fd8904 1個前のコミット
.
.
```
こんな感じでN個で指定した分だけコミットが取得出来ます
（今回は`HEAD~3`を指定したので3個分のコミットが取得出来ましたね）

:::note info
`HEAD`は**今現在自分が作業している場所を示すポインタ**です
つまり今現在の作業~3個前のコミットを取得したわけです
:::

次に各コミットの左端に`pick`となっている部分を`edit`に変更します
（今回は3個前のコミットを修正したいので3個前のやつを変更ですよ）
```bash:vim
edit 0d2711298 3個前のコミット
pick 55074c0d9 2個前のコミット
pick 7d6fd8904 1個前のコミット
```
`:q`で終了すると
```bash:terminal
~/dir　branch_name|rebase-i
$ |
```
こんな感じでプロンプトに`rebase-i`と表示されるようになります

:::note warn
各々の環境によって表示のされ方には違いがあります
もしrebase-iなどの**gitの状態**が表示されないなら改善をオススメします
:::

#### 2. 実際に追加したい修正作業をする
ここまで来たらもう簡単です
ほとんど[1個前のコミットの修正](#1個前のコミットの修正)と同じです
追加し忘れたファイル以外にも新たに別の変更を追加してもOKです
何度も修正しなくていいように慎重に作業しましょうｗ

#### 3. 修正をコミットし直す
修正作業が終わったのでいよいよコミットを修正します
といってもgit rebaseで該当コミットの編集モードに入っているので

```:terminal
git add .
git commit —-amend
```
お馴染みのこれでOKです

同じくコミットメッセージを修正する画面になるので
適宜操作して終了したらコミットの修正は完了です
（今回はコミットメッセージを`3個前のコミットにDを追加`に変更してみました）

確認してみましょう
```:terminal
$ git log -1 --name-only
commit 509bab5e2596d88b469bb692b9dc23bdbc1287e7 (HEAD -> xxx, origin/xxx
Author: xxxx <xxx@test.com>
Date:   Thu Mar 10 10:47:22 2022 +0900

    3個前のコミットにDを追加

app/views/A.html
app/views/B.html
app/views/C.html
app/views/D.html

```
ちゃんと出来てるー👏


#### 4. interactiveモードから抜ける
忘れずがちです
ちゃんと最後はinteractiveモードから抜けましょう
下記コマンドで抜けられます

```:terminal
$ git rebase --continue
```

:::note
interractiveモードを中止したい場合は
`git rebase --abort`を実行すると
gitのリポジトリはgit rebaseを実行する前に戻ります
:::

gitだけに限らずCUIでの操作は**今どの状態にいるか**を意識して作業しましょう
思わぬ事故を防ぐためには大事です

長くなりましたが説明は以上になります
お疲れ様でした！


## まとめ
- 1個前のコミットの修正は
`git commit --amend`を使う
- 2個以上前のコミットの修正は
`git rebase -i HEAD~N`と`git commit --amend`を使う
- rebaseを中止したい場合は
`git rebase --abort`を使う