52

假设我有四个项目,名为CoreABSuper。依赖树是这样的:

Super ---> Core
       |-> A -> Core
       |-> B -> Core

我希望每个项目都是独立的,也就是说,我希望能够自行检出和编译每个项目(当然每个项目都有其依赖项)。

我曾考虑将每个项目映射到存储库,然后使用子模块引用依赖项,但我发现该方法存在以下问题:

  1. 在查看Super及其所有依赖项时,我最终会得到三个Core副本。
  2. 由于子模块是完全独立的,这三个副本中的每一个都可能指向不同的Core版本,这将是一团糟。

所以......我错过了什么吗?我是否误解了 git 子模块或滥用了它们?这个问题还有其他解决方案吗(除了求助于二进制依赖)?

4

6 回答 6

18

您刚刚发现 Git 子模块缺少覆盖的依赖项:

如果 Super 依赖于 Core,那么它对 Core 的依赖应该“覆盖”A 和 B 对 Core 的依赖。

模仿的唯一方法是按照您的方式创建您的超级项目,
删除 A 和 B 的子模块核心。
(意味着超级现在依赖于 A' 和 B',A' 是没有核心的 A, B'是没有核心的B)

于 2009-09-14T03:55:32.317 回答
9

git 存储库应该是相当原子的,因为每个存储库都是用于特定目的的独立实体。超级项目除了结合项目A和B之外还有什么目的?如果没有任何独特的东西(即不在 A、B 或核心中的文件),那么它是相当多余的。

编辑:因为 git 子模块在我工作的一个地方特别痛苦,所以我们建立了自己的依赖系统,通过文本文件跟踪依赖的 repos。我们将其设置为始终跟踪分支的头部,而不是特定的提交。

我们能够像这样设置我们所有的项目,就好像它们是超级项目的一部分一样:

Super
|-A
|-B
|-Core

这些项目将使用相对路径相互引用,例如../A/include.h. 签出 repo A 将不起作用,您必须创建另一个“超级” repo 才能仅在 A 上工作:

AWorking
|-A
|-Core

编辑git 中这种行为的另一个原因是它无法跟踪根 repo 目录之上(即包含 .git 文件夹的文件夹之上)的东西,如果你想要你的超级项目和子项目,这肯定是必需的参考相同的存储库。

于 2009-09-14T03:13:19.820 回答
7

我认为这里的问题是 Git 的设计与您要解决的问题之间的不匹配。

Git 非常适合跟踪树。项目之间的依赖关系可以(并且可能会)形成一个图表。树是图,但图不一定是树。由于您的问题是如何有效地表示图形,因此树不是这项工作的最佳工具。

这是一种可能有效的方法:

一个 git 项目有一个 .gitmodules 目录,它记录“提示”,说明提交可能依赖于哪些项目,可以在哪里找到它们,以及它们应该插入到项目内的哪个路径。( http://osdir.com/ml/git/2009-04/msg00746.html )

您可以添加一个脚本,该脚本从一组项目中读取此信息,将在每个项目的 .gitmodules 文件中找到的提示映射到文件系统上实际放置这些项目的位置,然后从 git 期望的路径添加符号链接将子模块签出到各个项目的实际文件系统位置。

这种方法使用符号链接来打破树模并构建图表。如果我们直接在 git repos 中记录链接,我们将在各个项目中记录特定于本地设置的相对路径,并且项目不会像您想要的那样“完全独立”。因此,动态构建符号链接的脚本。

我认为这种方法可能会以不受欢迎的方式干扰 git,因为我们已经采取了它期望找到一件事的路径,并在那里放了其他东西。也许我们可以 .gitignore 符号链接路径。但是现在我们将这些路径写了两次并违反了 DRY。在这一点上,我们离假装使用子模块还很远。我们可以在每个项目的其他地方记录依赖关系,并将 .gitmodules 文件留给 git 期望的东西。因此,我们将创建自己的文件,例如 .dependencies,每个项目都可以在其中声明其依赖项。我们的脚本会在那里查看,然后去构建它的符号链接。

嗯,我想我可能刚刚描述了一个 ad-hoc 包管理系统,它有自己的轻量级包格式:)

megamic 的建议对我来说似乎很好地使用了 git 子模块。我们在这里只处理跟踪一个集合而不是一个图表,一个集合很容易适合一棵树。一层深的树本质上是一个父节点和一组子节点。

正如您所指出的,这并不能完全解决您的问题中所述的问题。我们可以分解出我们可能感兴趣的两种不同类型的“这与那个一起工作”信息: 1. 来自项目版本的声明(可能是项目作者)说“我需要项目 Y 的版本 X”2 . 您自己的构建设置使用的声明“我已经使用这组项目版本成功测试了我们的整个系统”

megamic 的答案解决了 (2) 但对于 (1) 我们仍然希望项目告诉我们它们的依赖关系是什么。然后我们可以使用来自 (1) 的信息来计算我们最终将记录为 (2) 的那些版本集。这是一个足够复杂的问题,需要有自己的工具,这让我们回到包管理系统:)

据我所知,大多数优秀的包管理工具都是为特定语言或操作系统的用户设计的。请参阅 Bundler 以获取 ruby​​ 世界中的“gem”包和 apt 以获取 Debian 世界中的“.deb”包。

如果有人知道一个很好的语言中立、操作系统中立的解决方案,它非常适合“多语言”(http://blog.heroku.com/archives/2011/8/3/polyglot_platform/)编程项目,我会很感兴趣的!我应该把它作为一个问题发布。

于 2011-08-08T03:45:25.533 回答
2

我认为您可以像这样管理一致性:在所有“核心”库中定义一个“参考”分支或一系列具有相同名称的标签(注意:您的示例中只有一个“核心”库)。然后指导子项目(A、B、...)的开发者尽快定期升级到“Core”的参考版本。

在运行构建之前,通过在干净、递归、“超级”的顶级签出中运行这三个命令,轻松检查“核心”是否在 A、B、C、... 中一致使用:

# 1.  Switch to the reference version (= "Force" consistency where need be)
git submodule foreach --recursive 'git checkout [origin/]reference || true'

# 2a. Show which inconsistencies you just forced; terse output
git status -s; git submodule foreach --recursive git status -s 2>/dev/null

# 2b. Same but verbose output
git submodule; git submodule foreach --recursive git submodule

# 3. Switch back to versions individually defined by sub-projects 
git submodule update --recursive

上面的“简洁输出”命令 2a 突出显示了哪些子项目没有使用 Core 的“参考”版本。

您可以轻松地扩展该方法以显示差异、强制升级或做任何其他您喜欢的事情。

于 2012-02-09T11:43:12.780 回答
2

使用硬链接将共享子模块转换为克隆的小型实用程序任务可能会起作用。

您可以在这里阅读我的完整解决方案: https ://stackoverflow.com/a/10265084/84283

于 2012-04-22T04:03:25.867 回答
0

由于您已经发现的原因,我不会尝试使用子模块映射依赖关系树。

子模块跟踪给定分支的给定修订,因此它们对于提供一组一致模块的快照很有用。

因此,如果您的项目需要将不同模块的特定版本集作为一个单元进行跟踪,您可以将它们组合为子模块。然后,您可以标记不同版本的不同模块集,以提供项目的历史记录,其中每个标记显示哪些模块的哪些版本在某个时间点兼容。

 tags/
     release1/ 
           |-> A@1.0
           |-> B@1.1
           |-> C@1.2
     release2/
           |-> A@2.0
           |-> B@1.3
           |-> C@1.5

至少我是这样理解它们的,尽管与 Git 的大多数事情一样,它可能还有更多的东西。在管理依赖项方面,我只能说找到另一种方式,这不是我理解的带有或不带有子模块的 Git 的设计目的。

于 2009-09-14T03:15:54.370 回答