版本控制是为文本设计的。图片打破了它的几个前提假设,理解这一点就知道该改用什么做法。

Git 为什么应付不好图片

  • 没有有意义的差异比对。 Git 无法在 JPEG 里存"改了什么";每个版本都是一个全新的二进制对象。
  • 历史是永久的。 每张图的每个版本都永远留在仓库里,即使删除之后也是。一张 5 MB 的图被替换二十次,就是 100 MB 的历史,存在于每一份克隆里,永远如此。
  • 冲突无法合并。 两个人同时改同一个 PSD 会产生任何工具都合并不了的冲突;总有一个人的工作被丢弃。
  • 克隆速度受损。 二进制沉重的仓库克隆和检出都会变慢,这会影响所有人,包括从不碰图片的人。

什么情况下 Git 没问题

  • 小的、稳定的、接近文本的素材:SVG 图标(它是 XML,确实能有意义地 diff)、小尺寸 PNG、网站图标。
  • 很少变动的素材。
  • 构建确实需要的一切,因为它必须与引用它的代码一起版本化。

仓库里放几个优化过的 SVG 和小 PNG,完全正常且合理。

什么时候用 Git LFS

Git 大文件存储用小指针替换仓库里的大文件,实际内容存在别处。

适用于:必须与代码同处一地的大型位图素材、视频、需要与项目一起版本化的设计文件。

要注意:LFS 需要在每次克隆时配置,托管服务有带宽和存储配额,而且把已有仓库迁移到 LFS 会重写历史。要在仓库变大之前就做决定,而不是之后。

应该改用什么做法

对多数团队,更好的答案是把大型二进制文件挡在代码仓库之外:

  • 设计源文件(PSD、Sketch、Figma、AI)属于自带版本历史的设计工具或素材管理系统,而不是 Git。设计工具本身的版本管理已经很好,有可视化历史和评论。
  • 摄影和原始媒体属于数字资产管理系统或结构化的共享盘。
  • 只有导出后、优化过、构建所需的素材才进仓库。
  • 大型媒体可以放在 CDN 或对象存储上,用 URL 引用,而那个引用本身在代码里被版本化。

可以替代 diff 的约定

既然无法对图片做差异比对,就让它的身份显式化:

  • 构建产物用内容哈希文件名:logo.a1b2c3.svg。文件一变名字就变,这既让缓存安全,也让版本混淆成为不可能。
  • 源素材用语义化命名:logo-primary-dark.svg,而不是 logo-final-2.svg。
  • 为素材建一份变更日志——一个简短文本文件,记录改了什么、何时改的。图片不能 diff,文本可以。
  • 绝不静默覆盖。 如果改动很重要,它就值得一个新名字或一条记录。

实用规则

  1. 在仓库变大之前定好策略。 事后从历史里清理二进制文件需要重写历史,会波及所有人。
  2. 在 CI 里加一道体积检查,遇到异常大的文件就失败——这是最有效的单一防线。
  3. 把设计源文件和构建不需要的导出件加进 gitignore。
  4. 提交前先优化,因为提交进去的版本是永久的。
  5. 写清楚每类素材住在哪里,让谁都不必猜某张图该进 Git、DAM 还是 CDN。

总体目标是:仓库装构建需要的东西,资产管理系统装设计流程需要的东西,各自做自己擅长的事。