首页 > 解决方案 > 有没有办法在没有嵌套子模块的情况下添加 git 子模块?

问题描述

我有三个存储库。

RepoCommon作为子模块添加到RepoTnRepoTh。两者RepoTnRepoTh都是两个不同的存储库,因为在开发的早期就认识到这两个是独立的应用程序。但是现在在后来的开发部分我们意识到RepoTn是高度依赖的RepoTh

作为解决方案,我正在考虑将RepoTh其作为子模块添加到RepoTn. 但困扰我的一件事是RepoCommon里面的“额外” RepoTh。如果我这样做,它将基本上具有以下结构:

我想知道当我想将它作为子模块添加RepoCommon到.RepoThRepoTn

注意:RepoTh需要继续存在,因为它也有自己的独立存储库。另外,我问这个的另一个原因是,如果我可以删除/忽略,RepoCommon我可以修改项目文件以确保系统使用正确的RepoCommon文件夹。如果这有什么不同,我正在使用 Visual Studio 2019。也欢迎任何其他关于更好解决方案的建议。

标签: gitgit-submodules

解决方案


最简洁的答案是不。这会产生一个问题,您可以使用长答案来解决该问题,但避免这种设置可能是明智之举。请参阅VonC 的回答

长答案

您必须记住这里的子模块什么——但在我们到达那里之前,让我们准确定义 Git存储库是什么。对此有很多小警告,但简而言之,Git 存储库是提交的集合1 每个提交都有一个唯一的编号:一个大而丑陋的哈希 ID,保留给提交,在任何地方的每个 Git 中。该提交用于保存 Git 在提交提交时所知道的每个文件的快照。2

除了这些在内部保存为 Git 称为blob 对象的存储文件外,Git 还可以保留 Git 在内部称为gitlink的内容。当我们进入子模块时,我们将在稍后查看 gitlink 的确切形式。

一旦你选择了某个提交——通过它的哈希 ID,例如你可能让 Git 按分支名称查找它——在存储库中,你可以让 Git签出该提交(使用git checkoutor,从 Git 2.23 开始,git switch)。这会将提交提取到一系列可用(和可修改!)的普通文件中。Git将3 个这些文件复制到 Git 的index中,这是 Git 用来进行提交的每个存储库的数据结构。4 如果提交包含任何 gitlinks,Git 也会将这些 gitlinks 复制到其索引中。


1最大的警告是.git存储库所在的目录实际上是两个主要数据库,以及一些辅助数据。一个从哈希 ID 映射到内部 Git 对象,其中包括提交,还包括其他三种对象类型。第二个将名称(包括人类在这里最常使用的分支和标签名称)映射到散列 ID,以便人类可以使用第一个数据库中的散列ID

当您克隆存储库时,您的 Git 按原样复制对象数据库,并使用其他 Git 的名称到哈希 ID 数据库来构建您自己的(不同的)名称到哈希 ID 数据库。没有其他任何辅助数据,例如钩子、索引或特殊的伪引用(如 CHERRY_PICK_HEAD)被复制。所以对象数据库很重要,而且由于人类使用名称数据库来定义他们的分支概念——这与 Git 的不太一样——第二个数据库也很重要。然而,在这里,我们主要关注对象数据库。

2除了快照,提交存储元数据,但这里我们只关心快照本身。

3存储的文件以一种特殊的、只读的、仅限 Git 的格式保存,其中文件被压缩和(重要地)去重复。进入索引的实际上是 blob 哈希 ID,因此索引“副本”根本不是真正的副本;但索引还包含文件的名称,以及使 Git 运行得更快的数据。

4索引具有其他角色,但通过保存每个文件的“副本”以及文件的路径名,索引充当下一次提交的模板。当您使用git add时,您实际上是在更新 Git 的索引,这样下一次提交就不会与当前提交相同。


gitlinks 如何成为子模块

Git 中的子模块只是另一个独立的 Git 存储库,具有一种特殊的关系:子模块 Git 存储库被其他某个 Git 存储库控制。控制 Git 存储库是超级项目,受控 Git 存储库是子模块。

鉴于存储库通过签出提交来工作——这样你就有可以使用的文件,而不是只有 Git 本身可以使用的特殊压缩格式的文件——超级项目 Git 本身需要做的所有事情,一次子模块 Git 存储库被克隆到位,是git checkout 该子模块中运行。这就是它的作用:

cd $path
git checkout $hash

对于git checkout子模块中的某些提交,超级项目需要知道两件事:

  • 路径是什么?上面的值是什么$path
  • 什么是哈希 ID?上面的值是什么$hash

这两件事正是 gitlink 中的内容。每个提交都存储文件。提交也可以存储 gitlinks。文件或 gitlinks 被复制到超级项目 Git 的索引中。索引中的每个条目都包含一个路径名、一个哈希 ID 和一些其他内部 Git 数据。path-name-and-hash-ID 正是超级项目 Git 执行上述命令所需的。

所以这就是它的全部......好吧,几乎所有。还需要一件事:超级项目 Git可能需要运行git clone,才能实际创建子模块存储库。超级项目 Git 这里需要的信息存储在超级项目中的.gitmodules 文件中。5然而, 一旦超级项目 Git 运行git clone.gitmodules数据就不再需要了。6


5请注意,您可以创建一种半成品子模块,其中没有.gitmodules子模块的文件条目,但在超级项目的提交中存储了 gitlink。

6有些git submodule命令仍会.gitmodules为您更新,有些git submodule命令会使用从 复制.gitmodules到 的数据.git/config。但是,一旦子模块被克隆,这些对于日常使用都不是必需的。


子模块递归和git clone

如果 Git 存储库 A 将 Git 存储库 B 列为子模块(在 A.gitmodules和 gitlinks-in-commits 中),并且 Git 存储库 B 将 Git 存储库 A 列为子模块(在 B.gitmodules和 gitlinks-in-commits 中),则任何克隆和签出提交指示子模块克隆并签出其提交可能导致无限递归:

  • A 克隆 ​​B
  • A 告诉 B:检查提交 C B,其中 C B有一个用于 C A的 gitlink
    • 所以B克隆了A
    • B 告诉 A:检查提交 C A
      • 所以A克隆B
      • A 告诉 B:检查提交 C B
        • ...

那么,这里真正的关键是那些git clone和/或git checkout步骤。超级项目什么时候运行git clone?超级项目何时将子模块定向到git checkout某个提交?

如果您保持子模块递归模式关闭,答案是超级项目永远不会git clone自行运行,也永远不会自行运行git checkout。这使您可以:

  • 克隆A
  • 告诉 A 现在克隆 B 并在没有递归的情况下签出一个提交

然后停止。

如果你打开递归 A 将克隆 B 并告诉它检查提交,这可能会告诉 B 克隆 A,并且由于递归是打开的,这将永远持续下去(或者直到你用完磁盘空间)。所以只要把递归关掉就可以了——但是每个使用这些存储库的人都必须知道这样做。


推荐阅读