git rebase -i コマンドはいいぞ

この記事は[新卒1年目 Advent Calendar 2021](https://qiita.com/advent-calendar/2021/new-graduate)の2日目です。

# 概要

先輩から教えていただいた `git rebase -i` コマンドが非常に便利で、日頃から多用しています。
仕事で実際にやったことのあるケースをもとに、自分の使い方をご紹介します。

この記事で扱うコマンドは、以下の3つです。

- `edit`
- `reword`
- `fixup`

# 対象読者

この記事は、以下のような方を対象読者と想定しています。

- `git rebase -i` を完全に理解したい方
- gitを使った開発の経験が浅い方
- 自分同様まだ開発の実務経験が浅い方

# この記事の特徴

新卒1年目のアドカレで投稿する記事ということで、主に自分同様まだ実務経験の浅い方へ向けて書きました。
このコマンドを知らない頃の自分でも理解できるように、丁寧に書いてみたつもりです。
誰かの `git rebase -i` コマンドの理解を深めるお役に立てれば幸いです。

# ケース別 git rebase -i

この記事では、以下3つのケースで `git rebase -i` をどう使うかをご紹介します。

- 以前のコミットの内容を編集したい
- 以前のコミットのコミットメッセージのみ変更したい
- 複数のコミットをまとめたい

## 以前のコミットの内容を編集したい

実際にあったのは、以下のようなケースです。

- 「あ、さっきのコミットに、この内容も含めたいな」
- 「やべ、さっきのコミット、自分用のコメント残したままだった」

こんなときは `edit` を使います。

### 結論

1. `git rebase -i HEAD^`
2. `pick` → `edit` に変更して保存
3. 1つ前のコミットに戻るので、そこで編集する
4. `git add (1つ前のコミットに含めたいもの)` + `git commit --amend`
5. `git rebase --continue`

### 具体例

コミット: `add hoge`

```diff:hoge.txt
+ hoge
+ fuga
```

**「hogeだけを入れたかったが、間違えてfugaも入れてしまった。fugaを消したい」**

#### 手順1. `git rebase -i HEAD^`

```bash
$ git rebase -i HEAD^
```

#### 手順2. `pick` → `edit` に変更して保存

```diff
- pick xxxxxxx add hoge
+ edit xxxxxxx add hoge

  # Rebase xxxxxx..xxxxxx onto xxxxxx (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
  # 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.
  #
```

#### 手順3. 編集する

```diff:hoge.txt
  hoge
- fuga
```

#### 手順4. `git add` + `git commit --amend`

```bash
$ git add hoge.txt
$ git commit --amend
```

#### 手順5. `git rebase --continue`

```bash
$ git rebase --continue
```

これで完了です。

## 以前のコミットのコミットメッセージのみ変更したい

実際にあったのは、以下のようなケースです。

- 「さっきのコミットメッセージ、やっぱり違う表現の方がいいか」
- 「prefixを間違えてしまった、正しくせねば」

こんなときは `reword` を使います。

### 結論

1. `git rebase -i HEAD^`
2. `pick` → `reword` に変更して保存
3. コミットメッセージを変更して保存

### 具体例

コミット: `add fuga`

```diff:hoge.txt
+ hoge
```

**「hogeを追加したのに、間違えて、fugaを追加したというコミットメッセージにしてしまった」**

#### 手順1. `git rebase -i HEAD^`

```bash
$ git rebase -i HEAD^
```

#### 手順2. `pick` → `reword` に変更して保存

```diff
- pick xxxxxxx add fuga
+ reword xxxxxxx add fuga

  # Rebase xxxxxx..xxxxxx onto xxxxxx (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
  # 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.
  #
```

#### 手順3. コミットメッセージを変更して保存

```diff
- add fuga
+ add hoge

  # Please enter the commit message for your changes. Lines starting
  # with '#' will be ignored, and an empty message aborts the commit.
  #
  # Date:      xxxxxxxxxxxx
  #
  # interactive rebase in progress; onto xxxxxx
  # Last command done (1 command done):
  #    reword xxxxxx add fuga
  # No commands remaining.
  # You are currently editing a commit while rebasing branch 'xxxxxxx' on 'xxxxxx'.
  #
  # Changes to be committed:
  #       modified:   hoge.txt
  #
```

これで完了です。

## 複数のコミットをまとめたい

実際にあったのは、以下のようなケースです。

- 「細かくコミットしすぎたな、ちゃんと意味のある単位でまとめたい」
- 「ペア・モブプロやってて、ドライバーを交代するために無意味なコミット&プッシュをしまくった。作業が完了したので、PRを出すためキレイにまとめないとな」

こんなときは `fixup` を使います。

### 結論

1. `git rebase -i HEAD~2`
2. `pick` → `fixup` に変更して保存

### 具体例

コミット1: `add hoge`

```diff:hoge.txt
+ hoge
```

コミット2: `add hoge_spec`

```diff:hoge_spec.txt
+ hoge test
```

**「hogeのテスト追加は、hogeを追加したコミットにまとめて良さそうだ」**

#### 手順1. `git rebase -i HEAD~2`

```bash
$ git rebase -i HEAD~2
```

#### 手順2. `pick` → `fixup` に変更して保存

```diff
  pick xxxxxxx add hoge
- pick xxxxxxx add hoge_spec
+ fixup xxxxxxx add hoge_spec

  # Rebase xxxxxx..xxxxxx onto xxxxxx (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
  # 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.
  #
```

これで完了です。

コミット `add hoge_spec` の内容が `add hoge` に入ります。
なお、コミットメッセージは `add hoge` となります。

ちなみに `fixup` ではなく `squash` を使うと、コミットメッセージの編集もできます。

# おわりに

`git rebase -i` コマンドは非常に便利です。
この中でも、今回ご紹介した `edit` , `reword` , `fixup` は特に便利なコマンドです。
まだ使ったことのない方は、ぜひ一度お試しで使っていただき、その便利さを知っていただきたいです。