Git 仓库瘦身:诊断、备份与选择性清理
仓库大不一定需要重写历史。先区分工作区文件、Git 对象和远端存储;从当前目录删除文件,只会产生新的删除提交,历史中的文件仍然存在。
先诊断大小
在原仓库内查看状态和对象大小,以下命令不会清理历史:
sh
git status --short --branch
git count-objects -vH
git rev-list --objects --all |
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
awk '$1 == "blob"' |
sort -k3,3nr |
head -20第三列是对象未压缩的字节数,不等于 .git 在磁盘上的最终占用。若主要是可回收对象,可在没有其他 Git 写入任务时运行普通 git gc,再比较 git count-objects -vH。不要把立即清空 reflog、--prune=now 或 --aggressive 当作日常组合步骤。
只是不再需要某个分支或标签时,先检查其提交是否仍需保留,再按 Git 常用命令 处理指定引用;删除引用不保证立即释放远端空间。
保存备份,使用新克隆
以下流程仅适用于已经确认需要从历史中移除文件的情况。先与协作者约定暂停写入,确认仓库完整历史可获取,并单独保存原工作区的未提交文件、未推送分支和本地配置。
在原工作区以外选择空目录,将示例 URL 替换成自己的仓库地址。以下阶段使用同一个终端,repo-before.git 保留为备份,所有过滤只在 repo-clean.git 中进行:
sh
cleanup_remote='https://github.com/your-org/your-repo.git'
git clone --mirror "$cleanup_remote" repo-before.git
git -C repo-before.git fsck --full
git ls-remote --refs "$cleanup_remote" > remote-before.txt
git clone --no-local --mirror repo-before.git repo-clean.git
cd repo-clean.git
git count-objects -vH > ../size-before.txt
git show-ref > ../refs-before.txt任一步骤失败就先处理失败,不继续过滤。镜像备份保存远端可获取的 Git 引用和对象,不包含原工作区未提交文件、Git LFS 的实体文件或托管平台的全部 PR 内部引用;这些内容需要另行备份。使用前确认已经按 git-filter-repo 安装说明 安装该工具。
选择需要清除的历史路径
先分析,不修改历史:
sh
git filter-repo --analyze检查命令提示的 filter-repo/analysis 目录,核对大文件、旧路径和重命名记录。下面示例仅删除仓库根目录下的 media/obsolete.mp3 在历史中的记录,使用前改成已确认的路径:
sh
git filter-repo --path 'media/obsolete.mp3' --invert-paths如果明确要移除所有历史中的 MP3 文件,才改用下面这条;不要与上例一起机械执行:
sh
git filter-repo --path-glob '*.mp3' --invert-paths路径相对于仓库根目录,过滤不会自动跟随重命名,曾用过的其他路径需要一并指定。新克隆检查失败时先查明原因或重新克隆,不默认添加 --force。过滤会改写相关提交 SHA、影响签名和引用,并会执行清理;独立备份是恢复依据。
验证清理结果
sh
git fsck --full
git count-objects -vH
git for-each-ref --format='%(refname) %(objectname)'
git log --all -- 'media/obsolete.mp3'将大小、分支和标签与 ../size-before.txt、../refs-before.txt 比较。最后一条对应精确路径示例,预期无相关提交输出;若使用通配符,应按实际过滤范围核对全部路径。再运行 git filter-repo --analyze 复查报告,并从清理后的仓库另建普通克隆,检查文件及运行项目验证。
暂时保留 repo-before.git 和原工作区。不要为了让数字变小而删除唯一备份;本地对象大小下降,也不代表托管平台已经回收远端对象。
确需同步重写历史时才推送
只有协作者已经确认影响范围、验证通过且目标分支允许改写时,才更新远端。git-filter-repo 可能移除 origin,这是避免误推的保护;先查看当前远程配置,再添加一个明确用于发布的远程:
sh
git remote -v
git remote add publish "$cleanup_remote"下面仅演示更新 main,不自动更新所有分支和标签。先从清理前保存的远端清单读取旧 SHA,再核对当前远端是否仍是该值:
sh
cleanup_old_main=$(awk '$2 == "refs/heads/main" {print $1}' ../remote-before.txt)
git ls-remote publish refs/heads/main只有旧值非空且远端没有发生未处理变动时才执行:
sh
test -n "${cleanup_old_main:-}" &&
git push --force-with-lease="refs/heads/main:$cleanup_old_main" publish refs/heads/main:refs/heads/mainlease 拒绝时先停止并协调,不改成无条件强推。其他受影响分支和标签需逐个列入计划并保护其旧 SHA;仍引用旧历史的分支、标签或 PR 引用会保留旧对象,单独更新 main 不等于全仓库完成清理。
推送后用 git ls-remote publish refs/heads/main 与本地 git rev-parse refs/heads/main 对照,并通知协作者重新克隆或按约定迁移未合并工作,避免把旧历史再次合并回来。远端空间回收时间由托管服务决定。若处理的是泄露凭据,应先撤销凭据,历史过滤不能让已复制的凭据失效。
参考:git-filter-repo 使用文档、Git push 的 lease 保护。
Git 学习路径
历史清理会改变提交记录,操作前可先对照 Git 重置提交记录;如果只需要日常分支和标签操作,返回 Git 常用命令。