---
url: https://eoht.net/fe/git/clean-up.md
description: 先检查 Git 对象大小，再通过独立备份和新克隆选择性清理历史文件，验证仓库完整性，并在确需改写共享历史时按分支保护推送。
---

# Git 仓库瘦身：诊断、备份与选择性清理 {#reduce-git-repository-size}

仓库大不一定需要重写历史。先区分工作区文件、Git 对象和远端存储；从当前目录删除文件，只会产生新的删除提交，历史中的文件仍然存在。

## 先诊断大小 {#diagnose-git-repository-size}

在原仓库内查看状态和对象大小，以下命令不会清理历史：

```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 常用命令](/fe/git/command) 处理指定引用；删除引用不保证立即释放远端空间。

## 保存备份，使用新克隆 {#back-up-before-history-cleanup}

以下流程仅适用于已经确认需要从历史中移除文件的情况。先与协作者约定暂停写入，确认仓库完整历史可获取，并单独保存原工作区的未提交文件、未推送分支和本地配置。

在原工作区以外选择空目录，将示例 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 安装说明](https://github.com/newren/git-filter-repo/blob/main/INSTALL.md) 安装该工具。

## 选择需要清除的历史路径 {#select-history-paths-to-remove}

先分析，不修改历史：

```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、影响签名和引用，并会执行清理；独立备份是恢复依据。

## 验证清理结果 {#verify-history-cleanup}

```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` 和原工作区。不要为了让数字变小而删除唯一备份；本地对象大小下降，也不代表托管平台已经回收远端对象。

## 确需同步重写历史时才推送 {#publish-rewritten-history}

只有协作者已经确认影响范围、验证通过且目标分支允许改写时，才更新远端。`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/main
```

lease 拒绝时先停止并协调，不改成无条件强推。其他受影响分支和标签需逐个列入计划并保护其旧 SHA；仍引用旧历史的分支、标签或 PR 引用会保留旧对象，单独更新 `main` 不等于全仓库完成清理。

推送后用 `git ls-remote publish refs/heads/main` 与本地 `git rev-parse refs/heads/main` 对照，并通知协作者重新克隆或按约定迁移未合并工作，避免把旧历史再次合并回来。远端空间回收时间由托管服务决定。若处理的是泄露凭据，应先撤销凭据，历史过滤不能让已复制的凭据失效。

参考：[git-filter-repo 使用文档](https://github.com/newren/git-filter-repo/blob/main/Documentation/git-filter-repo.txt)、[Git push 的 lease 保护](https://git-scm.com/docs/git-push)。

## Git 学习路径 {#git-learning-path}

历史清理会改变提交记录，操作前可先对照 [Git 重置提交记录](/fe/git/reset-commit)；如果只需要日常分支和标签操作，返回 [Git 常用命令](/fe/git/command)。
