引言:你删除的提交,并没有完全消失
很多开发者以为,只要强制推送(git push -f)就能把上一次不小心提交的敏感信息从仓库里'抹掉'。但现实是:即使提交在分支里消失了,它仍然存在,并且依然可访问。
这种现象意味着:
- 误提交的密钥依旧可能被恢复
- 只要有人知道提交哈希,就能找到它
- 这类'消失的提交'可能长期存在,不会被自动删除
换句话说:删除(覆盖)提交 ≠ 删除风险。
什么是'被删除的提交'?
当你执行以下操作:
git reset --hard HEAD~1
git push --force origin main
你只是让这个提交在分支历史中变得'不可见',而不是'不可访问'。
Git 的工作机制决定了:
- 提交对象仍然存在于服务器
- 只是没有分支引用它
- 但只要知道哈希值,就可以访问
所以它更像是'隐藏',而非'抹除'。
为什么 GitHub 会保留这些提交?
原因可能包括:
- 审计与追踪需求
- 拉取请求(PR)历史
- 分叉网络依赖关系
- 代码托管的内部索引
这些系统让平台很难(也不愿意)彻底删除提交对象。结果就是:强推后'消失'的提交仍在后台保留。
示例
- 直接创建仓库并且模仿错误提交密钥
# 初始化仓库
mkdir test-repo && cd test-repo
git init
echo "正常文件" > file1.txt
git add . && git commit -m "第一次提交"
# 不小心提交密钥
echo "SECRET_KEY=abc123" > .env
git add . && git commit -m "添加配置文件"
- 尝试强制推送来删除上一次提交
# 回退到第一次提交
git reset --hard HEAD~1
# 强制推送
git push --force origin main
- 通过上一次提交的哈希访问提交
# 即使强制推送后,只要知道提交哈希仍可查看
git show <commit_hash>
我们在 GitHub 上在线检查,发现该提交仍然可以被访问(甚至使用四个十六进制数字访问也足够了)。
如何找到这些'消失的提交'?
核心思路是:通过 Git 或平台日志中记录的'零提交推送事件'来定位被覆盖的提交。
当你执行 git push -f 时,如果这次强制推送没有新增提交,而仅仅是将分支指针移动到了更旧的提交位置,那么这次推送事件在日志中会呈现一种特殊现象:
- 推送事件存在
- 但提交列表为空
这意味着:本次推送实质上是'丢弃'了原先在分支头部的一些提交,使分支指向了历史中的某个旧节点。而这些被跳过的提交,就是因强推而'消失'的提交。
举个例子帮助定位
假设某分支原有提交顺序为:
A — B — C(分支原头部)
某次强制推送将分支指针从 C 直接移回 A,那么这次推送:
- 在 Git 服务端(如 GitHub/GitLab)的推送日志中会被记录为一次分支更新;
- 但提交列表中不会包含任何新的提交(因为 C→A 是回退);
- 此时,提交 B 和 C 就成为'被抛弃'的提交,但它们并没有被立即从仓库中删除,而是成为'悬空对象'(dangling commits),仍可通过特定方式找回。
如何利用这类日志进行恢复?
- 查看 Git 服务端推送日志(Push Logs)
- 在 GitHub 上,进入仓库 → 'Insights' → 'Push Logs',查看分支的推送历史。如果某次推送的提交数为 0,且之后分支的头部变更为更早的提交,则很可能是一次覆盖性强推。
- 通过日志找到这次推送之前的提交哈希(即被覆盖前的分支头部),即可用于恢复。
于是,只要找到这类'零提交推送事件',就能定位到被删除的提交哈希。
为什么这些提交仍然危险?
因为提交中可能包含:
- 云服务密钥
- 私有令牌
- 数据库连接密码
- 生产环境配置文件
即使提交被'删除',只要能访问它,就能读取敏感信息。并且现实中,很多密钥在长时间内不会被撤销,这就使它们仍然有效。
为什么会发生这个情况
当你重置后强制推送时(即 git reset --hard HEAD~1 后接 git push --force),你从分支中移除了 Git 对该提交的引用,使其无法通过正常的 Git 导航(如 git log)访问。然而,该提交在 GitHub 上仍然可以访问,因为 GitHub 存储了这些引用日志(reflogs)。
GitHub 远比一个单纯的 git 服务器复杂。它有许多层,包括拉取请求、分支、公私设置等等。为了支持所有这些功能,GitHub 会存储所有提交并且从不删除它们。这里有一些需要考虑的情况:
- 拉取请求是什么? 这些只是临时分支,可以通过获取所有引用来检索。
- GitHub 的分支网络如何工作? 当你'分支'一个仓库时会发生什么?所有数据都会被复制,包括你可能删除的提交。
对于这些情况,可能还有许多其他原因(审计?监控?),GitHub 会存储所有提交,即使你强制推送头部并'删除'提交,也不会删除它们。
如何大规模识别这些风险?
可以归纳成一个流程模型:
- 获取公开事件数据
- 筛选'零提交推送事件'
- 提取被覆盖的提交哈希
- 访问这些提交内容
- 扫描是否含敏感信息
- 筛选高价值目标
这套流程可以自动化,但核心原理不变。
最佳实践
当然还有更简单的!只要在推送前问问你的代码编辑器左边的 AI 助手一般就不会出现这个问题了。


