git revertとgit reset——どちらも「取り消し」に使われますが、Git初心者が最も混乱し、ベテランも--hard連打で変更を吹き飛ばしてしまうGit最大の危険ゾーンです。
多くの記事は「revertは安全、resetは危険」という表面的な結論で終わっています。しかし実務では「なぜそうなるのか」「どの層(HEAD/Index/Working Tree)に何が起きるのか」を理解していないと、--softと--mixedの違いで変更を紛失したり、--hardでコミット前の作業を数時間分ロストしたりします。
この記事では、Gitの3層モデルを土台に、revertとreset(--soft/--mixed/--hard/--keep/--mergeの5モード)がそれぞれの層に何をするのかを可視化し、8軸比較、判断フローチャート、range revert、revertしたコミットをrevertする難問、reset --hardで消した変更をreflogで復活させる緊急手順、チーム運用ポリシーまで、2026年の現場で詰まらないための完全ガイドとしてまとめます。
この記事で学べること
- 30秒で分かる結論:どちらを使うべきかの黄金律
- Gitの3層モデル(HEAD/Index/Working Tree)と取り消しコマンドの関係
resetの5モード(--soft/--mixed/--hard/--keep/--merge)を層単位で完全解剖revertの挙動と連続revert・範囲revert・merge revertの扱い- 8軸比較:履歴・安全性・復元性・merge対応・範囲指定など
- 判断フローチャート:今すぐどちらを使うか一発判定
- revertされたコミットをrevertする(revert-revert問題)の正しい解法
reset --hardで消えた変更をreflogで復旧する緊急手順- チーム運用ポリシーとGitHub Branch protectionとの連動
30秒で分かる結論
使い分けは「公開済みかどうか」と「何を残したいか」で決まります。理由は後半で解説します。
黄金律:push後=revert一択、push前=reset(soft/mixedを優先)。--hardは事前にcommitかstashをしてから使うのが鉄則。
Gitの3層モデル:取り消しコマンドの土台
revertとresetの挙動を正確に理解するには、Gitが扱う3つの層を把握する必要があります。「どのコマンドがどの層を変えるか」が分かれば、変更の紛失を防げます。
+----------------------------------------+
| Working Tree (作業ツリー) | ← 実際のファイル
| あなたがエディタで編集している内容 |
+----------------------------------------+
↑↓ git add / git restore
+----------------------------------------+
| Index (ステージング/インデックス) | ← 次のコミット候補
| git add で積み上げた変更 |
+----------------------------------------+
↑↓ git commit
+----------------------------------------+
| HEAD (最新コミット) | ← ブランチ先端
| リポジトリに記録された最新状態 |
+----------------------------------------+
3層の役割
- Working Tree:エディタで開いている実ファイル。
git statusで”modified”として表示 - Index:
git addで貯めた”次commitする候補”。staging areaとも呼ぶ - HEAD:現在のブランチ先端コミット。リポジトリ上の”記録された過去”
revertとresetは、この3層に対してまったく異なる操作をします。この違いを可視化するのが本記事の核心です。
revertの正体:取り消しを「新しいコミット」として追加
git revertは、指定したコミットの変更を反転した内容で新しいコミットを追加します。既存の履歴は変更されず、「取り消し行為そのもの」が履歴に刻まれる点が最大の特徴です。
# 特定のコミットを打ち消す git revert a1b2c3d # 実行後 # a1b2c3d の変更を反転する新コミットが作られる # エディタが開いてコミットメッセージを編集できる # → "Revert \"元のメッセージ\"" が自動入力 # エディタを開かず即時コミット git revert a1b2c3d --no-edit # コミットを作らず変更だけ適用(手動でcommit) git revert --no-commit a1b2c3d # revert中断 git revert --abort # 複数コミットをまとめてrevert(レンジ指定) git revert OLDER_SHA..NEWER_SHA # → それぞれ別のrevertコミットが順に作られる # レンジを1コミットにまとめてrevert git revert --no-commit OLDER_SHA..NEWER_SHA git commit -m "Revert feature X (multiple commits)"
revert前後のコミットグラフ
main A---B---C---D---E # Cにバグがあったとする
main A---B---C---D---E---C' # 履歴は一切削除されていない # C' は「Cを取り消す」内容を持つ新しいコミット
3層への影響
revertの「安全性」の正体
既存コミットのハッシュは変わらず、履歴は追加のみ。これはpush済みブランチ・他メンバーのローカル・PRやIssueのコミット参照・CIキャッシュなど既存の参照をすべて保ったまま変更を取り消せる、という意味です。共有ブランチで使える唯一の実用的な取り消し手段。
resetの5モード完全解剖:–soft/–mixed/–hard/–keep/–merge
git resetはHEADを指定位置に移動するコマンドで、5つのモードで「Index」「Working Tree」に対する影響度が変わります。多くの入門記事は--soft/--mixed/--hardの3つしか扱いませんが、trouble shootingでは--keepと--mergeの理解が必須です。
5モードの層影響マトリクス
--mixedがデフォルト(git reset HEAD~1はmixed扱い)。明示しないと意図せずunstageが行われるので、必ずモードを明示するのが熟練者のマナー。
–softの使いどころ
# 状況:コミット済みだが、ファイル追加漏れがあった git reset --soft HEAD~1 # HEADが1つ戻り、直前コミットの変更はすべてIndexに残る # 追加ファイルを入れて再commit git add forgotten_file.txt git commit -m "feat: 新機能(ファイル追加漏れ修正込み)" # 類似用途:複数コミットを1つにまとめたい git reset --soft HEAD~3 git commit -m "feat: 機能Xをまとめて実装"
–mixedの使いどころ
# 誤ってaddしたファイルをunstage git reset HEAD secret.env # ファイル自体は消えない(staging areaから外れるだけ) # コミット自体をやり直したいが変更はそのまま git reset HEAD~1 # HEADが戻り、変更がWorking Treeに残る
–hardの使いどころ(要注意)
# ローカル作業を丸ごと捨てる git reset --hard HEAD # 未コミットの変更はすべて消える # 過去の状態に完全に戻る git reset --hard HEAD~3 # リモートと完全同期(ローカル変更全消し) git fetch origin git reset --hard origin/main
--hardは未コミットの変更を問答無用で消します。実行前にgit statusで未コミット変更の有無を確認し、必要ならgit stashで退避しておくこと。消してしまってもgit reflogでHEAD移動履歴から復元できる可能性がありますが、未コミット変更は復元できないのが最大の落とし穴。
–keepの使いどころ
# 今いる位置に未コミット変更がある # 移動先で同じファイルが変わっていたら失敗する(安全側) git reset --keep main # エラー例 # error: Entry 'foo.js' not uptodate. Cannot update. # → 衝突するファイルがあるので移動を拒否 # この挙動により --hard のような事故を防げる
–mergeの使いどころ
# merge中にコンフリクトが出て「もう全部やめたい」 git reset --merge # Working Treeの未コミット変更を保ちつつmergeを中止 # --abort との違い # - git merge --abort : merge開始前の状態に戻る(標準) # - git reset --merge : mergeを中断しつつ、未コミット変更は保護
pathspec付きreset(ファイル単位)
# 特定ファイルだけHEADに戻す(--mixed相当) git reset HEAD file.txt # 特定ファイルだけ過去版に戻す git reset a1b2c3 -- file.txt # 注意:pathspec付きresetは --soft/--hard を受け付けない # → --hard でファイル単位にしたいなら git checkout や git restore を使う git restore --source=HEAD file.txt # 推奨
revertとresetを8軸で比較
機能の違いを8つの軸で整理します。実務ではこの表を見るだけで判断できます。
一言で:revertは”履歴にrevertした事実を残す”/resetは”ブランチが指す場所を変える”。目的が異なる別物で、どちらが優れているではなく使い分けの問題。
判断フローチャート:今すぐどちらを使うか
Q1: 取り消したいコミットはpush済みですか? YES → revert 一択(reset禁止) NO → Q2へ Q2: 残したい要素は何ですか? コミットそのもの以外を残したい(変更・add状態) → reset --soft コミットだけ消し、変更はファイルに残したい → reset --mixed(既定) すべて吹き飛ばしたい(完全に綺麗に) → reset --hard(要stash確認) Q3: 取り消したいのはファイル単位ですか? YES → git restore / git reset HEAD <file> を使う NO → そのままresetまたはrevert Q4: 取り消しを再確認・検証する必要がありますか? YES → revert --no-commit で変更だけ適用→テスト→commit NO → そのままrevert/reset
シナリオ早見表
- main/masterにpush済みの誤コミット →
git revert HEAD→ push - PRで指摘されたaddファイルを戻す →
git reset HEAD file.txt(pushしてない場合) - 数コミット分をまとめ直してきれいに →
git reset --soft HEAD~3+commit(未push時) - mergeが間違いだった(push後) →
git revert -m 1 <merge commit> - reset –hardで作業を消した →
git reflog→git reset --hard HEAD@{1}
実務シナリオ別の使い分け
シナリオ①:直前のコミットメッセージを書き直したい
# 一番簡単なのは amend(直前コミットそのものを編集) git commit --amend -m "正しいメッセージ" # 内容も変えたい場合 git add additional_file.txt git commit --amend --no-edit # 履歴を前のコミットに含めて再構築したい git reset --soft HEAD~1 # 変更はIndexにまとめて残り、好きなようにcommit可能
シナリオ②:PRに含めてはいけないファイルを除外
# まだcommit前 git reset HEAD .env # または git restore --staged .env # commit済みならまず履歴整形(pushしていない前提) git reset --mixed HEAD~1 git restore --staged .env echo ".env" >> .gitignore git add .gitignore git commit -m "Add .env to gitignore" # push済みで.envが流出した場合は history rewrite が必要 # → git filter-repo か BFG Repo-Cleaner
機密ファイルをpushしてしまった場合、revertでは過去の履歴に残り続けるため【Git】履歴に含まれる機密情報を完全に削除する方法でfilter-repoによる履歴書き換え+シークレットローテーションが必要です。
シナリオ③:mergeコミットを取り消したい(push済み)
# push済みmergeコミットを取り消す git revert -m 1 <マージコミット> # -m 1 = "一番目の親 (mainline)" を基準に差分を取る # これをつけないとGitが「どっちの親を基準にすべきか分からない」とエラー # fatal: commit <sha> is a merge but no -m option was given.
この操作には重要な副作用があります——revertしたfeatureブランチを再度mergeしても変更が入らない現象が起きます。詳細は【Git】mergeコミットを取り消す方法を参照。
シナリオ④:リモートと完全同期したい
# ローカル変更を全部捨て、リモートと完全一致させる git fetch origin git reset --hard origin/main # 対象ブランチを明示的に git fetch origin feature git reset --hard origin/feature # 直後に未追跡ファイルも消してクリーンに git clean -fd
reset --hard origin/<branch>は強力ですが、ローカルの作業中ファイルも含めて問答無用で上書き。実行前にgit stash -uで未追跡含めて退避することをおすすめします。
シナリオ⑤:複数の過去コミットをまとめてrevert
# OLDER_SHAより新しくNEWER_SHAまでの全コミットをrevert git revert OLDER_SHA..NEWER_SHA # → コミットごとにrevertコミットが個別に作られる # 1コミットにまとめたい場合 git revert --no-commit OLDER_SHA..NEWER_SHA git commit -m "Revert feature X (rolled back 5 commits)" # 逆順に適用されることに注意 # 順番次第でコンフリクトしやすいので --no-commit で最後にまとめるのが安全
上級テクニック:revertのrevert・ORIG_HEAD・部分取り消し
revertしたコミットをrevertする(revert-revert)
「revertしたけど、やっぱりrevertを取り消したい」状況はよくあります。解法はrevertコミットをさらにrevertすること。
# もとの流れ # C (機能追加) → C' (C をrevert、機能削除) # C' を revert すれば機能が戻る git revert C' # → C'' が作られ、再び機能が戻った状態になる # 履歴 # ... → C → C' → C'' ... # + + # 機能 復活
revertしたfeatureブランチを再度mergeしても内容は戻りません。Gitはそのコミットが既に履歴にあると見なすためです。解決には「①revert-revertで機能を復活させる」「②ブランチを新規に切り直す」のどちらかが必要。詳しくは【Git】mergeコミットを取り消す方法参照。
ORIG_HEADでresetを取り消す
Gitは危険な操作(reset/merge/rebase/pull)の直前位置を自動的にORIG_HEADに保存します。やり直しが1コマンドで可能です。
# 誤って reset --hard してしまった直後 git reset --hard ORIG_HEAD # → resetする前の位置に戻る # ORIG_HEADの中身を確認 git rev-parse ORIG_HEAD git log ORIG_HEAD -1 --oneline
ORIG_HEADは1回分の保険。次に危険操作をすると上書きされるので、複数回遡りたい場合はgit reflogで時系列から任意の位置を選ぶのが確実。
reflogで完全復旧する緊急手順
# HEADの移動履歴を確認
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# f4e5d6c HEAD@{1}: commit: feat: 機能Y ← これが欲しい
# 9a8b7c6 HEAD@{2}: commit: feat: 機能X
# HEAD@{1}に戻す
git reset --hard HEAD@{1}
# または直接ハッシュ指定
git reset --hard f4e5d6c
# もしすでにgc済みで消えていたら git fsck --lost-found で探す
git fsck --lost-found
ls .git/lost-found/commit/
reflogの寿命
reflogエントリはデフォルトで90日保持されます(gc.reflogExpire)。その期間内ならどんなreset --hardからも復旧可能。ただし未コミットの変更はreflogに記録されないため、--hard前に必ずgit stashするのが鉄則。
ファイル単位の精密な取り消し
# ファイル単位で過去版に戻す(Working Treeのみ) git restore --source=HEAD~2 file.txt # ファイル単位でIndexだけ戻す(unstage) git restore --staged file.txt # ファイル単位で両方戻す git restore --source=HEAD --staged --worktree file.txt # ハンク単位(行の一部)だけ取り消す git restore -p file.txt # → "y"/"n"/"s"でハンクごとに選択
最新のGit(2.23+)ではgit restoreが推奨。git checkout -- fileの代わりにgit restore file、git reset HEAD fileの代わりにgit restore --staged fileを使うと意図が明確になります。
やらかした時の緊急復旧フロー
レベル1:resetしてしまったコミットを取り戻す
# 直前のresetだけ取り消したい
git reset --hard ORIG_HEAD
# 数回前のresetを取り消したい
git reflog
git reset --hard HEAD@{5}
レベル2:–hardで消えた未commit変更を取り戻す
未コミットの変更は原則として復元できません。ただしエディタ(VS Code等)が自動保存したバックアップ、OSのゴミ箱、IDE内のローカル履歴機能などが残っている可能性があります。VS CodeならFile → Revert FileやLocal History(Timeline)で数時間分の編集履歴が復元できることも。
レベル3:push後に気づいたforce pushされたresetを取り戻す
# 自分のローカルが上書きされた場合でも、 # 他メンバーがまだfetchしていなければ彼らのローカルにコミットが残っている # 他メンバーから該当ブランチをforce pushしてもらう # もしくはremote側のreflogを確認(GitHub APIでは参照できない) # GitHubの場合 # - Events APIで過去のpushイベントを確認 # - Security Adminに連絡して削除前のSHAから復元依頼
レベル4:revertしすぎて元に戻したい
# revertを取り消す git revert <revertコミット> # 複数のrevertを一気に取り消す git revert OLDER_REVERT..NEWER_REVERT
revertを連打してしまった場合、reflog+reset(未push前提)で一気に元に戻すのも選択肢。push済みならrevert-revert戦略が唯一の正攻法。
チーム運用:revert/resetルールの明文化
resetの誤用は個人の事故にとどまらず、force pushで他メンバーの作業を消すことがあります。チームとしての明確なルールが必要です。
## 取り消し操作のルール
### push済みブランチ
- 取り消しは **revert のみ**
- mainブランチでの `reset` / `push --force` は禁止(Branch protectionで物理強制)
- mergeコミットの取り消しは `revert -m 1` を必ず使用
### 未push ブランチ(個人作業)
- `reset --soft` / `reset --mixed` は自由に使用可
- `reset --hard` は直前に `git status` 確認+必要なら `stash` 退避
- 共有featureブランチでは force-with-lease 必須(他メンバーに事前連絡)
### 事故対応
- `reset --hard` で消した場合、まず `git reflog` 確認→`ORIG_HEAD` か `HEAD@{N}` で復旧
- 90日以内なら `git fsck --lost-found` で孤児コミットも復旧可能
- 未コミット変更のロストは IDE の Local History を確認
### GitHub設定
- main/release 系は Branch protection で force push と削除を禁止
- Require pull request reviews:最低1名以上レビュー
- Require status checks:CIのgreen必須
Branch protectionで物理強制が最強
「main/masterではresetを禁止」を口約束にせず、GitHub/GitLabのBranch protection rulesで設定するとgit push --force自体が物理的に拒否されます。リポジトリ新設時の初期タスクとして組み込むのが鉄則。
よくある質問
git reflogで90%復元可能。git reflogでHEAD@{N}を探し、git reset --hard HEAD@{N}で戻します。ただし未コミットのWorking Tree変更は復元不能です。VS CodeのTimeline機能やエディタの自動保存バックアップを確認するのが最終手段。詳細は本記事「やらかした時の緊急復旧フロー」参照。--no-editがついているか、環境変数GIT_EDITOR/EDITORが未設定の可能性があります。git config --global core.editor vim(または好きなエディタ)を設定しておきましょう。CI環境で実行する場合は--no-editで自動メッセージのまま進めるのが一般的。git log --onelineで正しいSHAを確認)。②revertが未commit状態で止まっている(git statusでrevertが完了しているか確認→必要ならgit commit)。③同じ内容が別のコミットにも分散している(複数コミットをrevertする必要あり)。reset HEAD~1はデフォルトで--mixed扱い。HEADが戻りIndexもリセットされます。--softを明示するとIndexはそのまま残るため、「add済み状態を保ったままcommitだけやり直したい」場合に必要。意図せず--mixedになって驚かないよう、モードは常に明示が安全。関連記事
- 【Git】コミットの取り消し方 — reset・revert・amendの使い分けカタログ
- 【Git】pushを取り消す方法 — revert/force-with-lease/機密漏洩時の緊急対応
- 【Git】消したファイルを戻す方法 — restore/checkout/revertの使い分け
- 【Git】特定のコミットまで戻す方法 — reset/switch/revertレンジの使い分け
- 【Git】addの取り消し方法 — restore –staged/reset HEAD/ハンク単位unstage
- 【Git】マージの取り消し方法 — merge –abort/reset/revert -mの状態別対処
- 【Git】mergeコミットを取り消す方法 — revert -m 1徹底解説&再マージ問題
- 【Git】履歴に含まれる機密情報を完全に削除する方法 — filter-repoによる履歴書き換え
- 【Git】rebaseとmergeの違いと使い分け完全ガイド — 履歴統合の使い分け
- 【Git】よく使うgitコマンド決定版チートシート — 取り消し系を含む早見表
- 【Git】push後に別ブランチの変更が混ざってしまったときの対処法 — 5パターン診断・revert活用
まとめ
- revert=「取り消しコミット」を追加、履歴は改変しない(push後の唯一の安全策)
- reset=HEADを別コミットに移動、–soft/–mixed/–hard/–keep/–mergeの5モード
- 3層モデル(HEAD/Index/Working Tree)で各モードの影響範囲を理解するのが近道
- 黄金律:push後=revert一択、push前=reset(主にsoft/mixed)
--hard前には必ずgit statusとgit stashで保険- 事故時は
ORIG_HEADまたはreflog→reset --hard HEAD@{N}で復活 - mergeコミット取り消しは
revert -m 1必須、再mergeの罠に注意(revert-revert) - チーム運用:main/masterはBranch protectionでforce pushを物理禁止、CONTRIBUTING.mdでルール明文化
revertとresetは「どちらが安全か」ではなく「目的が違う別物」として理解するのが正解。3層モデルとreset 5モード、判断フローを押さえれば、取り消し操作で迷うことはなくなります。最強の防御はreflogの存在を知っていること、最強の予防はBranch protectionで物理強制すること——この2つを実装しておけば、Gitの取り消しは怖くなくなります。

