🧭

Gitでコミットを取り消す5つの方法を整理して比較する

はじめに

フォルシア株式会社エンジニアの山本です。未経験で転職して以来、チーム開発の中でコミットを取り消す機会が増えてきました。

個人開発ではやり直しも自由ですが、チーム開発では履歴の整合性が重要です。誤ってコミットを消したり、force pushで混乱を招いたりしないためには、正しい知識が欠かせません。

この記事は、Gitを日常的に使う初中級者ITエンジニアが、状況に応じて安全にコミットを取り消せるようになることを目的としています。

Gitには複数の取り消し方がありますが、仕組みを理解して使い分ければ怖くありません。

概要(記事のまとめ)

結論から言えば、状況に合わせて以下のコマンドを使い分けるとよいでしょう。

  1. プッシュ前(安全)
  • reset --mixed: コミットを取り消し、変更はワーキングツリーに残す。ステージングは空になる。
  • reset --soft: コミットを取り消し、変更はステージ済みのまま残す。
  • commit --amend: 直前コミットを修正版に差し替え。変更があれば追加、なければメッセージ修正。
  1. プッシュ前(要注意)
  • reset --hard: コミットもステージもワーキングツリーもすべて消す。ただし30日以内であればreflogで復旧可能。
  1. プッシュ後
  • revert: 履歴を壊さず、対象コミットを「逆差分」で打ち消す新しいコミットを作る。公開後の取り消しはこれ一択。後続の変更とぶつかるとコンフリクトが発生するので注意。

プッシュ前の取り消し-安全な操作

プッシュ前なら、取り消しはgit resetで行いましょう。安全に使えるものが3つあり、わずかに仕様が異なります。

  • コミットを取り消し、ステージング前に戻す: git reset --mixedあるいは単にgit reset
  • コミットを取り消すが、ステージング済にする: git reset --soft
  • コミットをちょっと修正: git commit --amend

reset --mixed

コミットを取り消して、その内容をステージング前に戻すのがgit reset --mixed <コミット>です。ここで<コミット>は、コミットを一意に特定するための「コミットハッシュ」です。コミットハッシュはgit logで確認できます。初心者が戸惑いやすい点ですが、まずは「直前のコミットに戻したいときはHEAD^」と覚えておくと便利です。

実はresetコマンドの既定は--mixedのため、省略も可能です。

このコマンドは「変更内容はそのまま、ステージング前から選びなおしたい」ときに使いましょう。基本、これを使えば問題ありません。

reset --soft

コミットを取り消すが、その内容はステージング済みの状態にするのがgit reset --soft <コミット>です。

このコマンドは「コミットメッセージを直したい」ときに使いましょう。プッシュしたい変更内容そのものに手を加える必要がないときや、複数のコミットをまとめて再構成したいときに便利です。

commit --amend

直前のコミットを修正版に差し替えるときに使うのがgit commit --amendです。ステージングに新しい変更がある場合はコミット自体の差し替え、なければメッセージだけ修正します。

ファイルを1個addし忘れたとか、コミットメッセージをタイプミスしたときに便利です。

プッシュ前の取り消し-要注意

よく知られたコマンドとしてgit reset --hard <コミット>がありますが、これは慎重に使いましょう。コミット、ステージングを取り消すだけでなく、ワーキングツリー(作業ディレクトリ)の変更をも取り消すものだからです。

以下の場合だけに使うことを勧めます。後述のように復旧は可能ですが、やや高度な知識が必要なため、実行は慎重に検討しましょう。

  • 実験的に書いたコードを全て破棄したい
  • 生成物が散らかったリポジトリを完全に掃除したい

プッシュ後の取り消し

プッシュ後の取り消しはgit revert <コミット>で行いましょう。履歴を壊さず安全なので、チーム開発ならこれ一択です。

ただしrevertは内部仕様が少し複雑で、理解せずに使うと混乱します。手順を確認しながら慎重に実行しましょう。

このコマンドは「そのコミットで行った変更を逆向きに適用する新しいコミット」を作るというものです。よって、後続のコミットで同じ場所が編集されていると、逆差分が適用できずコンフリクトが起こります。

具体例で考えてみます。まず、あなたは以下の流れでコミット/プッシュしました。

コミットA (初期)
↓
コミットB (関数fooを追加)
↓
コミットC (fooを加筆)
↓
プッシュ

このときBの内容に誤りを見つけ、revertで修正しようとすると、コンフリクトが起こってしまいます。Bの逆差分であるコミットとコミットCのうち、どちらを優先すべきか判断できないからです。その場合は手動で解消する必要があります。

resetでコミット履歴は残らない

私はかつて「softを使うと、リセットしたというコミット履歴が残るんだっけ。履歴も残したくないからhardを使うか…」などとぼんやり思っていました。これは誤解で、実際は以下が正確です。

  1. reset --soft/--mixed/--hardのいずれでも、コミット履歴は残らない
  2. revertだけが「取り消し履歴を残す」方法である

プッシュ後の取り消しはrevert一択と書いたのはそのためで、プッシュ後にresetを使うとコミット履歴が消え、リモート側との整合性が崩れてしまいます。

整合性が崩れた状態でプッシュしようとすると拒否されますが、この状態でさらにgit push --forceをすると、リモートも書き換えられてしまいます。チーム開発の共有ブランチでこれをすると他のメンバーにも大迷惑がかかるため、絶対にやめましょう。

reflogによる復旧

git reset --hardを使ってワーキングツリーの変更内容を消してしまっても、実は復旧可能です。それがgit reflogです(reference+logでreflogと覚えるといいかも)。

復旧できる理由を理解するには、gitの内部実装を少し知っておく必要があります。

reset --hardによってワーキングツリーが「消える」と先述しましたが、これは厳密には誤りです。消えるのはコミットそのものではなく、コミットへの参照です。

各コミットは親コミットへの参照をもち、鎖のように「グラフ」を作ります。より正確には、コミットどうしの参照関係を「頂点」と「辺」とで表した構造、いわゆる有向グラフを作ります。これによって、ブランチが分かれた場合のコミット関係も、枝分かれとして表現できます。

git reflogは「過去にHEADがどの参照を指さしていたか」に関するログを表示するもので、このログには「すでに参照が外れてしまったコミット」も含まれています。ゆえに、これを実行することで、以下のようにコミットのすべての履歴が表示できます。

41324ab (HEAD -> master) HEAD@{0}: checkout: moving from test to master
4b2bf31 HEAD@{1}: commit: test
41324ab (HEAD -> master) HEAD@{2}: checkout: moving from master to test
41324ab (HEAD -> master) HEAD@{3}: commit (initial): first commit

おおむね以下の意味です。

HEAD@{0} … testブランチからmasterブランチへ移動
HEAD@{1} … testブランチにコミット
HEAD@{2} … masterブランチからtestブランチへ移動
HEAD@{3} … masterブランチにコミット

この履歴を読んで、戻したいコミットのログ番号を指定し、以下を実行することで、ブランチとしてワーキングツリーが復活します。

git branch ブランチ名 HEAD@{ログ番号}

ただし、これが可能なのはそのコミットがどこからも参照されなくなってから約30日後まで。Gitは参照されなくなったオブジェクトをガベージコレクションで削除するので、早めの対応が必要です。

まとめ

  • プッシュ前はgit reset --mixedを基本に、--soft--amendで細かく調整する。
  • --hardは実験コードや生成物の掃除など、内容を破棄してよいときだけ使う。
  • プッシュ後はgit revert一択。履歴を壊さず安全に取り消せる。
  • --hardで内容を誤って消した場合もgit reflogで復旧できる可能性がある。

状況ごとの使い分けを理解しておけば、どの取り消し操作も怖くありません。

参考: 公式ドキュメントの定義

Git公式ドキュメント(git-reset)では、各モードを次のように説明しています(拙訳、要約)。

  • --soft: HEADのみを移動し、ステージングエリアやワーキングツリーは変更しない。
  • --mixed: ステージングエリアをリセットし、ワーキングツリーは保持する(既定動作)。
  • --hard: ステージングエリアとワーキングツリーの両方をリセットし、変更を破棄する。

git-revertについても、公式には次のように定義されています。

“Revert the changes that the related patches introduce, and record some new commits that record them.”
(既存のコミットが導入した変更を打ち消す新しいコミットを記録する)

この記事を書いた人

山本 留央

2025年キャリア入社

前職では電子部品の熱設計・構造設計を担当。異業種間の温度差は想定よりも高めです。

FORCIA Tech Blog

Discussion