開発中に間違ってCommit,Push,Mergeした時の対処法[git revertとgit reset(soft mixed hardの違い)]

この記事では開発途中にプルリクエストを間違ってマージしてしまった際やmainブランチに直接Pushした際，変更内容を戻す(revert,reset)方法について記載する．


今回の操作は基本的なgit操作を熟知している人が行うべきである．そのため以下の記事の内容を知っていることは前提とする

https://qiita.com/JavaLangRuntimeException/items/6b46551f56e0def76eba



**そもそも...**

:::note alert
親ブランチに直接プッシュ，マージする際はプロジェクトマネージャーに相談し,勝手に親ブランチに変更を加えないようにすること
:::
:::note info
githubではプルリクエストができてマージができないようにするWriter権限がある，admin権限付与は適切な人のみにすること．
:::

# git revert git resetとは
## 事象確認
![スクリーンショット 2024-05-22 11.33.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/b07c92fe-ad5c-cdbb-24b2-197f30ead02a.png)

このような作業ツリーの構造だった場合，
**1．コミットを取り消したい！**
**2．細かくコミットしすぎたのでまとめたい！**
**3．featureブランチから直接mainブランチにマージしてしまった！**，
**4．developmentブランチからmainブランチに間違ったタイミングでマージしてしまった！**， 
**5．mainブランチ/developmentブランチに直接プッシュしてしまった！**，
時に，どのように対処するかについて記載する．

## revertとresetの違い
### 使用シナリオ
**git revert**
公開されたコミットを取り消す際に使用される．コミット履歴を維持しながら，特定のコミットの変更を元に戻すことができる．

**git reset**
ローカルでのコミットの取り消しや，ブランチを特定のコミットに戻す際に使用される．通常，公開されていないコミットに対して使用する．

### 動作の違い
**git revert**
指定したコミットの変更を取り消すための新しいコミットを作成する．元のコミットは履歴に残り，新しいコミットで変更が打ち消される．

**git reset**
現在のブランチを指定したコミットまで移動させ，それ以降のコミットを取り消す．コミット履歴が変更され，指定したコミット以降のコミットは削除される．

### コミット履歴への影響
**git revert**
新しいコミットを作成することでコミットを取り消すため，**コミット履歴が維持される**．取り消したコミットと新しいコミットの両方が履歴に残る．

**git reset**
指定したコミット以降のコミットを完全に削除するため，**コミット履歴が変更される**．取り消したコミットはなくなる．

### 作業ツリーとインデックスへの影響
**git revert**
作業ツリーとインデックスを更新する．新しいコミットによって，作業ツリーとインデックスが変更される．

**git reset**
作業ツリーとインデックスを指定したコミットの状態に戻す．--hardオプションを使用すると、作業ツリーとインデックスの変更が失われる．

### リモートリポジトリへの影響
**git revert**
作成された新しいコミットは，通常のgit pushでリモートリポジトリにプッシュできる．他の開発者のリポジトリに影響を与えることはない．

**git reset**
変更したコミット履歴をリモートリポジトリにプッシュするには，通常はgit push --forceが必要．これにより、他の開発者のリポジトリに影響を与える可能性がある．



:::note info
一般的に，git revertは，コミット履歴を維持し，他の開発者への影響を最小限に抑えるために推奨される．git resetは，ローカルでのコミットの操作に適している．
:::

:::note warn
**git reset** を使用する場合は，リモートリポジトリへの影響を考慮し，他の開発者との調整が必要．
**git revert** を使用する場合は，取り消すコミットを正確に指定し，新しいコミットのメッセージを適切に記述することが重要．
:::

## git resetの種類
git resetは3種類のオプションがある．
**git reset --soft**
**git reset --mixed**
**git reset --hard**
である．
>resetの種類はこの記事でも説明している
>https://qiita.com/bearl27/items/288ead3137bef3670d94

### HEADとoriginについて
git resetの種類を知る前に,Gitを触っているとよく見る**HEAD**と**origin**について説明する．

 ![スクリーンショット 2024-05-22 20.40.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/cfa383ff-9d2b-8cb6-0533-7e1d0135448e.png)


**HEAD**
現在作業しているコンテキスト(コミット)で，通常，チェックアウトしたブランチの最新のコミットを表す．

**origin**
 Git で最も一般的に使用されるリモートリポジトリの参照名であり，リモートリポジトリとの間でコードの同期を行う際に使用される．つまりローカルブランチとリモートブランチの区別をするためにリモートブランチにつける名前のこと．

### ワーキングエリアとステージングエリアについて
resetの仕組みはワーキングエリアとステージングエリアがおおいに関係している
![スクリーンショット 2024-05-22 20.43.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/e41bccc2-bbdd-89cf-d68e-72d2cae8305c.png)

**ワーキングエリア**
プロジェクトの実際のファイルが存在する場所．ファイルの編集、新規作成、削除などの作業はすべてワーキングディレクトリで行われる．

**ステージングエリア**
次のコミットに含める変更を準備する場所．つまりローカルブランチに変更するファイルを確認する最終ステージ．


### git reset --softとは
```
git reset --soft  <コミットのハッシュ>
```

HEADのみが指定したコミットに移動する，ステージングエリアとワーキングエリアの内容は変更されない．つまり指定したコミットにステージングエリアの内容が集約される．これは、現在の変更を保持したまま、新しいコミットを行う，つまり**コミット(ファイルの変更履歴)をまとめる**時に便利である．
![スクリーンショット 2024-05-22 21.40.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/c55875da-0fa6-13f6-cf52-cb40b01f21f2.png)


### git reset --mixedとは
```
git reset --mixed  <コミットのハッシュ>
```
このオプションを使用すると，HEAD とステージングエリアが指定したコミットに移動するが，ワーキングエリアは影響を受けない．つまり，ステージングされた変更は取り消されるが，ワーキングエリア内のファイル自体の編集内容はそのまま残る．**コミットを取り消し，git addするファイルを変更する**際に便利である．
![スクリーンショット 2024-05-22 21.48.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/2bfb50bd-5fa7-3c69-c9ad-3957d5c27362.png)

### git reset --hardとは
```
git reset --hard  <コミットのハッシュ>
```
HEAD，インデックス，そしてワーキングディレクトリがすべて指定したコミットの状態に完全にリセットされる．これにより，現在のブランチ上のすべてのローカル変更（ステージングおよび未ステージングの変更）が失われる．**変更履歴をなくし，現在作業しているファイルを特定のコミットの状態に戻したい**時に便利である．

![スクリーンショット 2024-05-23 21.42.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/1076a117-00b2-f758-9718-18e2b6493db1.png)



# 実際の事例からどう対処するのか
ここからは実際に起こりうる例に基づいてどのように対処するかの例を記載する．

## 直前のコミットを取り消したい

>リモートブランチのコミットを取り消す場合ではなくローカルブランチの直前のコミットを取り消す
```
git reset --mixed HEAD^
```
:::note info
これでローカルブランチの最新のコミットが解除されるのでコミットし直してプッシュすればリモートに反映される．(mainブランチだった場合)
```
git push -f origin/main
```
:::


## mainブランチで細かくコミットしすぎたので1つのコミットにまとめたい

:::note info
これをすることで複数のコミットが一つにまとめられる
:::

mainブランチをチェックアウト
```
git checkout main
```

リセットをする．
これはワーキングエリアやステージングエリアの内容を保持したままHEADを戻すことができる．
```md:HEAD(現在の作業位置)からnこ前のコミットにHEADを戻す．
git reset --soft HEAD~n
```
>`HEAD~n`の部分は`git log`によって出てくるコミットハッシュを指定しても良い．指定した場合，HEADから指定したコミットまでコミットをまとめることができる

これでnこ分の変更内容が`git add`された状態になる．
この後commitをする．
```
git commit -m "Commit message"
```
これでnこ分の変更内容が`git commit`された状態になった．

:::note info
一個前のコミットに追加でファイルをコミットしたい際には以下のコマンドを実行する
```md:sample.txtを追加する
git add sample.txt
git commit --amend -m "Update Commit message"
```
>`git commit --amend -m "Update Commit message"`のみ実行すればコメントのみ変更できる

:::

その後，リモートにフォースプッシュする．
```
git push -f origin main
```
>この操作はgit rebaseを使うと操作しやすい．以下の記事を参考にすること．
>[鋭意製作中]

## 間違えたコミットを残して変更内容を戻したい

:::note info
これをすることで変更したコミットを残しつつ取り消すことができる(mainブランチを例にする)
:::

mainブランチをチェックアウト
```
git checkout main
```
取り消したいコミットの1つ前のコミットのハッシュを確認
```
git log
```
取り消したいコミットのハッシュをメモして，以下のコマンドを実行する．
```
git revert <取り消したいコミットのハッシュ>
```
この後コミットメッセージを入力するためのエディタが出てくるので1行目にコメントを入力する．
このエディタを保存して閉じればコミットできる．

:::note info
エディタを保存して閉じる方法(デフォルトのVimなら `Esc`→`:wq`と入力)
:::

mainブランチ(ローカルブランチ)の変更をorigin/mainブランチ(リモートブランチ)にプッシュする．
```
git push -f origin main
```



## ある地点までのすべてのコミットを削除して変更内容を戻したい

:::note warn
これをすると指定したコミットより後のコミットが全て削除される．
リモートブランチにプッシュする際は履歴が変更されるため，他の開発者は自分のローカルリポジトリを更新する必要がある．
(mainブランチを例にする)
:::

mainブランチをチェックアウト
```
git checkout main
```
取り消したいコミットの1つ前のコミットのハッシュを確認
```
git log
```
取り消したいコミットの1つ前のコミットのハッシュをメモして，以下のコマンドを実行し，メモしたコミットまでmainブランチをリセットする．
```
git reset --hard <コミットのハッシュ>
```
mainブランチ(ローカルブランチ)の変更をorigin/mainブランチ(リモートブランチ)にフォースプッシュする．
```
git push -f origin main
```
> これでコンフリクトを無視してプッシュできる

:::note info
featureブランチから間違ってmainブランチにマージしてしまった時やdevelopmentから間違ったタイミングでマージした時も同様の処理をする．
:::



## developmentブランチの内容をmainブランチに強制的に上書きしたい

:::note warn
これをするとmainブランチの現在の内容が完全に失われるので注意．
:::

developmentブランチをチェックアウト
```
git checkout development
```

origin/developmentブランチ(リモートブランチ)から最新の状態をdevelopmentブランチ(ローカルブランチ)に同期する．
```
git pull origin development
```
mainブランチをチェックアウト
```
git checkout main
```
origin/mainブランチ(リモートブランチ)から最新の状態をmainブランチ(ローカルブランチ)に同期する．
```
git pull origin main
```
mainブランチの内容を破棄して，developmentブランチの内容で上書きする．
```
git reset --hard development
```
mainブランチ(ローカルブランチ)の変更をorigin/mainブランチ(リモートブランチ)にフォースプッシュする．
```
git push -f origin main
```

これで完了