対話的に指定ファイルの指定部分のみステージする[git]


自分は短気で面倒くさがりなので、よく`git add .`としてしまう

まあgitマスターからすると発狂ものかもしれないが、おそらくこんなgitの操作をやっているのは自分だけじゃない気がする

しかし作業ツリーにあるファイル全てステージなんてことをしちまうと以下のようなことが起きる可能性がある(いや実際よく起きていた)

- エディタで自動的に整形した内容がコミット(いいか悪いかは環境によるけど)
- キーボードに肘が当たったかなんかで予期せぬ文字がいつの間にか入っていて、気づかずコミット
- tmpで作ったはずのテストファイルなんかが一緒にコミット

...などなど

エディタの自動整形がdiffに入っちゃうと、GUIでdiffを見ている人からするととっても見づらいログになっちゃうし(CUIだとdiffに`-b`という素晴らしいオプションが付いている)、自分が予期せぬコードとかファイルがログに残っていると、何より恥ずかしい

ということで以下はファイルやファイル内の変更行を対話的に細かくコミットするやり方

## やりたいこと

- file : `second commit`という行のみコミット
- new-file : ステージするファイル
- tmp-file : ステージしないファイル

``` diff
$ git status --short
 M file
?? new-file
?? tmp-file

$ git diff
diff --git a/file b/file
index c74fa01..fa0600c 100644
--- a/file
+++ b/file
@@ -2,8 +2,9 @@ first commit
 first commit
 first commit
 first commit
-
+
 first commit
+second commit
 first commit
 first commit
 first commit
@@ -15,3 +16,4 @@ first commit
 first commit
 first commit
 first commit
+
```

## 作業

対話シェルモードに移行する

```
$ git add -i
           staged     unstaged path
  1:    unchanged        +2/-2 file

*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now>
```

それぞれのコマンドはスペルの頭文字をタイプするか、番号をタイプすることで実行する

- 1: status : ステータスを確認
- 2: update : ファイルをステージする
- 3: revert : ステージを取り消す
- 4: add untracked : トラッキングされていないファイル(新規ファイル)をステージする
- 5: patch : 部分的にステージ
- 6: diff : ステージしたファイルの差分を確認(作業ツリーのファイルではない)

``` diff
           staged     unstaged path
  1:    unchanged        +3/-1 file

*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now> p #部分的にステージする
           staged     unstaged path
  1:    unchanged        +3/-1 file
Patch update>> 1 #fileを選択する
           staged     unstaged path
* 1:    unchanged        +3/-1 file # 選択された状態
Patch update>> #Enterでファイル選択を終了する
diff --git a/file b/file
index c74fa01..fa0600c 100644
--- a/file
+++ b/file
@@ -2,8 +2,9 @@ first commit
 first commit
 first commit
 first commit
-
+
 first commit
+second commit
 first commit
 first commit
 first commit
Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]?
```

patchを実行すると対話モードに移行する。

- y : ステージする
- n : ステージしない
- q : 対話モードを終了する
- a : そのファイルの以降を全てステージする
- d : そのファイルの以降を全てスキップする
- g : 指定のハンクへ移動
- K : 前のハンクへ移動
- J : 次のハンクへ移動
- e : ハンクを編集

``` diff
diff --git a/file b/file
index c74fa01..fa0600c 100644
--- a/file
+++ b/file
@@ -2,8 +2,9 @@ first commit
 first commit
 first commit
 first commit
-
+
 first commit
+second commit
 first commit
 first commit
 first commit
Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? s #ハンクを分割する
Split into 2 hunks.
@@ -2,5 +2,5 @@
 first commit
 first commit
 first commit
-
+
 first commit
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?  n #自動整形された行はステージしない
@@ -6,4 +6,5 @@
 first commit
+second commit
 first commit
 first commit
 first commit
Stage this hunk [y,n,q,a,d,/,K,j,J,g,e,?]? y #ステージする
@@ -15,3 +16,4 @@ first commit
 first commit
 first commit
 first commit
+
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? n #自動整形された行はステージしない
```

それ以上ハンクがない場合、自動的に対話モードが終了する

```
*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now> a #トラッキングされていないファイルをステージするモードに移行
  1: new-file
  2: tmp-file
Add untracked>> 1 #new-fileのみをステージ
* 1: new-file
  2: tmp-file
Add untracked>> #Enterで終了
added one path

*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now> s #ステータスを確認
           staged     unstaged path
  1:        +1/-0        +2/-1 file
  2:        +0/-0      nothing new-file

*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now> q #終了
```

以上の操作で以下のようなステータスになる

``` diff
$ git status --short
MM file
A  new-file
?? tmp-file
$ git diff
diff --git a/file b/file
index 928e9ef..fa0600c 100644
--- a/file
+++ b/file
@@ -2,7 +2,7 @@ first commit
 first commit
 first commit
 first commit
-
+
 first commit
 second commit
 first commit
@@ -16,3 +16,4 @@ first commit
 first commit
 first commit
 first commit
+
$ git diff --cached
diff --git a/file b/file
index c74fa01..928e9ef 100644
--- a/file
+++ b/file
@@ -4,6 +4,7 @@ first commit
 first commit

 first commit
+second commit
 first commit
 first commit
 first commit
diff --git a/new-file b/new-file
new file mode 100644
index 0000000..e69de29
```

あとは普通にコミットするだけだが、癖で`git commit -a`とか間違えてやっちゃわないこと!

# おまけ

ちなみにSourceTreeで上記の内容を操作するのはとっても簡単だった。

![スクリーンショット 2017-03-24 10.24.16.png](https://qiita-image-store.s3.amazonaws.com/0/132627/9bac30f6-ecf1-a382-b325-42ac883c9cfa.png "スクリーンショット 2017-03-24 10.24.16.png")

コミットしたい行を選択する

![スクリーンショット 2017-03-24 10.26.252.png](https://qiita-image-store.s3.amazonaws.com/0/132627/bee6f9c6-bf16-cc29-7c66-84e181cf8159.png "スクリーンショット 2017-03-24 10.26.252.png")

選択した行をステージへ移動を選択する

![スクリーンショット 2017-03-24 10.26.25.png](https://qiita-image-store.s3.amazonaws.com/0/132627/dbb42711-373b-5fdd-b6c6-b944f65bfba2.png "スクリーンショット 2017-03-24 10.26.25.png")

追加したい新規ファイルのみを選択する

![スクリーンショット 2017-03-24 10.36.14.png](https://qiita-image-store.s3.amazonaws.com/0/132627/3316193b-24e9-e736-a720-5f3497d90cb6.png "スクリーンショット 2017-03-24 10.36.14.png")

以上

![スクリーンショット 2017-03-24 10.38.30.png](https://qiita-image-store.s3.amazonaws.com/0/132627/62a2e561-08dc-419c-3174-d9cafe63fc1c.png "スクリーンショット 2017-03-24 10.38.30.png")