GitHub push 失败:如何扫描并清理 Git 历史中的大文件
@[toc]
摘要:从 GitHub 100 MiB 大文件拒绝问题出发,说明为何删除文件后仍无法推送,并给出 PowerShell 扫描、历史清理、Git LFS 与防复发方案。
在把一个已有 Git 项目首次推送到 GitHub 时,可能会遇到如下错误:
1 | remote: error: File vendor/example/.git.zip is 151.91 MB; |
这里最容易产生的误解是:既然问题文件已经不需要了,那么再新增一个 commit 把它删除,然后重新 git push 不就可以了吗?
答案是否定的。
GitHub 检查的不只是当前工作区最终长什么样,还会接收并检查这次 push 所需要传输的 Git 对象。只要超大文件仍存在于待上传的历史提交中,即使后面的 commit 已经把文件删除,那个大文件对应的 Git blob 仍然属于历史的一部分,push 依然可能被拒绝。
这类问题真正需要处理的是“Git 历史中的大对象”,而不仅仅是“当前目录里的大文件”。
1. GitHub 为什么会拒绝大文件
截至本文整理时,GitHub 官方文档对普通 Git 仓库的文件大小约束为:
- 通过浏览器上传时,单文件不能超过 25 MiB;
- 普通 Git 操作添加或更新超过 50 MiB 的文件时会收到警告;
- 普通 Git 仓库会阻止超过 100 MiB 的文件;
- 如果确实需要版本化管理更大的文件,应考虑 Git Large File Storage(Git LFS)。
这里需要区分“仓库当前版本”和“Git 历史”。Git 的 commit 并不是简单地保存一份文件列表,而是通过 tree、blob 等对象构成历史快照。一个 150 MiB 的压缩包一旦进入某个 commit,即使后续删除,它对应的 blob 仍可能继续保存在历史中。
例如:
1 | A ---- B ---- C |
当前 HEAD 位于 C,工作目录中已经没有 big-file.zip。
但是第一次向远端推送 A、B、C 时,B 所引用的 150 MiB blob 仍然必须被传输。GitHub 因此仍会拒绝这次 push。
所以:
1 | git rm --cached "path/to/big-file.zip" |
只能表示“从这个新 commit 开始不再跟踪该文件”,不能删除旧 commit 中已经存在的 blob。
.gitignore 也是同样的道理。它只能阻止后续未跟踪文件被再次加入 Git,不会自动修改历史。
2. 先判断:大文件是在当前目录,还是已经进入 Git 历史
处理之前建议分别扫描两遍。
第一遍检查当前工作区,可以知道哪些超大文件现在还真实存在于项目目录中;第二遍检查 Git 对象历史,可以找出已经删除、移动或改名,但仍残留在历史中的大文件。
以下命令均按 Windows PowerShell 编写。
2.1 扫描当前项目中的 100 MiB 以上文件
在 Git 仓库根目录执行:
1 | $Limit = 100MB |
示例输出:
1 | 151.91 MiB vendor/example/.git.zip |
这里显式排除了仓库自己的 .git 目录,否则 Git pack 文件本身也可能被扫描出来,造成干扰。
2.2 扫描整个 Git 历史中的 100 MiB 以上 blob
仅扫描工作目录是不够的。真正决定 GitHub 是否会因为旧提交而拒绝 push 的,是历史对象。
执行:
1 | $Limit = 100MB |
这组命令的逻辑是:
git rev-list --objects --all枚举所有可达 Git 对象及路径;git cat-file --batch-check查询对象类型和实际大小;- 只保留
blob; - 再筛选大小达到 100 MiB 的对象。
如果当前目录已经没有某个文件,但这里仍然能看到它,说明问题就在 Git 历史中。
3. 先决定大文件到底应该怎么管理
发现大文件后,不要马上批量删除。先判断它属于哪一种情况。
| 文件类型或用途 | 推荐处理 |
|---|---|
| 临时压缩包、构建产物、缓存、日志、备份 | 从 Git 历史删除,并加入 .gitignore |
.git.zip、嵌套仓库备份等 Git 元数据 |
通常直接排除,不建议放进 Git LFS |
| 必须随项目版本化的大型模型、资源、媒体或二进制文件 | 使用 Git LFS |
| 发布给用户下载的固件包、安装包、大型归档 | 更适合 GitHub Release 或独立制品存储 |
尤其是类似:
1 | vendor/example/.git.zip |
这种文件通常只是另一个 Git 仓库元数据的压缩备份。把它继续放进主仓库,无论普通 Git 还是 Git LFS,通常都没有必要,而且容易让仓库体积不断膨胀。
本文后面的批量清理方案适用于“这些大文件本来就不应该进入普通 Git 历史”的情况。
如果扫描结果中存在必须保留版本的大文件,应先跳到“Git LFS”一节,不要直接用 --strip-blobs-bigger-than 一刀切。
4. 批量清理所有不需要的 100 MiB 以上历史文件
历史重写属于破坏性操作。对于个人仓库、首次推送且 GitHub 已经整体拒绝的仓库,风险通常较低;对于已经多人协作、远端已有分支和 tag 的仓库,则必须先协调,因为历史重写会改变相关 commit 的 SHA。
4.1 先确认仓库状态
1 | git status |
如果当前还有未提交修改,建议先处理干净再进行历史重写。
还可以额外创建一个仓库级备份:
1 | git bundle create "../repository-before-large-file-cleanup.bundle" --all |
这个 bundle 位于当前仓库目录之外,可以在误操作时作为恢复来源。
4.2 备份当前仍存在的大文件
历史清理可能导致这些文件从当前工作树消失。如果目标是“文件继续留在电脑上,只是不进入 Git”,可以先复制到仓库外的临时目录。
1 | $Limit = 100MB |
这里记录的是当前工作区中的大文件。历史中已经不存在于工作区的旧文件不需要恢复到本地。
4.3 安装 git-filter-repo
GitHub 官方文档在处理需要重写历史的数据时也使用 git-filter-repo。Windows + Python 环境下可以通过 pip 安装:
1 | python -m pip install git-filter-repo |
安装后确认:
1 | git filter-repo --version |
如果 PowerShell 找不到该命令,需要检查 Python 的 Scripts 目录是否已经加入 PATH。
4.4 从整个 Git 历史删除超过 100 MiB 的 blob
确认扫描结果中的所有超大 blob 都可以删除后,执行:
1 | git filter-repo --strip-blobs-bigger-than 100M --force |
这条命令的关键不是删除“当前文件”,而是重写 Git 历史,把所有超过阈值的 blob 从可达历史中剔除。
因此它有两个重要影响:
- 相关历史 commit 的 SHA 会变化;
- 如果远端已经存在这些旧 commit,后续必须以历史重写的方式更新远端。
这也是为什么它不能被简单理解为“新增一个删除 commit”。
5. 恢复本地文件,并用一个新 commit 防止再次提交
历史清理完成以后,如果这些大文件仍需要留在电脑上,可以从临时目录恢复,并把对应路径加入 .gitignore。
1 | if (!(Test-Path ".gitignore")) { |
然后只提交 .gitignore:
1 | git add -- .gitignore |
从使用者视角看,这样可以把“以后不要再提交这些本地大文件”的策略集中放在一个新的 commit 中。
但需要准确理解:
.gitignore可以集中成一个新 commit;历史中的大 blob 则是通过git filter-repo被移除的,历史重写会改变旧 commit 的对象关系和 SHA。
不存在一种“完全不改旧历史、只新增一个 commit,同时又让 GitHub 看不到旧大文件”的普通 Git 操作。
6. 清理后再次扫描,而不是直接 push
先重新执行历史扫描:
1 | $Limit = 100MB |
如果没有任何输出,说明当前所有可达 Git 历史中已经没有达到 100 MiB 的普通 blob。
再检查本地文件是否确实被忽略:
1 | git status --ignored |
以及确认最终提交:
1 | git log --oneline -5 |
7. 最后如何重新推送 GitHub
先检查远端:
1 | git remote -v |
历史清理工具可能会调整远端配置,因此不要直接假设原来的远端名称还存在。
如果这是第一次向 GitHub 推送,而且之前的 push 已经整体被拒绝、远端目标分支实际上还没有建立,那么重新添加或确认远端后,通常直接正常 push 即可:
1 | git push -u origin master |
如果你的远端名称不是 origin,例如叫 github:
1 | git push -u github master |
如果远端已经存在旧历史,并且你明确要用清理后的本地历史替换它,应先与其他协作者确认,再使用:
1 | git push -u origin master --force-with-lease |
相比无条件的 --force,--force-with-lease 会额外检查远端引用是否仍符合本地预期,可以降低覆盖他人新提交的风险。
多人共享仓库中,历史重写之后其他开发者已有的 clone 也需要同步处理,不能把它当成一次普通的 fast-forward push。
8. 如果大文件必须保留:使用 Git LFS,而不是删除
Git LFS 的机制不是把超大二进制直接作为普通 Git blob 存入仓库,而是在 Git 历史里保存较小的 pointer,由 LFS 存储实际对象。
对于以后新增的大文件,可以先配置跟踪规则:
1 | git lfs install |
之后再正常 git add、git commit。
但有一个非常重要的限制:
1 | git lfs track "*.zip" |
不会把旧 commit 中已经存在的普通 Git blob 自动转换成 LFS 对象。
如果大文件已经进入历史,需要使用 git lfs migrate。例如先查看整个历史的情况:
1 | git lfs migrate info --everything |
再针对确实应该进入 LFS 的类型执行迁移,例如:
1 | git lfs migrate import --everything --include="*.bin,*.zip" |
Git LFS 官方文档明确说明,这类历史迁移同样会重写历史并改变 Git 对象 ID,所以如果远端已有对应历史,仍然需要协调并进行强制更新。
因此,Git LFS 解决的是“这个大文件应该继续版本化,但不适合普通 Git blob”的问题;git filter-repo 删除历史解决的是“这个大文件根本不应该进入仓库”的问题。两者不能混为一谈。
9. 一套适合首次推送失败场景的处理顺序
对于下面这种典型情况:
- 本地已有较长 Git 历史;
- 第一次推送 GitHub;
- GitHub 因历史中存在 100 MiB 以上文件而整体拒绝;
- 大文件属于备份、缓存、构建产物或嵌套
.git压缩包; - 希望文件继续留在本机,但不进入远端仓库;
推荐流程可以归纳为:
1 | 扫描当前工作区大文件 |
这比反复执行“删除文件 → 新增 commit → 再 push”更可靠,因为它直接处理了 GitHub 真正拒绝的对象:历史中的超大 blob。
10. 防止下一次再踩同样的问题
对于源码仓库,最有效的防复发措施不是等 GitHub 拒绝后再清理,而是在提交之前区分“源码”和“制品”。
通常应该优先排除:
1 | # Build outputs |
实际项目不能机械复制这些规则,应根据构建目录和交付方式调整。例如某些项目确实需要版本化少量 ZIP 测试资源,就不能直接使用全局 *.zip 忽略规则。
提交前还可以快速检查暂存区:
1 | git diff --cached --name-only |
如果项目经常产生大型固件、模型、日志或压缩制品,则应该在仓库层面提前约定:
- 哪些文件属于源码;
- 哪些文件用 Git LFS;
- 哪些文件发布到 GitHub Release;
- 哪些文件只存放于制品服务器或对象存储;
- 哪些目录必须进入
.gitignore。
这样比事后清洗 Git 历史的成本低得多。
结语
GitHub 的大文件报错表面上是一个“文件超过 100 MiB”的问题,本质上却经常是对 Git 历史模型理解不完整导致的:当前目录中看不到文件,并不意味着 Git 历史中不存在它;新增一个删除 commit,也不等于删除旧 blob。
处理这类问题时,最值得记住的判断顺序只有三步:
- 先扫描当前工作区,再扫描完整 Git 历史;
- 判断大文件应该“删除历史”还是“迁移到 Git LFS”;
- 只在确认历史中没有不允许的超大 blob 后再推送。
如果大文件本来就不应该进入仓库,git filter-repo + .gitignore 是直接的处理路线;如果大文件本来就需要版本化,则应该设计 Git LFS 或独立制品管理方案,而不是单纯删除。
参考资料
- GitHub Docs — About large files on GitHub
https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github - GitHub Docs — Adding a file to a repository
https://docs.github.com/en/repositories/working-with-files/managing-files/adding-a-file-to-a-repository - GitHub Docs — Removing sensitive data from a repository(包含
git-filter-repo历史重写说明)
https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository git-filter-repo— 官方 GitHub 仓库与文档
https://github.com/newren/git-filter-repo- Git LFS —
git lfs migrate文档
https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-migrate.adoc











