ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Git LFS实战:从安装配置到大文件上传的完整指南

Git LFS实战:从安装配置到大文件上传的完整指南 处理大文件是Git用户迟早要面对的一道坎。我第一次被它坑是团队把一个接近3GB的Unity资源包直接丢进代码仓库之后所有人clone代码的时间从原来的几十秒变成了十几分钟仓库体积一路涨到几个GB最后只能用重置历史的方式收场。后来换用Git LFS配合合理的使用规范才算真正把这个问题管住了。Git LFS的安装和大文件上传实践也就成了我日常工作里最高频的一类操作。下面直接把这个主题从安装、配置、上传到排查的完整过程拆开讲给做游戏、做设计、做算法以及维护多媒体素材库的工程团队一个能照着做的参考。1. 为什么普通Git面对大文件会“爆仓”1.1 二进制对象的存储逻辑和文本完全不同在Git的正常工作流里每次提交都会把文件内容作为对象写入.git目录再通过tree和commit对象串成一条完整的提交链。对于文本文件Git有一套压缩和增量存储机制可以把多次修改的相似版本合并成较少的存储空间diff和log也都能正常工作。但二进制文件完全是另一回事它本身已经经过压缩就算你只改了100MB文件里的一小段Git也几乎找不到可复用的片段只能把整份新文件作为完整对象保存下来。一个100MB的压缩包如果被修改了十次库里的历史就相当于存了十个接近100MB的快照仓库体积直接膨胀到1GB以上。再加上Git对二进制文件不会做有意义的差异比较你在提交界面里看到的只会是“二进制文件差异无法显示”。换句话说普通Git只适合管理那些能逐行比较、能增量合并的文本型内容。一旦仓库里混入设计源文件、模型权重、安装包和视频素材无论服务器端怎么优化每次改动都会把整份文件重新塞进对象库仓库体积增长速度会远远超出所有人的预期。我第一次遇到这种事时还以为是某个人误提交了编译产物后来才发现是大家每天都在更新同一个美术资源包。1.2 LFS的指针文件机制把数据和仓库分开了Git LFS的做法用一个原理就能说清楚大文件本身不进Git对象库只有一小段描述性的“指针文件”进Git库真实文件内容单独存到LFS服务器上。指针文件非常小一般就长这个样子version https://git-lfs.github.com/spec/v1 oid sha256:d2c6a0b8c0e5a4f7b5d60ea1f4c3f9b9e8a5f045bb7c6c5c6d7d8e9f0a1b2c3d size 157286400在普通提交里Git看到这样一个一百多字节的文本文件自然把它当文本对象来处理commit、diff、branch都很轻量。只有当真正需要这个版本时LFS客户端才会根据指针里的oid去LFS服务器拿取原始文件放到工作区里让文件恢复成完整内容。这个设计的优势很明显仓库体积只随文件版本数量和指针大小增长而不是随文件本身体积增长。假设团队天天改一个500MB的设计源文件历史里每次提交只增加几百字节指针而不是新增一个500MB的完整对象。这也是为什么LFS能从根本上解决“推不上去、拉不下来、仓库越来越大”这三个常规Git问题的原因。理解了指针文件机制后面排查各种“文件显示成指针”的问题也会简单很多。1.3 到底什么场景才值得用Git LFS根据我的经验三类场景最适合上LFS游戏制作和数字媒体比如Unity、UE工程里的美术资源、音频采样、FBX动画单个文件动辄几十上百MB算法训练比如模型权重、训练数据集、Embedding文件设计出版工作流比如PSD、TIFF、InDesign、CAD施工图等源文件。对于这类素材既希望留痕又希望多人协作过去只能靠压缩包、网盘和邮件来回传很容易版本错乱改用LFS以后至少能回到Git的统一工作流里。不合现场景也不少比如频繁生成的构建产物、临时安装包、日志dump。如果不关心内容历史只求给团队一个下载入口放对象存储或内部文件服务器比塞进LFS更合理。还有一个容易踩的坑是随手把整个assets目录都track进来导致大量无关的二进制文件也一并通过LFS传输最后体验反而比普通Git还要差。所以“什么文件该上LFS”不完全是技术问题更是协作规范问题应该在一开始就和团队成员把规则讲清楚。2. Git LFS安装不同平台的安装方案与验证2.1 安装前先检查Git版本和下载渠道Git LFS是Git的独立扩展安装包托管在git-lfs的官方release页面支持Windows、macOS、Linux的多种CPU架构。安装之前先确认Git本身能正常工作最好使用Git 2.x以上版本因为LFS依赖Git的filter扩展机制和对象体系老版本Git虽然也能勉强配合但容易在fetch、checkout时出现兼容性异常。检查命令很简单git --version git lfs version如果第二条命令返回的不是版本号说明LFS还没装。下载渠道方面Windows一般习惯安装Git for WindowsmacOS常用HomebrewLinux发行版也有对应包管理器源。不过要注意一个现实情况和官方release相比部分Linux包管理器的源里的版本可能滞后遇到旧版本时建议从release页面直接下载官方包避免因为版本太低而缺功能。2.2 Windows下安装的两种方式和PATH问题Windows下最简单的方式是安装Git for Windows新版已经内置Git LFS。如果你之前装过直接执行git lfs version确认没问题就行如果提示找不到命令说明安装时没有勾选LFS组件或者PATH没配好。这时候不用重装整个Git直接去官方release页拿exe独立安装装完后重新打开一个终端窗口再执行版本检查。无论用哪种方式安装完成后都要回到命令行执行一次git lfs install。这一步的作用是把LFS所需的filter写入全局配置否则以后commit时Git不知道要调用LFS处理大文件会出现“文件被直接提交成普通对象”的尴尬情况。我在很多同事的环境里看到过类似问题明明LFS已经装了但提交大文件时仓库还是疯狂膨胀最后一个个查过去全是没跑git lfs install。细看的话还要用where git-lfs确认命令路径是否在PATH里如果发现不在可以把Git安装目录下的cmd目录手动加入PATH。很多初学者在这个环节卡住并不是LFS没安装而是当前终端没有刷新环境变量。2.3 macOS和Linux用包管理器安装macOS用户一般直接在终端执行brew install git-lfsUbuntu和Debian可以用sudo apt install git-lfsCentOS/RHEL一类的发行版用yum或dnf也能装但源里的git-lfs版本落后是常有的事。如果包管理器版本明显偏低更好的办法是从官方release页下载linux的tar包解压把git-lfs这个二进制文件放到/usr/local/bin并赋予可执行权限。这个方式在各类Linux服务器上都很通用尤其是那种联网环境受限的内网机器很多人习惯预先把官方包下载下来再分批传到目标机器上安装。Linux下安装完毕后仍要记得执行git lfs install而不是装完二进制就以为收工了这一步才是filter生效的关键。2.4 安装完成后的初始化验证清单不管哪个平台装完以后建议做一组快速检查避免“看起来装了但push时没用上LFS逻辑”的隐性问题git lfs version git lfs install git config --global --list | grep lfs如果配置里能看到filter.lfs.process和filter.lfs.required相关项说明LFS处理流程已经挂到Git上。这时候可以用一个小测试仓库验证创建一个目录执行git lfs track *.bin把一个几十MB的二进制文件提交推送然后重新clone一遍确认clone出来的文件内容完整而不是一行指针。这个验证流程看起来基础但能提前拦住绝大多数安装问题。安装时还要留个心LFS对象默认缓存在.git/lfs/objects目录checkout历史版本时会把对应对象重新下载到本地。如果一个仓库的历史里包含几十GB的大文件工作机磁盘要预留足够的空间。否则你可能会遇到“仓库clone成功了但切个分支磁盘就满了”的奇怪现象。这个细节我在实际维护中碰到很多次提前告诉团队成员往往能省不少救火时间。3. 仓库配置与迁移策略别上来就直接传3.1 用git lfs track声明需要管理的文件类型很多人在执行完git lfs install之后就直接把大文件add进仓库结果别人clone下来发现文件没有按预期变成完整内容。原因很简单LFS需要知道“哪些文件要走LFS”否则即使客户端已经安装Git默认还是按普通对象存储大文件。正确流程是先在仓库根目录声明要跟踪的类型git lfs track *.zip git lfs track *.psd git lfs track model/*.h5 git lfs track assets/**/*.bin执行之后Git会自动生成或更新.gitattributes文件。这个文件必须提交到仓库里因为LFS的track规则是跟随仓库走的只有把.gitattributes提交到远程团队所有成员clone之后才会自动套用相同规则。只在自己本机track远远不够一旦有同事在规则缺失的情况下提交了大文件这个文件就会以普通Git对象形式躺进历史仓库又会开始膨胀。3.2 规则范围宁可精确不要贪大关于track范围我经常跟团队强调一句话如果你把几乎整个项目目录都track了等于把LFS变成了默认存储方案这不是好事。合理的做法是只跟踪“会在同一路径反复被修改的二进制文件”比如美术源文件、数据集文件、模型权重。至于安装包、构建产物这类体积大但内容基本不变的文件可以按团队下载需求决定是否纳入。因为每track一种类型clone、checkout时都有可能触发一次LFS拉取。规则越宽不必要网络流量越多对只写代码、不关心大资源的同事来说这种额外等待非常影响体验。还要留意.gitattributes的合并冲突。多人同时修改track规则时可能会出现两套规则的合并结果导致同一文件既被普通Git处理又被LFS接管。我建议把.gitattributes当作公共配置文件看待修改前先pull最新代码必要时在团队里知会一声避免大家各自加规则又互相覆盖。这个文件很小但属于“一处错误影响全仓库”的高风险文件值得认真对待。3.3 已有大文件和历史怎么办migrate的正确姿势如果项目已经运行了一段时间历史里已经塞进了不少大文件单纯靠“后续文件走LFS”并不能让已有对象库瘦身。这时候要用迁移命令git lfs migrate info git lfs migrate import --include*.zip --everything第一条命令会统计各种扩展名在历史中的累计体积方便你决定纳入哪些类型。第二条命令会重写整个历史把匹配文件的普通Git对象替换成LFS指针本质上会产生一批全新的commit对象所有commit的SHA都会改变。执行之后需要通过git push --force更新远程同时要求所有团队成员在拉取最新分支前先删除旧本地仓库或备份旧分支否则旧引用会把已经被替换的大的文件对象重新推回去迁移白做。我个人的建议是迁移流程一定要在低峰期进行并且提前和所有协作者确认清楚。一个多人合作的主力仓库从migrate info到最终强制推送整个过程最好控制在半天以内避免新旧历史并存时间太久造成混乱。如果仓库非常大可以单独克隆一份作为“迁移专用仓库”在专用仓库里跑migrate确认结果没问题后再对外同步这样至少能把操作失误的风险隔离掉。3.4 分支和远端不匹配时的处理技巧迁移之后还有一个容易忽略的点如果仓库里有很多远端分支或tag直接migrate import --everything会重写所有commit和tag但远端未必能一次全部覆盖成功。建议先用git lfs migrate info做一次体积体检确认哪些类型占用最多再决定迁移范围。如果只想把当前主线迁移过去可以暂时不执行--everything让影响面小一点。我在团队里常用的顺序是先在这个仓库里清理不相关的远端缓存分支只保留要处理的那条主分支再跑migrate成功后统一通知团队重置仓库。这样操作下来强制推送的失败概率会小很多。另外迁移后最好检查一下remote仓库的LFS配额因为Git LFS的存储空间通常单独计算如果历史里有几千个几十MB的旧文件一次性推上去可能直接把配额打满。这个坑我在切换托管平台时遇到过当时迁移还没跑完远端就已经拒绝接收新对象了。4. 大文件上传全流程实操4.1 从新仓库开始的完整操作示例如果你是从零开始建仓库整个过程其实非常顺滑git init big-demo cd big-demo git lfs install --local git lfs track *.bin cat .gitattributes git add . git commit -m init project with lfs track git remote add origin gitgithub.com:your-team/big-demo.git git push -u origin main这里有两个容易出问题的细节。一是--local参数它只在当前仓库写入LFS filter配置适合一台机器上同时维护多个仓库、且各仓库的LFS规则不一致的场景。如果你希望所有仓库都默认启用LFS filter就不用加--local直接git lfs install写全局配置就好。二是.gitattributes和被跟踪的大文件一定要在同一个提交里。如果你先提交了*.bin文件再补上track规则之前那条commit里的bin文件仍然会以普通Git对象形式存在后续就算规则生效历史里那个旧对象也不会自动变成指针。对这个行为最好的应对是先提交track规则确认git lfs track的输出符合预期然后再添加真正的大文件并提交。4.2 推送过程中的进度观察和断点恢复当执行git push并且本地存在尚未上传的LFS对象时你会看到类似这样的输出Uploading LFS objects: 100% (3/3), 1.2 GB / 1.2 GB, 4.2 MB/s这说明Git LFS先把真实内容传到了远端LFS存储传完之后再正常推送普通Git指针和commit。如果推送中途断网或者被服务端限流导致中断重新执行git push即可LFS会检查本地缓存目录里哪些对象已经完成上传只补传剩余部分。这个机制对几十GB的资源集尤其重要省去了每次失败都要全部重传的痛苦。不过要注意的是这个“断点续传”依赖本地缓存.git/lfs/objects里的对象完整性。如果你手动删过缓存或者切换了机器重新推送时LFS会完整上传一次。对超大批文件上传期间尽量保持网络稳定不要说断就断。曾经有同事在一个弱网环境里反复回报文件“卡住”后来发现是公司Wi-Fi对上行流量做了限制换成有线网络后问题立刻消失。遇到慢速上传时先排查基础网络比改配置更靠谱。4.3 影响上传速度的config调整抛开网络问题LFS也提供了一些可调的参数常见的几个如下git config lfs.concurrenttransfers 8 git config lfs.batchsize 100m git config lfs.transfer.maxretries 5lfs.concurrenttransfers控制并发的HTTP传输数量。默认值在不同版本里不太一样你可以用git lfs env查看当前生效值。如果本地上行带宽就10Mbps并发调到16也没多大用处反而会让一批请求互相抢带宽单个文件的上传速度更慢。我一般建议办公网络环境用4内网服务器之间可以用8或更高。lfs.batchsize表示单次batch交互的字节数调大后请求次数会变少但内存占用会上升不太建议超过200MB。lfs.transfer.maxretries是传输失败的最大重试次数适合在Wi-Fi有偶发丢包的环境下多给几次机会避免小抖动就直接失败。这些配置只影响当前用户的LFS行为不会跟随仓库同步。团队要想统一参数可以把建议值写进README或者脚本里方便新同事一次配置到位。对大多数场景默认值其实已经够用真正改善上传体验的往往是先确保网络质量和服务端容量。4.4 大文件的锁定与合并冲突规避二进制大文件还有一个痛点没法做行之有效的文本合并。代码冲突可以手动解决二进制文件冲突虽然可以选择保留哪个版本但很容易丢工作成果操作起来也让人提心吊胆。Git LFS为此提供了文件锁机制在支持锁的服务端常见如GitHub、GitLab上可以这样使用git lfs lock Assets/Character/hero.fbx git lfs locks git lfs unlock Assets/Character/hero.fbx文件锁的意义是同一时间只有拿到锁的人可以上传该文件的新版本其他人push时会被服务端拒绝。这样相当于给美术同学、算法同学在修改某个资产前提供了一个“先占坑”的机制能明显减少因为多人同时改动同一个模型、同一个权重文件而产生的合并灾难。不过文件锁更像一个协作约定如果成员之间不沟通就直接解锁这套机制也起不到作用。建议在团队规范里明确约定修改大文件前先看git lfs locks确实没有锁再申请锁用完后及时释放。5. 常见问题与排查技巧实录5.1 认证失败和批量请求报错LFS上传本质上是独立的HTTP接口请求和Git本身使用的认证体系通常是打通的。遇到类似batch response: HTTP 401这样的报错第一个怀疑对象是凭据问题。尤其是个人电脑上同时存了多个托管平台的账号token很容易让LFS请求带错身份。这种情况建议改为使用专用的访问令牌按托管平台的要求添加到凭据管理器里不要用明文密码。如果换成SSH方式后依然报401还可以检查远程地址是否存在输入错误或者密钥是否被当前用户加载。另一个容易导致批量请求报错的情况是服务端LFS容量已经耗尽。403、429一类的报错除了访问权限问题很大概率是配额满了。登录托管平台后台找到仓库的LFS用量和配额页面确认还有没有剩余空间。Git LFS的流量和存储额度在很多托管平台上是独立计费团队里某个人上传一个超大文件就可能把月度配额打满。出现这种情况后优先清理不用的历史对象或者考虑升级套餐再重新发起上传。5.2 本地文件明明很大checkout后却变成一行指针这种情况很典型clone完仓库打开一个文件发现里面不是真实内容而是一行版本号、oid和size信息文件大小也只剩一百多字节。原因通常是当前环境的LFS filter没有生效可能是在没执行过git lfs install时clone或checkout也可能是在CI流水线里没有安装LFS插件。修复方式很简单把LFS环境配置补齐后重新拉取真实对象git lfs install git lfs pull git lfs checkout如果是在自动化构建的Docker容器里需要在镜像安装阶段手动加上git-lfs并在构建步骤里调用git lfs pull。我见过不止一次构建产物变成“一堆指针文件”的问题排查到最后都是容器环境没有初始化LFS。这类问题不报错只是产物内容不对非常隐蔽。建议在CI脚本里加一个前置检查步骤直接执行git lfs env或git lfs pull避免把指针文件当成真实文件打包上线。5.3 本地LFS缓存膨胀与仓库瘦身LFS缓存目录是.git/lfs/objects当你在多个分支之间切换、反复checkout历史版本时系统会把相应的对象下载到本地。时间一长这个目录可能变得比工作区本身还大。执行git lfs prune可以删除当前分支和最近引用不再需要的LFS对象。如果磁盘仍然紧张可以先配合git fetch --prune清掉已删除远端的旧分支引用再执行prune效果更好。需要提醒的是git lfs prune只在本地有效它不会清理服务器端的LFS对象。服务端上的历史对象清理一般要回托管平台的后台操作否则即使你把Git历史重写了服务器的配额依然被旧对象占用。这就是有些团队“迁移之后仓库还是很大”的原因之一。5.4 几个帮我省下大量时间的LFS使用习惯第一个习惯是“不要一上来就做历史迁移”。如果仓库已经很大先用git lfs migrate info做个体检按扩展名统计大文件体积再决定哪些类型优先纳入。先迁移当前活跃的主分支把其他分支的迁移放后面风险和成本都会低很多。第二个习惯是“尽量让LFS文件和代码改动分开提交”。想像一下你推几个100MB的权重文件时又在一坨代码改动中间塞了一个错误版本。回滚的时候得把大文件重新拉一遍缩到最小影响范围。分开提交看似多花一点时间review时也清晰很多。第三个习惯是“明确LFS不是备份工具”。LFS文件独立存储在服务器上如果账号出问题、配额超限或者服务商改规则源文件可能会丢。我一般会另外把最终源文件备份到内部文件服务器或对象存储LFS只承担版本流转和协作分发。依赖它来代替备份风险太大。第四个习惯是“新增track规则前先确认团队环境”。如果还有同事用很旧的Git客户端LFS兼容性可能有问题轻则clone时拉不到对象重则把指针当普通文件提交上去造成二次膨胀。团队多人协作时最好在README里写清楚最低Git版本和LFS版本要求。回到开头那个让我“重建历史”的3GB资源包。如果那时候我能提前明白LFS的track规则、migrate影响和团队约定就不会把所有人的clone都拖进十几分钟的等待。Git LFS本身不复杂复杂的是工程习惯。你在安装或上传中遇到问题先检查环境变量和filter配置再看服务端配额最后回到团队规范多数问题都属于这三条线。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进