最近项目组很多新项目都使用到了 Git LFS, 解决了 Git 大文件提交容易导致仓库尺寸爆炸的问题.
Git LFS: 简单来说就是用来存储源码仓大文件的, 会把大文件放在"别的地方"存储, 保持源码仓的 Size.
有必要学习一下基础用法.
安装
这个东西可以看成是 Git 的一个扩展插件, 一般安装比较新的 Git 之后, 都自带这个插件.
可以使用命令查看 Lfs 版本:
git lfs version
如果执行后报错
git:'lfs' 不是一个 git 命令, 说明本机 没装 Git LFS, 或装了但没进PATH. Git LFS 不是 Git 自带的子命令, 需要单独安装:
系统 安装命令 macOS brew install git-lfsUbuntu/Debian sudo apt-get install git-lfsCentOS/RHEL sudo yum install git-lfsWindows 重装 Git for Windows, 勾选 Git LFS 装完初始化一次即可, 看到
Git LFS initialized./git-lfs/3.x.x ...就说明好了:git lfs install git lfs version
使用
- 启用
LFS功能
git lfs install
这个命令单个用户只需要执行一次, 后续提交都会自动使用
LFS功能.
- 将文件向
LFS提交
git lfs track path/file.bin
这个操作会修改
.gitattributes文件, 这个文件记录了LFS管理文件的信息.
文件路径也可以使用通配符*来匹配多个文件.
- 提交文件
git add .gitattributes path/file.bin
git commit -m "add file.bin"
需要同时提交
.gitattributes和 二进制文件, 否则LFS无法识别.
- 推送到远程仓库
git push origin master
- 其他参考
使用命令git lfs, 想知道的都有写:
git lfs help
也可以去官网瞅瞅: https://git-lfs.com/
把"已经提交过"的大文件 LFS 化
上面 git lfs track 有个容易踩的坑: 它只对之后的提交生效, 已经在历史里的旧 blob 不会自动转成 LFS 指针.
想把仓库里早就存在的大文件(一堆 .bin.zip)一次性转 LFS, 核心命令是 git lfs migrate import, 它会重写提交历史, 把匹配到的文件替换成 LFS 指针.
标准流程
# 1. 先镜像备份, 别省 —— 迁移会改写 commit SHA
git clone --mirror <repo-url> backup.git
# 2. 盘点: 哪些大文件占空间, 命中了多少个
git lfs migrate info --everything --include="*.bin,*.img,*.so,*.zip"
# 3. 真正迁移 (多个类型写在同一个 --include, 用逗号分隔)
git lfs migrate import --everything --include="*.bin,*.img,*.so,*.zip"
# 4. 验证
git lfs ls-files
git lfs migrate info --everything
# 5. 清理本地旧对象
git reflog expire --expire-unreachable=now --all
git gc --prune=now
# 6. 强推远程 (改写历史, 需通知协作者重新 clone)
git push --force --all
git push --force --tags
几个关键点:
--include只能给一个, 多个模式用逗号拼一起, 别写成多个--include.--everything覆盖所有分支和标签, 更彻底但更慢; 只想动当前分支就换成--include-ref=main.- 这一步改写了 SHA, 协作者本地仓库会和远程分叉, 必须重新 clone 或
reset --hard.
只迁某个目录, 还带子目录
实战里最常见的是 AOSP 这类大仓: 只把某个目录下的 APK(还嵌套子目录)转 LFS, 不想误伤别处文件. 这时用路径 glob, 而不是只按后缀:
# 先确认能命中
git lfs migrate info --everything --include="device/byteos/base/apks/**/*.apk"
# 命中无误后再 import
git lfs migrate import --everything --include="device/byteos/base/apks/**/*.apk"
**/*.apk能匹配apks/下任意层级子目录的 APK, 只写*.apk只能匹配一层. 成功后.gitattributes里应出现:device/byteos/base/apks/**/*.apk filter=lfs diff=lfs merge=lfs -text
migrate 的几个坑(踩过的都懂)
现象 1: .gitattributes 还是空的, git lfs ls-files 没输出, 仓库毫无变化
日志里有一行 Rewriting commits: 0/0, 这就是答案: 这次迁移一个提交都没改写, 自然什么都没改, 也不会往 .gitattributes 写规则.
最常见原因是 info 加了 --everything 能看到文件, 但 import 没加 --everything. import 不加它时只扫当前 HEAD 可达的提交链; 如果这些大文件是在别的分支/标签上加的, 或当前分支没包含那些提交, 就会匹配不到:
# 正确: info 和 import 的扫描范围要一致
git lfs migrate import --everything --include="device/byteos/base/apks/**/*.apk"
现象 2: migrate info 始终是空的
说明这些文件当前不在任何被扫描的提交里. 先用普通 Git 确认它们在不在历史:
git log --all --oneline -- "device/byteos/base/apks/**/*.apk" | head
如果这里也没输出, 说明文件根本不在 Git 历史里(可能被 .gitignore 忽略、从未被 track), LFS 迁移当然无效.
排查清单: glob 路径大小写/空格/软链接对不上、文件被
.gitignore忽略、文件只在别的分支、仓库是 shallow/partial clone、上次 migrate 失败留下脏状态.
现象 3: git lfs ls-files 在大仓里很慢
正常现象, 它要扫当前分支所有 tracked 文件挑 LFS 指针. 想快点就加 grep 缩小到目标目录:
git lfs ls-files | grep "device/byteos/base/apks/"
大仓里 migrate 慢得离谱, 怎么提速
AOSP 这种超大仓, migrate import --everything 要遍历所有分支/标签/历史, 慢是必然的. 可以这样优化:
# 1. 别一上来就 --everything, 先只处理当前分支
git lfs migrate import --include-ref=refs/heads/main --include="device/byteos/base/apks/**/*.apk"
# 2. 调高 LFS 并发 (网络好可以试 32 / 64)
git config lfs.concurrenttransfers 16
# 3. 给 Git 本身加性能开关(对大仓遍历、索引加载有帮助)
git config core.preloadindex true
git config core.ignoreStat true
git config core.useBloomFilters true
# 4. 先估算要迁的文件数量, 心里有底
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| grep 'blob' | grep 'device/byteos/base/apks/' | grep '\.apk$' | wc -l
如果已经卡在
migrate import里: 新开终端ps aux | grep "git-lfs migrate", CPU/IO 一直在动就让它跑完; 长时间完全不动才考虑中断, 改用--include-ref缩小范围重来.
不想重写历史 / 不强推远程, 怎么办
migrate import 会改写 commit SHA、需要强推, 多人协作时风险很大. 如果只想"从现在起走 LFS", 不动旧历史, 有更稳的做法.
做法 A: 只 track, 以后新提交走 LFS(最省事)
git lfs track "device/byteos/base/apks/**/*.apk"
git add .gitattributes
git commit -m "track apks with git lfs"
代价: 历史里的旧文件还是普通 blob, 仓库体积不会立刻下降; 但从现在起新增/修改的匹配文件都会走 LFS. 不需要强推.
做法 B: --no-rewrite —— 在当前分支加一个新提交转指针
# 1. 先加规则并提交
git lfs track "device/byteos/base/apks/**/*.apk"
git add .gitattributes
git commit -m "track apks with git lfs"
# 2. --no-rewrite 生成新提交(把匹配文件转指针), 不动旧提交
git lfs migrate import --no-rewrite -m "migrate apks to git lfs" device/byteos/base/apks/**/*.apk
# 3. 验证后普通推送(不必 force)
git lfs ls-files | grep "device/byteos/base/apks/"
git push origin HEAD
--no-rewrite的关键: 它在当前分支新建一个提交把匹配文件转成 LFS 指针, 不重写旧提交树, 因此不用--force, 也不会让协作者分叉. 代价同样是旧历史里的 blob 不会瘦身.
三条路一句话总结:
- 想瘦历史 →
migrate import --everything+ 强推(改写 SHA, 通知全员重新 clone);- 想不动历史、不强推 →
track+migrate import --no-rewrite(只让当前/未来文件走 LFS);- 只想以后新文件走 LFS → 光
track一条规则就够.
证书问题
LFS 内部使用的 HTTP 协议操作大文件, 上传下载什么的会访问源码仓的 API. 一些仓库证书是自签的, 会报证书错误.
Remote "origin" does not support the LFS locking API. Consider disabling it with:
$ git config lfs.https://xxxx/org/project.git/info/lfs.locksverify false
Uploading LFS objects: 0% (0/1), 0 B | 0 B/s, done
batch response: Post https://xxxx/org/project.git/info/lfs/objects/batch: x509: certificate signed by unknown authority
error: failed to push some refs to 'ssh://git@xxxx/org/project.git.git'
可以通过配置不验证证书来绕过:
# 单个项目配置
git config http.sslVerify false
# 全局配置
git config --global http.sslVerify false