pull の直後に「意図しないファイルが消えた」ように見える場合でも、原因を切り分けて対処すれば高確率で復元できます。
多くは「pull が作ったマージ/リベース結果で削除が取り込まれた」「fast-forward で上流の削除が反映された」「コンフリクト解消中に削除を採用した」などのケースです。
本記事では、まず原因を診断し、次に安全な巻き戻し・個別復元・削除コミットの打ち消し(revert)という順で具体手順を示します。
原因の診断:pull 前後の差分と退路を確認する
直後であれば ORIG_HEAD
が「pull 実行前の先端」を指しているため、これを基準に消えたファイルを特定できます。
以降の操作で上書きする前に、現在地のバックアップブランチを切っておくと安心です。
# 退避(バックアップ)ブランチを作成
git switch -c safety/pull-rollback-$(date +%Y%m%d-%H%M)
# pull 前後の差分を確認(削除は D と表示)
git diff --name-status ORIG_HEAD..HEAD
# どこから来た削除かを辿る(履歴・直前操作)
git log --oneline --decorate --graph -n 30
git reflog
方法1:pull そのものを「なかったこと」にしてやり直す
まだ他の作業を重ねていないなら、ORIG_HEAD
に戻すのが最短です。作業ツリーの変更を残すかどうかでオプションを選びます。
# 作業ツリーも含めて完全に戻す(最も単純、変更は失われる)
git reset --hard ORIG_HEAD
# 作業ツリーは残し、履歴だけ pull 前へ戻す(削除分を手で選び直したいとき)
git reset --merge ORIG_HEAD
巻き戻したら、差分を確認してから改めて取り込みます。不要なマージコミットを避けたい場合は --ff-only
やリベース取り込みが安全です。
# 早送りだけ許可(削除が入るかどうかも事前 diff で確認)
git fetch --prune
git diff --name-status ORIG_HEAD..origin/main
git merge --ff-only origin/main
方法2:特定ファイルだけ pull 前の状態へ復元する
履歴全体はそのままに、消えた(または意図せず古くなった)ファイルだけを pull 前のスナップショットから戻せます。
まずは ORIG_HEAD
(または HEAD@{1}
)を指定して取り出します。
# pull 実行前(ORIG_HEAD)から必要ファイルを復元
git restore --source=ORIG_HEAD -- path/to/fileA path/to/fileB
# 旧構文(Git 2.23 未満でも可)
# git checkout ORIG_HEAD -- path/to/fileA path/to/fileB
# 反映してコミット
git add .
git commit -m "Restore files to state before pull"
方法3:コンフリクト解消中に「削除を採用」してしまった場合の復元
pull(マージ/リベース)で Deleted by them / Deleted by us の衝突を誤って削除側で確定すると、ファイルが消えます。
進行中の操作を確認し、どちらの変更を採るかを明示してやり直します。
# 進行中の状態を把握
git status
# マージ中に相手側(theirs)または自分側(ours)を採用して復元
git checkout --theirs path/to/file
# あるいは
git checkout --ours path/to/file
git add path/to/file
# マージを完了
git commit
# リベース中なら復元→ステージ→続行
# git add path/to/file
# git rebase --continue
# うまくいかなければやり直し
# git rebase --abort
方法4:上流で「削除コミット」が入っていたので取り戻したい(revert)
削除がリモート側の意図的なコミットで、そのまま fast-forward で取り込まれた場合は、該当コミットを打ち消すのが最もクリーンです。
削除コミットのハッシュを特定し、revert
するとファイルが復活する内容の新規コミットが作られます。
# 削除コミットを探す(削除のみ抽出)
git log --diff-filter=D --summary
# ハッシュ <DEL> を打ち消して復元
git revert <DEL>
git push
まとめて戻す必要がある場合は、範囲を指定して一度に打ち消すか、--no-commit
で内容を展開してから点検してコミットすると安全です。
# 例:連続した削除コミットをまとめて打ち消す
git revert --no-commit <BASE>..<HEAD-WITH-DELETES>
git commit -m "Revert unintended deletions from upstream"
方法5:pull –rebase 後にファイルが落ちた(HEAD@{1} から救出)
リベースは「自分のコミット列を付け替え」るため、分岐点付近で削除が採用されることがあります。直前の参照 HEAD@{1}
を起点に欲しいファイルだけ戻せます。
# 直前の参照からファイルを復元
git restore --source=HEAD@{1} -- path/to/file
git add path/to/file
git commit -m "Restore file lost after rebase"
どうしてもうまくいかないときの最終手段
参照を見失った場合でも、reflog
で過去の先端を辿る、あるいは「削除前」を含む既知のコミットから丸ごと取り出すアプローチが取れます。
それでも見つからないときは、他のクローンや CI ミラーから該当ファイルを持ち帰るのが確実です。
# どの時点にファイルがあったかを辿る
git reflog
# 見つけたハッシュ <H> から取り出す
git checkout <H> -- path/to/file
# 現在のブランチに戻してコミット
git add path/to/file
git commit -m "Restore file from <H>"
再発防止:取り込み前の確認と取り込み戦略の固定
取り込み前に必ず差分を可視化し、削除が含まれていないかを確認します。日常の取り込みは「fetch → diff → ff-only または rebase」に固定すると事故が減ります。
コンフリクト時は、どちらの変更を採るのかをファイル単位で明示する運用を徹底します。
# 取り込み前に削除の有無を確認
git fetch --prune
git diff --name-status HEAD..origin/main
# 推奨設定(直線履歴&誤マージ抑止)
git config --global pull.ff only
git config --global pull.rebase true
git config --global rebase.autoStash true
まとめ
pull 後の意図しない削除は、まず ORIG_HEAD
/ HEAD@{1}
を基準に原因を特定し、全体巻き戻し・個別ファイル復元・削除コミットの revert
から状況に応じて選ぶのが定石です。
コンフリクトを誤決定した場合は ours/theirs で復元して続行し、再発防止として「fetch→diff→ff-only/rebase」の型を習慣化すれば、同種の事故は大幅に減らせます。