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