【Git】個人的によく使うコマンドやオプションをまとめる

# はじめに
[Git Advent Calendar 2023 15日目](https://qiita.com/advent-calendar/2023/git)の投稿です。よろしくおねがいします。
ハンズオン形式で個人的によく使うコマンドをまとめておきます。完全に個人用のメモですが、ちょっと忘れたときなどに誰かの参考になればいいな～と思ってまとめます。

## まとめるコマンド（随時更新してくつもり）
- `git diff`
- `git reset`
- `git checkout`
- `git chery-pick`
- `git revert`
- `git rebase`


# git diff
`git diff`は2つのツリー間の差分をとるコマンド。

- `git diff`
    - 作業ディレクトリとインデックスの差分をとる。
    - 作業していて修正箇所がなんなのか確認するときによく使う。
- `git diff commit`
    - 作業ディレクトリと`commit`間の差分をとる。
    - あんま使わない。
- `git diff --cached commit`
    - インデックスと指定されたコミットの差分をとる。
    - `commit`に`HEAD`を指定してコミットする内容が想定通りか確認をするときに使う。
- `git diff commit1 commit2`
    - `commit1`と`commit2`の差分をとる。
    - コミット間でどういう修正があったか確認するときによく使う。

## ファイル名のみ差分取る
`git diff commit1 commit2 --name-only`

## 例
### 準備
リポジトリに「hello1」、インデックスに「hello2」、作業ディレクトリに「hello3」と書いた「sample.txt」ファイルを用意しておきます。
```batch
~/Desktop/git/sample3 (master)
$ echo "hello1" > sample.txt

~/Desktop/git/sample3 (master)
$ git add .

~/Desktop/git/sample3 (master)
$ git commit -m"first commit"

~/Desktop/git/sample3 (master)
$ git log --oneline
4256de5 (HEAD -> master) first commit

~/Desktop/git/sample3 (master)
$ echo "hello2" > sample.txt

~/Desktop/git/sample3 (master)
$ git add .

~/Desktop/git/sample3 (master)
$ echo "hello3" > sample.txt
```

### 実行

※「hello1」がコミット、「hello2」がインデックス、「hello3」が作業ディレクトリです。
```batch
~/Desktop/git/sample3 (master)
$ git diff
diff --git a/sample.txt b/sample.txt
index 14be0d4..1b069b1 100644
--- a/sample.txt
+++ b/sample.txt
@@ -1 +1 @@
-hello2
+hello3

~/Desktop/git/sample3 (master)
$ git diff 4256de5
diff --git a/sample.txt b/sample.txt
index 15b8f2a..1b069b1 100644
--- a/sample.txt
+++ b/sample.txt
@@ -1 +1 @@
-hello1
+hello3

~/Desktop/git/sample3 (master)
$ git diff --cached 4256de5
diff --git a/sample.txt b/sample.txt
index 15b8f2a..14be0d4 100644
--- a/sample.txt
+++ b/sample.txt
@@ -1 +1 @@
-hello1
+hello2
```

# git reset
`git reset`はリポジトリとインデックスと作業内容を既知の状態に変更するコマンド。

- `git reset --soft commit`
    - `HEAD`の参照とブランチの参照を指定されたコミットに変更。
    - インデックスと作業ディレクトリの内容は変更されない。
    - インデックスと作業ディレクトリの作業内容のコミット先を変えたいときに使う。
- `git reset --mixed commit`
    - `HEAD`の参照とブランチの参照を指定されたコミットに変更。
    - インデックスの内容もそのコミットが表すツリー構造にあうように変更。
    - 作業ディレクトリの内容は変更されない。
    - 個人的にはあんま使わない。
- `git reset --hard commit`
    - `HEAD`もインデックスも作業ディレクトリも変更。
    - 作業を無に帰したいときに使う。マージした後に`ORIG_HEAD`したり。

## 例
### 準備
リポジトリに「hello1」、インデックスに「hello2」、作業ディレクトリに「hello3」と書いた「sample.txt」ファイルを用意しておきます。
```batch
~/Desktop/git/sample3 (master)
$ echo "hello1" > sample.txt

~/Desktop/git/sample3 (master)
$ git add .

~/Desktop/git/sample3 (master)
$ git commit -m"first commit"

~/Desktop/git/sample3 (master)
$ git log --oneline
4256de5 (HEAD -> master) first commit

~/Desktop/git/sample3 (master)
$ echo "hello2" > sample.txt

~/Desktop/git/sample3 (master)
$ git add .

~/Desktop/git/sample3 (master)
$ echo "hello3" > sample.txt

~/Desktop/git/sample3 (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sample.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   sample.txt
```

### 実行
とりあえず`git reset --soft HEAD`して作業ディレクトリとインデックスが変わらないことを確認です。
```batch
~/Desktop/git/sample3 (master)
$ git reset --soft HEAD

~/Desktop/git/sample3 (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sample.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   sample.txt
```
次に`git reset --mixed HEAD`してインデックスが変わることを確認です。
今回、`HEAD`の内容にインデックスをリセットするので、`git status`した時に`Changes to be committed:～～`の内容が無くなることを期待します。

```batch
~/Desktop/git/sample3 (master)
$ git reset --mixed HEAD
Unstaged changes after reset:
M       sample.txt

~/Desktop/git/sample3 (master)
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   sample.txt

no changes added to commit (use "git add" and/or "git commit -a")
```
`unstaged`されました。

次に`git reset --hard`します。
ステージされてるファイルがなくなったので、インデックスと作業ディレクトリにファイル修正中のファイルがあるようにしておきます。
```batch
~/Desktop/git/sample3 (master)
$ git add .

~/Desktop/git/sample3 (master)
$ echo "hello4" > sample.txt

~/Desktop/git/sample3 (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sample.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   sample.txt
```

`git reset --hard`します。

```batch
~/Desktop/git/sample3 (master)
$ git reset --hard HEAD
HEAD is now at 4256de5 first commit

~/Desktop/git/sample3 (master)
$ git status
On branch master
nothing to commit, working tree clean
```
いままでの作業が無に帰り、一番初めのコミットの内容にインデックスも作業ディレクトリの内容もリセットされました。

`HEAD`にリセットすると分かりにくいかもしれないので、複数コミットを作って過去のコミットにリセットしてみます。
```batch
説明：もうひとつコミットをつくる
~/Desktop/git/sample3 (master)
$ echo "hello2" > sample.txt

~/Desktop/git/sample3 (master)
$ git add .

~/Desktop/git/sample3 (master)
$ git commit -m"second commit"
[master 6c08bf5] second commit
 1 file changed, 1 insertion(+), 1 deletion(-)

~/Desktop/git/sample3 (master)
$ git log --oneline
6c08bf5 (HEAD -> master) second commit
4256de5 first commit

説明：インデックスと作業ディレクトリに修正中のファイルを作る
~/Desktop/git/sample3 (master)
$ echo "hello3" > sample.txt

~/Desktop/git/sample3 (master)
$ git add .

~/Desktop/git/sample3 (master)
$ echo "hello4" > sample.txt

~/Desktop/git/sample3 (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sample.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   sample.txt
```
`git reset`してみます。
```batch
~/Desktop/git/sample3 (master)
$ git reset --soft 4256de5
```
インデックスと作業ディレクトリに変化はありませんが、、、
```batch
~/Desktop/git/sample3 (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sample.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   sample.txt
```

`HEAD`の位置が移動してます。
```batch
~/Desktop/git/sample3 (master)
$ git log --oneline --all
4256de5 (HEAD -> master) first commit
```
元の`HEAD`に戻します。
```batch
~/Desktop/git/sample3 (master)
$ git reset --soft 6c08bf5

~/Desktop/git/sample3 (master)
$ git log --oneline
6c08bf5 (HEAD -> master) second commit
4256de5 first commit
```

`git reset --mixed`してみます。

```batch
~/Desktop/git/sample3 (master)
$ git reset --mixed 4256de5
Unstaged changes after reset:
M       sample.txt

~/Desktop/git/sample3 (master)
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   sample.txt

no changes added to commit (use "git add" and/or "git commit -a")

~/Desktop/git/sample3 (master)
$ git log --oneline
4256de5 (HEAD -> master) first commit
```
インデックスとリポジトリに登録された内容が同一のオブジェクトになるので、ステージされてる状態がなくなり、`HEAD`が`4256de5`を指すようになりました。
元に戻って、、
```batch
~/Desktop/git/sample3 (master)
$ git reset --mixed 6c08bf5
Unstaged changes after reset:
M       sample.txt

~/Desktop/git/sample3 (master)
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   sample.txt

no changes added to commit (use "git add" and/or "git commit -a")

~/Desktop/git/sample3 (master)
$ git log --oneline
6c08bf5 (HEAD -> master) second commit
4256de5 first commit
```
最後に`git reset --hard`します。
```batch
~/Desktop/git/sample3 (master)
$ git reset --hard 4256de5
HEAD is now at 4256de5 first commit

~/Desktop/git/sample3 (master)
$ git status
On branch master
nothing to commit, working tree clean

~/Desktop/git/sample3 (master)
$ git log --oneline
4256de5 (HEAD -> master) first commit
```
いままでのすべての作業が無に帰りました。

:::note warn
`git reset --hard`をすると作業ディレクトリが指定したコミットID（何も指定しないと`HEAD`）に更新されることから、修正中のファイルがすべて消えます。特にUntrackedなファイルがある状態で`git reset --hard`すると、ファイルがなかったことになるので注意してください。
:::

# git checkout
今までブランチを指定してきた`git checkout`ですが、過去のコミットにも戻れます。
下のようなログがあったときに、、
```batch
~/Desktop/git/sample3 (master)
$ git log --oneline
4256de5 (HEAD -> master) first commit
```
2つコミットを付け足します。
```batch
~/Desktop/git/sample3 (master)
$ echo "hello10" >sample.txt

~/Desktop/git/sample3 (master)
$ git commit sample.txt -m"second commit"
[master 2e8e7e3] second commit
 1 file changed, 1 insertion(+), 1 deletion(-)

~/Desktop/git/sample3 (master)
$ cat sample.txt
hello10

~/Desktop/git/sample3 (master)
$ echo "hello20" > sample.txt

~/Desktop/git/sample3 (master)
$ git commit sample.txt -m"third commit"
[master 21e00a9] third commit
 1 file changed, 1 insertion(+), 1 deletion(-)
```

ログはこうなります。
```batch
~/Desktop/git/sample3 (master)
$ git log --oneline
21e00a9 (HEAD -> master) third commit
2e8e7e3 second commit
4256de5 first commit
```
おのおののコミット時点の「sample.txt」は↓の中身です。
- `4256de5`：hello1
- `2e8e7e3`：hello10
- `21e00a9`：hello20 ←`cat`するとわかるように現状このファイルが見れる状態
```
$ cat sample.txt
hello20
```
`4256de5`に時をもどしましょう。`git checkout commit`で`HEAD`がコミットIDに移動します。
```batch
~/Desktop/git/sample3 (master)
$ git checkout 4256de5
Note: checking out '4256de5'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 4256de5 first commit
```
ファイルの中身を確認すると、過去のファイルの中身に戻ってます。
```batch
~/Desktop/git/sample3 ((4256de5...))
$ cat sample.txt
hello1
```
作業前のもとの状態に戻るには`git checkout ブランチ名`です。
```batch
~/Desktop/git/sample3 ((4256de5...))
$ git checkout master
Previous HEAD position was 4256de5 first commit
Switched to branch 'master'

~/Desktop/git/sample3 (master)
$ git log --oneline
21e00a9 (HEAD -> master) third commit
2e8e7e3 second commit
4256de5 first commit
```

また、過去の状態を確認していて新しくブランチを作りたいときは、`git checkout -b <branchname>`で続けて作業ができます。

# git cherry-pick
`git cherry-pick コミットID`は指定したコミットIDの内容をカレントブランチに適用して新しくコミットを作ります。

`another`ブランチを作成してコミットを１つ作ります。
```batch
~/Desktop/git/sample3 (master)
$ git branch another

~/Desktop/git/sample3 (master)
$ git checkout another
Switched to branch 'another'

~/Desktop/git/sample3 (another)
$ echo "hello another" > sample.txt

~/Desktop/git/sample3 (another)
$ git commit sample.txt -m"another commit 1"
[another 0f6a11f] another commit 1
 1 file changed, 1 insertion(+), 1 deletion(-)

~/Desktop/git/sample3 (another)
$ git log --oneline --graph --all
* 0f6a11f (HEAD -> another) another commit 1
* 21e00a9 (master) third commit
* 2e8e7e3 second commit
* 4256de5 first commit
```
`git cherry-pick another`で、`master`ブランチに`another`ブランチの内容を取り込みます。
```batch
~/Desktop/git/sample3 (another)
$ git checkout master
Switched to branch 'master'

~/Desktop/git/sample3 (master)
$ git cherry-pick another
[master 231e0fe] another commit 1
 Date: Mon Nov 13 14:42:35 2023 +0900
 1 file changed, 1 insertion(+), 1 deletion(-)

~/Desktop/git/sample3 (master)
$ git log --oneline --graph --all
* 231e0fe (HEAD -> master) another commit 1
| * 0f6a11f (another) another commit 1
|/
* 21e00a9 third commit
* 2e8e7e3 second commit
* 4256de5 first commit

~/Desktop/git/sample3 (master)
$ cat sample.txt
hello another
```

:::note onfo
単純な例では競合が発生しませんでしたが、複数のコミットを`git cherry-pick`で入れ替えて取り込む場合など、競合が発生する可能性があります。
:::

# git revert
`git revert`はコミットを打ち消すコミットを作成します。
打ち消す方法としては`git reset`がありますが、`git revert`は打ち消したことがコミットとして履歴に残ります。
`master`ブランチの先頭のコミットの内容を取り消したいとします。
```batch
~/Desktop/git/sample3 (master)
$ git revert master
[master ae4838e] Revert "another commit 1"
 1 file changed, 1 insertion(+), 1 deletion(-)

~/Desktop/git/sample3 (master)
$ git log --oneline --graph --all
* ae4838e (HEAD -> master) Revert "another commit 1"
* 231e0fe another commit 1
| * 0f6a11f (another) another commit 1
|/
* 21e00a9 third commit
* 2e8e7e3 second commit
* 4256de5 first commit

~/Desktop/git/sample3 (master)
$ cat sample.txt
hello20
```
打ち消すコミットが作られて、打ち消した結果、`master`ブランチの直前のコミット`21e00a9`の内容に修正されました。

# git rebase
`git rebase`は一連のコミットの基点を変更する際に使います。
今、こういうログを用意します。
```batch
~/Desktop/git/sample3 (master)
$ git log --oneline --graph --all
* ae4838e (HEAD -> master) Revert "another commit 1"
* 231e0fe another commit 1
| * 0f6a11f (another) another commit 1
|/
* 21e00a9 third commit
* 2e8e7e3 second commit
* 4256de5 first commit
```
`another`ブランチが`21e00a9`から分岐していますが、これを任意のコミットから分岐させるのが`git rebase`です。例えば、`master`から分岐させてローカルで作業していた際にリモートの`master`ブランチの開発が進んでしまった場合に`git rebase`して最新の`master`の内容をローカルに反映させるときに使います。

早速つかってみます。`another`ブランチの基点を`ae4838e`から分岐させます。
```batch
~/Desktop/git/sample3 (master)
$ git checkout another
Switched to branch 'another'

~/Desktop/git/sample3 (another)
$ git rebase master
First, rewinding head to replay your work on top of it...

~/Desktop/git/sample3 (another)
$ cat sample.txt
hello20

~/Desktop/git/sample3 (another)
$ git checkout master
Switched to branch 'master'

~/Desktop/git/sample3 (master)
$ git log --oneline --graph --all
* ae4838e (HEAD -> master, another) Revert "another commit 1"
* 231e0fe another commit 1
* 21e00a9 third commit
* 2e8e7e3 second commit
* 4256de5 first commit
```
`master`ブランチに`another`ブランチの内容がすべてあったため、`another`ブランチを`ae4838e`に移動させるだけでした。

# さいごに
なにかで使ったコマンドはここにまとめて随時更新していこうと思います。