git rebase -iでsquashする三つの方法

masterへプルリク投げる前に自分のcommitたちをまとめたいときがよくあると思う。そういうとき、 `git rebase -i` のsquash、よく使うんだけど、いつも混乱しがちなのでまとめる。

まずはmasterはこんな感じである。

```shell
$ for i in {1..5}; do echo $i >> test.txt && git add . && git commit -m $i; done
[master (root-commit) 3a224be] 1
 1 file changed, 1 insertion(+)
 create mode 100644 test.txt
[master 838d0a8] 2
 1 file changed, 1 insertion(+)
[master f19e946] 3
 1 file changed, 1 insertion(+)
[master bd6aa59] 4
 1 file changed, 1 insertion(+)
[master 2b33437] 5
 1 file changed, 1 insertion(+)

# git logは1〜5まで
$ git log --pretty=oneline
2b3343726a9c12857e6c7ac5084102ee1ae5b5bf (HEAD -> master) 5
bd6aa59bf633990eac26574a728a84e5b2a8374e 4
f19e94688f07c53090cf0d61e9019415f3a8c4ae 3
838d0a8096c5857b3c99bcecb506ce180723fe6a 2
3a224be0527ddcbc9511dbd2baf338dc597514c4 1

# ファイルの状態
$ cat test.txt
1
2
3
4
5
```

ここで `git rebase -i` で3、4、5を一つのcommitにまとめたいとする。

一つ目はチルダをcommit分書いて実直にやる。

```
$ git rebase -i HEAD~~~ # 3つ分suqashしたいので3つ書く
```

デフォルトのエディタが開く。（俺はNeovimが開く）

```vim
  1 pick 3f3cb77 3
  2 pick 1ae99f3 4
  3 pick 51359cd 5
  4
  5 # Rebase e474c68..51359cd onto e474c68 (3 commands)
  6 #
  7 # Commands:
  8 # p, pick <commit> = use commit
  9 # r, reword <commit> = use commit, but edit the commit message
 10 # e, edit <commit> = use commit, but stop for amending
 11 # s, squash <commit> = use commit, but meld into previous commit
 12 # f, fixup <commit> = like "squash", but discard this commit's log message
 13 # x, exec <command> = run command (the rest of the line) using shell
 14 # b, break = stop here (continue rebase later with 'git rebase --continue')
 15 # d, drop <commit> = remove commit
 16 # l, label <label> = label current HEAD with a name
 17 # t, reset <label> = reset HEAD to a label
 18 # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
 19 # .       create a merge commit using the original merge commit's
 20 # .       message (or the oneline, if no original merge commit was
 21 # .       specified). Use -c <commit> to reword the commit message.
 22 #
 23 # These lines can be re-ordered; they are executed from top to bottom.
 24 #
 25 # If you remove a line here THAT COMMIT WILL BE LOST.
 26 #
 27 # However, if you remove everything, the rebase will be aborted.
 28 #
 29 # Note that empty commits are commented out
```

かなり丁寧に書かれているけど、`squash` しか使ったことない。ほかはどんなシーンで使うんだろう……。下記のように変えて保存する。`squash` は `s` でもいい。

```vim
  1 pick 3f3cb77 3
  2 squash 1ae99f3 4
  3 squash 51359cd 5
  4
  5 # Rebase e474c68..51359cd onto e474c68 (3 commands)
  6 #
  7 # Commands:
  8 # p, pick <commit> = use commit
  9 # r, reword <commit> = use commit, but edit the commit message
 10 # e, edit <commit> = use commit, but stop for amending
 11 # s, squash <commit> = use commit, but meld into previous commit
 12 # f, fixup <commit> = like "squash", but discard this commit's log message
 13 # x, exec <command> = run command (the rest of the line) using shell
 14 # b, break = stop here (continue rebase later with 'git rebase --continue')
 15 # d, drop <commit> = remove commit
 16 # l, label <label> = label current HEAD with a name
 17 # t, reset <label> = reset HEAD to a label
 18 # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
 19 # .       create a merge commit using the original merge commit's
 20 # .       message (or the oneline, if no original merge commit was
 21 # .       specified). Use -c <commit> to reword the commit message.
 22 #
 23 # These lines can be re-ordered; they are executed from top to bottom.
 24 #
 25 # If you remove a line here THAT COMMIT WILL BE LOST.
 26 #
 27 # However, if you remove everything, the rebase will be aborted.
 28 #
 29 # Note that empty commits are commented out
```

ファイルが切り替わって、コミットメッセージを書く画面になる。

```vim
  1 # This is a combination of 3 commits.
  2 # This is the 1st commit message:
  3
  4 3
  5
  6 # This is the commit message #2:
  7
  8 4
  9
 10 # This is the commit message #3:
 11
 12 5
 13
 14 # Please enter the commit message for your changes. Lines starting
 15 # with '#' will be ignored, and an empty message aborts the commit.
 16 #
 17 # Date:      Sat Aug 10 23:28:52 2019 +0900
 18 #
 19 # interactive rebase in progress; onto e474c68
 20 # Last commands done (3 commands done):
 21 #    squash 1ae99f3 4
 22 #    squash 51359cd 5
 23 # No commands remaining.
 24 # You are currently rebasing branch 'master' on 'e474c68'.
 25 #
 26 # Changes to be committed:
 27 #       modified:   test.txt
 28 #
```

とりあえず保存して終了して、logを見る。

```
git log --pretty=oneline
a140972cda0450597065f1f079f12a6ccc3c6cbb (HEAD -> master) 3
e474c683a9ed70d696c227bee3e732388e6395a6 2
225da32d0e56e5191f79a6ae2576aba41f8907d9 1
```

コミットは3つまでしかないけど……

```shell
cat test.txt
1
2
3
4
5
```

ファイルは最終版になっている。これでできれば問題ない。

---

で、これ3つだったからチルダ三つ書けばよかったけど、commitが20個くらいあったときにたまったもんじゃないので、数で指定する。

```shell
# commitが23個こある
$ git log --pretty=oneline
4f6feaf899348fcaf872d320089b8628e9f58c88 (HEAD -> master) 23
5c753e820c1eac5363c56db0b5f25866a50b7dc6 22
c8a5e93df58dbf15b8fd78bd9aa6f3b2dc22dcb4 21
0dbb58d310459bc8a521668be94186cde18e0ee2 20
097bea9836814957066a2579751b7cd2ddc31161 19
63aa8790d5574ffc283846fb9eda2d952cc3e53f 18
8c6eed835b6b5365f3fdc071c35ecfd6dab057f7 17
2c9fe014b19aa7187e7a91dc543ec1704d16fa06 16
b23ce97b37cd8fb194f23538de4d9325c97f7cf7 15
d0d60f2266a5de6774205a9aac6aaca6ec4ab0af 14
f5425c6fa53c682822540f8f474ea1e2eabf7e64 13
5ced175d0cc1a971fc450aab122e7d57a2a00dd4 12
ccca06d3a853a0bd0a4f2fcf1ea5b79f6c3b5864 11
2df08e4d200bef7ebe12145ccfe94e2b24ca92df 10
3622acf6db77df5bb6f79fddc5582ff1b3c0c244 9
56564478c8be23b07806511ccf21eb7c358297f9 8
e4d4a09665d99b96979fc7ae95bf1adf5e72318e 7
37b6c80440b280f9c80ca712b7452cd0a48ed56b 6
c54486c4ab50c05ee693fe637f22857c0e2869f8 5
517325fbc239b8a3733fcf5449aff4b11c56aa5e 4
e6e2114808e865a4a128793cfdfc9bd956549df4 3
4a4ef49d21d843d974513895b6d81bbbe72a491b 2
ae4d63a9735baa0de971b3fddf97888215c8f699 1

# ファイルはこうなってる
$ cat test.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
```

直近の20個のcommitをまとめたいときはこう。

```shell
$ git rebase -i HEAD~20

# エディタが開いてcommitをsquashする


# 23個あったcommitが4つにまとまってる
$ git log --pretty=oneline
f54fd7664412ac933274abf08a12896bf1c88f8f (HEAD -> master) 4
e6e2114808e865a4a128793cfdfc9bd956549df4 3
4a4ef49d21d843d974513895b6d81bbbe72a491b 2
ae4d63a9735baa0de971b3fddf97888215c8f699 1

# ファイルは最終版
$ cat test.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
```

もう一つ、ハッシュ使って指定したcommit **以降** のcommitをまとめることができる。さっきと同じ23個のcommitがあって、上20個をまとめたい。

```shell
git log --pretty=oneline
0cd87f36b5940a8e81a5473cf4be7451ef8c5b61 (HEAD -> master) 23 # ↓ここから
d8fd1cfda538963d0ac7a569a0f8f9384d5feabc 22
7e7436ea79adb8817a5089ec1598ccd303e0fb25 21
1cc44c5c9ff971f3021c937a5f1b84b4211e29ff 20
b1d3678e051169bd0307771eb24d9aa0df1a0bfd 19
a16f16e72d98fdfba0a5ed919ffc470405213ea3 18
577f33a002eb65c113cc2af383b6288fde9cb954 17
ecd12f8c4f83bb59442c91033e0ef87342d8509e 16
bdf6d5a26c39be77d7de7faee038c8ed8e5b2aeb 15
ffd685c937a5c6d63dff91d7706e8fb12a4576fb 14
102c00f672b6c9eb41d9aa770f5a554de418def6 13
6ed8476d24ec29034a5aaa9181d4e03d56f2907c 12
d9228429d0382fe06d160b3e512ad4b736ed9c40 11
617c837e02de8f2a8bbe6193e1309d9cc5288a38 10
36eb078c662fa240a5a3c40c37f6f4f5393ba0ef 9
3fc8a7e22ab510d91b1b21a87fddca5df63c305d 8
a368e9c492632c4f9b0117a6a2ba9731b745cc17 7
5a6b59f1ee9fe7265ae7e9cbe75d0b31a6d2ac25 6
234f8527ac93cd54815c36e71c6d9bcdf67dffd5 5
71ffca70ea806ff348ede8a88bbe7125f35b41da 4 # ↑ここまでをまとめたいとき
fa9a0870d16a3847ee815febe8d371431ae9bbac 3 # このハッシュをrebase -i に指定する。
1485f8b95eff035ba5f086e276eb31c37ec49bac 2
c6021485ee77d2aedfb7ef711ad8a4ff4e890fc8 1
```

こういう感じ。この後エディタが開くのは一緒。

```
git rebase -i fa9a0870d16a3847ee815febe8d371431ae9bbac
```

コミットログはこうなる。

```shell
git log --pretty=oneline
d96abf64f7fbc736db22f26d052917dfce8f34f0 (HEAD -> master) 4
fa9a0870d16a3847ee815febe8d371431ae9bbac 3
1485f8b95eff035ba5f086e276eb31c37ec49bac 2
c6021485ee77d2aedfb7ef711ad8a4ff4e890fc8 1
```

ファイルは当然最終版。

```shell
$ cat test.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
```

いつも自分のコマンドを打つ前に疑ってしまう。