🥺

【Git】やっちまったコミットを戻したい時のTips

2020/12/24に公開

はじめに

今回はGitを操作する際のTipsを紹介したいと思います。
具体的には「誤ったコミット」をしてしまった場合の対処法です。

こういった場合、よく紹介されているのがrevertを用いた打ち消しですが、今回はresetを使った方法についても紹介します。

共通

まずはrevert,resetともに共通して行う処理について記載します。
そもそもの意図として「誤ったコミット」を「なかったことにしたい」という意図は同じなので、過去のコミットの中から「どのコミットをなかったことにするのか」を特定します。

コミットログを参照

git logコマンドでコミットログを参照します。

git log

以下のように過去のコミットが順に表示されます。

commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Author: hogehoge hogehoge@example.com
Date: Tue Dec 22 17:03:53 2020 +0900
 
Fuga.txtを修正
 
commit yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
Author: fugafuga fugafuga@example.com
Date: Tue Dec 22 14:10:20 2020 +0900
 
Fuga.txtを追加
 
commit zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
Author: punipuni punipuni@example.com
Date: Tue Dec 22 12:00:00 2020 +0900
 
最初のコミット

ここで重要なのはコミットのハッシュになります。
上記の例の中でいうところのxxxxxx...yyyyyy...がそれにあたります。

revertを使う場合

revertは対象のコミットに対して「打ち消し」を行います。
打ち消したこと自体はコミットログに残るので、「コミットxxxを打ち消したコミット」のように後から参照することもできます。

やり方ですがgit revert 【対象コミットのハッシュ】で行います。
従ってyyyyyy...のコミットを打ち消したい場合は

git revert yyyyyy

と入力します。
再掲ですが、このコマンドで生成されるのは「yyyyy...というコミットを打ち消した新しいコミット」です。
なのでリモートに反映させるには当然pushを行う必要があります。

コミットの指定方法

reverにおける打ち消し対象のコミットの指定方法はハッシュ指定以外にも色々あります。
※下記方法の多くはrevertに限らず、コミットを指定する場合に有効な方法です。

直前のコミットを指定

直前のコミットを指定する場合はHEAD^で行うことができます。

git revert HEAD^

N個前のコミットを指定

HEAD~nでN個前のコミットを表します。
2つ前のコミットを指定する場合は下記のようにします。

git revert HEAD~2

範囲指定

..でコミットを範囲指定できます。
例えば、下記のような使い方をします。

git revert zzzzzz..xxxxxxx
git revert HEAD~3..HEAD^

resetを使う場合

resetは文字通りコミットを指定してそこまで(あるいはその範囲の)リセットを行います。
※厳密には上記のHEADの状態をリセットするという表現が正しいと思います。

基本的な使い方はrevertと同じです。

git reset yyyyyy

オプション

resetにはどのレベルまでリセットを行うかを指定するオプションがあります。
具体的には--mixed--soft--hardです。

それぞれの違いを紹介するために以下③ステップからhoge.txtを新規作成し、それをaddしてcommitしたものとします。

  • ①hoge.txtを作成
touch hoge.txt
  • ②hoge.txtをadd
git add hoge.txt
  • ③hoge.txtをcommit
git commit -m ‘¥hoge.txtを追加’

mixed

オプションを指定しない場合はこれがデフォルトになります。
特徴は「コミットとインデックスを削除するが、ファイルの変更は残す」です。
つまり上記の例の場合②と③がリセットされ①を行っただけの状態に戻ります。

git reset --mixed HEAD^

soft

softmixedよりマイルドなリセットです。
特徴として「コミットのみを削除する」ので、状態としては②を行った後の状態に戻ります。

git reset --soft HEAD^

hard

hardは完全リセットです。
従って①②③のいずれも行わなかった状態まで戻ります。
ファイルの変更も破棄される点に注意しましょう。

git reset --hard HEAD^

hardリセットを取り消したい場合

うっかりにうっかりを重ねて「正しいコミットをreset --hardしてしまった・・・」なんて場合もあると思います。
前述の通りreset --hardは何もかも消えてしまうわけですが、復元方法はあります。

実はgit reflogというコマンドがあり、その中にはちゃんとreset --hardしたログも残っています。

git reflog

これでgit reset --hardをする前の状態のハッシュがわかるので、そのハッシュを指定してgit resetをかけましょう。

git reset --hard 【戻したいハッシュ】

resetをリモートに適用

resetは基本的にコミットのリセットするのでrevertのように「取り消したことを表すコミット」をリモートにプッシュすることができません。
もしローカルで誤ったコミットをしてしまい、それをリモートにプッシュしてしまっている場合はもう一手間必要になります。

git push時に-fオプションをつけることで、リモートブランチに対して現在のローカルブランチの内容で強制的に更新をかけます。
下記はmasterに対してローカルブランチの内容で強制的にpushしているコマンド例です。

git push origin master -f

強制的にローカルブランチで更新するため、resetを行った後の状態でこれを行えばリモートにプッシュしてしまった誤ったコミットを取り消すことができます。
※これは完全に歴史改変になるため、オススメしません。

Protectがかかっているブランチの場合

どういった権限や設定で開発を行っているかは個々で異なるため一概には言えませんが、おそらく上記コマンドを実行した段階では下記のエラーが出ると思います。

You are not allowed to force push code to a protected branch on this project

これは対象ブランチ(例中ではmaster)にプロテクトがかかっているためです。
この場合設定の変更が必要です。

Gitlabの場合はリポジトリを開いて、【設定】→【リポジトリ】→【Protected Branches】から指定のブランチの【Unprotect】を実行することで解除できます(下図参照)。

protected

まとめ

今回はGitを操作している際に誤ったコミットをしてしまった場合の対処法としてrevertresetを紹介しました。
普段Gitを使って開発している方は多いと思いますがrevertresetは普段使いするようなコマンドでないため、いざ必要になった時にそれぞれの挙動が分かりにくかったりします。

大体のケースがここで紹介した方法で対応できると思うので、実際に必要になった際は参照いただけると幸いです。

Discussion