Git 和项目依赖关系

请考虑以下问题:

您如何使用 git 处理项目依赖关系?

我们的项目由多个相互依赖的存储库组成。目前,我们使用 svn:externals 来管理这些文件。用 git 处理这些问题的最好方法是什么?

如何使用 git 将一个非常大的存储库拆分为较小的组件?

这些是我们最常问的一些问题。

对于许多采用 git 的软件团队来说,这个话题似乎是一个很大的痛点,所以在本文中,我将尝试阐明这个问题。

显然,项目依赖关系和构建基础架构是两个交织在一起的领域,即使在 Atlassian 内部,也引发了关于“构建未来”的讨论。

使用单独的存储库而不是单个存储库可能会使某些事情变得更加困难。但这是软件项目演变中相对自然(有时是强制性的)步骤,这至少有两个主要原因:构建时间的增加以及项目之间的共同依赖关系。

简要介绍:指导方针和次优解决方案

那么回到这个问题:如何使用 git来跟踪和管理项目依赖关系

如果可能的话,您不能!

玩笑到此为止,我先粗略地回答一下,然后再深入介绍。请注意,没有灵丹妙药(无论是 git 还是其他方式)可以轻松解决与项目依赖关系相关的所有问题。

一旦项目增长到一定规模,将其拆分成逻辑组件是有意义的,但不要等到单个存储库中有一亿多行代码后再这样做。因此,以下只是指导方针,以便您可以设计自己的方法。

第一选择:使用适当的构建/依赖关系工具代替 git

我目前推荐使用依赖关系管理工具来处理大型项目的成长难题和构建时间。

将模块分隔在各个存储库中,并使用专为工作构建的工具管理它们的相互依赖关系。(几乎)每个技术堆栈都有一个。一些示例:

  • 如果您使用 Java 的话 Maven(或 Gradle

  • 用于节点应用的 Npm

  • BowerComponent.io 等,如果您使用 Javascript(已更新!)的话

  • 如果您使用 Python 的话,则为 Pip 和 requirements.txt

  • RubyGems,如果您使用 Ruby,则是 Bundler

  • 适用于 .NET 的 NuGet

  • 适用于 C++ 的 Ivy(或一些自定义 CMake 操作)(已更新!)

  • 适用于 Cocoa 的 CocoaPods iOS 应用

  • PHP 的 ComposerPhing(已添加!)

  • Go 中,构建/依赖关系基础架构在某种程度上内置在语言中(不过人们一直在开发更完整的解决方案,请参阅 godep)。对于我们的 Git 服务器 (Bitbucket),我们同时使用 Maven 和 Bower。构建时,所选工具将拉取正确版本的依赖关系,以便可以构建您的主项目。这些工具中有些存在局限性,所做的假设也并非最佳,但都是经过验证的可行方法。

拆分项目的痛苦

简单地说,在项目开始时,所有内容都打包在一个版本中。但是随着项目的发展,这可能会导致您的构建太慢——这时您需要“缓存”,而这正是依赖关系管理的用武之地。顺便说一句,这意味着子模块(见下文)非常适合动态语言。基本上我认为大多数人需要在某个时候担心构建时间,这就是为什么您应该使用依赖关系管理工具。

将组件拆分成不同的存储库会带来一些严重的麻烦。顺序不分先后:

  • 变更组件需要发布版本

  • 需要时间,可能会因为很多愚蠢的原因而失败

  • 对微小的变更感觉很愚蠢

  • 它需要为每个组件手动设置新的版本

  • 阻碍存储库的可发现性

  • 在单个存储库中并非所有源代码都可用时进行重构

  • 在某些设置(比如我们的设置)中,更新 API 需要产品的里程碑版本,然后是插件,然后是产品。我们可能漏了几样,但您应该懂的。距离这个问题的完美解决方案还很远。

第二选择:使用 git 子模块

如果您不能或不想使用依赖关系工具,git 有处理 submodules 的功能。子模块可能很方便,尤其是对于动态语言来说。但是,它们不一定能使您免于缓慢的构建时间。我已经写了一些这方面的指导方针和提示,还探讨了替代方案。网上也有人反对使用子模块

svn:external 和 git 之间的一对一匹配

但是!如果您正在寻求 svn:externalsgit 之间的一比一匹配,您可以使用 submodules 来确保 submodules 只跟踪发布分支,而不是随机提交。

第三种选择:使用其他构建和跨堆栈依赖关系工具

您不会一直想要一个完全统一、可以用单个工具构建和组装的项目。例如,一些移动项目需要兼顾 Java 和 C++ 依赖关系,或者使用专有工具来生成资产。对于那些更复杂的情况,您可以在上面多加一层来增强 git。这个领域一个很好的例子是安卓的代码存储库

其他值得探索的构建工具:

结论和进一步阅读

Charles O'Farrell 关于进一步继续阅读内部版本基础架构(及 Maven)主题的几点建议:

最后,我想引用上面后一篇文章中的精彩引文。尽管它与 Maven 有关,但它同样可以应用于其他构建和依赖关系工具:

缓存除了加快速度之外没有其他用处。您可以完全移除缓存,相关系统能照常工作,只是速度会变慢。缓存也没有副作用。无论您过去对缓存执行了哪些操作,将来对缓存的给定查询都会给同一个查询返回相同的值。

Maven 的体验与我描述的截然不同!Maven 存储库虽被当作缓存来使用,却并不具备缓存的属性。当您从 Maven 存储库请求某个资源时,您过去执行的操作会产生重要影响。它会返回您最近放入的内容。如果您在存入某个资源之前就去请求它,该操作还可能会执行失败。

为您推荐

Bitbucket 博客

DevOps 学习路径

了解有关 Git 的更多信息

在此中心查找更多 Git 指南和资源。