git filter-repo --force 误操作:未提交代码丢失与 VS Code Local History 恢复

2025年11月15日| Ruichen Zhou| 约 8 分钟阅读

git push 被一个 296MB 的 .onnx 文件阻断,为了从历史中移除这个文件,我在有大量未提交改动的工作区上执行了 git filter-repo --force,工作区被强制重置,最后靠 VS Code 的 Local History 逐文件恢复。

1. 环境和背景

  • 代码实际存放在中国的 Linux 工作站
  • 我使用两台本地电脑通过 VSCode Remote SSH 连接到工作站:
    • Windows 电脑:位于德国学生宿舍,长期在线
    • MacBook Air:便携设备,在宿舍和工位之间通勤使用
  • 所有 Git 仓库都在工作站上,Git 命令也都在工作站上执行

工作站是唯一的代码真源,Windows 电脑和 MacBook 只是不同的远程终端。

导火索:历史中的大文件导致 push 失败

在一次重构完成后,我准备把代码推送到 GitHub。git push 返回错误:

File model_visualizations/transformer_unet.onnx is 296.16 MB
this exceeds GitHub's file size limit of 100MB

这个 .onnx 文件是历史遗留的模型文件,在当前工作区已经删除,但仍存在于之前的某个 commit 中。要让仓库能推送,就需要从 Git 历史中彻底移除它。

2. 工作区的状态

在尝试解决大文件问题之前,工作区的状态大致是:

  • 大量 modified 文件train.py(训练流程重构、日志与监控调整)、data_loader.py(数据管线整理、数据格式兼容修改),以及 loss 计算、模型接口、配置加载等其他模块
  • 大量 deleted 文件:各类老的 archive/ 目录、旧的 backup_xxx/ 目录、已弃用的可视化与脚本
  • 大量 untracked 文件:新写的数据预处理脚本(如 scripts/1_prepare_data.py)、区域评估脚本、绘图脚本、论文整理用的 CSV 结果与笔记、若干新的 Notebook

这次比较彻底的重构和清理全部停留在工作区,没有进入任何一次 commit。这是后续问题的根源。

3. 走错了路

当时我没有先读 git filter-repo 的文档,而是直接问 AI 助手:“如何从 Git 历史中移除一个大文件,使仓库能推送到 GitHub?”

AI 给出的思路是:

  • 使用 git filter-repo --path <文件路径> --invert-paths,从历史中移除该路径
  • 如果工具提示需要 --force,可以添加这个参数继续执行

我几乎照抄了这些命令,没有结合当前仓库状态(尤其是未提交改动)做判断。

在工作站上执行:

git filter-repo --path model_visualizations/transformer_unet.onnx --invert-paths

工具返回警告,大意是:这是破坏性操作,检测到当前仓库不是一个“全新克隆”的仓库,因此拒绝执行;如果确认要执行,需要加入 --force

这本是一个明确的安全提示,但当时注意力全在“尽快让 push 通过”上,加上对 AI 建议的依赖,我继续执行了:

git filter-repo --force --path model_visualizations/transformer_unet.onnx --invert-paths

这一刻,问题正式发生。

4. 代码全没了

命令执行后,git status 的结果异常:

  • 之前被我删除的 archive/ 等目录重新出现在工作区
  • 许多文件回到了之前的状态,train.pydata_loader.py 等文件的重构内容不见了
  • 新增的脚本、实验结果文件不再出现在未追踪列表里

工作区被“强制替换”为某个旧 commit 对应的文件内容,所有未提交的修改被覆盖。

从 Git 的角度看这是合理行为:git filter-repo 在历史层面重写 commit、生成一套新历史,结束后把 HEAD 重定位到重写后的最新 commit,再把工作区同步到该 commit 的文件内容。这些改动从未进入 commit,自然不会出现在重写后的历史里,也不会被保留。

5. 用 VS Code Local History 恢复

这些未提交的变更已不在任何 Git 历史中,常规 Git 操作无法恢复。但编辑器还留着另一份历史:Local History。

因为我用两台电脑连接工作站编码,Windows 电脑和 MacBook 上的 VSCode 都对本地编辑过的文件做了时间线记录。这些记录存储在各自本地,git filter-repo 只作用于工作站上的 Git 仓库,不会影响它们。

恢复过程(主要在 Windows 电脑上):

  1. 通过 Remote SSH 连接到工作站,打开受影响的项目目录;
  2. 依次打开关键文件(例如 train.pydata_loader.py 等);
  3. 在 VSCode 侧边栏打开 Timeline / Local History 面板;
  4. 查看最近几次保存记录,逐条比对内容,找到符合“事故发生前”状态的版本;
  5. 恢复该版本内容并保存,把恢复内容再次写回工作站。

每个关键文件都重复这一步骤。一些只在 MacBook 上修改过的文件(例如项目的 README.md),在 MacBook 上做同样操作。过程逐文件手动完成,但避免了误恢复过旧的版本。

主要文件恢复完毕后,在工作站上检查:

git status

被恢复的文件显示为 modified,已删除的目录在文件系统层面被手动删除,新增脚本和实验文件重新处于 untracked 状态。确认符合预期后,做了一个完整的提交:

git add -A
git commit -m "Restore project state after mistaken filter-repo via editor local history"

仓库重新回到可控状态。

6. 教训

根源很具体:整个重构停留在工作区、没有任何一次 commit,而我在没有检查工作区状态、也不知道 filter-repo 会重置工作区的情况下,照抄了 AI 给的命令。停留在工作区的内容等于没有备份,任何误操作都可能让它直接丢失。

之后我给自己定了三条规则:

  1. 运行 git filter-repogit rebasegit reset --hard 或大规模目录重构前,先做一次快照提交,commit 不够“干净”也没关系:

    git add -A
    git commit -m "WIP: snapshot before history rewrite"
  2. 历史重写类命令只在工作区干净时执行。先 git status,看到 nothing to commit, working tree clean 才继续;否则先整理、提交,或明确放弃当前改动。

  3. 新实验、新重构从主分支开新分支,在分支上完成、提交、推送,通过 Pull Request 合并回主分支,把背景与目的写进 PR 描述:

    git switch main
    git pull
    git switch -c feat/new-experiment

工作区不干净的时候,别碰任何会重写历史的命令。git add -A && git commit 只需要几秒钟,但能省掉几个小时的恢复工作。

评论