Git で変更を取り消してもとに戻したいときの事例集です。作業ツリーの変更、インデックスの変更、もしくはコミットを取り消したいなど、事例ごとに対応するコマンドをまとめました。
目次
まだ git add していない変更を取り消す
git add によってインデックスも更新しておらず、コミットもまだしていない時です。作業ツリーでコードをいろいろ変更してしまったけど「いったん元に戻して最初からやりたい!」というケースです。
作業ツリー上の変更を元に戻す
まずおさらいですが、git checkout は、2つの異なる機能があるのでした。
git checkout の機能
- 作業ブランチを切り替える
- 指定したコミットの状態を、インデックスと作業ツリーに展開する
パラメータとしてブランチ名のみ指定すれば(1)の動作を、ファイルパスやファイル名を指定すれば(2)の動作をします。
今回、作業ツリーの変更を取り消すため、(2)の機能を利用します。git checkout のパラメータとして、直前のコミットにおけるファイル名を指定して、作業ツリーの変更内容を取り消すことができます。(実際はインデックスもHEADの状態に更新されますが、まだ何もaddされてなければ特に変化はありません)
// 作業ツリーの特定のファイルをHEADの状態に戻す git checkout HEAD <FILE_NAME> // 作業ツリーをHEADの状態に戻す git checkout HEAD .
もしくは、コミットを指定を省略した場合(ファイルパスだけを指定)インデックスの状態を作業ツリーに展開する動作になります。もしまだ一度も変更をgit add してインデックスを更新していないのであれば、下記のようにコミットを省略しても同じ効果が得られます。
// 作業ツリーの特定のファイルをインデックスの状態に戻す git checkout <FILE_NAME> // 作業ツリーをインデックスの状態に戻す git checkout .
簡単なバグ修正など、思いついた変更を試して、うまくいくかどうか試す。ダメだったので元に戻す。などのケースで活躍しそうですね。
git add したが、まだコミットしていない変更を取り消す
インデックスを変更してしまったが、まだコミットしていない時です。この内容は「git add の取り消し方法と、関連コマンド仕様まとめ」にも詳しくまとめていますので、合わせてご覧ください。
インデックスと作業ツリーの両方の変更をもとに戻す場合
こちらは前述と全く同じコマンドですが、直前のコミットの状態に戻すのであれば、そのコミット番号(SHA-1)か、もしくは、現在のブランチの先頭を指す「HEAD」を指定すればOKです。下記はHEADを使ったコマンド例です。
// 作業ツリーとインデックスの特定のファイルを元に戻す git checkout HEAD <FILE_NAME> // 作業ツリーとインデックスを元に戻す git checkout HEAD .
注意としては、「git checkout HEAD」などと、ファイル名/ファイルパスを指定しない場合では、HEADが指すブランチをチェックアウトするコマンド(上記の(1)の機能の動作)を意味してしまいます。ファイルパスの指定を忘れないでくださいね。
匿名ブランチのチェックアウト
指定するパラメータの種類による振る舞いの変化は、[gitc]git checkout[/gitc]の混乱しやすい部分でもあります。例えば、git では、「git checkout <コミット (SHA-1)>」もしくは「git checkout --detatch <ブランチ>」」で、匿名ブランチをチェックアウトできます。
「git log --decorate=full --all --one-line --graph」の実行:
インデックスだけの変更を取り消す(=git add 実行を取り消す)場合
作業ツリーをそのままにして、インデックスの状態だけをもとに戻すのは「git reset」コマンドの出番です。
ここでもおさらいですが、git reset は、下記の機能を持ったコマンドでした。
git reset の機能
- インデックスの状態を、HEAD(もしくは指定したコミット)の状態に強制的に戻す
- ブランチの先頭(=HEAD)を指定したコミットに移動する。(同時にインデックスやツリーも更新)
パラメータにファイルパス・ファイル名をしていすれば(1)の動作になり、HEADはそのままです。一方で、パスがなくコミットだけが指定されれば、(2)の動作となりHEADが書き換わります。
さて、下記のコマンドは、ブランチの指定に、現在のブランチの先頭HEADを指定しますので、実質的にはインデックスを上書きするコマンドです。作業ツリーの状態に一切の影響を与えません。
// インデックスされた特定のファイルの変更を取り消す(2つは同義) git reset <FILE_NAME> git reset HEAD <FILE_NAME>
もちろん、すべてのファイルをもとに戻すのであれば、
// インデックスされた特定のファイルの変更を取り消す(2つは同義) git reset . git reset HEAD .
作業ツリー上の変更も取り消したいのであれば、ハードオプションを利用します。
// インデックスと作業ツリーをもとにもどす(2つは同義) git reset --hard git reset --hard HEAD .
git reset についてはこちらの記事もぜひご一読下さい。
また、git add の取り消しは、別途こちらに詳しくまとめています
コミットしてしまった変更を取り消す
git commit でコミットされた変更を取り消す場合は、すこし状況が変わります。こちらは「git commit を取り消して元に戻す方法、徹底まとめ」にも詳しく網羅しています。
git revert で変更を打ち消すコミットを作成
git revert は特定のコミットの変更内容を取り消すような変更内容を持ったコミットを作成するコマンドです。どのコミットを打ち消すかを引数で指定しますが、直前のコミットを取り消すのであれば、現在のブランチの先頭コミットを表す「HEAD」も指定できます。
// 直前のコミットを取り消す内容をコミット
git revert HEAD
git reset を使って、直前のコミットを消去する
こちらは、上記でも利用したコマンドですが、強制的にHEADの状態を過去に戻すことで、コミット自体を取り消します。作業ツリーやインデックスも強制的にリセットされますので、実行前に、まだコミットされていない重要な変更が残っていない事を確認して下さい。
// 作業ツリー、インデックス、すべての変更をHEADに合わせる。 git reset --hard HEAD^
git reset は、オプション次第で、 作業ツリーだけはそのままにしておくこともできます。下記はオプションなし(--mixedオプション)です。
// インデックス、すべての変更をHEADに合わせる( 作業ツリーだけそのまま) git reset HEAD^
--softオプションでは、作業ツリーとインデックスがそのままで、HEADだけ書き換わります。
// ワークツリー、インデックスはそのまま。 git reset --soft HEAD^
繰り返しですが、git reset についてはこちらに詳しくまとめました。
すでにPushしたコミットを取り消す
もっとも深い変更の取り消しです。チームメンバーに影響する場合がありますので、相談しながら慎重に行って下さい。
RevertコミットをPushする
上記で紹介した「git revert」でつくった、取り消しコミットをPushすればOKです。これは、過去の履歴に盈虚を与えないので、比較的安全な方法です。
pushしたコミットを取り消す
通常、一度リモートにPushすれば、そのコミットは公開され、チームメンバーからアクセス可能になります。自分しか使っていないフィーチャブランチであれば比較的問題にならないことも多いですが、masterなどの統合ブランチですと話は別です。誰かがすでにFetch して、そこから別のフィーチャブランチをスタートさせているかもしれません。
通常、ローカルで履歴を書き換えます。
// 直前のコミットを消去する
git reset --hard HEAD^
そのご、そのブランチをPushするのですが、通常はリモート側の歴史との食い違いにより、コンフリクトします。
もしリモートの歴史を強制的に書き換えるのであれば、-f オプショを利用します。繰り返しですが、このオプションはチーム開発では慎重に使って下さい。
// Pushを矯正する
git push -f origin master
さて、うまく変更を元に戻せたでしょうか?以上です。
参考リンク
今回紹介したgit reset, git checkout は、変更の取り消しによく利用するコマンドですが、最も仕様が混乱しやすいコマンドの一つでもあります。ドキュメントもあわせてご覧ください。