取り消したいcommitをしてしまった。どうする?
誰にでも起こることです。早すぎるタイミングでcommitしてしまったり、含めるべきでないファイルを含めてしまったり、間違ったメッセージを書いてしまったり、コードが正しくないことに気づいたり。良いニュース:Gitはこれに対処できるように設計されています。さらに良いニュース:ほとんどの場合、作業を失うことなくcommitを取り消すことができます。
適切なアプローチは一つの重要な質問に依存します:そのcommitはリモートにpushされたものか、それともまだローカルにあるか?ローカルのcommitはより柔軟に対応できます。pushされたcommitは、他の人がすでにあなたの変更をpullしている可能性があるため、より安全な戦略が必要です。
git resetでローカルcommitを取り消す
commitがまだローカル(pushされていない)の場合、git resetが最も一般的なツールです。branchポインタを以前のcommitに戻し、事実上commitを「取り消し」ます。それらの変更に何が起こるかは、使用するフラグによります。
git reset --soft:commitを取り消し、すべてをstagingに保持
git reset --soft HEAD~1
これは最後のcommitを取り消しますが、すべての変更はstaging(インデックス)に保持されます。作業ディレクトリは影響を受けません。git commitを実行しなかったかのように、commitする直前の状態に戻ります。
使用するタイミング:commitメッセージを変更したい、commitにもっとファイルを追加したい、またはcommitをより小さなものに分割したい場合。コードは問題なく、単に早くcommitしすぎただけです。
git reset --mixed:commitを取り消し、変更をstagingから外す
git reset --mixed HEAD~1
これはgit resetのデフォルトの動作です(--mixedフラグは省略可能)。commitを取り消し、変更をstagingから外しますが、ファイルは作業ディレクトリで変更されたまま残ります。何も失われません。
使用するタイミング:commitに何を含めるか再考したい場合。一部のファイルだけをstagingに入れたい、またはcommitし直す前に変更を確認したい場合。
git reset --hard:commitを取り消し、すべての変更を破棄
git reset --hard HEAD~1
これはcommitを取り消し、すべての変更を完全に削除します。作業ディレクトリは前のcommitに一致するようにリセットされます。通常のGit操作では破棄された変更を復元する方法はありません。
使用するタイミング:変更がまったく不要だと確信している場合。うまくいかなかった実験的なコードをcommitしてしまい、白紙の状態に戻したい場合。注意して使用してください。
さらに前に戻す
HEAD~1は現在のHEADの1つ前のcommitを指します。さらに前に戻ることもできます:
git reset --soft HEAD~3
これは直近3つのcommitを取り消し、すべての変更をstagingエリアにまとめます。特定のcommitをハッシュで指定することもできます:
git reset --soft a1b2c3d
git commit --amendで最後のcommitを修正する
最後のcommitを完全に取り消すのではなく、微調整するだけなら、--amendが最も速い選択肢です:
git commit --amend -m "corrected commit message"
これは前のcommitを新しいものに置き換えます。忘れたファイルを追加するためにも使えます:
git add forgotten-file.ts
git commit --amend --no-edit
--no-editフラグは元のcommitメッセージを保持します。結果として、「おっと」commitの連続ではなく、単一の修正されたcommitになります。
重要:resetと同様に、amendも履歴を書き換えます。pushされていないcommitにのみ使用してください。pushされたcommitをamendしてforce-pushすると、元のcommitをpullした人全員に問題が発生します。
git revertでpushされたcommitを取り消す
commitが共有リモートにpushされた後は、履歴を書き換えるべきではありません。他の開発者がそのcommitに基づいて作業している可能性があります。代わりにgit revertを使用してください:
git revert HEAD
これは対象のcommitの正確に逆の操作を行う新しいcommitを作成します。元のcommitが行を追加した場合、revert commitはそれを削除します。ファイルを削除した場合、revert commitはそれを復元します。元のcommitは履歴に残り、revertがその上に追加されます。
使用するタイミング:すでにpushされたcommitを取り消す必要がある場合。履歴を書き換えるのではなく追加するため、安全です。
古いcommitをrevertする
最新のcommitだけでなく、任意のcommitをrevertできます:
git revert a1b2c3d
Gitはその特定のcommitの変更のみを取り消すrevert commitを作成しようとします。それらの変更がその後の作業と重なる場合、手動で解決が必要なコンフリクトが発生する可能性があります。
複数のcommitをrevertする
commitの範囲をrevertするには:
git revert HEAD~3..HEAD
これは範囲内の各commitに対して個別のrevert commitを作成します。単一のrevert commitを好む場合は、--no-commitフラグを追加して手動でcommitしてください:
git revert --no-commit HEAD~3..HEAD
git commit -m "revert last 3 commits"
適切なアプローチの選択
| 状況 | 方法 | 変更に何が起こるか |
|---|---|---|
| 早すぎるcommit、再stagingしたい | git reset --soft HEAD~1 |
変更はstagingに残る |
| 早すぎるcommit、作り直したい | git reset --mixed HEAD~1 |
変更は作業ディレクトリに残る |
| 破棄したいものをcommitした | git reset --hard HEAD~1 |
変更は削除される |
| commitメッセージの修正やファイルの追加が必要 | git commit --amend |
前のcommitが置き換えられる |
| pushされたcommitを安全に取り消す必要がある | git revert HEAD |
新しいcommitが変更を取り消す |
ミスからの復旧
git reset --hardを使ってしまい、使うべきではなかったと気づいた場合でも、復旧する方法があることが多いです。Gitは、HEADが指していた場所のログを保持しており、これをreflogと呼びます:
git reflog
これは最近のHEADの位置のリストを表示します。失ったと思ったcommitを見つけて、そこに戻ることができます:
git reset --hard HEAD@{2}
reflogはあなたのセーフティネットです。エントリはデフォルトで90日間保持されるので、ミスから復旧するための十分な期間があります。
GitSquidでcommitを取り消す
GitSquidは最も一般的な取り消し操作を簡素化します。commitした直後にCmd+Z(WindowsとLinuxではCtrl+Z)を押すと、最後のcommitを取り消し、変更をstagingに保持して作り直す準備ができます。より具体的な操作については、グラフ内の任意のcommitを右クリックすると、コンテキストメニューからresetとrevertのオプションにアクセスでき、フラグを暗記しなくても取り消したいcommitを正確に指定できます。
Gitのcommitを取り消すことはストレスである必要はありません。reset、amend、revertの違いを理解し、状況に合ったものを知れば、自信を持ってミスを修正できます。最も重要な区別はシンプルです:ローカルならresetまたはamend。pushされているならrevert。