删除 Git 历史中的大文件
用 git-filter-repo 找出并删除历史中的大文件,完成备份、分支与标签推送、GitLab 空间回收和清理验证。
大文件删除后,仓库仍然很大,通常是因为旧版本还在 Git 历史中。
用 git-filter-repo 移除这些历史记录,再更新远端分支和标签,才能清理这部分占用。
本文清理的是直接提交到 Git 的普通文件,范围包括远端的所有分支和标签。先在新克隆中处理,验证通过后再更新远端:
| |
推送后还要重新克隆检查,确认远端也已清理。本文不处理 Git LFS 文件;GitLab 页面仍显示较大占用时,再看末尾的服务端清理说明。
准备:安装 git-filter-repo
以下命令适用于 macOS / Linux 的 bash、zsh。全程使用同一个终端,后面的命令会继续使用前面设置的变量。
如果在交互式 zsh 中粘贴含 # 注释的命令,先执行 setopt interactivecomments,让它识别注释。
先新建操作目录,保留原来的开发目录:
| |
在当前使用的 Python 环境中直接安装。使用 Conda 的话,先激活你平时使用的环境;这里不再新建环境。
| |
a40bce548d2c 是正常输出。git-filter-repo 2.47.0 用工具脚本的内容哈希前 12 位作为版本标识,不是当前仓库的提交 ID。见官方源码。
要看 2.47.0 这样的包版本号,用 pip show:
| |
如果提示 git: 'filter-repo' is not a git command,说明 Git 还找不到这个工具。先确认安装成功,并保持在安装时使用的 Python 环境中。
1. 备份原始历史
克隆前先暂停团队推送、合并和自动写入任务,直到清理完成。 期间新增的提交可能被后面的强推覆盖。
第一行直接在终端执行,只替换引号里的网址,等号两边不要加空格。当前终端会保存 repo_url,后面的 "$repo_url" 原样保留即可,会自动读取这个地址。
关闭终端或新开标签页后,需要重新赋值;切换目录不影响它。
| |
这里需要完整克隆,不加 --depth、--single-branch 或 --filter。--tags 用于补齐标签,见 Git fetch 文档。
不必先 git switch。后面的清理会处理所有分支和标签,不是只处理当前分支。
删除前,先把这个仓库的分支、标签、提交记录和文件内容存成一个备份文件。万一删错,还能从备份中找回原来的历史。
| |
| |
运行后,上一级目录会多出 before-cleanup.bundle。is okay 表示备份校验通过,请保留这份文件,它不参与后面的删除。
1.1M 是清理前 .git 目录的大小,后面会再测一次作比较。结果块开头的 # 是本文添加的标记,不是终端原始输出。
备份需要额外磁盘空间,也不包含未提交的改动、LFS 文件或 GitLab 项目设置。这些内容要另外保存。见 Git bundle 文档。
2. 定位大文件
Git 把每个不同版本的文件内容保存为一个 blob,也叫文件内容对象。下面按大小列出最多十个这样的对象,最大的排在最后:
| |
| |
这是一个小型演示仓库的实际结果,不代表真实项目的文件规模。如果仓库不足十个文件内容对象,输出也会少于十行。
每行的三列分别是对象 ID、字节数、文件路径。以最后一行为例:
| 字段 | 示例含义 |
|---|---|
| 对象 ID | 9d869db… 开头的完整字符串,相当于这份文件内容的编号,后面验证时要用 |
| 大小 | 1048576 字节,即 1 MiB;这是未压缩大小 |
| 路径提示 | data/large-file.bin,相对于仓库根目录 |
同一个文件改过多次,可能会出现多个对象 ID;相同内容也可能被不同文件使用,所以列表中的路径不一定列全。
字节数是未压缩大小,不等于磁盘占用;路径是定位线索。字段定义见 cat-file 和 rev-list。
选好要删的文件后,把查到的路径和完整 ID 分别填进下面两行的单引号中。与 repo_url 一样,变量名不用改:
| |
后面的 "$target_path" 会使用你填好的路径,"$blob_id" 会使用你填好的 ID,不需要每次重新替换。其中,路径用于删除,ID 只用于验证。
3. 删除文件及其旧版本
这一步会删除指定路径下的文件及其所有旧版本。如果文件仍要使用,先另存一份,并调整项目的下载或部署流程。
清理会改写提交记录,受影响的提交及后续提交会换 ID,提交和标签上的签名也会被移除。如果 CI 或文档中写死了旧提交 ID,需要一并调整。见工具限制。
文件曾改名或移动过,要在同一次命令中包含旧路径,例如在下方命令末尾追加 --path 'old/large-file.bin'。
工具不会自动追踪重命名,见路径过滤规则。
删除命令中的两个参数要一起使用:
- 只写
--path:保留指定文件,删除其他文件的历史。 - 同时加上
--invert-paths:删除指定文件,保留其他文件的历史。本次要用这种写法。
| |
工具执行时,还会检查你是否在一份刚克隆的仓库中操作。这是为了避免你误在开发目录里运行,删掉尚未备份的工作。
如果检查不通过,先确认目录,必要时重新克隆。不要看到报错就直接加 --force。
它的意思是“跳过这项检查,继续改写历史”,不会帮你备份或恢复数据。见安全检查说明。
| 提示或变化 | 含义与处理 |
|---|---|
Refusing to destructively overwrite repo history | 工具拒绝执行,先按上面的说明检查当前目录 |
Removing 'origin' remote | 工具删除了本地保存的远端连接配置,没有删除远端仓库;第 5 步会重新添加 |
origin/feature 变成 feature | 远端分支在本地有了对应分支,后面可以把清理后的版本推回去 |
移除 origin 和转换分支都是工具的正常处理步骤。
4. 验证清理结果
| |
如果仍列出提交,先别推送,检查是否删对了路径。
删除时指定过旧路径,也要逐个查。下面换成自己的旧路径;没有则跳过:
| |
| |
| |
确认 ID 没抄错后,这里的 fatal 表示旧对象已找不到,符合预期。若输出 blob,说明内容还在,可能另一个路径也存了相同内容。
删过多个大对象就逐个检查,或重跑第 2 步查看剩余对象。
| |
| |
演示仓库从 1.1M 降到 144K,实际降幅取决于历史版本、压缩方式和剩余内容。
工具会自动整理数据,通常不用再运行 git gc。推送前还要确认保留的分支能正常构建、部署,未因删文件缺少必要资源。
5. 推送并复核远端
| |
| |
feature、main 是分支,* 标记当前分支,v1.0 是标签。只包含被删内容的分支或标签可能已消失。
下面的推送会覆盖远端历史,还会删除远端有、本地没有的分支和标签。
确认最初是完整克隆、备份校验通过,且远端没有新增提交。预览不会锁住远端,团队仍须暂停写入。
GitLab 项目还需管理员检查保护设置:
- 要改写
main等受保护分支:临时开启“允许强制推送”。 - 要删除受保护分支,或更新、删除受保护标签:先确认具体名称,再临时取消这些分支或标签的保护。
清理完成后恢复原来的保护设置。见 GitLab 分支保护和标签保护。
| |
| |
(forced update) 表示强制更新,[deleted] 表示删除。逐条核对,出现意料之外的条目先停下。
--prune 会同步删除这些已消失的分支或标签,防止它们在远端继续保留旧文件。见 Git push 文档。
确认预览范围正确后,去掉 --dry-run 正式执行:
| |
出现 [rejected]、[remote rejected] 或 failed to push,说明推送未全部成功。保持暂停写入,按报错解决权限或保护设置问题后重试。
不要改用 --mirror,它还会同步分支、标签之外的其他 Git 记录。
重新克隆,验证远端历史
推送全部成功后,重新克隆检查:
| |
在新克隆中重复第 4 步:查删过的旧路径、对象 ID 和 .git 大小。通过后说明重新下载的仓库已清理,但服务器可能还有待回收的旧数据。
GitLab 的服务端空间回收(按需)
新克隆已清理,但 GitLab 占用仍较大时,再考虑服务端清理:合并请求可能还关联着旧提交,或旧数据尚未回收。
服务端清理可能让部分合并请求提交、流水线和变更详情不可用。
项目若有 fork 关系,可能与其他仓库共用数据,先让管理员确认能否回收。见清理说明和 fork 限制。
commit-map 是工具生成的新旧提交 ID 对照表,GitLab 用它找到旧提交。确认可以清理后,在当前的 repo-verify 目录执行:
| |
接着按 GitLab 官方流程操作:
- 推送完成后至少等 30 分钟。
- 由维护者进入 Settings → Repository → Repository maintenance。
- 上传前面保存的
cleanup-commit-map.txt,执行 Start cleanup。 - 查看完成通知,再核对仓库大小。
自建 GitLab 的入口和回收策略以当前实例版本为准。
恢复协作
协作者先保存未提交、未推送的工作,再重新克隆并迁移改动。检查旧合并请求和 CI 副本,避免带回旧历史,再恢复正常写入。
仍需使用的大文件可放到对象存储或 Git LFS,记录获取方式与版本;用 .gitignore 防止误提交,但它不能清除已有历史。
Git LFS 的实体文件需要单独管理。如果涉及密码、密钥泄露,还要撤销凭据并处理平台缓存,不能只做仓库瘦身。
评论
使用 GitHub 登录后参与讨论,评论将公开保存在 GitHub。
站长管理删除会同步到 GitHub,无法撤销。
阅读到这里时加载评论。