· 技术文档

Git 和图片存储

核心问题:Git 与大文件的矛盾

如果你熟悉 Git,应该知道它最初是为管理文本文件而设计的,并不适合管理大型二进制文件(如图片、视频等)。这一局限性源于 Git 的基本工作机制:

  • 完整历史记录:Git 保存完整的提交历史,所有曾经加入并提交的文件都会存储在 .git 目录中,即使这些文件后来被删除了。克隆仓库时,默认会下载整个历史。

  • 快照而非差异:Git 不会仅存储文件的差异部分,而是在每次提交时存储文件的完整副本。这对文本文件来说很高效(因为差异计算简单且体积小),但对二进制文件却会导致仓库体积的快速增长。

随之而来的问题

随着项目历史的增长,频繁修改和提交的图片文件会导致:

  1. 仓库体积膨胀:即使只修改了图片的一小部分,Git 也会存储整个文件的新副本
  2. 克隆时间延长:新成员加入团队时,需要下载所有历史版本的图片
  3. 存储限制:许多 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
阿里云云效

实用建议

针对这个项目,我们建议:

  1. 早期阶段:直接在 Git 中管理图片文件是可行的,特别是当图片数量有限且不经常修改时。

  2. 中期优化:当观察到克隆时间变长,可考虑使用部分克隆和稀疏检出功能。

  3. 图片最佳实践

    • 在提交前优化图片(压缩、调整尺寸)
    • 避免频繁修改同一图片
    • 考虑使用矢量图形(如 SVG)替代位图
  4. 监控仓库大小:定期检查 .git 目录大小,发现异常增长时及时处理。

为什么这些功能来得这么晚?

你可能会问,为什么像”按需下载”这样的基本功能,Git 直到最近几年才支持?这是因为 Git 的设计哲学与普通下载工具不同:

  • Git 需要确保所有操作(包括分支、合并、历史查看)在部分数据情况下仍能正确工作
  • 需要解决如何跟踪和操作那些尚未下载的文件
  • 所有 Git 命令和客户端都需要适配这种新模式
  • 服务端也需要支持新的协议扩展

扩展阅读

若想深入了解这些概念,推荐以下资源:

GitLab 官方博客

GitHub 官方博客

    返回