Git LFS 大文件版本管理
普通 Git 会把每个版本的文件内容写成 blob。源代码通常便于增量压缩,设计源文件、模型、音视频、压缩包和大型测试样本却常常已经压缩,改一点也可能产生一个全新的大 blob。仓库历史因此不断增重,删除工作树里的文件也不会删除旧提交中的对象。Git LFS 的做法是把一个很小的 pointer 提交进 Git,把真实内容按对象 ID 上传到 LFS 端点;检出时再由 clean/smudge filter 或 git lfs pull 还原内容。
最典型的事故不是 git push 失败,而是它成功了:Pull Request 里已经出现 assets/model.bin,同事也能 clone,CI 却在构建时读到三行 pointer。Git 远端只收到了引用和 pointer,LFS 对象上传因为认证或网络中断没有完成;“Commit 存在”与“二进制已交付”被错误地当成一件事。
先建立正确的选型边界。需要与源码 Commit 原子关联、按版本回溯且不适合文本合并的源资产,适合 Git LFS。频繁生成的构建产物更适合制品仓库;TB 级数据集、随机读取和生命周期管理应进入对象存储或数据平台;密钥、数据库备份与生产数据不能因为体积大就进入版本库。LFS 减轻 Git pack 压力,却新增了对象端点、配额、下载可用性和历史恢复责任。
从客户端和仓库状态开始
先准备一个不含真实业务数据的测试目录,并确认 Git 与 Git LFS 都可执行:
git --version
git lfs version
git lfs envgit lfs env 会展示 filter 配置、当前仓库和可能的 LFS endpoint。分享日志前删除账号、内部域名、代理和认证头。团队接入前还要确认:托管平台支持 LFS、仓库身份有上传权限、预算和单文件上限已核对、CI runner 能访问对象端点,而且源码归档和 Release 下载是否包含真实对象已有明确约定。
Git LFS 是客户端扩展,不需要为每个仓库启动一个本地服务。Git LFS 官方安装入口提供 Windows、macOS、Linux 包与 PackageCloud 入口;macOS 也可使用 Homebrew 的 brew install git-lfs。Git for Windows 发行包通常包含 Git LFS,但仍应以 git lfs version 为准。安装渠道不同,升级和卸载必须回到同一渠道;官方站点当前还发布了安全更新提示,组织批准版本不能长期停在“命令还能运行”的状态。
安装二进制后,为当前用户注册 filter:
git lfs install
git lfs version
git config --show-origin --get-regexp '^filter\.lfs\.'git lfs install 通常只需按用户执行一次。它配置 filter.lfs.clean、filter.lfs.smudge、filter.lfs.process 和 filter.lfs.required,并不自动决定哪些文件进入 LFS。若在临时环境中只想给当前仓库启用,可先阅读当前版本的 git lfs install --help,再评估 --local;不要在共享主机上无审查地覆盖系统配置。
容器和 CI 镜像必须显式包含匹配平台架构的 Git LFS。仅把宿主机 .gitconfig 挂进容器,不会凭空提供 git-lfs 二进制。托管平台的 LFS 服务也不是客户端安装的一部分:自建端点的部署、存储和备份由平台运维负责,开发接入要验证客户端到端点的协议与权限。
用 .gitattributes 声明仓库策略
在仓库根目录执行:
git lfs track "*.psd"
git lfs track "assets/models/**"
git lfs track
git diff -- .gitattributes典型规则类似:
*.psd filter=lfs diff=lfs merge=lfs -text
assets/models/** filter=lfs diff=lfs merge=lfs -text.gitattributes 是需要提交和评审的项目配置。不要只提交大文件而漏掉它,也不要用过宽的 *.* 把源码、锁文件和小文本全部送进 LFS。属性按路径匹配,子目录中的 .gitattributes 可以改变后代路径行为;出现疑问时用下面的命令看最终命中来源:
git check-attr -a -- assets/demo.psd
git lfs track理解 pointer 与本地对象
工作树中通常看到真实文件,index 和 Commit 里存的是 pointer:
version https://git-lfs.github.com/spec/v1
oid sha256:<object-id>
size <bytes>oid 以内容摘要标识对象,size 是原文件大小。真实内容一般缓存在 .git/lfs/objects,随后上传到远端 LFS endpoint。不要手改 pointer 的 oid 或 size;这会制造 Git 历史有引用但远端无对象的断链。
控制下载时机
网络昂贵或 CI 只做文本检查时,可以先跳过自动下载:
GIT_LFS_SKIP_SMUDGE=1 git clone <repository-url>
cd <repository>
git lfs fetch --include="assets/models/**" --exclude=""
git lfs checkout assets/modelsfetch 只把对象放入本地缓存,checkout 用已有缓存填充工作树;pull 通常等价于获取当前引用所需对象并检出。跳过 smudge 后,构建步骤必须显式拉取它真正需要的路径,否则“克隆成功”可能在编译阶段才暴露三行 pointer。
用隔离仓库证明 pointer 链路
下面的实验在 Git Bash、macOS 或 Linux shell 中创建一个临时仓库。它不依赖真实账号;最后一条命令会删除全部实验数据。
ROOT="$(mktemp -d)"
cd "$ROOT"
mkdir lfs-demo && cd lfs-demo
git init
git config user.name "Example User"
git config user.email "dev@example.com"
git lfs install --local
git lfs track "*.bin"
mkdir assets
printf 'git-lfs-demo-content\n' > assets/demo.bin
git add .gitattributes assets/demo.bin
git commit -m "test: add lfs object"
git lfs ls-files
git show HEAD:assets/demo.bin
git lfs fsck预期结果有三层:git lfs ls-files 列出 assets/demo.bin;git show 显示带 version、oid sha256 和 size 的 pointer,而 cat assets/demo.bin 仍显示真实内容;git lfs fsck 成功退出。这样才证明属性、clean filter、Commit pointer 和本地对象一致。
为了验证“干净检出拿到真实对象”,可用本地仓库再克隆一次:
cd "$ROOT"
git clone lfs-demo lfs-clone
cd lfs-clone
cat assets/demo.bin
git lfs ls-files
git lfs fsck若本地 Git 因 file 协议策略拒绝 LFS 传输,不要永久放宽全局安全设置;这只说明本机禁止该实验传输。真正项目验证应推送到一个可删除的测试远端,再从空目录克隆。实验清理:
cd /
rm -rf "$ROOT"Windows PowerShell 可在自己创建的临时目录中执行等价步骤,清理前必须打印并确认目标路径;不要把示例中的递归删除改成指向现有仓库。
新项目接入应走一个小型变更,而不是让每位开发者自己选择扩展名:
先用 git lfs migrate info --include-ref=main 或仓库对象分析确认真正占空间的类型,区分源资产、构建产物和数据文件。在独立分支添加最窄的 .gitattributes 规则,提交一个无敏感信息的样例对象。推送分支后从空目录克隆,验证工作树是真实内容、git lfs fsck 成功,并检查 CI、IDE、代码评审和归档下载行为。
在 PR/MR 中写清对象 owner、预计新增量、托管平台配额、锁策略、历史文件是否迁移,以及失败时如何撤回规则。
只转换当前工作树而不改旧历史时,可以在提交 .gitattributes 后执行:
git add --renormalize .
git status --short
git diff --cached --stat
git commit -m "chore: convert tracked assets to lfs"这会让当前版本按新属性重新进入 index,但旧 Commit 仍保留普通大 blob,仓库历史体积不会神奇缩小。若目标是清理历史,先冻结写入并备份所有引用,再分析和迁移:
git lfs migrate info --everything
git lfs migrate import --everything --include="*.psd,assets/models/**" --object-map=lfs-object-map.csv
git lfs fsckimport --everything 会重写可达历史并改变 Commit ID、Tag 指向和开放 PR/MR 的基线。git lfs migrate 手册还说明:迁移只改本地引用,不会自动推送;--everything 会考察所有 refs 可达的 Commit,但更新的是本地 refs,远端引用仍需人工审核后推送。它需要协调强推、分支保护、签名、Fork 和所有开发者重新同步,不能作为个人“仓库瘦身命令”直接执行。历史不允许重写时,应接受旧 blob 继续存在,只保证新版本进入 LFS。
CI 接入通常分两类:构建任务需要真实资产时,在 checkout 后执行 git lfs pull 或平台官方 checkout 的 LFS 选项;纯 lint 任务可以跳过 smudge,但必须确保不会把 pointer 当输入。缓存 .git/lfs/objects 可以提速,却要按仓库和对象 ID 隔离,并设置容量与淘汰策略,不能跨不可信项目共享可写缓存。
检查规则、对象和状态:
git lfs version
git lfs env
git lfs track
git lfs ls-files --all
git lfs status
git check-attr -a -- <path>
git lfs fsck控制对象传输:
git lfs fetch origin main
git lfs checkout <path>
git lfs pull origin main
git lfs push --dry-run origin main
git lfs push --all origin maingit lfs push 手册规定 --all 会上传指定引用可达的全部 LFS 对象,适合迁移补传与核对,不适合作为每次提交的默认动作。先用 --dry-run 看范围,再确认远端、带宽和权限。
不再跟踪某类文件时:
git lfs untrack "*.bin"
git add .gitattributes
git add --renormalize .
git status --shortuntrack 只改变今后的属性匹配,不会自动把历史 pointer 导出成普通 blob,也不会删除远端对象。需要全历史退出时使用 git lfs migrate export,其风险与 import 同级,必须按历史改写项目治理。
对不可合并的二进制源文件,可先在 .gitattributes 中声明锁需求,再操作锁:
git lfs track --lockable "*.psd"
git add .gitattributes
git lfs lock assets/design.psd
git lfs locks
git lfs unlock assets/design.psd锁是服务端协调能力,不是文件系统强制锁。git lfs lock 手册要求远端实现 Locking API;离线编辑、无锁客户端或有权限的强制解锁都可能绕过它,团队仍需明确 owner、租约意识和冲突处置。
推送仍报普通 Git 大文件超限
已经执行 git lfs track,托管平台仍拒绝某个大文件。运行 git check-attr -a -- <path>,再用 git show HEAD:<path> 看 Commit 中是 pointer 还是真实二进制;执行 git lfs migrate info --everything 查旧历史。规则在大文件提交之后才加入,文件未重新暂存,或旧分支和旧 Commit 仍含普通 blob。未推送时重新暂存或迁移未发布提交;已共享历史则先决定是否允许仓库级重写。git show 显示合法 pointer,git lfs fsck 成功,推送前 git lfs push --dry-run origin <branch> 能列出对象。
克隆成功,但工作树只有三行 pointer
文件内容以 version https://git-lfs.github.com/spec/v1 开头。检查 git lfs version、git lfs env、git lfs status 和 GIT_LFS_SKIP_SMUDGE,再执行 git lfs fetch 观察认证或网络错误。客户端未安装、smudge 被跳过、LFS endpoint 不可达、对象未上传或当前身份无下载权。安装并注册 Git LFS;修复端点认证后执行 git lfs pull,若远端缺对象则由持有正确本地对象的提交者补传。工作树文件大小符合 pointer 的 size,git lfs fsck 成功,干净克隆也能还原。
git lfs fsck 报 missing 或 corrupt
完整性检查指出对象缺失或哈希不一致。记录 pointer 的 OID,运行 git lfs fetch --all origin 后复查;同时确认问题来自本地缓存还是远端。上传中断、缓存损坏、迁移后对象未补传,或有人手改 pointer。不要伪造 OID;从可信副本恢复相同内容并上传,必要时回退引用到完整对象仍存在的 Commit。本地 fsck、远端补传和空目录克隆三项都成功。
锁定时报 404、501 或“不支持 locking”
普通 LFS 上传可用,git lfs lock 失败。查看 git lfs env 的 endpoint,并核对托管平台或自建服务的官方锁 API 支持。服务端没有实现 locking、LFS URL 指向错误、权限不足,或代理拦截对应请求。先确认服务能力与仓库权限;不支持时采用文件 owner、短分支和串行编辑流程,不能假装锁已生效。两个测试身份分别执行 lock、locks、unlock,并确认未持锁者的推送策略符合预期。
配额未预警却突然无法上传或下载
Git 推送元数据成功,LFS batch 请求失败,CI 或新成员无法取回对象。在平台账单与仓库设置中分别检查存储、带宽、预算和单文件限制;用 git lfs logs last 保留脱敏错误。把 Git 仓库容量误当 LFS 配额,忽略重复下载、Fork、归档或 CI 拉取带宽。暂停非必要下载,补充预算或迁移不适合版本化的资产,并为关键构建准备受控恢复方案。测试对象可上传,空 runner 可下载,预算告警能在耗尽前触发。
prune 后本地构建缺文件
执行 git lfs prune 后,切换旧分支或离线构建需要重新下载。用 git lfs prune --dry-run 查看候选对象,并确认对象是否已推送、旧引用是否仍需要。误把 prune 当远端清理或不了解本地保留窗口。git lfs prune 手册描述的是本地对象清理,并会依据最近引用、未推送对象和保留偏移计算候选集。在线时重新 fetch;对离线环境扩大保留策略,清理前先做 dry-run 和远端完整性确认。目标分支在断网前已 checkout 所需对象,构建不触发网络访问。
Git 远端和 LFS endpoint 可能不是同一 URL,但通常共享托管平台身份。排障时要分别观察 Git fetch/push 与 LFS batch/object 请求;“Git 能拉代码”不能证明大对象端点也通过代理、防火墙和证书检查。git lfs env、git lfs logs last 可能包含敏感地址,进入工单前必须脱敏。
最小权限至少区分读取源码与上传大对象。只读构建账号通常只需下载;迁移账号可能需要重写引用和上传全量对象,应是短期授权;锁管理员或强制解锁能力不能默认授予所有成员。PAT、SSH、OIDC 或平台令牌应按托管平台的身份体系配置,长期令牌不能进入 URL、.lfsconfig、脚本或日志。
.lfsconfig 可以改变仓库使用的 LFS URL,因此它是需要安全评审的代码配置。来自不可信仓库的端点可能诱导凭证发送到错误服务。团队应限制允许的域名、审查 URL 变更,并在代理层保留不含对象内容的必要审计。
架构选型先看内容生命周期。需要与源码 Commit 原子指向、按版本回溯且单文件无法文本合并的资产,适合 LFS;需要包语义、签名、晋级和保留策略的二进制适合制品仓库;海量数据、在线分发和复杂生命周期适合对象存储;可重建产物不应入库。LFS 降低 Git pack 压力,却增加独立端点、配额、下载可用性和历史完整性责任。
团队至少维护五项基线:允许进入 LFS 的路径和类型、单文件与仓库增长预算、远端平台和 owner、历史迁移审批流程、对象恢复与退出演练。.gitattributes 变更必须由熟悉资产用途和成本的人评审,不能只由提交者自批。
对锁定资产,应记录哪些扩展名必须先锁、锁多久、离职或异常锁怎样强制释放,以及强制释放由谁批准。对 CI,应按任务最小下载,缓存设容量上限,禁止把真实客户数据作为“测试大文件”缓存到共享 runner。
长期维护要同时巡检客户端安全版本、远端平台限制、对象增长、下载带宽、缺失 OID、锁滞留和规则漂移。退出 LFS 之前先回答“历史是否改写、远端对象保留多久、旧 Commit 如何重现、所有克隆何时重新同步”,而不是只删 .gitattributes。
历史迁移改变的是协作坐标系
migrate import --everything 会生成新的 Commit ID。开放 PR、签名 Commit、Tag、Fork、外部依赖和部署清单若仍引用旧 SHA,就会出现“双历史”。迁移前应列出所有 refs 和消费者,冻结写入,保留对象映射;迁移后验证默认分支、Tag 和关键构建,再分批恢复保护规则。不能只强推 main 就宣布完成。
pointer 已提交不等于对象已交付
Git 服务可以先收到 pointer,而 LFS 上传因网络或认证失败中断。评审网页看到文件名并不能证明对象可下载。交付门禁应至少包含 git lfs fsck、推送对象检查和空环境克隆;关键发布还应从与开发机不同的只读身份拉取,验证权限和缓存没有掩盖缺失对象。
配额是容量、流量与可用性的组合风险
大对象一次存储并不等于只计费一次。CI、Fork、归档和重复克隆会放大带宽;预算封顶后可能直接阻断上传或下载。架构评审应估算月新增对象、活跃 runner 下载次数和保留期,并设置 50%、75%、90% 分级告警。若正常开发依赖临时扩容才能运行,说明资产边界需要重构。
锁解决并发意图,不解决一致性
锁不能合并二进制内容,也不能保证离线编辑者看到最新版本。拿锁前应先更新分支,提交后及时解锁;强制解锁前必须确认持锁者工作是否已保存。对跨时区团队,长期独占锁会成为排队系统,应考虑拆分资产、按目录分片或迁移到支持协同编辑的专用系统。
本地清理与远端删除不是一回事
git lfs prune 主要回收本地缓存;删除分支、untrack 或重写 Git 历史也不保证托管平台立即删除远端对象。远端保留、计费停止和不可恢复删除取决于平台规则。包含敏感数据时,LFS 不是擦除工具:应立即轮换秘密,联系平台按官方流程处置,并审计所有已下载副本。
已确认文件适合与源码 Commit 一起版本化,不是可重建产物、密钥、备份或海量数据集。Git LFS 来自官方或组织批准渠道,客户端安全公告和版本基线已核对。.gitattributes 规则足够窄,已提交并能用 git check-attr 解释最终命中。
git show HEAD:<path> 是合法 pointer,工作树是完整文件,git lfs fsck 成功。已从空目录和只读身份验证真实对象可下载,而不是只在开发者本地缓存命中。已明确 track 和 untrack 不迁移历史,历史改写有冻结、映射、强推与回滚方案。
CI 明确哪些任务下载 LFS 对象,缓存按仓库隔离且有容量上限。锁服务端能力、强制解锁权限、owner 和滞留锁巡检均已验证。存储、带宽、单文件限制、预算和告警按目标托管平台官方口径确认。
prune 前执行 dry-run,知道它清理的是本地缓存而非远端账单。.lfsconfig、代理日志和错误日志不含真实凭证,端点域名经过安全审查。退出、对象恢复、客户端升级和平台迁移均有 owner 与演练记录。
