一个包含多年提交历史、设计源文件、录屏和测试夹具的 iOS 仓库,首次放到云端 Mac 时,最容易把“机器准备慢”误判成 CPU 或磁盘问题。实际瓶颈往往发生在构建之前:普通 Git 对象被完整下载,Git LFS 在检出阶段自动拉取全部大文件,CI 最后却只编译其中一个 App。解决思路不是盲目缓存整个目录,而是先固定输入,再分层控制提交对象、工作区路径和 LFS 内容。
先测量检出成本
不要只记录 git clone 的耗时。至少拆成网络传输、工作区展开和 LFS 下载三个阶段,并保存以下结果:
git --version
git lfs version
du -sh .git
du -sh .git/lfs 2>/dev/null || true
du -sh .
git count-objects -vH
git lfs ls-files | wc -l
首次运行与复用工作区要分别测量。首次运行反映远端传输成本,复用运行则暴露无效清理、历史对象增长和 LFS 缓存膨胀。还应记录最终提交值,而不是只写分支名:
git rev-parse HEAD
git status --short
优化目标不是让目录看起来更小,而是让同一提交、同一目录集合和同一批 LFS 对象能够被稳定重建。
组合部分克隆与稀疏检出
部分克隆的 blob:none 会推迟普通文件内容的下载,稀疏检出则限制工作区实际展开的目录。两者可以叠加,但不会自动限制 Git LFS,因此要明确跳过首次 smudge。
export GIT_LFS_SKIP_SMUDGE=1
git clone --filter=blob:none --no-checkout "$REPO_URL" app
cd app
git lfs install --local
git sparse-checkout init --cone
git sparse-checkout set App Packages Shared
git fetch --depth=1 origin "$BUILD_REF"
git checkout --detach FETCH_HEAD
git rev-parse HEAD
--cone 模式适合按目录组织的工程,规则简单,也较少出现父目录文件意外消失的问题。App、Packages 和 Shared 必须替换为真实构建依赖;如果工作区、脚本或配置文件位于仓库根目录,需要确认它们仍被包含。
部分克隆需要远端支持对象过滤。若命令出现过滤能力警告,应把它视为普通克隆回退,并重新记录 .git 大小,不能假设优化已经生效。
只拉当前任务需要的 LFS 对象
检出完成后,LFS 文件仍可能只是指针。先列出当前提交引用的对象,再按任务路径拉取:
git lfs ls-files
git lfs pull --include="App/Assets/**,Shared/Fixtures/**"
git lfs fsck
路径过滤要依据构建输入,而不是依据文件扩展名猜测。界面测试可能依赖图片、视频和本地化夹具;只拉源码目录会让编译通过,却让测试在运行阶段失败。
| 检查项 | 合格结果 | 常见问题 |
|---|---|---|
git lfs ls-files |
能看到当前提交的 LFS 跟踪项 | .gitattributes 未随目标提交取出 |
| 文件头检查 | 文件是实际二进制内容 | 仍是包含 oid sha256 的指针 |
git lfs fsck |
本地对象校验通过 | 下载中断或对象目录损坏 |
git status --short |
工作区无意外修改 | 工具在检出后改写资源文件 |
不要把 git lfs pull 放在没有路径约束的公共初始化脚本里,否则每个任务都会下载与自身无关的素材。更稳妥的做法是让每条流水线声明自己的 LFS 输入集合。
处理 CI 中的并发与凭据
每个 CI 任务应使用独立工作区。多个任务共享同一个可写仓库时,稀疏规则、索引锁和 LFS 临时文件可能互相影响。即使只读代码相同,也不要让两个任务同时修改 .git/info/sparse-checkout。
凭据只在拉取阶段注入,并限制到目标仓库所需权限。完成 git fetch 与 git lfs pull 后,应清理临时环境变量和凭据辅助配置。日志中不要输出带令牌的远端地址,可用以下命令确认展示值是否安全:
git remote -v
git config --local --get-regexp 'credential|lfs' || true
子模块需要单独处理。主仓库的部分克隆和稀疏规则不会自动传递给子模块;如果子模块也使用 LFS,应在其目录内安装本地 LFS 配置并执行对应拉取。
避免错误清理
git lfs prune 适合独占仓库,不适合多个任务共享的 LFS 对象目录。共享环境中更安全的策略是为任务创建独立目录,任务结束后整体删除。若必须复用工作区,先确认没有并发任务,再执行清理和完整性检查。
用构建前验收代替事后猜测
真正进入 xcodebuild 前,增加一个轻量验收门。它至少确认提交值正确、工作区干净、关键工程文件存在、LFS 指针已被替换,并输出磁盘占用。示例检查可以写成:
test -f App/App.xcodeproj/project.pbxproj
test -s App/Assets/LaunchVideo.mov
if grep -q "oid sha256:" App/Assets/LaunchVideo.mov; then
exit 1
fi
git diff --exit-code
git lfs fsck
du -sh . .git .git/lfs
最后保留四项数据:提交哈希、稀疏目录清单、LFS include 规则和验收结果。这样当云端 Mac 上的任务失败时,可以先判断输入是否完整,再进入编译器、签名或测试日志,避免把仓库准备错误包装成随机构建故障。
常见问题
部分克隆和稀疏检出可以替代 Git LFS 吗?
不能。部分克隆减少普通 Git 对象的初始传输,稀疏检出限制工作区路径,Git LFS 则管理已被 LFS 跟踪的大文件内容,三者解决的是不同层面的问题。
为什么检出后仍然只有 LFS 指针文件?
通常是启用了跳过 smudge,但没有执行后续的 git lfs pull。先确认当前提交确实引用该文件,再检查 include 路径、LFS 配置和凭据,最后运行 git lfs fsck 验证本地对象。
CI 任务结束时可以直接执行 git lfs prune 吗?
仅在仓库目录不与其他任务共享时执行。共享对象目录中的清理可能删除其他并发任务仍需使用的对象;更稳妥的做法是为每个任务准备独立工作区并整体回收。
Dedicated cloud Mac
在独享物理节点上运行构建任务
按天、周、月或季选择 MiniRents M4 与 MiniRents M4 Pro,并按团队和代码仓库位置选择节点。设备为独享物理机、非虚拟机。