你 clone 了仓库,进入 assets/ 目录打开一个 PNG 文件,发现里面是三行文本:version https://git-lfs.github.com/spec/v1、oid sha256:abcdef...、size 2048000。程序加载图片时报「无效文件格式」或「不是有效的 PNG」。这是 Git LFS 的 pointer 文件——它只是真实内容的元数据引用,真实的二进制内容存储在 LFS 服务器上,还没有被下载到你的工作区。造成这种情况的原因有多种,从 Git LFS 未安装到访问权限问题都可能是根因。
常见原因
1. Git LFS 未安装或未初始化
clone 时本机没有安装 git-lfs,或者安装后没有执行 git lfs install,smudge filter(把 pointer 替换为真实内容的钩子)没有被激活。clone 完成后工作区里全是 pointer 文件。
怎么判断:git lfs version 若命令不存在或版本低于 2.0,说明未安装或版本过旧。git config --list | grep lfs 若无输出,说明 LFS filter 未注册。
2. clone 时使用了 --no-checkout 或 GIT_LFS_SKIP_SMUDGE=1
某些 CI 脚本为了加速 clone,设置了 GIT_LFS_SKIP_SMUDGE=1 跳过 smudge 阶段,导致 LFS 文件全部停留为 pointer 状态。
怎么判断:echo $GIT_LFS_SKIP_SMUDGE 若输出 1,就是这种情况。也检查 CI 配置文件里是否有这个环境变量设置。
3. LFS 服务器访问权限不足
用于 push/pull 的 token 没有 LFS 读取权限(在 GitHub 上需要 repo scope,对于 GitHub Enterprise 可能需要额外的 LFS 访问配置)。git lfs pull 时报 401 或 403 错误。
怎么判断:git lfs pull 2>&1 | head -20 查看是否有认证错误;git lfs env 查看 LFS 使用的凭证和 endpoint 配置。
4. LFS endpoint 指向了错误的服务器
仓库迁移(从 GitHub 迁到 GitLab,或者迁移到私有 LFS 服务器)后,.lfsconfig 或 Git config 里的 LFS URL 还是旧地址,git lfs pull 连接到不存在的服务器或者没有该对象的服务器。
怎么判断:git lfs env | grep 'Endpoint' 查看当前 LFS endpoint;cat .lfsconfig 查看仓库级别的 LFS 配置。
5. 部分文件被纳入 LFS 追踪,部分没有
.gitattributes 里 *.png filter=lfs diff=lfs merge=lfs -text 规则添加之前已经提交的 PNG 文件,这些旧版文件是普通 blob 而非 LFS pointer,checkout 时不会触发 LFS 下载。新文件是 pointer,旧文件是真实内容,混在一起让人困惑。
怎么判断:git lfs status 或 git lfs ls-files 查看哪些文件被 LFS 追踪;git show HEAD:path/to/old-file.png | head -3 若第一行是 version https://git-lfs.github.com,是 LFS pointer;否则是普通 blob。
6. git lfs migrate 迁移后历史重写未推送
执行了 git lfs migrate import --include="*.psd" 把历史里的大文件转换为 LFS pointer,但 force push 没有成功,远端还是旧历史,其他人 pull 后看到的是未迁移的混合状态。
怎么判断:git lfs ls-files --all | wc -l 与远端实际 LFS 文件数量对比(在 GitHub LFS storage 页面查看)。
最短修复路径
Step 1:确认 Git LFS 已安装并初始化
git lfs version # 应该输出版本号如 git-lfs/3.4.0
git lfs install # 注册 smudge/clean filter
Step 2:强制拉取所有 LFS 文件内容
git lfs pull
# 若只需要特定目录:
git lfs pull --include="assets/"
Step 3:若 GIT_LFS_SKIP_SMUDGE 被设置,重新 checkout
unset GIT_LFS_SKIP_SMUDGE
git lfs pull
# 或者重新检出工作区:
git checkout -- .
Step 4:修复 LFS endpoint URL
# 查看当前配置
git lfs env
# 修改为正确的 endpoint
git config --local lfs.url https://correct-lfs-server.com/org/repo.git/info/lfs
# 或者修改 .lfsconfig 文件
Step 5:重新克隆(最彻底的修复)
# 确保 GIT_LFS_SKIP_SMUDGE 未设置
unset GIT_LFS_SKIP_SMUDGE
git clone https://github.com/org/repo.git
Step 6:验证文件已正确下载
git lfs ls-files
file assets/logo.png # 应该输出 PNG image data,而不是 ASCII text
预防建议
- 团队 onboarding 文档第一步就写:
brew install git-lfs && git lfs install,缺这一步后续所有 LFS 文件都是 pointer。 - 在仓库的 README 或 CONTRIBUTING.md 里注明该仓库使用 Git LFS,并列出需要安装的命令。
- CI 环境的 Docker 镜像里预装
git-lfs,不依赖 runner 环境自带。GitHub Actions 的ubuntu-latest已预装git-lfs,但需要确认版本。 - 若 CI 用了
GIT_LFS_SKIP_SMUDGE=1加速 clone,在需要访问 LFS 文件的 step 之前显式执行git lfs pull --include="需要的路径"。 - 仓库迁移后,在新位置验证
git lfs ls-files --all输出数量与旧仓库一致,再通知团队更新 remote URL。 - 为 LFS 存储设置监控告警,当 bandwidth 或 storage 接近套餐上限时提前扩容,避免拉取失败。
- 区分「需要 LFS 的文件类型」并在
.gitattributes里统一管理,避免同一类型的旧版本是 blob、新版本是 LFS pointer 的混合状态。
常见问答 (FAQ)
Q: 怎么快速判断一个文件是 LFS pointer 还是真实内容?
A: file path/to/file 若输出 ASCII text 而该文件应该是图片/二进制,大概率是 pointer。或者 head -1 path/to/file 若第一行是 version https://git-lfs.github.com/spec/v1,确定是 pointer。
Q: LFS 的 bandwidth 配额用完了,文件拉不下来怎么办?
A: GitHub LFS 免费套餐每月 1 GB bandwidth,用完后 git lfs pull 会报 429 Too Many Requests。短期方案:购买 Data Pack 或升级套餐;长期方案:把 LFS 迁移到 Cloudflare R2 或 AWS S3 自建 LFS 服务器(如 lfs-s3 方案),无带宽限制。
Q: 我想把某个文件从 LFS 改回普通 blob,怎么操作?
A: 从 .gitattributes 删除该文件类型的 LFS 规则,然后 git lfs migrate export --include="*.psd" --everything,再 force push。注意这会重写历史,所有协作者需要重新 clone。
Q: git lfs pull 和 git lfs fetch 有什么区别?
A: git lfs fetch 只把 LFS 对象下载到本地 LFS 缓存(.git/lfs/objects/)但不更新工作区;git lfs pull = git lfs fetch + git lfs checkout,会同时更新工作区,是日常使用更常用的命令。
相关阅读
- 历史里的大文件让 push 失败
- 二进制文件合并冲突 — 手动无法 resolve
- Monorepo partial clone 数据过期
- Submodule 怎么都拉不到最新 commit
- Clone 之后 git hooks 不执行
- 凭证 helper 锁住,pull / push 全失败
标签: #git #version-control #排查