【Git初心者へ送る記事②】やっちまった！を回避するためのコマンド集

本記事はQmonus Value Streamの投稿キャンペーン記事です。
# はじめに
初めてチーム開発に参加したとき、git操作にビビりまくる時期は誰しもが通るはず
そこで今回は、「やべぇ、やっちまった！」という状況に焦点を当てて、ピンチを乗り切るGitコマンド集を紹介していきます！

## ちょっとここでクイズ:writing_hand:

このgitコマンドは何でしょう？？  「**`git rerere`**」

これがわかる方はきっとチーム開発の経験がおありだと思いますので、あまりこの記事では得られるものがないと思いますが、もしわからない方がいたらこの記事は役に立つかもしれません。

### ヒント
初心者の時に一番ビビってしまうのがコンフリクトなのではないでしょうか？？
そんなコンフリクトのストレスを激減させてくれるコマンドが **`git rerere`** なのです。

## 解説 git rerere
git rerere（ **Reuse Recorded Resolution** ）はめっちゃくちゃ便利な機能で、**以前に解決したマージコンフリクトを Git が記憶し、同様のコンフリクトが再び発生した際に自動的に適用**してくれるという優れものです。

下の図をご覧ください。

```mermaid
sequenceDiagram
    participant D as 開発者
    participant G as Gitリポジトリ
    participant R as Rerereキャッシュ

    D->>G: コンフリクトを解決
    G->>R: 解決方法を記録
    Note over R: コンフリクトパターンと<br>解決方法を保存

    D->>G: 同様のコンフリクトが発生
    G->>R: 解決方法を問い合わせ
    R-->>G: 記録された解決方法を提供
    G-->>D: 自動的にコンフリクトを解決
```

### 使い方

git rerere を有効にするには、以下のコマンドを使用します。
```
git config --global rerere.enabled true
```

このコマンドをプロジェクトディレクトリで実行しておくことによって、**一度起きたコンフリクトと同じ内容のコンフリクトが再度起こった時**に、記憶しているgitが自動で解決してくれるます。

## 例
1回目のコンフリクト(ここは自分で解決)

```bash
git merge <branch>  # コンフリクトが発生
# コンフリクトを解決
git add <conflicted_files>
git commit -m "Resolved conflict"
```

2回目の同じコンフリクトが起こった(ここでは自動で解決してくれる)

```bash
git merge <branch>  # 1回目と同じコンフリクトが発生
# ここでgit rerereが自動的にコンフリクト解決を適用してくれる
git status  # コンフリクトが解決されたことを確認しましょう
```

というような感じで、ピンチの時に役に立つコマンドを10個ほど紹介していきますので是非、最後まで読んでみてください:muscle:

## 1. git merge --abort

**状況**: mergeしてconflict発生。いろいろやったけどやっぱりmergeやめたい場合

**使用方法**:
```
$ git merge --abort
```

**説明**: 
- マージプロセスを中止し、マージ開始前の状態に戻します。
- 変更内容は失われますが、元の状態に簡単に戻れます。

## 2. git reset

**状況**: 直前のcommitを取り消したい、または特定のcommitまで戻りたい場合

**使用方法**:
```
# 直前のcommitを取り消し（変更はステージングエリアに残る）
$ git reset --soft HEAD^

# 直前のcommitを完全に取り消し（変更も削除）
$ git reset --hard HEAD^

# 特定のcommitまで戻る
$ git reset --hard <commit-hash>
```

**説明**:
- `--soft`: commitを取り消すが、変更内容は保持します。
- `--hard`: commitと変更内容両方を取り消します（注意が必要）。

## 3. git revert

**状況**: 特定のcommitの変更を打ち消したいが、履歴は残したい場合

**使用方法**:
```
$ git revert <commit-hash>
```
※commitのhashは`$ git log`で確認


**説明**:
- 指定したcommitの変更を打ち消す新しいcommitを作成します。
- **履歴を書き換えない**ため、共有リポジトリでも安全に使用できます。

## 4. git stash

**状況**: 作業中の変更を一時的に退避させたい場合

**使用方法**:
```
# 変更を退避
$ git stash

# 退避した変更を確認
$ git stash list

# 退避した変更を復元
$ git stash apply
```

**説明**:
- 作業中の変更を一時的に保存し、クリーンな状態に戻します。
- **ブランチの切り替えや緊急の作業が必要な場合**に便利です。

## 5. git reflog

**状況**: 誤って削除したcommitや失われたブランチを復元したい場合

**使用方法**:
```
# reflogを表示
$ git reflog

# 特定の状態に戻る
$ git reset --hard <reflog-entry>
```

**説明**:
- gitの操作履歴を表示し、失われたcommitやブランチを見つけることができます。
- 最後の手段として使用し、慎重に操作する必要があります。

## 注意事項

- `git reset --hard`や`git reflog`を使用する際は、変更内容が完全に失われる可能性があるため、十分な注意が必要です。
- 重要な変更を行う前に、必ずバックアップを取ることをお勧めします。
- 共有リポジトリで作業している場合、`git revert`の使用を検討してください。履歴を書き換えない方法が望ましいです。
- 
## 6. git cherry-pick

**状況**: 特定のcommitだけを現在のブランチに適用したい場合

**使用方法**:
```
$ git cherry-pick <commit-hash>
```

**説明**:
- 他のブランチの特定のcommitを現在のブランチに適用します。
- **バグ修正や特定の機能だけ**を取り込みたい場合に便利です。

## 7. git commit --amend

**状況**: 直前のcommitメッセージを変更したい、または小さな変更を追加したい場合

**使用方法**:
```
# commitメッセージを変更
$ git commit --amend

# 変更を追加してcommitを修正
$ git add <変更したファイル>
$ git commit --amend
```

**説明**:
- 直前のcommitを修正します。
- **push済みのcommit**には使用しないでください。

## 8. git clean

**状況**: 追跡されていないファイルをすべて削除したい場合

**使用方法**:
```
# 削除されるファイルを確認
$ git clean -n

# 実際に削除
$ git clean -f
```

**説明**:
- 追跡されていないファイルを削除し、ワーキングディレクトリをクリーンにします。
- 注意: 削除されたファイルは復元できません。

## 9. git bisect

**状況**: バグが発生したcommitを特定したい場合

**使用方法**:
```
$ git bisect start
$ git bisect bad  # 現在の状態が悪いことを指定
$ git bisect good <known-good-commit>  # 正常だったcommitを指定

# 各チェックアウトごとにテスト
$ git bisect good  # または $ git bisect bad

# 終了時
$ git bisect reset
```

**説明**:
- バイナリサーチを使用して、**バグが導入されたcommit**を特定します。
- 大規模なプロジェクトでバグの原因を素早く見つけるのに役立ちます。。

## 10. git push の取り消し

pushを取り消す方法はいくつかありますが、ここでは主に2つの方法を紹介します。状況に応じて適切な方法を選んでください。

### 方法1: 新しい取り消しcommitを作成する（推奨）

この方法は安全で、他の開発者への影響が最小限に抑えられます。

#### 手順:

1. 取り消したいcommitの直前の状態に戻す新しいcommitを作成します。
   ```
   $ git revert <取り消したいcommitのハッシュ>
   ```

2. 変更をpushします。
   ```
   $ git push origin <ブランチ名>
   ```

#### 利点:
- 履歴が保持されるため、安全です。
- 他の開発者への影響が最小限です。

#### 欠点:
- 取り消しのための新しいcommitが履歴に残ります。

### 方法2: 強制pushを使用する（注意が必要）

この方法は履歴を書き換えるため、注意して使用する必要があります。

#### 手順:

1. ローカルの履歴を戻します。
   ```
   $ git reset --hard <戻したいcommitのハッシュ>
   ```
   
   </br></br>
2. 変更を強制pushします。
   ```
   $ git push -f origin <ブランチ名>
   ```

#### 利点:
- クリーンな履歴が維持されます。

#### 欠点:
- リモートの履歴が書き換えられるため、他の開発者に影響を与える可能性があります。
- 強制pushは危険な操作であり、データ損失のリスクがあります。

## 注意事項

1. pushの取り消しを行う前に、必ずチームメンバーに通知してください。

2. 可能な限り、`revert` が好ましい。特に共有ブランチ（mainやdevelopなど）では、この方法が推奨されます。

3. 強制pushは個人的なfeature branchでのみ使用し、共有ブランチでは避ける方がいい。

4. 強制pushを行う前に、必ずバックアップを取ってください。


pushの取り消しは慎重に行う必要がある操作です。常に安全側に立ち、不確かな場合は経験豊富な開発者に相談してください。

# おまけ
##  知っておくと便利なgit pull --rebase

1. **初期状態**:
    - リモートリポジトリ（`origin`）にはコミット `A`, `B`, `C` が存在します。
    - ローカルリポジトリには、リモートリポジトリと同じコミット `A`, `B`, `C` が存在し、さらに新しいコミット `D`, `E` が加えられています。
    ```react
    リモートリポジトリ: A - B - C
    ローカルリポジトリ: A - B - C - D - E
    ```
    </br></br>
    
2. **リモートに新しいコミットが追加**:
    - リモートリポジトリに新しいコミット `F`, `G` が追加されます。

    ```react
    リモートリポジトリ: A - B - C - F - G
    ローカルリポジトリ: A - B - C - D - E
    ```
    </br></br>
    
3. **`git pull --rebase` の実行**:
    - ローカルリポジトリで `git pull --rebase origin` を実行します。
    - このコマンドは、まず `git fetch` を行い、リモートの変更をローカルの追跡ブランチに取り込みます。
    - **次に、ローカルのコミット `D`, `E` を退避させ、リモートの新しいコミット `F`, `G` を適用し、その後に退避させていた `D`, `E` を再適用します。**
    
    ```
    リモートリポジトリ: A - B - C - F - G
    ローカルリポジトリ:  A - B - C - F - G - D'- E'
    ```
    

このプロセスにより、ローカルブランチのコミット `D`, `E` がリモートリポジトリの最新の変更の上に再配置されて履歴が線形に保たれます。

**また、`D'`, `E'` は元の `D`, `E` と同じ内容ですが、新しいベース（`G`）からの変更として再適用されています。**

# おわりに
ひとまず初心者の方はこれだけ知っておけばなんどか乗り越えられるでしょうというコマンドを紹介しました。
pushの取り消し以外はそこそこ使用する場面に出くわすかと思いますので、そのよきにこの記事が役に立てばいいかなと思います。