コミットの整理に git add -p を使おう

git のコミットを適切に作れていますか？

自分はコードを書く前にTODOを作成しますが、どのようにコミットを分けるかも意識しています（TODO ≒ コミット）
ただ最初から全てが見えていない時もあり、予定通りコミットを分けることができないこともあります。
後からコミットを整理しますが、ファイル単位だけでなく同じファイル内の修正でも別コミットにしたい時がありますよね。
そんな時に`git add -p`使ってコミットを整理します。

## `git add -p` とは

`git add --help`でヘルプを確認します。

> -p, --patch
> Interactively choose hunks of patch between the index and the work tree and add them to the index. This gives the user a chance to

修正のまとまり（hunk）をインタラクティブに選択して、インデックスに追加することができます。
ファイル単位だけでなく、自分で行を選択してインデックスに追加できるので、細かくコミットを作ることができます。

### インタラクティブモード時のコマンド

```
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help
```

たくさんありますが`y`, `n`, `q`, `s`, `e`あたりを使えれば良いと思います。

## やってみる

実際に以下のファイルを修正していきます。

```ruby:sample.rb
def hello
  puts 'Hello'
  aaa
end

def good
  puts 'Gooood'
end

def nice
  puts 'Nice'
end

hello
good
```

- 修正内容
  - 不要なaaaを削除
  - Goodのtypo修正
  - niceメソッドの呼び出し追加

```diff
diff --git a/sample.rb b/sample.rb
index e828f1c..92cd6cb 100644
--- a/sample.rb
+++ b/sample.rb
@@ -1,10 +1,9 @@
 def hello
   puts 'Hello'
-  aaa
 end

 def good
-  puts 'Gooood'
+  puts 'Good'
 end

 def nice
@@ -13,3 +12,4 @@ end

 hello
 good
+nice
```

この修正を1つのコミットではなく3つのコミットに分けるために`git add -p`を使っていきましょう。

`(1/2) Stage this hunk`と表示されているので、2つのhunkに分かれています。
aaaの削除と、Goodのtypo修正が同じhunkになっているので分けたいですね。
その時は`s`で分割します。

```diff
❯ git add -p
diff --git a/sample.rb b/sample.rb
index e828f1c..92cd6cb 100644
--- a/sample.rb
+++ b/sample.rb
@@ -1,10 +1,9 @@
 def hello
   puts 'Hello'
-  aaa
 end

 def good
-  puts 'Gooood'
+  puts 'Good'
 end

 def nice
(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,s,e,?]? s
```

`(1/3) Stage this hunk`となり3分割されました。
aaaの削除だけになったので`y`でインデックスに追加、残りは`n`でインデックスに追加しないようにします（`q`で残りを全て追加しないでもok）
あとはコミットすれば`aaa`の削除だけでコミットを作れます。

```diff
Split into 2 hunks.
@@ -1,6 +1,5 @@
 def hello
   puts 'Hello'
-  aaa
 end

 def good
(1/3) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y

@@ -4,7 +3,7 @@
 end

 def good
-  puts 'Gooood'
+  puts 'Good'
 end

 def nice
(2/3) Stage this hunk [y,n,q,a,d,K,j,J,g,/,e,?]? n

@@ -13,3 +12,4 @@ end

 hello
 good
+nice
(3/3) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? n
```
同じような手順で他のコミットも作っていけそうですね。

また先ほどは`s`で自動分割してましたが、`e`を使うと自分で分割することもできます。

```diff
❯ git add -p
diff --git a/sample.rb b/sample.rb
index e828f1c..92cd6cb 100644
--- a/sample.rb
+++ b/sample.rb
@@ -1,10 +1,9 @@
 def hello
   puts 'Hello'
-  aaa
 end

 def good
-  puts 'Gooood'
+  puts 'Good'
 end

 def nice
(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,s,e,?]? e
```

エディタが立ち上がり、手動でインデックスに追加しないコードを選べます。
追加したコード（+の行）はコードを消せば、対象から外れます。
削除したコード（-の行）は`-`を` `にすれば、対象から外れます。
（実際のファイルは変更されません）

```diff
# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,10 +1,9 @@
 def hello
   puts 'Hello'
-  aaa
 end
 
 def good
-  puts 'Gooood'
+  puts 'Good'
 end
 
 def nice
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# 
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.
```

ファイルごとのコミットだけでなく、行単位でコミットが出来そうですね。

## まとめ

コミットを細かく分けるために`git add -p`を使いました。
コミットを細かく分けるメリットは複数ありますが、やはりレビューがしやすいことが一番大きいと思います。
適切に分割されているとレビューワーが見やすくなるので、レビューの精度が上がります。
手間はかかりますが、それよりもメリットの方が大きいので積極的にコミットを分割していきましょう。

## References

https://git-scm.com/docs/git-add