開発ブログ

WWWクリエイターズが送る、Git、CSS、HTML、コマンドライン、Macの便利機能など、開発に関する役立ち情報発信します。気まぐれに更新。

git commit の取り消し&やり直し、完全攻略。

最終更新:2017-08-06 by Joe

git commit の取り消しや、やり直し操作に関する方法をまとめました。Git はどんな失敗コミットもすべてを記録しています。いつでも昔の記録からもとに戻せばいいのです。

直前のコミットを取り消す

「git commit」操作を取り消す

下記のコマンドで、直前の git commit の実行を取り消しする事ができます。

「git reset --soft」は、HEAD(=現在のブランチの先頭)だけを、一つ前の状態、すなわちHEAD^(緑色) にリセットするコマンドです。

実行後は、ブランチの先頭を示すHEADが、もともとHEAD^(緑色)の位置に移動します。作業ツリーとインデックスの状態(紫色)は影響をうけず、そのままの状態です。

実行後:

git reset --soft

「HEAD^」は「HEAD」の一つ前のコミットを意味しますので、これにより、コミットを実行する前、すなわち、「作業ツリーの状態をaddしてインデックスを更新したが、まだ commitをしていない状態」に戻れます。

あとは、必要に応じて修正を加え、git commit をやり直す事ができます。(この操作は体感的には「git commit --amend」とよく似ています。

「git add」と「git commit」操作をどちらも取り消す

あなたが望めば、git reset の引数なし(=mixedモード)を使えば、インデックスも同時にリセットすることができます。これによって、変更内容を作業ツリーにとどめたまま、「addとcommit 両方をやり直す」という操作も実行できます。

実行後、インデックスも同時に、HAED^(緑色)の状態にリセットされます。この状態から、addとcommitをやり直すことができます。

実行後:

git reset --mixed

この方法で、add含めてやり直すことで、コミットを小さく何回かに分けてコミットを作り直す事ができます。

ドカっとまとめてコミットを入れてしまったても、やり直しの際にコミットを分割して、履歴をキレイに作り直したりできます。

コミット自体を完全に取り消す

コミットを作成したけど、そもそも不要な変更だった・・・といった場合など、不要になったコミットを完全に削除し、「完全になかったことに・・」したい時の操作です。

しくみは単純ですが、git reset --hard <commit> は、現状のブランチの先頭(branch headと呼びます)を強制的に指定のコミットに合わせるコマンドです。

作業ツリー、インデックス、ブランチの先頭とブランチ履歴が、全てを直前のコミットに強制的に一致させします。

実行後、インデックス、および作業ツリーまで、HEAD^(緑色)の状態になる事に注意して下さい。

git reset --hard

上記で分かるように、「git reset --hard xxxx」を行うと、作業ツリーの内容もろとも強制上書きされて、作業ツリーとインデックスに残されていた変更は吹き飛び、消えてしまいます。

これらのコミットされていない変更は、stashなど別の方法で記録していない限り、取り返す事が絶対にできなくなります。これはくれぐれもご注意下さい。なんだか怖い時はとりあえずcommitや、stashしておけば、安心かもしれません。

誤ってgit reset --hard して履歴から消えてしまった・・、コミット取り返したい、すなわち「やり直しをやり直したい(?)」といった方には、git reflog という手段があります。

一度でもコミットして、変更が何らかの形で記録されていれば、この方法は非常に役立ちます。

git reset について補足
このように、git resetはコミットの取り消しや、やり直し作業に非常に有効なコマンドです。下記に簡単にreset のモードを比較します。

モード指定 動作
git reset --soft 現在のbranchの先頭だけをリセット。(インデックス、作業ツリーはそのまま)
git reset
(git reset --mixed)
現在のbranchの先頭と、インデックスをリセット
git reset --hard 現在のbranchの先頭、インデックスも作業ツリーも全部リセット

git reset のモードについては、こちらの記事により詳しく解説しています。ぜひご覧ください。

直前のコミットの内容を修正する

コミットをしたけど、ちょっとしたミス(タイプミス、ログの消し忘れ、など)があって、修正してコミットし直したい時、直前のコミット内容を修正します。

これは、前述の git reset --soft とよく似ています。

もし直前の間違ったコミットをすでに、Pushしてしまってた後であれば、リモートにプッシュしたコミット内容と、ローカルで修正したコミット内容で、歴史に食い違いが生じる事になります。このため、amend操作のあと、改めてpushすると必ずコンフリクトします。これの対応方法は後述します。

昔のコミットの状態に戻す

現在のブランチの状態を強制的に、昔の特定のコミットの状態に戻します。

上述の git reset --hardを利用します。

現在のブランチの先頭(HEAD)を指定したコミットに移動します。その指定コミット移行のコミットはブランチの歴史からなくなります。(必要に応じて、git reflogから参照して、取り戻せます)

よくある勘違いが「git checkout <commit>」によって戻そうとしてしまうことです。git checkout では、引数にブランチ名でなくコミットを指定すると、「匿名ブランチ(anonymous branch)」を自動で作成しチェックアウト、そして、その匿名ブランチの先頭をその指定したコミットとする」とう振る舞いになります。コミットをブランチから削除するようなコマンドではありません。

git checkoutはあくまでブランチを移動するためコマンドなのです。

コミットを、別のブランチに移動する

こちらは応用編です。

一度作成したコミットを取り消して、そのコミットごと変更内容を別のブランチに移動します。

[1] 新しい別ブランチにコミット移動する

さて、コミットすべきブランチを間違えてしまいました。その変更を単純に取り消しするのではなく、新しいブランチにコミットを移動してしまいます。

(1)により、コミットをブランチ new-branch に保存しておきます。(3)により、original-branchの先頭のコミットを消去します。この操作は、new-branchの歴史はそのままですので、変更内容自体は別のブランチに保存したままoriginal-branchの歴史を書き直せるというわけです。

履歴はこんな感じになったでしょうか?

[2] 既存の別ブランチにコミットを移動する

移動先が既存のブランチでなくても、要領は同じです。こちらでは、あえて別のコマンド、cherry-pickを使って移動を実現してみます。

Pushしたコミットを削除する

ここまでは、ローカルでの取り消し作業の話でした。

一度Push してしまうと、リモートに変更が反映され、もしそれがチームで共有ブランチであれば、そのコミットを参照している他のチームメンバーの事を考える必要があります。

対応方法において、2つの方針を紹介します。

[1] git revertで「打ち消しコミット」を作り、履歴上に記録する

間違ったコミットをpushしてしまったとき・・、これは、基本的には、完全な取り消しは諦めて、 git revert コマンドを使って「打ち消しコミット(revert commit)」を作成して下さい。

git revert はgit commit 自体を取り消したり、消去したりわけではなく、変更を「打ち消す」ようなコミットを新しく作成するコマンドです。

過ちのコミットがしっかりと歴史に(コミット履歴に)刻まれてしまいます。ちょっとなんだか気持ち悪いのも事実です。

特にチームメーンバーが同じリモートレポジトリを使っているのであれば、突然の取り消しは混乱を招く可能性があります。(あなたがさっきfetch してブランチアウトした元のコミットが突然消えてしまったら、きっと混乱するはずです。)

補足ですが、git revert で注意が必要なのは、マージコミットを取り消す場合です。git revert での取り消しについて、詳しくはこちらをどうぞ:

[2] pushしたコミットを履歴から削除する

もし、完全に一人で開発していたり、深夜残業してて「よし、絶対自分しか作業していないぞ、しめしめ・・」という時に、こっそり直したい時・・。「リモートの歴史を強制的に書き換える」方法があります。

繰り返しですが、チームで共有しているブランチに対してこれやると普通は怒られますので、不安な人は必ずチームの先輩に確認してからやって下さい。

間違った操作を全部取り消して、やり直す

commit やマージを色々まちがえてしまって、全部もう何もかも嫌になって、イチからやり直したい時があります。せっかちな私は、何度これにお世話になったかわかりません。

HEADを含む、参照(refs)に変更があったすべての記録を記録しています。HEAD@{xx}というのは、過去何番目のHEADの状態かということですね。git reflogすごいです。

git reflog を使った操作はこちら:

昔のコミットを歴史から消し去る

これも、おきまりのgit コマンドです。

現在のブランチで、指定したコミットまでを一気にリベースします。

こんな感じで、テキストエディタで編集する用にファイルが立ち上がるので

「pick」って書いてある部分を「p, r, e, s, f ,x ,d 」のいずれかに変更して、保存すれば、まとめて書き換えてくれます。これも便利なコマンどです。だいたい使うのは「s(コミットを直前のコミットに統合)」「f(sと一緒。ログも破棄)」ですね。

書き直した履歴を、強制的に push する

紹介したやり方でブランチ歴史を書き直した場合、もし修正前をすでにpushしてしまっているのであれば、修正後のpushは完全にコンフリクトします。

その場合、下記のコマンドで強制上書きできます。

繰り返しになりますが、チームのみんなが参照しているブランチ(masterなど)にこれを行わない用にして下さい。他の人全員が、次回のpush でコンフリクトすることになるので、すごい迷惑になると思います。

さて、gitの取り消し操作に関しては、以上です。

私はいつも間違いやすいので、上記で紹介したコマンドはよく使う物ばかりです。

参考リンク

git commitの取り消し、やり直し、歴史の変更に関しては、いつでもできるようになっておくべきでしょう。間違いは必ず発生するので、ここの操作でまごつかずにスムーズに解決し、開発に集中できるようになっておくと良いと思います。

また、コミット前の「git add」に関する取り消しはぜひこちらをご覧ください。