【Git】やっちまったコミットを戻したい時のTips
はじめに
今回は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
soft
はmixed
よりマイルドなリセットです。
特徴として「コミットのみを削除する」ので、状態としては②を行った後の状態に戻ります。
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】を実行することで解除できます(下図参照)。
まとめ
今回はGit
を操作している際に誤ったコミットをしてしまった場合の対処法としてrevert
とreset
を紹介しました。
普段Git
を使って開発している方は多いと思いますがrevert
やreset
は普段使いするようなコマンドでないため、いざ必要になった時にそれぞれの挙動が分かりにくかったりします。
大体のケースがここで紹介した方法で対応できると思うので、実際に必要になった際は参照いただけると幸いです。
Discussion