git filter-repo --force 误操作:未提交代码丢失与 VS Code Local History 恢复
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.py、data_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 电脑上):
- 通过 Remote SSH 连接到工作站,打开受影响的项目目录;
- 依次打开关键文件(例如
train.py、data_loader.py等); - 在 VSCode 侧边栏打开 Timeline / Local History 面板;
- 查看最近几次保存记录,逐条比对内容,找到符合“事故发生前”状态的版本;
- 恢复该版本内容并保存,把恢复内容再次写回工作站。
每个关键文件都重复这一步骤。一些只在 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 给的命令。停留在工作区的内容等于没有备份,任何误操作都可能让它直接丢失。
之后我给自己定了三条规则:
-
运行
git filter-repo、git rebase、git reset --hard或大规模目录重构前,先做一次快照提交,commit 不够“干净”也没关系:git add -A git commit -m "WIP: snapshot before history rewrite" -
历史重写类命令只在工作区干净时执行。先
git status,看到nothing to commit, working tree clean才继续;否则先整理、提交,或明确放弃当前改动。 -
新实验、新重构从主分支开新分支,在分支上完成、提交、推送,通过 Pull Request 合并回主分支,把背景与目的写进 PR 描述:
git switch main git pull git switch -c feat/new-experiment
工作区不干净的时候,别碰任何会重写历史的命令。git add -A && git commit 只需要几秒钟,但能省掉几个小时的恢复工作。