Gitの履歴を壊してしまった時の復旧テクニック5選
Gitの履歴を壊してしまった時の復旧テクニック5選
git reset --hardを実行した直後に「あ、消したらだめだった」と気づく経験は、エンジニアなら一度はある。Gitはデータを消しているように見えて、実はほとんどの場合は復旧できる。前職で後輩が1週間分のコミットを吹っ飛ばしたのを5分で復旧させたこともある。焦る前に知っておくべき復旧テクニックをまとめる。
1. git reflog で消えたコミットを探す
Gitの最強の安全網がreflog。HEADの移動履歴がすべて残っている。
git reflog
出力例:
a1b2c3d HEAD@{0}: reset: moving to HEAD~2
e4f5g6h HEAD@{1}: commit: feat: ユーザー認証を追加
i7j8k9l HEAD@{2}: commit: fix: バリデーションのバグ修正
HEAD@{1}が消したかったコミットより前の状態なら:
git checkout -b recovery e4f5g6h
新しいブランチとして復元できる。reflogのエントリはデフォルト90日間保持される。
2. git reset –hard で消したコミットを戻す
git reset --hard HEAD~3で3コミット前に戻してしまった場合。
# まずreflogで消えたコミットのハッシュを確認
git reflog
# 消す前の状態のハッシュにリセット
git reset --hard e4f5g6h
--hardで消したコミットも、GCが走るまではオブジェクトDBに残っている。reflogで見つけてそのままreset --hardで戻せる。
3. git stash で消えた変更を取り出す
git stash popでコンフリクトが起きて変更が消えた場合。
# stashの一覧を確認
git stash list
# 特定のstashの内容を確認
git stash show stash@{0} -p
# 取り出す
git stash apply stash@{0}
stash popは適用後にstashを削除するが、適用に失敗した場合はstashリストに残ったままになる。git stash listで確認すれば見つかることが多い。
4. git cherry-pick で特定コミットだけ救出する
ブランチを間違えてコミットしてしまった場合や、リベース後に特定コミットだけ必要な場合。
# 正しいブランチに切り替える
git checkout main
# 必要なコミットだけ持ってくる
git cherry-pick a1b2c3d
# 複数コミットをまとめて持ってくる
git cherry-pick a1b2c3d..e4f5g6h
cherry-pickはコミットの内容だけをコピーして新しいコミットとして作成する。元ブランチには影響しない。
5. git fsck で孤立したオブジェクトを探す
reflogにも残っていない場合の最終手段。
git fsck --lost-found
実行すると.git/lost-found/ディレクトリに孤立したオブジェクトが保存される。
# 孤立したコミットの一覧
git fsck --lost-found | grep "dangling commit"
# コミットの内容を確認
git show a1b2c3d
dangling blobはファイルの中身が単体で残っているもの。テキストファイルなら内容を直接読める。
git cat-file -p a1b2c3d
復旧できないケース
正直に言っておく。以下のケースは復旧が難しい:
予防策
復旧より予防が大事。
大きな操作の前に必ずバックアップブランチを作る:
git branch backup/before-rebase-$(date +%Y%m%d)
--forceと--hardは慎重に:
# 代わりに --force-with-lease を使う(リモートの変更を上書きするのを防ぐ)
git push --force-with-lease origin feature-branch
まとめ
Gitの復旧テクニックをまとめると:
| 状況 | コマンド |
|——|———|
| reset –hard で戻しすぎた | git reflog → git reset --hard {hash} |
| 間違ったブランチにコミット | git cherry-pick {hash} |
| stash の内容が消えた | git stash list → git stash apply |
| reflog にもない | git fsck --lost-found |
Gitはほとんどの場合、データを完全には消さない。焦らずgit reflogから始めれば、たいていの事故は5分以内に解決できる。