1. 当Git仓库变成"胖子":我们遇到了什么问题
第一次发现Git仓库出问题是在某个周一的早晨。CI/CD流水线突然报错,Jenkins控制台里赫然显示着"git clone failed"的红色警告。我尝试调整clone深度、延长超时时间,甚至换了台服务器重试,结果都一样——这个曾经乖巧的仓库现在像个任性的孩子,死活不肯被完整克隆。
用du -sh .git命令查看本地仓库大小后,我吓了一跳:一个原本应该以代码为主的工程,.git目录竟然占用了近10GB空间!进一步分析发现,罪魁祸首是那些被频繁修改的二进制文件——设计同事上传的PSD源文件、Unity场景资源、编译后的.so/.dll文件,它们就像仓库里的"垃圾食品",每次修改都会产生全新的副本,导致仓库体积呈指数级增长。
这让我想起之前遇到过的一个极端案例:某图形化编程项目用图片格式存储源代码,单个文件就超过50MB,开发团队每天提交几十次变更,结果不到三个月就把Git服务器硬盘撑爆了。Git的设计初衷是高效管理文本文件(差异比较和压缩都很容易),但对二进制文件却显得力不从心——它会把整个文件重新存储,而不是只记录变化部分。
2. Git LFS:给二进制文件开个"外挂仓库"
Git LFS(Large File Storage)就像是给Git装了个外挂硬盘。它的核心原理很巧妙:用轻量的文本指针代替实际的二进制文件存储在Git仓库里。这些指针看起来像这样:
version https://git-lfs.github.com/spec/v1 oid sha256:9171c8350d72ccca6ad60ac80b577157ad1f9fd44ca05744216e02ccbfcdf491 size 10260当执行git clone时,默认只会下载这些指针文件。真正需要用到二进制内容时(比如checkout到某个分支),LFS会自动从专用服务器下载对应的文件。整个过程对开发者基本透明,日常的git pull/push等操作都不需要改变习惯。
我特别喜欢LFS的这几个设计:
- 智能缓存:下载过的文件会缓存在本地,切换分支时不用重复下载
- 按需加载:可以只下载当前分支需要的文件,不像传统Git必须全量克隆
- 版本控制:依然保留完整的修改历史,只是存储方式更高效
3. 迁移前的"战前准备"
决定迁移后,我列了个检查清单:
备份!备份!备份!
用git bundle create repo.bundle --all创建完整仓库快照,并上传到安全的云存储。这是最后的救命稻草。团队沟通
在群里发了迁移公告,约定两小时的维护窗口。特别提醒:- 迁移期间禁止推送代码
- 迁移后所有成员需要重新克隆仓库
- 本地未提交的改动要先stash或备份
环境检查
确保所有开发机和CI服务器都安装了Git LFS客户端。可以用这个命令测试:git lfs env | grep "git-lfs/"如果没安装,各平台的安装方法如下:
- Ubuntu/Debian:
sudo apt install git-lfs - CentOS/RHEL:
sudo yum install git-lfs - MacOS:
brew install git-lfs
- Ubuntu/Debian:
服务端配置
如果是自建GitLab,需要管理员在/etc/gitlab/gitlab.rb中添加:gitlab_rails['lfs_enabled'] = true然后执行
gitlab-ctl reconfigure。云端Git服务(如GitHub/GitLab.com)默认已开启。
4. 实战迁移:五步瘦身计划
4.1 第一步:识别"肥胖元凶"
先用这个命令找出仓库里的大文件:
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 \ | cut -c 1-12,41- \ | $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest输出结果类似:
5c2d3e1b1a8 4.0MiB path/to/bigfile.psd a1b2c3d4e5f 12MiB assets/texture.png4.2 第二步:创建迁移策略
根据扫描结果,我决定迁移这些类型的文件:
- 图片:*.psd, *.png, *.jpg
- 压缩包:*.zip, *.tar.gz
- 编译产物:*.so, *.dll, *.a
- 数据集:*.bin, *.data
对应的.gitattributes模板:
*.psd filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text4.3 第三步:重写历史
这是最关键的步骤,使用git lfs migrate命令:
git lfs migrate import --include="*.psd,*.png,*.zip,*.so,*.dll" --everything这个过程可能会很耗时(我们的仓库用了约45分钟)。如果中断了,可以用--skip-fetch参数继续。
4.4 第四步:验证迁移结果
检查哪些文件已被LFS管理:
git lfs ls-files确认.gitattributes文件是否自动生成:
git show HEAD:.gitattributes4.5 第五步:强制推送
git push --force --all git push --force --tags注意:这步会重写所有分支的历史,确保团队其他成员都已知晓!
5. 迁移后的维护与优化
5.1 团队成员如何适配
对于已经存在的本地仓库,最简单的处理方式是:
rm -rf /path/to/repo git clone git@example.com:repo.git如果有未提交的修改,可以:
git checkout -b old-master-backup git branch -D master git fetch origin git checkout master # 然后cherry-pick需要的提交5.2 清理本地缓存
迁移者的本地仓库需要执行:
git lfs pull git reflog expire --expire-unreachable=now --all git gc --prune=now5.3 CI/CD配置调整
在Jenkins等CI系统中需要:
- 安装Git LFS插件
- 在构建步骤中添加:
git lfs install --skip-repo git lfs pull
6. 效果对比:数字会说话
我们统计了迁移前后的关键指标:
| 指标 | 迁移前 | 迁移后 | 变化率 |
|---|---|---|---|
| 仓库大小(.git) | 9.8GB | 420MB | -95.7% |
| 完整克隆时间 | 32分钟 | 2分钟 | -93.8% |
| 浅克隆(深度=1)时间 | 18分钟 | 45秒 | -95.8% |
| 日常提交速度 | 8-12秒 | 1-2秒 | -85% |
| 分支切换速度 | 15-20秒 | 3-5秒 | -75% |
特别惊喜的是,CI流水线的平均执行时间从原来的52分钟降到了37分钟,主要节省在代码拉取环节。对于频繁提交的二进制文件(如UI设计稿),每次提交的大小从平均50MB降到了不到1KB。
7. 那些年我们踩过的坑
坑1:文件类型遗漏
第一次迁移后,发现某些.so文件没被处理。原因是文件名中有版本号(如libxxx-1.2.3.so)。解决方案是调整匹配模式:
git lfs migrate import --include="*.[so],*.dll" --everything坑2:权限问题
自建GitLab服务器出现LFS上传失败,原因是nginx配置缺少:
client_max_body_size 500m;坑3:本地缓存爆炸
LFS默认缓存路径在~/.git/lfs/objects,可以用这个命令清理旧版本:
git lfs prune坑4:部分文件不想迁移
有些二进制文件确实需要直接存在Git里(比如小图标),可以在.gitattributes中添加:
*.ico binary8. 进阶技巧:让LFS更高效
按需下载
只获取当前需要的LFS文件:git lfs fetch --include="assets/textures/*.png"批量迁移历史分支
对于有上百个分支的仓库,可以先用脚本列出所有分支:git for-each-ref --format='%(refname:short)' refs/heads/ > branches.txt然后批量迁移:
while read branch; do git lfs migrate import --include="*.psd" --branch="$branch" done < branches.txt监控LFS使用量
GitHub API可以查询LFS用量:curl -s -H "Authorization: token YOUR_TOKEN" \ https://api.github.com/repos/owner/repo | grep -i lfs迁移后的仓库整理
使用BFG工具进一步清理历史:java -jar bfg.jar --convert-to-git-lfs '*.psd' --no-blob-protection repo.git