GITでコミットを削除する3つの方法|GITやばい辞典

WEB制作の現場で本当に起きる現場で知らないと本当に困るGITやばい辞典 画像で詳細解説

それは、突然やってくる。

「あ、今のコミット、なかったことにしたい…」 「テスト用のコードをそのまま上げちゃった!」

Gitを操作していて、自分の足跡(歴史)を消したくなる瞬間は、誰にでも訪れます。

しかし、一口に「削除」と言っても、状況によって正解はバラバラ。 直前のミスをなかったことにしたいのか、数日前の歴史を闇に葬りたいのか、あるいは「なかったことにしたという証拠」をあえて残したいのか——。

本記事では、Gitで歴史を修正するための3つの必殺技「Reset」「Rebase」「Revert」を徹底解説します。

この記事でやること

目標

過去の特定の位置のコミットを削除することを目標とします。

ゴール

「Reset」「Rebase」「Revert」を使ってコミットを削除する方法の概要を理解し、VSCodeとコマンドそれぞれで作業できるようになる。

予備知識

本題に入る前に、「Reset」「Rebase」「Revert」を実行した後のコミット履歴のイメージをおさらいしておきます。

コマンドコマンドハッシュ変更特徴・使いどころ
Resetgit reset HEAD~1あり過去に戻す
Rebasegit rebase -iあり指定のコミットを削除する。以降の全コミットが作り直される
Revertgit revert <ID>なし新しい打ち消しコミットを作る

目次

1.Resetでコミットを削除する方法

「Reset」は、指定した時点まで時間を巻き戻すコマンドです。 「削除」というよりは「特定のコミットに移動する」ために使われます。

git reset --hard 75abbb6

または

git reset --hard HEAD~4

コマンドの場合はこのように入力します。ポイントは削除したい親のコミットを指定することです。

VSCodeの場合はこのとおり。削除したい親コミットを右クリックして「Reset current branch to this Commit...」をクリックします。

モードが「Soft」「Mixed」「Hard」から選べますが、今回は「Hard」を選んで「Yes, reset」をクリック。

モードは別の記事で解説します。

ローカルのHeadの位置がテストの親コミットに移動しました。

これは、ローカルからは無事削除が完了し、リモートにはまだ残っていることを示しています。

変更の同期をクリックするとどうなる?

ローカルが過去に戻った状態で「変更の同期」をクリックすると、元にもどります。リセットを取り消したい時は何も変更をしていない場合は簡単に戻ることができます。

リモートにローカルのリセットを反映するには強制プッシュが必要になります。

「他人の変更を謝って上書きする可能性があります。」という怖いメッセージが表示されるとおり、チーム開発の場合はプッシュ後のリセットはリスクが多きいので慎重に行ってください。

プッシュが完了すると、リモートからもコミットが削除されて晴れて「テスト」以降が消えてなくなりました。

削除したのを戻したい時は?

リセットをプッシュ後に元に戻したこともあるかもしれません。そんな時もリセットを使います。

まずは「git reflog」でGit操作の履歴を確認します。

「リセットを元に戻したい」ということは、リセットの履歴を探して、それの一つ前に戻せばよいということになります。

git reset --hard 3c3ac33

または

git reset --hard HEAD@{1}

無いものを復活するのは、VSCode上ではできないのでコマンドで実行します。

「git reset -- hard HEAD@{1}」でエラーが表示された!なぜ?

「git reset -- hard HEAD@{1}」で「error: unknown switch `e'」というエラーが表示されました。

これはGitのせいではなく「WindowsのPowerShell」が { }(波括弧)を特殊な記号として解釈してしまっていることが原因です。HEAD@{1} の部分がうまく伝わらず、変な場所でコマンドが途切れて「e なんてオプション知らないよ!」と怒られています。

解決策としては、引用符で囲むか、ハッシュ値を直接使う方法があります。

git reset --hard 3c3ac33

または

git reset --hard 'HEAD@{1}'

2.Rebaseの「drop」でコミットを削除する方法

次に、Rebaseでコミットを削除する手順を見ていきましょう。

Rebase は、過去のコミット履歴を並び替えたり、まとめたり、削除したりするためのコマンドです。
履歴そのものを書き換えるため、実行後はコミットの歴史が作り直されます。

手順①:リベースの開始

こちらはリベース前の状態です。「テスト」コミットを削除していきたいと思います。

git rebase -i 75abbb6

または

git rebase -i --root

コマンドの場合は、「git rebase -i ハッシュ値」です。「-i」はインタラクティブリベース(対話式で進める)になります。ハッシュ値は1つ前のコミットのハッシュ値を指定します。最初から修正したい場合は「--root」を指定することもできます。

VSCodeでも同じ作業が可能です。こちらも消したいコミットの1つ前をクリックして、「Rebase current branch on this Comment」をクリックします。

確認ダイアログが表示されるので、「Launch interactive Rebase in new Terminal」のチェックを入れて「Yes, rebase」をクリックします。

Rebaseのインタラクティブモードが開始され、エディタ(Vim)が起動します。VimにはTODOリストが表示されます。

TODOリストには、左からコマンド、コミットハッシュ値、コミットメッセージが記載されています。並び順が日付の昇順(上が古い)になっているのでご注意ください。

今回は「テスト」コミットを削除したいので「テスト」の行の「pick」を「drop」に変更します。TODOリストでは基本的にコマンドだけを編集して、次のステップで実際の修正を行います。

他にもコマンドには以下のような値が用意されています。

コマンド省略形何をするか(要点)よく使う?
pickpそのままコミットを採用する★★★
rewordrコミット内容はそのまま、コメントのみ編集★★★
editeそのコミットで停止し、内容を修正できる。git rebase --continueで再開★★
squashs直前のコミットに統合し、コメント編集画面が出る★★★
fixupf直前に統合、コメントは残さない★★★
fixup -Cf -C統合し、このコミットのコメントを採用
fixup -cf -c統合し、コメント編集画面を開く
execx各ステップでシェルコマンドを実行
breakbここで一旦止める(後で continue)
dropdコミットを完全に削除★★
labell現在の位置にラベルを付ける
resett指定ラベルまで HEAD を戻す
mergemマージコミットを作る
update-refuref 更新を遅延させる
:wq

編集したら「wq」で保存してVimを終了します。

Vimが終了すると、dropを指定したコミットが削除されます。ただし、過去のコミットを削除すると高い確率で競合が発生します。

競合が発生したら、VSCodeの場合はマージエディタで編集していきます。マージエディタの使い方は割愛します。

マージが完了すると自動的に修正内容がステージングされるので「続行」をクリックします。これで競合は解決しローカルの修正は完了です。

リモートに反映したい場合は、強制プッシュが必要です。チーム開発の場合は気をつけてくださいね。

変更の右上にある「⋯」をクリックします。

「プッシュ(強制)」をクリックします。

git push -f origin main

強制プッシュのコマンドはこちら。

「変更の強制プッシュを行おうとしていますが、これは破壊的なことがあり、他人の変更を謝って上書きする可能性があります。続行しますか?」

と怖いメッセージが出たら「OK」をクリックしましょう。

これで、リモートとローカルが同期されました。

コミットを削除すると、修正以降のコミットのハッシュ値が変わっていることが分かります。

コミットのハッシュ値が変わる理由

ハッシュ値は『親コミットのハッシュ値』『中身』『メッセージ』などを混ぜて作られるため、親のハッシュ値が変わったり中身が消えたりすると、連鎖的にそれより後のハッシュ値がすべて計算し直しになるためです。このハッシュ値が変わると、他のPCでは整合性が合わなくなりチームに大混乱をもたらすことになります。

リベース前に戻す方法

リベースで間違ってコミットを修正してしまったときのために、元に戻す方法も覚えておきましょう。

まず、リフログでコミットの履歴が一覧を確認します。並び順は日時の降順(新しい方が上)になっています。

リセットの位置ですが「rebase (start)」が目印になります。startの一つ前に戻したいので、そのハッシュ値をコピーします。ここでは、「3c3ac33」になります。

git reset --hard 3c3ac33

リセットのコマンドを実行します。

リセットが成功するとこのようになります。リモートと一致していないので分岐してしまっていますね。

こちらを1つにするために、ここでも強制プッシュが必要です。

git push -f origin main

強制プッシュのコマンドはこちら。

無事に完了すると1本の元の状態に戻ります。

ハッシュ変更が伴う操作の注意点

Gitのコミットハッシュ(ID)は、その内容や親コミット、時刻などの情報から計算されています。そのため、Reset, Rebase, Amend を行うと、見た目の内容が同じでも**「全く別の新しいコミット」**として生成されます。

影響1:強制プッシュの必要性 リモート(GitHub等)にプッシュ済みのコミットに対してこれらの操作を行うと、通常の git push は拒否されます。--force-with-lease を使う必要があります。

影響2:チーム開発での混乱 他の人があなたの古いコミットをベースに作業を始めていた場合、あなたが履歴を書き換えてプッシュすると、他の人の環境で「履歴の不整合(コンフリクト)」が発生し、修正に多大な手間がかかります。

結論:

  • 自分のローカル作業中なら:ResetRebase で自由に整理してOK。
  • 既に公開・共有された後なら:Revert を使うのが安全。

2.Rebaseの「squash」でコミットを削除する方法

次は、リベースの別のモード「squash」で過去のコミットを削除する方法です。「squash」は前のコミットに次のコミットを統合するコマンドです。

git rebase -i 75abbb6

または

git rebase -i --root

リベースのコマンドは前章の「drop」と同じです。

Vimが起動しTODOリストが表示されるので、消したいコミットのコマンドを「pick」から「squash」に変更します。

:wq

変更したら「wq」で保存してVimを終了します。

すると、統合する側のコミットにマージが実行され、変更がステージングされた状態になります。統合する側のコミットメッセージの変更もここでできます。

:wq

コミットメッセージを変更したら「wq」を実行してVimを終了します。

保存が完了するとローカルとリモートが分離した状態になります。ローカルの方にはテストがないことが分かります。

この状態で強制プッシュを実行します。

怖いメッセージが表示されるので「OK」をクリック。

プッシュが完了すると、ローカルとリモートが一つに統合されたことが分かります。

コミットIDも変わっていることが分かります。

2.Rebaseでコミットを削除する方法(fixup編)

「fixup」は操作は「squash」と基本的には同じです。違う点は「fixup」ではVimが起動しない点だけです。つまり、コミットメッセージは今のままと変わらないことになります。

TODOリストでは、統合したいコミットのコマンドを「pick」から「fixup」に変更し「wp」で保存します。

Vimが起動しないので、いっきにここまで来ました。この状態は「squash」の時と同じですので、強制プッシュします。

強制プッシュが完了すると「テスト」コミットが削除されていることが分かります。

3.Revertでコミットを削除する方法

最後にRevertを見ていきましょう。

Revert は、指定したコミットの変更内容を打ち消すためのコマンドです。
過去の履歴を削除・書き換えるのではなく、「取り消し用の新しいコミット」を追加します。

git log --all --oneline --decorate --graph

まずは、消したい「テスト」コミットのIDを調べます。

リバートは修正したいハッシュ値がほしいので、「テスト」のハッシュ値 6d541b0を使います。

git revert 6d541b0

または

git revert HEAD^    1つ前のコミットを消したい時
git revert HEAD^^   2つ前のコミットを消したい時
git revert HEAD^^^  3つ前のコミットを消したい時
git revert HEAD^3   数字で指定することも可能

このようにコマンドを入力します。

VSCodeの場合も削除したいコミットを右クリックして「Revert」をクリックし、

「Yes, revert」をクリック。

Error: Unable to Revert Commit
error: could not revert 6d541b0... テスト
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git revert --continue".
hint: You can instead skip this commit with "git revert --skip".
hint: To abort and get back to the state before "git revert",
hint: run "git revert --abort".
hint: Disable this message with "git config advice.mergeConflict false"
Auto-merging index.txt
CONFLICT (content): Merge conflict in index.txt

この時、コンフリクトが発生することがあります。

原因は「テスト」で行った変更箇所を、その後の「コミット1〜3」のどれかで上書き(更新)してしまったからです。

こうなっても慌てる必要はありません。いったんリバートをキャンセルするか、競合を解消する方法を次にご紹介します。

ただ、リバートの競合解決は正しい知識がないと迷宮入りすることがあるので、一番は競合させないことです。

ちなみに、競合が発生しない場合はここで終了です。

リバートをキャンセルする方法

git revert --abort

リバートをキャンセルする方法は簡単です。「--abort」コマンドを実行するだけです。

これで、何事もなかったかのように元にもどります。

リバートの競合を解消する方法

まず、リバートの競合を修正する際に2つのルートがあるので全体の流れを抑えておきましょう。

リバートの競合を修正する2つのルート

  1. マージした結果、コミットの変更があった→「git revert --continue」で終了(通常はこれ)
  2. マージした結果、コミットの変更が何もなかった→「git revert --continue」は使えないので自分で「git commit」で終了(リバートした意味がないので通常はありえない)

つまり、通常はリバート競合を完了させるためには、「git revert --continue」ができればOKということです。

そこで大事になってくるのが「git revert --continue」が何を行っているのか?ということです。「git revert --continue」の中身はこちら。

git revert --continue の中身

  1. メッセージの準備: Revert "テスト" というコミットメッセージを自動で作る
  2. コミットの実行: git commit を実行する
  3. モードの解除: 「リバート中」というフラグを削除し通常モードに戻す

そうなんです。「git revert --continue」はコミットメッセージの作成からコミットまで行ってくれているんですね。

なので、マージ後の「git commito -m "ホゲホゲ"」みたいなことはしなくてよいということになります。

これさえ知っていればリベート競合なんて何も怖くありませんね。

それでは、さっそくリバートで発生した競合を解決していきましょう。

リバートの競合を解消する方法はVSCodeの場合は、「マージエディタで解決」を使用します。※マージエディタの使い方は割愛します。

競合時にマージエディタを使うメリット3点

1. 「変更前・変更後・結果」が3画面で同時に見える

普通のエディターだと、1つのファイル内に <<<<<<<>>>>>>> という不気味な記号が混ざり、どこからどこまでが自分のコードか分からなくなります。

マージエディターなら:

  • 左側: 現在のコード(Incoming)
  • 右側: 戻したいコード(Current)
  • 下側: 合体後の結果(Result)

これらを見比べながら「採用」ボタンをポチポチ押すだけで済むので、凡ミスが激減します。

2. git add を自動でやってくれる

マージエディターで「完了」を押すと、VSCodeが裏側で git add(ステージング)まで自動で済ませてくれます。 初心者が一番忘れる「修正したのに add し忘れて Git status がエラーのまま」という事態を防いでくれます。

3. 記号の消し忘れがない

手動で直していると、たまに ======= という記号を1行消し忘れて、プログラムが動かなくなる(構文エラー)ことがありますが、エディターを使えばその心配はゼロです。

注意点としてマージをする場合、通常「git add」→「git commit」が必要になりますが、マージエディタを使った場合「git add」まで自動的にやってくれます。

そこで、通常は「git commit」は自分で行う必要があるのですが、リバートの場合は、continueがコミットまで実行してくれるので、ここで git commit をしたくなるのはぐっと我慢です。

マージが解決すると、画面が通常に戻ったように見えるので、一見リバートが完了したように見えます。が、リバートによるコミットが増えていないのでまだリバート続行中の状態です。

リバート中にgit statusした時のスクリーンショット

「git status」で確認しても「reverting」となっていることが分かります。

git revert --continue

というわけで、「--continue」でリバートを完了させます。

コンティニューが正常に終了すると打つ消すためのコミットが1つ追加されます。つまり、裏でコミットが実行されたことになります。

最後にプッシュして完了です。

「git revert --continue」で「nothing to commit」と表示されてがうまくいかない時は?

「git revert --continue」してもこのように「nothing to commit」が表示されて先にすすめないことがあります。

これは、マージした結果、変更内容がなかった場合におこります。

git commit --allow-empty -m "Revert テスト"

その時は、 git commit --allow-empty -m "メッセージ" で強制的に空のコミットを作る必要があります。

この状態になったらプッシュして完了です。