添加 [操作指南] 清理 Git 历史中的大文件 (减小 .git/pack 体积)
@@ -0,0 +1,139 @@
|
||||
### 📌 问题描述与现象
|
||||
在项目开发过程中,如果不慎将大文件(如编译产物、二进制文件、设计图、数据包等)提交到了 Git 仓库中,即使后续在代码中删除了它们,这些文件仍然会留在 Git 的提交历史中。
|
||||
这会导致本地 `.git/objects/pack/` 目录下的 `.pack` 文件异常庞大(达到几百 MB 甚至更大),严重拖慢 `git clone`、`git pull` 的速度。
|
||||
|
||||
### ⚠️ 注意事项(必读)
|
||||
|
||||
- **破坏性操作:** 重写历史会改变所有的 Commit Hash。
|
||||
|
||||
- **团队协作:** 操作完成后,所有团队成员必须丢弃本地旧仓库,重新 Clone。如果有人基于旧历史提交代码并强推,大文件会被“复活”。
|
||||
|
||||
- **切勿手动删除:** 绝对不要直接手动删除 `.git/pack/` 下的文件,这会导致仓库直接损坏。
|
||||
|
||||
### 🔧 解决方案:使用 `git-filter-repo` 重写历史
|
||||
`git-filter-repo` 是目前官方推荐的重写历史工具,速度快且安全(替代了已废弃的 `git filter-branch`)。
|
||||
|
||||
### 第一步:安装 `git-filter-repo`
|
||||
在执行清理的机器上,需要先安装该工具:
|
||||
```bash
|
||||
# Ubuntu / Debian
|
||||
sudo apt install git-filter-repo
|
||||
|
||||
# CentOS / RHEL (需要 EPEL 源)
|
||||
sudo yum install epel-release
|
||||
sudo yum install git-filter-repo
|
||||
|
||||
# macOS
|
||||
brew install git-filter-repo
|
||||
|
||||
# Windows (通过 Python)
|
||||
pip install git-filter-repo
|
||||
```
|
||||
|
||||
### 第二步:准备工作(备份与克隆)
|
||||
强烈建议在一个全新的克隆副本中操作,避免污染当前工作区。
|
||||
```bash
|
||||
# 1. 获取仓库的 Gitea 克隆地址(例如:https://gitea.your-domain.com/org/repo.git)
|
||||
# 2. 重新克隆一份干净的代码
|
||||
git clone https://gitea.your-domain.com/org/repo.git repo-clean
|
||||
cd repo-clean
|
||||
```
|
||||
|
||||
### 第三步:找出历史中的大文件(诊断)
|
||||
运行以下命令,列出历史记录中体积最大的 20 个文件,确认你要删除的目标:
|
||||
```bash
|
||||
git rev-list --objects --all \
|
||||
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
|
||||
| awk '/^blob/ {print substr($0,6)}' \
|
||||
| sort --numeric-sort --key=2 \
|
||||
| tail -n 20
|
||||
```
|
||||
*(输出结果的最后一列就是文件路径)*
|
||||
|
||||
### 第四步:从历史中彻底删除大文件
|
||||
根据上一步找到的路径,执行删除操作。
|
||||
```bash
|
||||
# 删除单个文件
|
||||
git filter-repo --invert-paths --path "path/to/large-file.zip"
|
||||
|
||||
# 删除整个目录(例如 node_modules 或 dist)
|
||||
git filter-repo --invert-paths --path "build/"
|
||||
|
||||
# 按照后缀名批量删除(例如所有 .psd 文件)
|
||||
git filter-repo --invert-paths --path-glob "*.psd"
|
||||
```
|
||||
> 参数说明:--invert-paths 表示“排除/删除”指定的路径。如果不加这个参数,效果会变成“只保留”指定的路径(会把其他代码全删了,千万别漏加!)。
|
||||
|
||||
### 第五步:清理本地垃圾对象
|
||||
虽然历史被重写了,但大文件的数据还残留在 .git 的缓存里。需要执行垃圾回收(GC)才能真正减小 pack 文件体积:
|
||||
```bash
|
||||
# 清除 filter-repo 留下的备份引用
|
||||
rm -rf .git/filter-repo
|
||||
|
||||
# 强制过期所有 reflog,并立即执行垃圾回收与重新打包
|
||||
git reflog expire --expire=now --all
|
||||
git gc --prune=now --aggressive
|
||||
```
|
||||
执行完后,可以运行 du -sh .git 检查体积是否已经恢复正常。
|
||||
|
||||
### 🚀 第六步:推送到 Gitea 远端
|
||||
根据实际情况,选择以下一种方式同步到 Gitea:
|
||||
|
||||
#### 方式 A:强制推送覆盖原仓库(推荐,保留 Issues 等)
|
||||
如果该仓库在 Gitea 上没有过多的 Fork,或者你拥有强制推送权限:
|
||||
```bash
|
||||
# 因为历史变了,必须加 --force
|
||||
git push origin --force --all
|
||||
git push origin --force --tags
|
||||
```
|
||||
|
||||
#### 方式 B:删除 Gitea 仓库并重建(最彻底)
|
||||
如果这是一个新项目,不需要保留 Gitea 上的 Wiki、Issues、PR 记录:
|
||||
|
||||
1. 进入 Gitea 仓库页面 -> 设置 -> 危险操作区 -> 删除此仓库。
|
||||
2. 在 Gitea 上重新创建同名的空仓库。
|
||||
3. 在本地执行:
|
||||
```bash
|
||||
git remote remove origin
|
||||
git remote add origin https://gitea.your-domain.com/org/repo.git
|
||||
git push -u origin --all
|
||||
git push origin --tags
|
||||
```
|
||||
|
||||
### 💣 终极捷径:不需要保留历史记录
|
||||
如果这个项目根本不需要看以前的提交记录(比如只是个文档库或个人笔记),最快的方法是“清空历史,只保留当前代码”:
|
||||
```bash
|
||||
# 1. 在当前代码目录下,创建一个没有任何历史的孤立分支
|
||||
git checkout --orphan latest_branch
|
||||
|
||||
# 2. 把当前所有文件加入暂存区
|
||||
git add -A
|
||||
|
||||
# 3. 提交一次
|
||||
git commit -m "初始化提交:清理历史大文件"
|
||||
|
||||
# 4. 删除原来的主分支,并把当前分支改名
|
||||
git branch -D main # 或者 master,看你的默认分支名
|
||||
git branch -m main
|
||||
|
||||
# 5. 强推到 Gitea
|
||||
git push origin --force --all
|
||||
```
|
||||
|
||||
### 🛡️ 预防措施(防患于未然)
|
||||
为了避免下次再出现这种情况,请在项目根目录的 .gitignore 中严格限制大文件:
|
||||
```gitignore
|
||||
# 常见的大文件/编译产物
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
*.iso
|
||||
*.exe
|
||||
*.dll
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
*.psd
|
||||
*.ai
|
||||
```
|
||||
**进阶建议:** 如果是必须版本控制的大文件(如游戏资源、大型数据集),请使用 Git LFS (Large File Storage)。
|
||||
Reference in New Issue
Block a user