git revertとgit resetで複数のコミットを取り消す


マージしたいけど特定のコミットが邪魔!

Git 初心者の方はこんな経験や考えをしたことはないでしょうか?

  • メインブランチに開発ブランチの内容を取り込みたいけど、特定のコミットが邪魔!
  • コミットに含める情報を情報に過不足があり、コミットを修正したい!(もしくは、コミットを取り消したい!)

私は、最近そんな経験をしました。
今回はそんな悩みを解決すべく、どのような Git 操作をすればよいのかを解説していきます。

Git でそんな事できるん?

Git 初心者の方は「そんな操作できるん?」と思うかもしれません。
答えは「Yes」です。
かくいう私も今日まで同じ様に思っていました。

「Git コワイ」状態の私に立ちはだかったキーワードは 2 つ!
それは…

  • git revert
  • git reset

このキーワードをもとにコミット履歴を取り消して行きましょう。

今回の具体的なゴール

コミットを17debe5 Commit Bの状態まで戻す事をゴールとします。

82430c7 (HEAD -> main, origin/main, origin/HEAD) Commit E
aa284e6 Commit D
2ccc737 Commit C
17debe5 Commit B
c4ac9f6 Commit A

git revertでコミットを取り消す

git revertは、既存のコミットを取り消すコマンドです。
ただコミットを取り消すと言うよりは「コミットを取り消した」というコミットが積み上がるイメージです。
「ん?どういう意味?」ってなりますよね…
実際にやってみるとわかりやすいです。

コミットを取り消してみる

まずは、現在のコミットを取り消して見ましょう。
以下のコマンドを実行することで、現在のコミットを取り消すことができます。

git revert HEAD

コマンド実行後、コミットメッセージの編集画面に移動します。
適当に編集してコミットします。(今回はコミットメッセージは、そのままでいきます。)

Revert "Commit E"

This reverts commit 82430c735011da543b9a2a1f2ad1546a1798af8f.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch main
# Your branch is up to date with 'origin/main'.
#
# Changes to be committed:
#       modified:   index.html
#

HEAD^の部分をコミット ID に変えることも可能で、下記のようなコマンドを実行することで同様の内容を実行できます。

git revert <コミットID>

# 今回の例で言うとこんな感じです。
git revert 82430c7

git revertの挙動を理解する

話が少しそれてしまいましたが、本題に戻ります。
先程のgit revert HEADをした後のコミット履歴を確認してみます。
すると新しいコミットが積み上がっているのが確認できます。

c95c375 (HEAD -> main) Revert "Commit E"
82430c7 (origin/main, origin/HEAD) Commit E
aa284e6 Commit D
2ccc737 Commit C
17debe5 Commit B
c4ac9f6 Commit A

最新のコミットc95c375は「82430c7を取り消しましたよ」というコミットです。
git diffで差分を確認してみると、戻したい状態になっているのが確認できると思いますので、ぜひ試して見てください。

複数のコミットを消す

今回は、複数のコミットを取り消したいので、あと数回git revertする必要があります。
取り消したいコミットが少ない場合であれば、git revert <コミットID>を数回繰り返しても良いですが、コミットの数が多くなると面倒です。
そういったときは、いくつかのやり方で効率的にコミットを取り消すことができます。

以下のいずれかのコマンドで、2ccc737 Commit Cからaa284e6 Commit Dを取り消すことができます。

# コミットをひとつずつ指定して取り消す
git revert 2ccc737 aa284e6

# コミットを範囲指定で取り消す
git revert 2ccc73..aa284e6

「コミットを範囲指定で取り消す」場合は、古いコミットを先に書かないとエラーになります。

https://zenn.dev/nekoniki/articles/f238efa56eb869

コミット履歴を確かめてみると、コミット82430c7 Commit Eから2ccc737 Commit Cまでの内容が取り消されてるのが確認できます。
コミットは増えてしまいましたが、実質17debe5 Commit Bの状態まで戻っているはずです。

5217ef0 (HEAD -> main) Revert "Commit C"
3ac5c06 Revert "Commit D"
c95c375 Revert "Commit E"
82430c7 (origin/main, origin/HEAD) Commit E
aa284e6 Commit D
2ccc737 Commit C
17debe5 Commit B
c4ac9f6 Commit A

プッシュしてリモートブランチの内容も更新しておきましょう。

git resetで履歴を抹消する

git resetは、コミット履歴を消去するコマンドです。
git revertと違う点は、取り消した記録が残らないということです。

git resetの挙動を理解する

git resetをやっていきたいと思いますが、その前にgit resetには複数のオプションがあることを認識しておきましょう。
代表的な(よく使われる)オプションは以下の 3 種類です。

  • git reset --hard
  • git reset --mixed
  • git reset --soft

今回は、82430c7 Commit Eから2ccc737 Commit Cのコミットを削除して、ファイルもの内容も17debe5 Commit Bの状態に戻したいのでgit reset --hardを使用していきます。

3 つのgit resetの挙動については、以下の記事で詳しく説明されているので、参考にしてみてください。

https://qiita.com/shuntaro_tamura/items/db1aef9cf9d78db50ffe

実際にコミット履歴を削除していく

今回は、先程のgit revertしてプッシュした後の状態から17debe5 Commit Bの状態に戻していきます。
まずは、コミット履歴を確認しましょう。
プッシュした後なので、ローカルとリモートブランチの両方のポインタが5217ef0 Revert "Commit C"を指していることを確認できます。

5217ef0 (HEAD -> main, origin/main, origin/HEAD) Revert "Commit C"
3ac5c06 Revert "Commit D"
c95c375 Revert "Commit E"
82430c7 Commit E
aa284e6 Commit D
2ccc737 Commit C
17debe5 Commit B
c4ac9f6 Commit A

まずは5217ef0 Revert "Commit C"を抹消してみます。
直前のコミットを抹消するには以下のコマンドを実行します。

git reset --hard HEAD^

# コミットID指定でも可能です
git reset --hard 5217ef0

コミット履歴を確認してみると…
なんと、5217ef0 Revert "Commit C"が消えています!
ファイルを確認してみると、直前のコミットの状態に戻っていると思います。

3ac5c06 (HEAD -> main) Revert "Commit D"
c95c375 Revert "Commit E"
82430c7 Commit E
aa284e6 Commit D
2ccc737 Commit C
17debe5 Commit B
c4ac9f6 Commit A

git reset --hard HEAD^のコマンドを数回打ってコミットを抹消するのもいいですが、git revert同様に一気にコミット履歴を抹消することもできます。

複数のコミットを抹消する

複数のコミットを消すには以下のコマンドで実行可能です。
HEAD~nをつけることで n 個前のコミットまで抹消することができます。

git reset --hard HEAD~5

実際にコミット履歴を確認すると、までコミットが戻っていることが確認できます。

17debe5 (HEAD -> main) Commit B
c4ac9f6 Commit A

これで、今回の目的は達成です。
あとは、プッシュしたら任務完了です。

と、言いたいところですが、恐らくgit pushではエラーが発生するでしょう。
ローカルリポジトリがリモートリポジトリのコミットより遅れている状態だとプッシュできない仕様になっています。
なのでこういう場合は、強制的にプッシュします。

# このコマンドは取り扱いに注意しましょう
git push -f

ただし、git push -fは、大変強力なコマンドですので、複数人で開発を行っている場合は、周りの人と相談して使った方が良いかもしれません。

間違ってgit reset --hardをしてしまったら

もし、そんなことが起きてしまったらgit reflogを使うと良いです。
これにより、コミット ID 付きの操作履歴を見ることができます。
戻したいコミットを見つけたら以下のコマンドを実行することでコミットが復活します。

git reset --hard <戻したいコミットID>

Thanks and Source

https://zenn.dev/nekoniki/articles/f238efa56eb869

https://qiita.com/chihiro/items/2fa827d0eac98109e7ee

https://qiita.com/shuntaro_tamura/items/db1aef9cf9d78db50ffe

https://qiita.com/ymzkjpx/items/00ff664da60c37458aaa

https://www.r-staffing.co.jp/engineer/entry/20191025_1

https://www.r-staffing.co.jp/engineer/entry/20191129_1

https://www.r-staffing.co.jp/engineer/entry/20191227_1

https://www-creators.com/archives/1069#1