[toc]

概述:git pull 拉取远程后,本地某个项目的源码被”弄丢”了,工作区只剩 bin/、obj/ 编译产物。本文记录如何通过 reflog + 孤立提交(dangling commit) 找回源码并重新纳入版本控制。

场景还原

某个仓库(此处为 .Net-Code,内含 FunctionPointerDemo、HarmonyDemo 等项目)在 git pull 后:

  • 两个项目的源码文件(Program.cs、csproj、README.md 等)消失,目录里只剩 bin/ 和 obj/(编译产物)。
  • git status 却没有显示这两个目录被删除。

为什么 git status 不显示删除?

因为这两个项目所在的 commit,在 pull 的 fast-forward 合并后脱离了 main 分支的历史,变成了”孤立提交(dangling commit)“。它们的文件对象仍然完整保存在 .git 对象库里,只是没有任何分支引用它们了。

恢复五步法

第 1 步:确认是 submodule / gitfile 结构

.git 是个普通文件(不是目录)时,说明是 submodule:

bash
cat .git
# gitdir: ../.git/modules/.Net-Code

这能帮你理解仓库的 git 数据实际存放在哪里,但不影响恢复本身。

第 2 步:用 reflog 找到”丢失”的提交

bash
git reflog -10

reflog 记录了 HEAD 移动的历史,即使提交脱离了分支,只要它曾经是 HEAD,就会留在 reflog 里。这里找到两个关键提交:

6d2ded6 HEAD@{2}: commit: feat(clrjit): 添加 FunctionPointerDemo 示例项目及相关文件
aab3d12 HEAD@{3}: commit: feat(Harmony 2.x): 添加 Harmony 2.x 示例项目及相关配置文件

第 3 步:确认这些 commit 里的文件内容

bash
# 查看某个提交里包含哪些文件
git ls-tree -r --name-only 6d2ded6 | grep -i functionpointer
git ls-tree -r --name-only aab3d12 | grep -i harmony
 
# 确认提交对象是否完整可读(未损坏)
git cat-file -p 6d2ded6^{tree} >/dev/null && echo "OK"

第 4 步:从孤立提交单独检出目录(核心恢复动作)

bash
# 只恢复这两个目录,不创建分支、不改 HEAD、不动其它文件
git checkout 6d2ded6 -- FunctionPointerDemo/
git checkout aab3d12 -- HarmonyDemo/

说明:git checkout <commit> -- <path> 会把指定路径从那个提交恢复到工作区 + 暂存区,是非破坏性操作,不会影响其它未提交的改动。

第 5 步:确认并提交

bash
# 查看恢复的文件(显示为 A = 新添加到暂存区)
git status --porcelain FunctionPointerDemo/ HarmonyDemo/
 
# 提交,让它们重新进入历史
git commit -m "chore: restore FunctionPointerDemo & HarmonyDemo from dangling commits"

关键概念

什么是 Dangling Commit(孤立提交)?

当提交被 checkout、reset、merge 等操作”跳过去”后,如果没有分支或 tag 引用它,它就变成 dangling。但它不会立即被清除,对象仍保存在 .git/objects/ 中,直到被 git gc 真正清理。因此:

  • ✅ 只要 reflog 还在,就能找回
  • ⚠️ 长时间后或执行 git gc --prune 后,孤立对象可能被永久清除

为什么源码会”丢失”?

本例根因是 fast-forward 合并:pull 时远程历史是本地历史的”超集”,git 直接快进。但这两个项目所在的本地提交(6d2ded6、aab3d12)不在远程分支上,快进后这些提交脱离了 main,工作区对应文件被远程版本覆盖/清理。

常用排查命令速查

目的命令
查看提交包含的文件git ls-tree -r --name-only <commit>
查看提交内容git show <commit>
找回丢失的提交git reflog
某提交在哪些分支git branch --contains <commit>
某提交在哪些远程分支git branch -r --contains <commit>
恢复某目录到工作区git checkout <commit> -- <path>
检查对象是否损坏git cat-file -p <commit>^{tree}
查看所有孤立对象git fsck --lost-found

预防建议

  1. pull 前先 commit:确保本地改动都提交了,避免被快进覆盖。
  2. 用 git pull --rebase 代替默认 merge,减少历史分叉。
  3. 定期 git fsck --lost-found:及时发现并备份孤立对象。
  4. 关键源码尽早推送远程,避免只存在于本地分支。

场景仓库:Windows-API-Usage/.Net-Code,恢复 2026-08-15