子模块从不“指向标签”(这通常是不可能的,正如您所发现的:签出标签名称只会导致 HEAD 分离)。请注意,标签名称只是一个特定提交的人类可读名称。任何时候使用标签,可以将标签解析为正确的提交哈希 ID,然后直接使用哈希 ID。
子模块的point,就 Git 而言,是 处于分离 HEAD 模式。超级项目 Git 说明了使用哪个提交。超级项目提交——现在在超级项目中实际签出的提交——列出了每个子模块的原始提交哈希 ID。超级项目 Git 然后执行:
git -C path/to/submodule checkout <hash>
使用超级项目提供的哈希。所以这与标签“一样好”:我们只是将哈希 ID 存储在超级项目的 commit 中,而不是将其存储在子模块。
这意味着我们可以使用我们无法控制的子模块。我们不能在那个子模块中创建新的标签名称,但是我们可以git checkout任何我们喜欢的提交,然后在超级项目中创建一个新的commit,上面写着@987654323 @。然后我们就完成了。
剩下的唯一问题,真的,就是这个:
-
好的,所以我们有一个超级项目,以及一些提交 a123456,当该提交被签出并运行 git submodule update --init 时,签出该标签。
-
但现在我们要在超级项目中进行一些新的和改进的提交。
-
在这些新的和改进的提交中的至少一个(例如,下一个版本)中,我们希望一个特定的子模块 sub/mod 位于 v3.1415926(即提交 feedc0ffee),而不是旧的和糟糕的v1.4142136(提交badcab1e)。那么我们如何确保超级项目中的这些新提交使用哈希 ID feedc0ffee?
答案是:只需在子项目中签出所需的提交in,例如:
git -C sub/mod checkout v3.1415926
然后在超级项目中运行git add:
git add sub/mod
那么这一切是什么?
git submodule update --init --recursive --remote
这里的--remote 参数意味着:我有一些名字存储在某个地方。对于每个子模块,执行git -C path/to/submodule fetch——这是--remote 部分的第一部分——然后是git -C path/to/submodule checkout名称存储的name。这是--remote 的第二部分。 递归地执行此操作,即,如果子模块本身是更多子模块的超级项目,那么也对这些子模块执行此操作。这是--recursive 部分。
这是一个非常强大的东西,有多个活动部件:
-
name 存储在哪里?您如何知道每个子模块使用的名称?
- 谁在控制
name 如何解析为哈希 ID?我们在子模块中运行git fetchin,所以它是子模块的远程!
- 我们将获得哪些哈希 ID?这取决于该子模块中更新的
git fetch 名称(如果有)。
这些问题都有答案,但只有“名称存储在哪里”应该(或可以)在这里回答。名称来自:
- 超级项目的
.git/config,如果它设置在那里;或
- 超级项目的
.gitmodules,如果它设置在那里;或
-
master。
除了硬编码的master之外,这些都有更多的控制旋钮:您可以使用git config或git submodule来更新.git/config和/或.gitmodules。
这一切都相当复杂,也相当微妙,因为有人很容易在自己的.git/config 中设置它而忘记更新.gitmodules。那么你,如果你不是这个人,就会得到错误的名字!出于这个原因,我通常建议只手动完成所有操作,如果您是要选择哪个子模块继续哪个提交的人。