· 技术文档
Git 和图片存储
核心问题:Git 与大文件的矛盾
如果你熟悉 Git,应该知道它最初是为管理文本文件而设计的,并不适合管理大型二进制文件(如图片、视频等)。这一局限性源于 Git 的基本工作机制:
-
完整历史记录:Git 保存完整的提交历史,所有曾经加入并提交的文件都会存储在
.git
目录中,即使这些文件后来被删除了。克隆仓库时,默认会下载整个历史。 -
快照而非差异:Git 不会仅存储文件的差异部分,而是在每次提交时存储文件的完整副本。这对文本文件来说很高效(因为差异计算简单且体积小),但对二进制文件却会导致仓库体积的快速增长。
随之而来的问题
随着项目历史的增长,频繁修改和提交的图片文件会导致:
- 仓库体积膨胀:即使只修改了图片的一小部分,Git 也会存储整个文件的新副本
- 克隆时间延长:新成员加入团队时,需要下载所有历史版本的图片
- 存储限制:许多 Git 托管平台对仓库大小有严格限制(通常为 5GB 左右)
传统解决方案
针对这些问题,开发者长期以来采用了多种策略:
1. 使用图床
优点:
- 仓库体积保持小巧
- 图片可独立管理和优化
缺点:
- 公共图床存在隐私和持久性风险
- 自建图床需要额外的基础设施
- 工作流程变得复杂(先上传图片,再引用链接)
2. Git LFS(Large File Storage)
Git LFS 是一种 Git 扩展,专为处理大型文件设计:
# 安装 Git LFS
git lfs install
# 指定需要 LFS 管理的文件类型
git lfs track "*.png" "*.jpg"
# 克隆时跳过 LFS 文件(按需下载)
GIT_LFS_SKIP_SMUDGE=1 git clone <repository-url>
工作原理:将大文件存储在单独的服务器上,仓库中只保留文件指针。
优点:
- 减小 Git 仓库体积
- 按需下载大文件
- 与 Git 工作流无缝集成
缺点:
- 免费使用受限:GitHub 等平台对 LFS 有严格的免费下载量限制(如 GitHub 每月仅 1GB),超出需付费
- 增加复杂性:即使是简单的 pull/push 操作也涉及额外的 LFS 对象传输
- 协作障碍:当团队成员不熟悉 LFS 工作流时可能出现混淆
- 历史文件问题:对已提交到仓库的大文件转为 LFS 管理比较麻烦
3. Git Submodule
将项目拆分为多个子仓库,图片文件单独存放:
# 添加图片子模块
git submodule add <repository-url> images
# 克隆主项目及其子模块
git clone --recurse-submodules <main-repository-url>
优点:
- 可以按需克隆子模块
- 图片集合可以独立管理和版本控制
缺点:
- 增加项目复杂性
- 子模块操作对新手不友好
- 维护多个仓库的额外工作
4. 清理历史记录
使用 git-filter-repo 等工具可以彻底清除某些大文件的历史记录:
# 安装 git-filter-repo
pip install git-filter-repo
# 移除所有大于 10MB 的文件的历史记录
git filter-repo --strip-blobs-bigger-than 10M
注意:这会重写历史,所有团队成员需要重新克隆仓库。
现代 Git 解决方案
近年来,Git 引入了两个强大功能来直接解决大文件问题:
理解部分克隆和稀疏检出的区别
在深入具体用法前,我们需要明确这两个概念的本质区别:
部分克隆 (Partial Clone):
- 作用层面:对象存储层(.git 目录)
- 目的:控制从服务器下载哪些 Git 对象(commits, trees, blobs)
- 结果:
.git
目录变小,但工作目录仍包含所有文件
稀疏检出 (Sparse Checkout):
- 作用层面:工作目录层
- 目的:控制工作目录中显示哪些文件和文件夹
- 结果:工作目录变小,但
.git
目录仍包含完整历史
形象比喻
想象你要搬家到新公寓:
- 部分克隆:你决定只从老家运输部分物品(比如不运家具,只运衣服和书籍)
- 稀疏检出:物品都运到了新公寓的储藏室,但你只把部分物品摆放在客厅里
为什么要配合使用?
单独使用任一功能都有局限性:
仅使用部分克隆:
git clone --filter=blob:limit=1m <repository-url>
- ✅
.git
目录较小(大文件未下载) - ❌ 工作目录仍包含所有文件夹结构
- ❌ 当你访问大文件时,Git 会自动从服务器下载
仅使用稀疏检出:
git clone <repository-url>
git sparse-checkout set src/components
- ❌
.git
目录仍然很大(包含完整历史) - ✅ 工作目录只显示你关心的文件夹
- ❌ 克隆时间没有改善
配合使用的威力:
# 步骤1:部分克隆 - 不下载任何文件内容
git clone --filter=blob:none --no-checkout <repository-url>
cd <repository-directory>
# 步骤2:稀疏检出 - 只检出需要的目录
git sparse-checkout init --cone
git sparse-checkout set src/components src/utils
git checkout
这样做的好处:
- ✅ 克隆速度快(没下载文件内容)
- ✅
.git
目录小(只有元数据) - ✅ 工作目录干净(只有你需要的文件)
- ✅ 按需获取(当你真正需要其他文件时才下载)
具体使用方法
部分克隆的其他选项
除了上面展示的 --filter=blob:none
(不下载任何文件内容),部分克隆还有其他过滤选项:
# 仅排除大文件(1MB以上)
git clone --filter=blob:limit=1m <repository-url>
# 排除指定路径的文件
git clone --filter=tree:0 <repository-url>
稀疏检出的灵活配置
稀疏检出支持多种模式和配置:
# 使用文件模式指定更复杂的模式
git sparse-checkout set --no-cone "src/*" "docs/*.md"
# 动态添加或移除目录
git sparse-checkout add "tests/"
git sparse-checkout reapply
这种组合使用的方式类似于下载种子文件时选择性下载某些内容,既节省了网络流量,又减少了本地存储需求。
支持情况
目前各平台对这些新功能的支持情况:
平台 | 部分克隆 | 稀疏检出 |
---|---|---|
GitHub | ✓ | ✓ |
GitLab | ✓ | ✓ |
Bitbucket | ✓ | ✓ |
腾讯 Coding | ✗ | ✗ |
阿里云云效 | ✓ | ✗ |
实用建议
针对这个项目,我们建议:
-
早期阶段:直接在 Git 中管理图片文件是可行的,特别是当图片数量有限且不经常修改时。
-
中期优化:当观察到克隆时间变长,可考虑使用部分克隆和稀疏检出功能。
-
图片最佳实践:
- 在提交前优化图片(压缩、调整尺寸)
- 避免频繁修改同一图片
- 考虑使用矢量图形(如 SVG)替代位图
-
监控仓库大小:定期检查
.git
目录大小,发现异常增长时及时处理。
为什么这些功能来得这么晚?
你可能会问,为什么像”按需下载”这样的基本功能,Git 直到最近几年才支持?这是因为 Git 的设计哲学与普通下载工具不同:
- Git 需要确保所有操作(包括分支、合并、历史查看)在部分数据情况下仍能正确工作
- 需要解决如何跟踪和操作那些尚未下载的文件
- 所有 Git 命令和客户端都需要适配这种新模式
- 服务端也需要支持新的协议扩展
扩展阅读
若想深入了解这些概念,推荐以下资源:
GitLab 官方博客
- How Git Partial Clone lets you fetch only the large file you need
- Speed up your monorepo workflow in Git