- はじめに
- 1. 復元可能性マップ — この記事の核心となる 1 枚
- 2. 3 つの領域と、それを動かすコマンド
- 3. reset — 3 つのモードの実際の違い
- 4. revert — すでに共有された歴史を扱う唯一の方法
- 5. reflog — どこまで、いつまで残るのか
- 6. gc — オブジェクトが実際に削除される時点
- 7. fsck — reflog にもないときの最後の網
- 8. 強制プッシュ事故とチームの状況
- クイズ: 理解度を確認しましょう
- おわりに
- 参考資料
- 関連記事
はじめに
Git で何かを間違えたとき、まず知るべきなのはコマンドではありません。いま失ったものがまだどこかに残っているかどうかです。残っているなら落ち着いて取り出せばよく、残っていないなら他の人の複製を探す必要があります。この判断ができないと、復元可能な状況で時間を浪費し、復元不可能な状況で無駄な試行を繰り返すことになります。
このブログには git 取り消し — 状況別に選ぶ restore、reset、revert、reflog がすでにあります。あの記事は 1 人で作業するローカルの流れを状況別に案内する実践的なチートシートです。この記事はその隣に置くリファレンスを目指します。復元可能性の境界を期限切れポリシーとともに数値で整理し、強制プッシュ事故やワークツリー・サブモジュールのようにチーム環境でしか起きない状況まで扱います。チートシートが「いま何を打つか」に答えるとすれば、この記事は「これは生き返らせられるのか」に答えます。
前提バージョンは Git 2.40 以上です。git restore と git switch は 2.23 で導入され、長く実験的という表示が付いていたので、古い環境では git checkout を使う必要があるかもしれません。
1. 復元可能性マップ — この記事の核心となる 1 枚
まず結論です。以下の表がこの記事で最も重要です。
| 失ったもの | 復元可能性 | 手段 |
|---|---|---|
| コミット済みの内容 | 非常に高い | reflog、fsck |
git add でステージしたが未コミット | 可能 | git fsck --lost-found の blob |
| 修正しただけで add もコミットもしていない変更 | 不可能 | エディタのローカル履歴、バックアップのみ |
| stash してから drop したもの | 可能(期限切れ前) | reflog、fsck |
| 削除したブランチ | 非常に高い | reflog、fsck |
| rebase で消えたコミット | 非常に高い | ORIG_HEAD、reflog |
| 強制プッシュでリモートから消えたコミット | 条件付き | 誰かのローカル複製、サーバー側の reflog |
gc --prune=now 以降の到達不可オブジェクト | 不可能 | なし |
追跡されていないファイルを git clean | 不可能 | なし |
太字で示した 3 行が境界線です。Git が守ってくれる範囲は「一度でもオブジェクトデータベースに記録されたもの」です。記録されたことのない変更は Git の責任の外です。
そこから実務のルールが 1 つ出てきます。危険な作業をする前に、まずコミットするか stash してください。一時コミット 1 つが、復元可能性を「不可能」から「非常に高い」へ変えます。
2. 3 つの領域と、それを動かすコマンド
Git は 3 か所に状態を置きます。作業ツリー、インデックス(ステージング領域)、そして HEAD が指すコミットです。取り消しコマンドは結局のところ「どの領域をどこに合わせるのか」です。
git status
git status --short --branch
git diff
git diff --staged
git diffは作業ツリーとインデックスの差分です。git diff --stagedはインデックスと HEAD の差分です。
この 2 つのコマンドの違いを明確に理解すると、git status の案内文が正確に何を意味しているのかが分かります。
作業ツリーとインデックスを戻す現代的なコマンドは git restore です。
git restore path/to/file
git restore --staged path/to/file
git restore --staged --worktree path/to/file
git restore --source=HEAD~2 path/to/file
- 引数なしの
git restore <ファイル>は、作業ツリーの修正を捨てます。コミットも add もしていない変更なら、この時点で永遠に消えます。 --stagedはインデックスだけを戻します。作業ツリーの修正は残ります。--sourceは特定のコミットの内容から取ってきます。
破壊的コマンドの警告: git restore <ファイル> は確認を求めずに戻します。生き返らせる方法がないので、捨てようとしている内容が本当に不要かを git diff で先に確認してください。
3. reset — 3 つのモードの実際の違い
git reset は HEAD を動かします。オプションは、インデックスと作業ツリーをどこまで一緒に動かすかを決めます。
git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1
| モード | HEAD | インデックス | 作業ツリー | いつ使うか |
|---|---|---|---|---|
--soft | 移動 | 維持 | 維持 | コミットだけ取り消して再コミットするとき |
--mixed | 移動 | 移動 | 維持 | 既定値。add まで取り消すとき |
--hard | 移動 | 移動 | 移動 | すべて捨てるとき |
破壊的コマンドの警告: git reset --hard は作業ツリーの未コミットの変更を削除し、その変更は復元できません。コミットそのものは reflog で生き返らせられますが、コミットされたことのない修正は違います。この区別が非常に重要です。
安全な習慣は、実行前に必ず確認することです。
git status
git stash push -m 'before hard reset 2026-08-15'
git reset --hard HEAD~1
git reset --hard の直後に「しまった、あのコミットが必要だった」となったら、reflog が答えです。
git reflog
git reset --hard HEAD@{1}
ORIG_HEAD も便利です。reset、merge、rebase のようなコマンドは、実行直前の HEAD をここに保存します。
git reset --hard ORIG_HEAD
4. revert — すでに共有された歴史を扱う唯一の方法
プッシュ済みのコミットを消そうとして reset のあとに強制プッシュをすると、そのブランチをすでに受け取っている全員のリポジトリがずれます。共有された歴史には revert を使います。歴史を消す代わりに、反対の内容を持つ新しいコミットを追加する方式です。
git revert 3f2a1b9
git revert --no-commit 3f2a1b9
git revert HEAD~3..HEAD
git revert --abort
マージコミットを戻すときは、どちらを残すかを指定する必要があります。
git revert -m 1 8c4d2e0
-m 1 は第 1 の親(たいていはマージを受け取った側、つまり main)を基準にするという意味です。親の番号は次で確認します。
git log --merges -1 --format='%H %P'
マージの revert には後続の罠があります。マージを戻したあとで同じブランチをもう一度マージすると、Git はすでにマージ済みと判断し、戻した変更を再び取り込みません。この場合は「revert の revert」をするか、ブランチを作り直す必要があります。チーム作業でよく起こる混乱なので、マージを revert するときは、そのブランチをあとでどう入れ直すかの計画も一緒に立ててください。
5. reflog — どこまで、いつまで残るのか
reflog は参照が動いた記録です。コミットハッシュを知らなくても「2 段階前の HEAD」を指せるようにしてくれます。
git reflog
git reflog show main
git reflog --date=iso | head -20
git log -g --oneline
ここで必ず知っておくべき性質が 3 つあります。
1 つ目、reflog はローカルです。ドキュメントが明示しているとおり、reflog はリポジトリごとに存在し、プッシュされません。他の人のリポジトリにあなたの reflog はなく、あなたが新しく clone すれば reflog は空です。clone 直後には安全網がないという意味です。
2 つ目、期限があります。既定値は明確です。
| 設定 | 既定値 | 対象 |
|---|---|---|
gc.reflogExpire | 90 日 | 現在のチップから到達可能な項目 |
gc.reflogExpireUnreachable | 30 日 | 到達不可能な項目 |
つまり rebase や reset で捨てられたコミットの reflog 項目は、30 日が過ぎると整理の対象になります。重要なリポジトリなら延ばせます。
git config gc.reflogExpire '365 days'
git config gc.reflogExpireUnreachable '180 days'
git config --get gc.reflogExpire
3 つ目、reflog の項目が残っていても、オブジェクトが消えていれば意味がありません。次の節に続きます。
ブランチを誤って消したときの復元はこうです。
git reflog --all | grep -i 'feature/payment'
git branch feature/payment 9a1c3f7
git reflog expire を直接使うことはまれですが、何を消すのかを事前に見ることはできます。
git reflog expire --dry-run --expire-unreachable=30.days --all
--dry-run(-n)は実際には消さず、対象だけを見せてくれます。
6. gc — オブジェクトが実際に削除される時点
Git は到達不可能なオブジェクトをすぐには消しません。git gc が整理しますが、猶予期間を置きます。
| 設定 | 既定値 | 意味 |
|---|---|---|
gc.pruneExpire | 2.weeks.ago | これより古いルーズオブジェクトを整理 |
gc.auto | 6700 | ルーズオブジェクトがこの程度を超えると自動実行 |
ドキュメントによれば git gc は既定で 2 週間より古い到達不可オブジェクトを取り除きます。猶予期間は、他のプロセスが同時に書き込んでいるときの破損を防ぐための仕掛けでもあります。
絶対にやってはいけないことがあります。
# 危険: 復元の余地を即座になくす
git gc --prune=now
git reflog expire --expire-unreachable=now --all
破壊的コマンドの警告: この 2 つのコマンドを一緒に実行すると、到達不可能なオブジェクトが即座に削除され、どんな方法でも復元できません。リポジトリの容量を減らそうとして検索で出てきたコマンドをそのまま貼り付ける事故がよくあります。事故直後に復元を試みている最中なら、gc は絶対に実行しないでください。ドキュメントもまた、--prune=now が同時書き込みの状況で破損リスクを高めると警告しています。
逆に、復元の余地を広げたいなら次のように設定します。
git config gc.pruneExpire '90 days'
git config gc.auto 0
gc.auto 0 は自動 gc を切ります。大きなリポジトリで予期しないタイミングに gc が走るのを防ぎたいときに使います。その代わり、定期的な手動実行が必要になります。
7. fsck — reflog にもないときの最後の網
reflog の項目は期限切れになったが、オブジェクトはまだ残っている、という状態があります。このとき git fsck で孤児オブジェクトを探します。
git fsck --unreachable
git fsck --dangling
git fsck --lost-found
git fsck --no-reflogs --unreachable --name-objects
--unreachableはどの参照からも到達できないオブジェクトを列挙します。--danglingは直接参照されていないオブジェクトを見せます(既定の動作)。--lost-foundは見つけたオブジェクトをファイルとして取り出してくれます。ドキュメントによれば、コミットは.git/lost-found/commit/、それ以外は.git/lost-found/other/に書き出され、blob は名前の代わりに内容がファイルとして保存されます。--no-reflogsは reflog からのみ参照されるコミットを到達可能とは見なしません。「昔はブランチだったが今はそうでない」コミットを探すときに使います。--name-objectsは、そのオブジェクトにどう到達できるかの経路も一緒に見せます。
見つけたコミットの内容を確認して救い出します。
git fsck --lost-found
ls .git/lost-found/commit/
git show --stat 4b7e91c
git branch recovered-4b7e91c 4b7e91c
コミットせずに git add だけした状態で reset --hard を実行したなら、blob は残っています。ファイル名は失いましたが、内容は救えます。
git fsck --lost-found
grep -rl 'function calculateTax' .git/lost-found/other/ | head
内容の特徴的な文字列で探すのが現実的な方法です。
stash も同じ原理で生き返らせます。git stash drop や git stash clear はスタックの項目を消すだけでオブジェクトは残すので、期限切れ前なら復元できます。
git fsck --unreachable | grep commit
git show --stat 7d3e5a2
git stash apply 7d3e5a2
stash は内部的にコミットオブジェクトとして保存されるので、git stash apply にハッシュを直接渡せます。ただしここでも時間制限は同じです。gc がすでに整理していれば、どんなコマンドでも生き返らせられません。
まとめると、fsck は 「オブジェクトは残っているのに、それを指す名前が消えた状況」 のためのツールです。オブジェクトそのものが削除されたあとでは、何の役割も果たしません。だからこそ、事故を認識した直後に取るべき最初の行動は復元コマンドを探すことではなく、そのリポジトリで gc が走らないようにすることです。
8. 強制プッシュ事故とチームの状況
リモートブランチが強制プッシュで上書きされたとき、復元経路は 3 通りです。
経路 1 — 上書きした人のローカル reflog。最も速い方法です。
git reflog show origin/main
git log --oneline origin/main@{1}
経路 2 — 他の人のローカル複製。まだ fetch していない人がいれば、その人のリモート追跡ブランチに以前の状態が残っています。事故を認識した直後に、チームのチャンネルで「いまは fetch や pull をしないでください」と知らせることが、実質的に最も重要な対応です。
経路 3 — サーバー側の記録。GitHub、GitLab などほとんどのホスティングは、イベントの記録に以前のコミットハッシュを残します。ハッシュさえ分かれば生き返らせられます。
git fetch origin 9a1c3f7
git branch rescue 9a1c3f7
そもそも事故を減らす設定もあります。
git push --force-with-lease origin main
git push --force-with-lease=main:9a1c3f7 origin main
--force-with-lease は、リモートが自分の最後に見た状態のままであるときだけ上書きします。その間に他の人がプッシュしていれば拒否されます。強制プッシュが必要な状況なら、必ずこちらを使ってください。ただし、先に fetch してしまうと lease の基準が更新されて保護効果が弱まる点は知っておく必要があります。
ワークツリーとサブモジュールは別途気を配る必要があります。
git worktree list
git worktree remove ../feature-wt
git submodule status
git submodule update --init --recursive
ワークツリーを強制的に削除すると、その中の未コミットの変更は消えます。サブモジュールは親リポジトリが特定のコミットを指しているだけなので、サブモジュールのリポジトリでそのコミットが消えると、親の参照は生きていても内容を取ってこられません。サブモジュール側のブランチを消す前に、親が参照しているコミットかどうかを確認してください。
最後に、Git が絶対に守ってくれないものを改めて強調します。
git clean -n -d
git clean -f -d
破壊的コマンドの警告: git clean -f -d は追跡されていないファイルとディレクトリを削除し、復元手段がまったくありません。必ず -n(dry run)でリストを先に確認してください。設定ファイルやローカルデータがリストにあるなら実行せず、除外パターンを指定する必要があります。
クイズ: 理解度を確認しましょう
クイズ1: git reset --hard で戻しました。何が復元でき、何が復元できませんか?
答え: コミット済みの内容は reflog で復元できますが、コミットされたことのない作業ツリーの修正は復元できません
解説: reset は HEAD を動かすだけでオブジェクトを消さないので、捨てられたコミットは依然としてオブジェクトデータベースにあります。
git reflog
git reset --hard HEAD@{1}
一方、編集しただけで add もコミットもしていない変更は、Git が一度も見たことのないものです。この場合に残る望みは、エディタのローカル履歴かファイルシステムのスナップショットだけです。だからこそ、危険な作業の前には一時コミットか stash を先にするのがルールなのです。
クイズ2: 3 週間前の rebase で消えたコミットを探そうとしています。reflog になければ次の手段は?
答え: git fsck で到達不可オブジェクトを探します。ただし gc がすでに整理していれば不可能です
解説: 到達不可の reflog 項目の既定の期限は 30 日なので、3 週間ならまだ残っている可能性が高いです。なければオブジェクトそのものを探します。
git fsck --lost-found --no-reflogs --unreachable
ls .git/lost-found/commit/
git show --stat <ハッシュ>
gc.pruneExpire の既定値は 2 週間なので、その間に gc が実行されていればすでに削除されているかもしれません。これが、重要なリポジトリで期限の設定を延ばしておく理由です。
クイズ3: 同僚が main に強制プッシュをしました。まず最初に取るべき行動は?
答え: チームに fetch と pull を中断するよう知らせることです
解説: 技術的な復元よりも、このコミュニケーションが先です。まだ fetch していない人のリポジトリには以前の状態の origin/main が残っており、それが最も確実な複製です。全員が fetch してしまえば、その複製は消えます。
以降の復元は次の順序です。
git reflog show origin/main
git log --oneline origin/main@{1}
git branch rescue origin/main@{1}
再発防止は、ブランチ保護ルールと --force-with-lease を使うルールです。
クイズ4: リポジトリが大きいという理由で、検索結果で見た gc コマンドを実行しようとしています。どの組み合わせが危険ですか?
答え: reflog を即座に期限切れにして gc --prune=now を一緒に実行する組み合わせです
解説: 次の組み合わせは到達不可能なすべてのオブジェクトを即座に削除し、復元手段を残しません。
git reflog expire --expire-unreachable=now --all
git gc --prune=now
特に復元作業の最中には絶対に実行してはいけません。容量が目的なら、何が容量を占めているのかを先に確認するほうがよいです。
git count-objects -vH
大容量ファイルが履歴に入ってしまったことが原因なら、必要なのは gc ではなく履歴の書き換えであり、それはチーム全体が合意しなければならない作業です。
クイズ5: マージコミットを revert したあと、同じ機能ブランチをもう一度マージしたのに変更が入ってきません。なぜでしょうか?
答え: Git はそのブランチをすでにマージ済みと見なすからです
解説: revert は歴史を消さず、反対の変更を追加します。マージコミット自体は履歴に残っているので、もう一度マージしても「すでに合流したコミット」と判断して何も取り込みません。解決策は 2 つあります。
git revert <revert コミットのハッシュ>
取り消しをもう一度取り消すか、機能ブランチを新しいブランチとして作り直してコミットを移し替えます。
git switch -c feature/payment-v2 origin/main
git cherry-pick <コミットの範囲>
クイズ6: いま clone したばかりのリポジトリで reset --hard を実行しました。安全網の面で何が違うでしょうか?
答え: clone 直後には reflog に以前の状態がほとんどなく、戻れる地点が足りません
解説: reflog はリポジトリごとのローカルな記録であり、clone では引き継がれません。clone したばかりのリポジトリの reflog には clone の項目が 1 つあるだけです。ただしリモート追跡ブランチはそのままあるので、リモートの状態にはいつでも戻れます。
git reflog
git reset --hard origin/main
危険なのは clone 直後にローカルだけで作った作業です。その作業はリモートにもなく reflog の履歴も短いので、途中途中でコミットしておく習慣が唯一の防御です。
おわりに
Git の取り消しで覚えるべきなのはコマンドの一覧ではなく、3 つの時間です。到達不可の reflog 項目は既定で 30 日、到達可能な項目は 90 日、ルーズオブジェクトは gc 基準で 2 週間です。この数字を知っていれば、「3 週間前のものを見つけられるだろうか」という問いに根拠を持って答えられます。
そして境界線を 1 つ覚えてください。Git は自分が見たことのあるものだけを守ってくれます。コミットされていない修正、追跡されていないファイル、clean で消したものは Git の保護の外です。
実務の習慣に置き換えるとこうなります。危険な作業の前には一時コミットを作り、強制プッシュは必ず --force-with-lease で行い、重要なリポジトリでは期限の設定を延ばしておきます。この 3 つで、ほとんどの事故が「復元可能」の側に移ってきます。
参考資料
- git-reflog 公式ドキュメント (2026-08-15 確認)
- git-gc 公式ドキュメント (2026-08-15 確認)
- git-fsck 公式ドキュメント (2026-08-15 確認)
- git-reset 公式ドキュメント (2026-08-15 確認)
- git-revert 公式ドキュメント (2026-08-15 確認)
関連記事
- 前の記事: SSH 運用完全ガイド
- 次の記事: Linux ログ運用完全ガイド
- git 取り消し — 状況別に選ぶ restore、reset、revert、reflog — ローカルの作業フロー中心のチートシート
- Git のオブジェクトと内部構造 — オブジェクトデータベースが何を保管するのか
- Git プレイグラウンド — コマンドの結果を安全に実験
- Git コマンド検索 — 状況に合うコマンドを検索
현재 단락 (1/197)
Git で何かを間違えたとき、まず知るべきなのはコマンドではありません。**いま失ったものがまだどこかに残っているかどうか**です。残っているなら落ち着いて取り出せばよく、残っていないなら他の人の複製...