git checkout -p の使い方

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [はじめに](#はじめに)
1. [やりたいこと](#やりたいこと)
1. [方法](#方法)
1. [総括](#総括)
1. [参考文献](#参考文献)
1. [付録](#付録)

# はじめに
<!-- 発端や概要を記載 -->
ソースコードを編集しているときに変更した行をステージングさせたくないけど、変更前に戻したいときってありますよね？（ない場合はこの記事は参考になりません。。）
その時、vscodeの[Git History](https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory#review-details) という拡張機能は便利でワンクリックで戻せるので普段はそれを使っています。
ですが、サーバなどのリモート環境での操作時など、ほかの人に迷惑をかけたくない状況の時、クライアントとサーバ間で情報をやり取りするエディタまたはサーバ上のメモリをたくさん食うエディタ（例えばvscode）を使いたくないときがあります。
その時に、gitコマンドだけでvscodeの便利な機能を使えたらいいなと考え検索した結果、思いのほか情報が少ないように感じたので、自分なりにまとめました。
本来ならvimやneovimでできるといいのですが、見つからなかったのでgitコマンドで頑張りました。（もし知っている方、いらっしゃれば教えてほしいです。。）

<!-- 各チャプター -->
# やりたいこと
下図のようにステージング前の変更部分を元に戻したい。
![プレゼンテーション1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516498/3dcc95b7-d71f-e3ef-e82b-b17b145cb84e.gif)

# 方法
完全に同じようにはできませんが、似たようなことはできます。
`git checkout -p <file>`を使います。

## 今回の例で使うファイル
- URL
  - [ファイルを管理しているgitのURL](https://github.com/tama14142356/testgit)
  - [test.py(変更前)](https://github.com/tama14142356/testgit/blob/main/test.py)
  - [test.py(変更後)](https://github.com/tama14142356/testgit/blob/change/test.py)

- 今回の例の再現の為の準備
  再現したい場合は、少し面倒ですが、以下の順に行ってください。
  1. [gitのURL](https://github.com/tama14142356/testgit)からgit cloneする
  1. changeブランチの[test.py](https://github.com/tama14142356/testgit/blob/change/test.py)を入手  
  ダウンロードするか、ローカルでブランチ切り替えしてからどこか別のところにコピー
  1. mainブランチのtest.pyをそのchangeブランチのtest.pyで上書きしてください。

- コード
<details><summary>変更前のコード(test.py)</summary><div>

```pyton:test.py
from matplotlib import pyplot as plt
import numpy as np

for i in range(10):
    colums_name, table_name = "similarity{}".format(i), "table{}".format(i)
    similarityes = np.random.randn(1000)

    hist, bins, _ = plt.hist(similarityes, bins=10)
    imfile = "{}.png".format(colums_name)
    plt.savefig(imfile)
    # im = plt.imread(imfile)
    # Log the image
    # clear figure
    plt.clf()
```

</div>
</details>

<details><summary>変更後のコード(test.py)</summary><div>

```python:test.py
from matplotlib import pyplot as plt
import numpy as np

for i in range(10):
    colums_name, table_name = "similarity{}".format(i), "table{}".format(i)
    similarityes = np.random.randn(1000)

    hist, bins, _ = plt.hist(similarityes, bins=10)
    test = "test"
    test2 = "test2"
    test3 = "test3"
    imfile = "{}.png".format(colums_name)
    plt.savefig(imfile)
    # clear figure
    yyy = "yyy"
    plt.cla()
```

</div>
</details>

## 変更状況
変更状況は以下のようになっている（上記のgif画像の最初の画像と同じ）と仮定し、このファイルの変更を例にして、その変更の順番通りに使い方を説明します。

```
$ ls -a
.git test.py
$ git diff
diff --git a/test.py b/test.py
index 20adb80..788eb57 100644
--- a/test.py
+++ b/test.py
@@ -6,9 +6,11 @@ for i in range(10):
     similarityes = np.random.randn(1000)
 
     hist, bins, _ = plt.hist(similarityes, bins=10)
+    test = "test"
+    test2 = "test2"
+    test3 = "test3"
     imfile = "{}.png".format(colums_name)
     plt.savefig(imfile)
-    # im = plt.imread(imfile)
-    # Log the image
     # clear figure
-    plt.clf()
+    yyy = "yyy"
+    plt.cla()
```

## 使い方の基本
hunkと呼ばれるgitが勝手につくってくれるgit diffの塊ごとに編集
具体的には、

```
$ git checkout -p test.py
diff --git a/test.py b/test.py
index 20adb80..788eb57 100644
--- a/test.py
+++ b/test.py
@@ -6,9 +6,11 @@ for i in range(10):
     similarityes = np.random.randn(1000)
 
     hist, bins, _ = plt.hist(similarityes, bins=10)
+    test = "test"
+    test2 = "test2"
+    test3 = "test3"
     imfile = "{}.png".format(colums_name)
     plt.savefig(imfile)
-    # im = plt.imread(imfile)
-    # Log the image
     # clear figure
-    plt.clf()
+    yyy = "yyy"
+    plt.cla()
(1/1) Discard this hunk from worktree [y,n,q,a,d,s,e,?]?
```

となるので、`?`の横に`y`と打てば、ここで表示されている変更部分は全て破棄されます。

<details><summary>上記コマンド実行後のコード</summary><div>

```pyton:test.py
from matplotlib import pyplot as plt
import numpy as np

for i in range(10):
    colums_name, table_name = "similarity{}".format(i), "table{}".format(i)
    similarityes = np.random.randn(1000)

    hist, bins, _ = plt.hist(similarityes, bins=10)
    imfile = "{}.png".format(colums_name)
    plt.savefig(imfile)
    # im = plt.imread(imfile)
    # Log the image
    # clear figure
    plt.clf()
```

</div>
</details>


## 少し応用(hunkの分割)
上記と同じような状況で`?`の横に`s`と打つと以下のようになります。

```
(1/1) Discard this hunk from worktree [y,n,q,a,d,s,e,?]? s
Split into 3 hunks.
@@ -6,5 +6,8 @@
     similarityes = np.random.randn(1000)
 
     hist, bins, _ = plt.hist(similarityes, bins=10)
+    test = "test"
+    test2 = "test2"
+    test3 = "test3"
     imfile = "{}.png".format(colums_name)
     plt.savefig(imfile)
(1/3) Discard this hunk from worktree [y,n,q,a,d,j,J,g,/,e,?]? 
```

今回の場合はhunkが3つに分割されます。
これで分割されたhunkごとに変更の破棄ができるようになります。

`s`を入力した後すぐに、最初のhunkに対してどうしますか？と再度聞かれるようになるので、分割前と同様に`y`と入力すれば、分割された最初のhunkで表示されている変更は全て破棄されます。

## 1行ずつ変更(さらに応用)
本来なら1行ずつ変更したいので、もっと分割してほしいところですが、ここでもう一度`s`を入力しても分割してくれません。。
なので、その場合は`?`の横に`e`を入力して、直接編集します。
すると以下のようなvim/neovimが立ち上がります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516498/fc982ff6-8b9d-5076-afae-03e7bbe396f2.png)

これ以降は分割されたhunckに対して、編集画面を立ち上げたうえでの編集方法を順番に見ていきます。
編集と保存にはvimを使っているので、わからない場合はvimの操作方法を調べてください。(例えば、[よく使う Vim のコマンドまとめ](https://qiita.com/hide/items/5bfe5b322872c61a6896))

### 1つ目のhunck(追加分の取り消し)
1つ目は元ファイルからの追加分だけが差分として表示されています。
この時の編集の仕方を示していきます。
ここで例として、`test2 = "test2"`の行だけ変更を元に戻したい（その行を削除したいとした）時を考えます。
この場合の編集方法は以下です。

`test2 = "test2"`の行以外の`+`を` `(空白)に変えて保存後、閉じます。
画像で示すと、以下のように編集・保存して、閉じます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516498/43d18192-d14e-7989-e8fb-2fbf99cf4f97.png)
閉じると、以下のような画面に戻って次のhunkについてどうするか聞かれます。

```
(1/3) Discard this hunk from worktree [y,n,q,a,d,j,J,g,/,e,?]? e
@@ -9,5 +12,3 @@
     imfile = "{}.png".format(colums_name)
     plt.savefig(imfile)
-    # im = plt.imread(imfile)
-    # Log the image
     # clear figure
(2/3) Discard this hunk from worktree [y,n,q,a,d,K,j,J,g,/,e,?]?
```

### 2つ目のhunck(削除分の取り消し)
2つ目は元ファイルからの削除分だけが差分として表示されています。
この時の編集の仕方を記載していきます。
例によって、1行ずつ編集したいので、`?`の横に`e`と入力して編集画面を立ち上げます。
今回の例の場合は、以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516498/6ea7e0cf-c0eb-f638-0679-7152a421a0f2.png)

ここで例として、`# Log the image`の行だけを復活したいとした場合を考えます。

この時の編集方法は、以下です。
その行だけ残してほかの変更部分は行ごと削除して保存後、閉じます。
画像で示すと、以下のように編集・保存して閉じます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516498/80e1c8d9-431b-ad7c-0a11-5b5db2b43f93.png)
閉じると、以下のような画面に戻って次のhunkについてどうするか聞かれます。

```
(2/3) Discard this hunk from worktree [y,n,q,a,d,K,j,J,g,/,e,?]? e
@@ -13,2 +14,3 @@
     # clear figure
-    plt.clf()
+    yyy = "yyy"
+    plt.cla()
(3/3) Discard this hunk from worktree [y,n,q,a,d,K,g,/,e,?]?
```

### 3つ目のhunck(削除分と追加分が混在している場合)
3つ目は元ファイルからの削除分も追加分も差分として表示されています。
つまり、vscodeでいうところの青い部分のことを指します。
今回の例では、以下のような変更部分です。

| 変更前 | 変更後 |
|:-------|:-------|
| `plt.clf()` | `yyy = "yyy"` <br> `plt.cla()`|

この時の編集の仕方を示していきます。
編集方法は特に何も変わりません。今までの方法を組み合わせるだけでできます。

ここで例として、`plt.clf()`を編集して、書き換えたかった文字は、`yyy = "yyy"`というものに戻したい場合（`plt.cla()`はいらないし、`plt.clf()`も復活しなくていい場合）を考えます。

この時の編集方法は、以下です。
`yyy = "yyy"`の前の`+`を` `に変え、`plt.clf()`の行を削除して保存後、閉じます。
画像で示すと、以下のように編集・保存後、閉じます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516498/dac9fb70-b8e5-ce3f-55ac-5957b30a523e.png)
閉じると、全てのhunckについての編集が終わったので、以下のような画面に戻って編集作業が終了します。

```
(3/3) Discard this hunk from worktree [y,n,q,a,d,K,g,/,e,?]? e

$  
```

#### 余談
もう一つの例として、ただ単に`plt.clf()`を復活して本当に今の変更をなかったことにしたいだけの場合（`plt.cla()`と`yyy = "yyy"`の追加分がいらない場合）を考えます。

この時は、何も編集せずに保存して閉じれば実現できます。
画像で示すと、以下のような編集です。
（この場合は、編集ではなく、初めから`y`を入力しても同じですし、そのほうが楽かもしれません。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516498/5fd1054e-05b3-f6ca-eeab-6c88490b519b.png)
この場合も閉じると、全てのhunckについての編集が終わったので以下のような画面に戻って編集作業が終了します。

```
(3/3) Discard this hunk from worktree [y,n,q,a,d,K,g,/,e,?]? e

$  
```

# 総括
なるべく分かり易いようにまとめたつもりですが、あくまで自分のメモ要素が強いので冗長だったり、分かりにくかったりするかもしれません。。
ですが、もし自分だけでなく誰かの役に立っていることがあったとしたら、うれしいです。

## 使い方のまとめ
- 大きな塊で処理したいだけなら、`git checkout -p <file>`の後に`y`でOK
- もう少し小さい塊でやりたいなら、`git checkout -p <file>`の後に`s`を入力してから、`y`を入力
- 1行ずつ細かく見たいなら、`git checkout -p <file>`の後に`e`を入力して、直接編集
- 編集の思想としては基本的に何も編集しない場合は全ての変更を破棄するという思想です。（残したければ、あるいは復活させたくなければ編集する。）

# 参考文献
- [git diff で変更を比較する](https://www.atlassian.com/ja/git/tutorials/saving-changes/git-diff)
- [git add -p 使ってますか？](https://qiita.com/cotton_desu/items/bf08ac57d59b37dd5188)
- [Gitで編集したファイルの1部分だけをコミットする方法](https://parashuto.com/rriver/tools/git-add-patch-option)
- [よく使う Vim のコマンドまとめ](https://qiita.com/hide/items/5bfe5b322872c61a6896)

# 付録
今回の例で行ったコマンドの全体像のスクショ：
<details><summary>スクショ</summary>
![スクリーンショット 2021-07-12 080647.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516498/a9b7efa9-2f3c-3093-ccc8-5b3851e6ae94.png)
</details>
今回取り上げた1行ずつ編集する例の通りに変更を元に戻した時のコード：
（ただし、最後の行は余談ではないほうを選んだ場合のコード）
<details><summary>test.py</summary><div>

```python:test.py
from matplotlib import pyplot as plt
import numpy as np

for i in range(10):
    colums_name, table_name = "similarity{}".format(i), "table{}".format(i)
    similarityes = np.random.randn(1000)

    hist, bins, _ = plt.hist(similarityes, bins=10)
    test = "test"
    test3 = "test3"
    imfile = "{}.png".format(colums_name)
    plt.savefig(imfile)
    # Log the image
    # clear figure
    yyy = "yyy"

```
</div>
</details>