【Git】プルリクエスト前に複数のコミットを1つにまとめてコミットログを読みやすくする

## はじめに
細かいコミットが大量にあったり、メモレベルのコミットがあったりすると、プルリクエスト時にコミットログが追いにくくなり、レビューアに負担がかかる場合があります。
rebaseコマンドを利用して複数のコミットログを1つにまとめる方法をご紹介します。

## 前提条件・環境情報
- Windows10
- 開発環境
  - Visual Studio 2019
  - .NET Framework 4.7
  - C#
- [git本体](https://git-scm.com/)がインストール済みであること
- vimの基本操作(編集と保存)ができること

:::note info
git本体以外の環境は適宜読み換えてください。
gitコマンドを利用した方法となりますので、環境に依存しない普遍的な内容となっています。
:::

:::note warn
コミットをまとめる前の変更履歴がリモートリポジトリにPushされている場合、コンフリクトが発生します。
「git push -f origin <Branch name>」で強制的に変更する必要があります。
記事最後の参考資料も参考にしてみてください。
:::

## 題材にするソースコードとコミットログ

### ソースコード
単純なコンソールプロジェクトです。

```c#
using System;

namespace ConsoleApp1
{
    internal class Program
    {
        private static int Main(string[] args)
        {
            try
            {
                // コメントを追加
                var str = "これはテストです";

                System.Console.WriteLine(str);

                return 0;
            }
            catch (Exception ex)
            {
                System.Console.WriteLine(ex.ToString());

                return -1;
            }
        }
    }
}
```

### コミットログ
コミットログが新しい順で表示されます。

```shell
$ git log --oneline
564627d (HEAD -> master) #1 CodeMaidで綺麗にした
1c4520d #1 正常系・異常系の戻り値を指定
57eef82 #1 try-catchで囲む
891e7e8 #1 System.Console.WriteLineを追加
01bc665 #1 コメントを追加
13fcfba #1 「var str」を追加
aed97a0 プロジェクト ファイルを追加します。
f15572e .gitignore および .gitattributes を追加します。
```

## 複数のコミットを1つにまとめる方法
コミットのまとめ先(例えば「13fcfba」とする)の1つ前のコミットID(aed97a0)を指定します。

```bash
git rebase -i aed97a0
```

***

vimでrebaseの指示モードになります。

```bash
pick 13fcfba #1 「var str」を追加
pick 01bc665 #1 コメントを追加
pick 891e7e8 #1 System.Console.WriteLineを追加
pick 57eef82 #1 try-catchで囲む
pick 1c4520d #1 正常系・異常系の戻り値を指定
pick 564627d #1 CodeMaidで綺麗にした

# Rebase aed97a0..564627d onto 57eef82 (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
```

***

まとめるコミットIDのpickをsまたはsquashに変更します(「i」で編集モードにして変更する)。
1行目がコミットのまとめ先になるので、pickのままにします(sにするとrebaseエラーになる)。

```bash
pick 13fcfba #1 「var str」を追加
s 01bc665 #1 コメントを追加
s 891e7e8 #1 System.Console.WriteLineを追加
s 57eef82 #1 try-catchで囲む
s 1c4520d #1 正常系・異常系の戻り値を指定
s 564627d #1 CodeMaidで綺麗にした
```

***

保存して終了します(Escでノーマルモードにして、「:wq」で保存してvimを終了する)。

```
:wq
```

***

vimでコミットコメントの指示モードになります。コミットをまとめるにあたり、新しいコミットログが求められるからです。

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

#1 「var str」を追加

# This is the commit message #2:

#1 コメントを追加

# This is the commit message #3:

#1 System.Console.WriteLineを追加

# This is the commit message #4:

#1 try-catchで囲む

# This is the commit message #5:

#1 正常系・異常系の戻り値を指定

# This is the commit message #6:

#1 CodeMaidで綺麗にした
```

***

コミットログを書き換えて、保存して終了します。

:::note warn
issueとコミットを紐づけるために「#2」のように先頭にissue番号を記載しますが、
シャープ「#」はコメントアウト(無効なコメント)とみなされるので、先頭に半角スペースを入れるとよいでしょう。
:::

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

 #1 XXXバッチの新規作成
```

***

複数のコミットが1つにまとまりました。

```bash
$ git log --oneline
af7f258 (HEAD -> master)  #1 XXXバッチの新規作成
aed97a0 プロジェクト ファイルを追加します。
f15572e .gitignore および .gitattributes を追加します。
```

***

差分でも複数のコミットが1つにまとまったことが確認できます。

```bash
# 差分を確認
# git diff 変更前のコミットID 変更後のコミットID
git diff aed97a0 af7f258

diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs
index ea9bacc..156cfdc 100644
--- a/ConsoleApp1/Program.cs
+++ b/ConsoleApp1/Program.cs
@@ -1,15 +1,26 @@
 ﻿using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;

 namespace ConsoleApp1
 {
-    class Program
+    internal class Program
     {
-        static void Main(string[] args)
+        private static int Main(string[] args)
         {
+            try
+            {
+                // コメントを追加
+                var str = "これはテストです";
+
+                System.Console.WriteLine(str);
+
+                return 0;
+            }
+            catch (Exception ex)
+            {
+                System.Console.WriteLine(ex.ToString());
+
+                return -1;
+            }
         }
     }
-}
+}
```

## 間違えた時は...

### コミットログにチケット番号が記載されていなかったのでコミットコメントを修正したい
amendオプションで直前のコミットを書き換えることができます。

```bash
git commit --amend -m "#1 XXXバッチの新規作成"

$ git log --oneline
1632572 (HEAD) #1 XXXバッチの新規作成
aed97a0 プロジェクト ファイルを追加します。
f15572e .gitignore および .gitattributes を追加します。
```

### rebaseの指示モードでまとめ先のコミットもsquashにしてしまった

コミットのまとめ先が分からないので`Cannot 'squash' without a previous commit`というエラーメッセージが表示されます。
この場合、`git rebase --abort`で操作を取り消してください。

### rebase自体を間違えたのでやり直したい

reflogコマンドで戻したい履歴を確認します。
今回の場合、「HEAD@{16}」時点まで戻したいとします。

```bash
$ git reflog
af7f258 (HEAD -> master) HEAD@{0}: rebase (finish): returning to refs/heads/master
af7f258 (HEAD -> master) HEAD@{1}: rebase (squash): #1 XXXバッチの新規作成
b85537d HEAD@{2}: rebase (squash): # This is a combination of 5 commits.
a4bf388 HEAD@{3}: rebase (squash): # This is a combination of 4 commits.
84fefd5 HEAD@{4}: rebase (squash): # This is a combination of 3 commits.
31331f7 HEAD@{5}: rebase (squash): # This is a combination of 2 commits.
13fcfba HEAD@{6}: rebase (start): checkout aed97a0
564627d HEAD@{7}: rebase (abort): updating HEAD
564627d HEAD@{8}: reset: moving to HEAD@{7}
2168520 HEAD@{9}: rebase (squash): # This is a combination of 5 commits.
f1184fd HEAD@{10}: rebase (squash): # This is a combination of 4 commits.
fa651ab HEAD@{11}: rebase (squash): # This is a combination of 3 commits.
6b60eb4 HEAD@{12}: rebase (squash): # This is a combination of 2 commits.
13fcfba HEAD@{13}: rebase (start): checkout aed97a0
564627d HEAD@{14}: rebase (finish): returning to refs/heads/master
564627d HEAD@{15}: rebase (start): checkout aed97a08
564627d HEAD@{16}: commit: #1 CodeMaidで綺麗にした
1c4520d HEAD@{17}: commit: #1 正常系・異常系の戻り値を指定
57eef82 HEAD@{18}: commit: #1 try-catchで囲む
891e7e8 HEAD@{19}: commit: #1 System.Console.WriteLineを追加
01bc665 HEAD@{20}: commit: #1 コメントを追加
13fcfba HEAD@{21}: commit: #1 「var str」を追加
aed97a0 HEAD@{22}: commit: プロジェクト ファイルを追加します。
f15572e HEAD@{23}: commit (initial): .gitignore および .gitattributes を追加します。
```

***

指定した時点までのコミットを打ち消します。

:::note alert
コミットをなかったことにするので注意して操作してください。
必要に応じてバックアップをとっておくことをお勧めします。
:::

```bash
git reset --hard HEAD@{16}
```

***

元に戻っていることを確認します。

```bash
$ git log --oneline
564627d (HEAD -> master) #1 CodeMaidで綺麗にした
1c4520d #1 正常系・異常系の戻り値を指定
57eef82 #1 try-catchで囲む
891e7e8 #1 System.Console.WriteLineを追加
01bc665 #1 コメントを追加
13fcfba #1 「var str」を追加
aed97a0 プロジェクト ファイルを追加します。
f15572e .gitignore および .gitattributes を追加します。
```

## 参考資料
- [rebase -i でコミットをまとめる - Qiita](https://qiita.com/takke/items/3400b55becfd72769214)
- [Git squashで複数のコミットを1つでまとめる - dev.yakuza](https://dev-yakuza.posstree.com/git/git-squash/)
- [git rebase -i でコミットをまとめて履歴を綺麗にしよう！ | WWWクリエイターズ](https://www-creators.com/archives/2850)