開発ブログ

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

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

最終更新:2017-12-04 by Joe

Git における、git commit の取り消し方法や、やり直し操作に関する方法をまとめました。Git はどんなコミットでもすべてを記録していますので、一度間違えたとしても、いつでも昔の記録からもとに戻せ事が保証されています。

取り消しや、やり直し方法をマスターすれば、バージョン管理ツールとしてのメリットを最大限享受することができます。

ローカルのコミットを取り消す方法

コミットした直後に「あっ、この変更入れ忘れた!」「あっ、いらないファイルを混ぜてコミットしちゃった!」など、確認不足による間違いは、時間に追われるエンジニアに非常にしばしば発生します。そのような間違いコミットは、まだpushしていなければ、容易に取り消したり、その後コミットをやり直すことができます

コミットの取り消しにまつわるケースを、下記の6種類に分けてご紹介します。

コミット取り消しの事例6選

  1.  直前のコミット操作を取り消す「git reset  --soft HEAD^」
  2.  直前コミットを訂正する「git commit --amend」
  3.  ブランチ状態を強制的に引き戻す「git reset --hard」
  4. 変更の打ち消しコミットを作る「git revert」
  5. 過去のコミットを歴史から削除する「git rebase -i」
  6. 「コミットの取り消し」を取り消す「git reflog」

それでは、見ていきます。

[1] 直前のコミット操作を取り消す「git reset --soft HEAD^」

コミットを取り消す「git reset --soft」は、git commit の実行を取り消すための、シンプルなコマンドです。

// 直前のコミット操作を取り消す
git commit reset --soft HEAD^

下記のように、コミットを取り消した後も、インデックスと作業ツリーの状態はそのまま残りますので、その後、修正を追加してコミットをやり直すことが可能です。

git reset でコミットを取り消す。

もし、この後、コミットをやり直すのであれば、下記の手順です。

コミットを取り消した後に、再コミットする。

git reset --soft は、HEAD参照の書き換えだけで留めてくれますので、直前の修正を取り消した直後の追加修正が行いやすい特徴があります。HEAD^に対してリセットする時の操作は、後述の「git commit --amend」によく似ています。

上記は HEAD^に対してのリセットでしたが、任意のいかなるコミットに対してリセットをかけることができます。(後述の、git commit --amend では、直前のコミットを修正する事しかできません。)

注意が必要なのは、この操作でコミットの識別子(SHA-1)も更新されますので、バージョン管理システム上は「古いコミットを破棄し、新しいコミットで置き換えた」という扱いになります。つまり、もし古いコミットをすでにPushしてしまっているのであれば、新しいコミットを作成してそれをPushした時、リモート側の古いコミット履歴と食い違いが生じるので、必ず衝突(コンフリクト)を引き起こします。このようなコンフリクトの解決方法は、この記事の後半の「コミットの取り消しをリモートに反映する方法」をご覧ください。

git reset のモードについて

上記の通り、git reset --softは、HEADだけを書き換えますが、引数なしのデフォルト(mixed)モードでは、HEADとインデックスを同時に、また、hardモードでは、さらに作業ツリーを含めて、すべて指定された状態に上書きするのでした。

soft モード

git reset --soft はブランチの先頭を取り消す。

mixed モード(引数なしのデフォルト)

mixedモードは、インデックスも上書き。

hard モード

hard モードは、作業ツリーを含めて上書き。

git reset のモードをまとめます。

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

mixed モードは、インデックスを書き換えますので、再コミットを作りたい時は、git add をつかったステージングをやり直す必要があります。

また、hard モードにおいては、作業ツリーの変更は、stashなど別の方法で記録していない限り、取り返す事が絶対にできなくなります。これはくれぐれもご注意下さい。怖い時はとりあえずcommitや、stashしておけば、保存されますので、安心かもしれません。

git reset はこちらにも詳しくまとめています。ぜひご一読下さい。

[2]  直前コミットを訂正する「git commit --amend」

こちらは「コミットの取り消し」というよりは、「コミットのやり直し」ですが、もっとも簡単な直前コミットの修正コマンドです。git commit に「--amend」オプションをつければ、直接的に直前のコミットを修正することができます。

// 直前のコミットを修正する。
git commit --amend

ここで上書きされる「直前のコミット」とは、すなわち「現在のブランチの先頭」であり、「HEAD」という参照で指し示されるコミットの事です。

前述の「git reset --soft HEAD^」の操作とよく似ています。

git commit --amend でコミットをやり直す

同様に、Push時にコンフリクトが発生しますので、「コミットの取り消しをリモートに反映する方法」をご覧下さい。

 

[3] ブランチ状態を強制的に引き戻す「git reset --hard」

直前のコミットの取り消しだけでなく、直近のいくつかのコミットで根本的な間違いを犯してしまった時です。「いくつかの変更をコミットしたけど、そもそも私の担当じゃなかった」「修正の方針変更が生じた」などでしょうか。

// ブランチを<commit>の状態に戻す
git reset --hard <commit>

この操作で、ブランチの先頭が指定したコミットを指すようになります。

git reset --hard で状態を引き戻す

[4] 変更の打ち消しコミットを作る「git revert」

この方法は、コミットの取り消しや打ち消しではありません。言うなれば「変更内容の取り消するような逆の変更を含んだ新しいコミットを作成するためのコマンドです。git revert により作成されたコミットを「revertコミット」と呼ぶことがあります。

// 直前の変更を打ち消すコミットを作成
git revert 

// 上と同義
git revert HEAD

このrevert コミットは、バージョン管理上、単なる1つの新しいコミットですので、ブランチの過去の履歴に影響を与えません。ですので、Pushしてもコンフリクトする事がありません。

蛇足ではありますが、git revert で注意が必要なのは、マージコミットを取り消す場合です。git revert での取り消しについて、詳しくはこちらをご覧ください。

[5] 過去のコミットを歴史から削除する「git rebase -i」

「git rebase -i」は、正確にはブランチの歴史を修正するためのコマンドで、ブランチ履歴の整理のために、よく使うのgit コマンドの1つです。「i」は「interactive(インタラクティブ)」の略です。

例えば、下記のように取り消したいコミットの直前を指定します。あくまで「リベース」を実行していますので、「どのコミットを取り消したいか?」ではなく、「どのコミットに対して、リベースをかけるか」を引数で指定する点に注意して下さい。

// 現在のブランチを、<commit> までリベース。
git rebase -i <commit>

実行後、このようにテキストエディタで編集する用にファイルが立ち上がります。

pick 6fcde18 Replace the news link.
pick 460b0b8 Set public false for the news.
pick de9f1d2 Fix the bug .
pick 4b0346d Remove news.

# Rebase b4792e2..4b0346d onto b4792e2 (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit

各行の左側に「pick」と書いてありますが、部分を「p, r, e, s, f ,x ,d 」のいずれかに変更して、保存すれば、まとめて書き換えてくれます。もし取り消したいコミットがあれば、「drop」もしくは「d」を記述します。

ただし、過去のコミットの破棄は、その後、そのコミットに依存する変更を含むすべてに影響を与えます。そのような場合は、このリベース処理の途中で、何度もコンフリクトの解消をするように促される事になります。

「直前のコミットの取り消し」と異なり「過去の特定のコミットの取り消し」は、歴史上大きな影響を及ぼす可能性があるといえるでしょう。

一般的に、git rebase -i は、「s(コミットを直前のコミットに統合)」「f(sと一緒。ログも破棄)」などを利用して、ログをキレイに整える目的で利用することが多いでしょう。

[6] 「コミットの取り消し」を取り消す「git reset --hard HEAD@{N}」

少し込み入ったパターンですが、commit 取り消し操作など、色々操作をまちがえてしまって、必要だったコミットが履歴に見当たらなくなってしまうことがあります。なんだか全部もう何もかも嫌になって、「あの頃からすべてやり直せたら・・」という気もちになる時があります。

そんなときは「git reflog」が便利です。git reflog は、HEADを含む「参照」の移動をすべて記録していますので、過去の好きなタイミングに帰ることができるのです。

// いまのブランチのすべてのコミット歴史を表示
git reflog

デフォルトではHEADの移動をすべて表示します。

fc1fd0f HEAD@{0}: reset: moving to HEAD
fc1fd0f HEAD@{1}: commit: Fix the bug.
82f1826 HEAD@{2}: commit: Fix the bug.
7a52e59 HEAD@{3}: cherry-pick : Fix the layout.
ba43eb4 HEAD@{4}: commit: Fix the bug.
c57472a HEAD@{5}: commit: Add the related css props.
88e418c HEAD@{6}: commit: Narrow the margin between paragraphs
7d8611b HEAD@{7}: commit: Update the title tag.
8226a0d HEAD@{8}: commit: Add the adobe affilicate tag.

HEAD@{xx}というのは、過去何番目のHEADの状態かということですね。元に戻したいポイントがわかれば、あとは、git reset --hard で強制的に巻き戻します。

// 指定のコミットに、ブランチ状態を強制上書き。全部取り消し。
git reset --hard HEAD@{14}

git reflogすごいです。git reflog を使った操作はこちら:

git reflog のマニュアルです。

git checkoutとの振る舞いの違い

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

逆に、git checkoutの引数にコミットを指定し、かつ、同時にファイルパスを指定すれば、インデックスと作業ツリーにそのファイル状態を配置することができます。しっかりと仕様を理解して、混乱を避けるようにしましょう。

コミット取り消しをリモートに反映する方法

上記で、紹介したやり方でローカルのブランチ歴史を書き直した場合、もし修正前をすでにpushしてしまっているのであれば、修正後のpushは完全にコンフリクトします。その場合、下記の「-f」オプションを付けてプッシュすることで、強制上書きできます。

// 強制的にpushして、リモートの履歴を上書きする
git push -f origin <branch name>

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

一人で開発しているレポジトリであれば、気楽に行ってしまって良いかもしれません。

参考

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

下記は関連記事です。

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

閉じる