怖くない切り戻し git-revert / git-reset


# はじめに

master ブランチに最新版がマージされたあと、問題が見つかってリリース前に戻したいときがたまにあります。しかし `git-revert`/`git-reset` は普段使うことがないので、少し身構えてしまいますね。今回はこれらのコマンドを簡単なサンプルを見ながら動きを確認していきます。

# git-revert/git-resetとは

どちらも変更点を打ち消すためのGitコマンドになります。
どのような違いがあるのか見てみましょう。
一応、公式のドキュメントに書いてある説明です。

- [revert](https://git-scm.com/docs/git-revert): いくつかの既存のコミットを元に戻す
- [reset](https://git-scm.com/docs/git-reset): 現在のHEADを指定された状態にリセットする
  だそう。

難しいのでざっくり以下の認識でOKです。
![スクリーンショット 2021-02-03 19.07.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/5d7113f3-6247-8820-f167-d1b2af78c89c.png)
どういうことか触りながらサンプルを見ながら説明します。

# まずはおさらい git-rebase / git-merge

## git-rebase

分岐先のコミットを古いものから順に舐めていき、分岐元へと順次取り込んでいく。

![スクリーンショット 2021-02-03 19.11.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/6184526e-e0d5-5aa9-3bf1-32537e85861d.png)

## git-merge

分岐先の差分を分岐元に合体する。

![スクリーンショット 2021-02-03 19.12.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/9c7d77f1-1fd1-0059-ca33-904007972d02.png)
(squashしているとかno-ffとかの細かい話は割愛)

# ハンズオン

では、次に git-revert/git-reset のサンプルを見ながら手を動かしながら見てみましょう。

## git-reset

以下のコードをコピペして実行してください。実行すると下図のような状態になります。

```bash
# msaterブランチを作成
mkdir gitHandsOn && cd gitHandsOn
git init
echo "A" >> work.txt
git add . && git commit -m "Commit A"

# featureブランチに切り替えてコミットAからコミットDを作成
git checkout -b feature
echo "B" >> work.txt
git add . && git commit -m "Commit B"
echo "C" >> work.txt
git add . && git commit -m "Commit C"
echo "D" >> work.txt
git add . && git commit -m "Commit D"

# git-rebase
git checkout master && git rebase - && git log --oneline
```

![スクリーンショット 2021-02-03 19.15.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/6cf26cde-03e9-8b6c-cbad-425335b535f0.png)

次にgit-resetしてみましょう。
まずはさきほどのコミットDを打ち消してみます。

```bash
git checkout master
# コミットログを表示
git log --oneline
---------------------------------------------------------------
8254890 (HEAD -> master, feature) Commit D
5a5afaf Commit C
09d38a1 Commit B
383454e Commit A
---------------------------------------------------------------
# コミットDを打ち消してみましょう
# まずは以下のようにコミットDのコミットログを指定して、git-resetを叩いてください
git reset --hard 8254890
git log --oneline
```

いかがですか、消えましたか？

消えませんね。

どういうことか。

git-resetは`指定されたコミットにHEADを移動させる`コマンドなので、コミットDを指定してもコミットDは打ち消されません。

では改めてコミットDを打ち消してみましょう。

```bash
git checkout master
# コミットログを表示
git log --oneline
---------------------------------------------------------------
8254890 (HEAD -> master, feature) Commit D
5a5afaf Commit C
09d38a1 Commit B
383454e Commit A
---------------------------------------------------------------
# コミットDを打ち消してみましょう
# 改めて以下のようにコミットDのコミットログを指定して、git-resetを叩いてください
git reset --hard 5a5afaf
git log --oneline
---------------------------------------------------------------
5a5afaf (HEAD -> master) Commit C
09d38a1 Commit B
383454e Commit A
---------------------------------------------------------------
```

コミットDが無事に打ち消されました。

![スクリーンショット 2021-02-03 19.19.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/398cbcd8-fdf2-3b40-6176-2afdaf20169a.png)

## 演習1

最初にコピペしたコードを使って以下の状態にしてください。
※ 答えは一番最後に載せておきます。

![スクリーンショット 2021-02-04 23.11.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/5c8a39b3-9923-36ec-d5a2-bfc4fe6a0406.png)

### hard と soft の違いについて

`hard` はコミットを打ち消すとともにローカルの内容も移動後のHEADの時点に戻します。
`soft` オプションを使用すると、コミットは打ち消されるが、ローカルの内容はそのまま保持されます。これは、修正内容を維持しつつコミットだけを削除したい場合に適しています。

## 演習2

最初にコピペしたコードを使って以下の状態にしてください。
※ 答えは一番最後に載せておきます。

![スクリーンショット 2021-02-04 23.14.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/1e650042-499c-f7d1-6cfc-977157cfe706.png)

## git-revert

以下のコードをコピペして実行してください。実行すると下図のような状態になります。

```bash
# msaterブランチを作成
mkdir gitHandsOn && cd gitHandsOn
git init
echo "A" >> work.txt
git add . && git commit -m "Commit A"

# featureブランチに切り替えてコミットAからコミットDを作成
git checkout -b feature
echo "B" >> work.txt
git add . && git commit -m "Commit B"
echo "C" >> work.txt
git add . && git commit -m "Commit C"
echo "D" >> work.txt
git add . && git commit -m "Commit D"

# git-merge
# mergeの際にコミットコメントが必要なのでそのまま保存をしてください
git checkout master && git merge --no-ff - && git log --oneline
```

![スクリーンショット 2021-02-04 23.16.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/05808df1-18b1-0045-abee-245925169718.png)
マージコミットを打ち消してみましょう。

```bash
git checkout master
# コミットログを表示
git log --oneline
---------------------------------------------------------------
5b96ca7 (HEAD -> master) Merge branch 'feature'
2757f8e Commit A
914017f (feature) Commit D
711f950 Commit C
b66b20c Commit B
---------------------------------------------------------------
# マージコミットを指定してrevertを実行しくてださい
git revert 5b96ca7
```

いかがですか、消えましたか？

消えませんね。

どういうことか。

マージコミットを打ち消したいときは mainline というオプションを付与してあげる必要があります。

そしてマージコミットを打ち消す場合には mainline に 1 を指定してあげます。

### 小話 - mainline

mainline とは Git の parent-number のことです。git-merge とは２つのブランチを合体させることですが、どのブランチのコミットを合体させたかを以下のコマンドで調べることができます。

```bash
$ git show マージコミットのID
commit 5b96ca711b150aa66108fead2442699f8203ad9d (HEAD -> master)
Merge: 2757f8e 914017f
Author: takiguchi-yu

 Merge branch 'feature'
```

ここでいうと
2757f8e が mainline=1 で
914017f が mainline=2 ということになります。

では改めてマージコミットを打ち消してみましょう。

```bash
git checkout master
# コミットログを表示
git log --oneline
---------------------------------------------------------------
5b96ca7 (HEAD -> master) Merge branch 'feature'
2757f8e Commit A
914017f (feature) Commit D
711f950 Commit C
b66b20c Commit B
---------------------------------------------------------------
# マージコミットを指定してrevertを実行しくてださい
git revert --mainline 1 5b96ca7
git log --oneline
---------------------------------------------------------------
ce3046a (HEAD -> master) Revert "Merge branch 'feature'"
5b96ca7 Merge branch 'feature'
2757f8e Commit A
914017f (feature) Commit D
711f950 Commit C
b66b20c Commit B
---------------------------------------------------------------
```

無事できました。revert は reset と違ってコミットが残ります。
`コミットログが残せる＝一旦、チーム内でレビューを通せる`、などのメリットがありますね。

![スクリーンショット 2021-02-04 23.22.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/b95b0dfb-f316-b6a2-3028-8a8a56975b84.png)

## 演習3

最初にコピペしたコードを使って以下の状態にしてください。
※ 答えは一番最後に載せておきます。

![スクリーンショット 2021-02-04 23.24.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59081/556f03ca-a9f6-3781-b9bd-b542c4b94ee7.png)

# さいごに

結構業務だと対策前進してしまうことも多いのですが、こうしたコマンドを知っておくことで気軽に？切り戻しができますね！

## 演習の解答サンプル

### 演習1

```bash
git checkout master
git log --oneline
---------------------------------------------------------------
8254890 (HEAD -> master, feature) Commit D
5a5afaf Commit C
09d38a1 Commit B
383454e Commit A
---------------------------------------------------------------

# git-reset はHEADの移動させるコマンドなので、コミットAを指定してあげれば良い
git reset --hard 383454e
git log --oneline
---------------------------------------------------------------
383454e (HEAD -> master) Commit A
---------------------------------------------------------------
```

### 演習2

```bash
git checkout master
git log --oneline
---------------------------------------------------------------
8254890 (HEAD -> master, feature) Commit D
5a5afaf Commit C
09d38a1 Commit B
383454e Commit A
---------------------------------------------------------------

# softでresetすれば良い
git reset --soft 383454e
git log --oneline
---------------------------------------------------------------
383454e (HEAD -> master) Commit A
---------------------------------------------------------------
```

### 演習3

```bash
git checkout master
git log --oneline
---------------------------------------------------------------
5b96ca7 (HEAD -> master) Merge branch 'feature'
2757f8e Commit A
914017f (feature) Commit D
711f950 Commit C
b66b20c Commit B
---------------------------------------------------------------

# この場合は普通にコミットDのコミットIDを指定してあげれば良いですね
git revert 914017f
git log --oneline
---------------------------------------------------------------
ad37cf4 (HEAD -> master) Revert "Commit D"
76572dd Merge branch 'feature'
8b4e87e Commit A
3912023 (feature) Commit D
7454740 Commit C
99f5f37 Commit B
---------------------------------------------------------------
```