TL;DR
git status 比较HEAD 提交(当前提交)和索引。任何不同的地方都准备提交。然后它比较索引和工作树。有什么不同的是不为提交暂存。在这种情况下,Git 将索引 gitlink 与子模块中的实际提交哈希 ID 进行比较。当它说:
modified: submodule_name (new commits)
这只是意味着索引中的 gitlink 与子模块中的HEAD 提交哈希不匹配。这并不意味着子模块中有 个新的提交,也不意味着那里有 没有 个新的提交;它只是意味着现有的索引 gitlink 哈希 ID 与现有的子模块结帐不匹配。
长
子模块总是有很多混乱。您链接到的问题(Why is my GIT Submodule HEAD detached from master?)作为其公认的答案,其答案并未真正触及问题的核心,我认为可以通过以下对话进行总结:
简而言之,这就是它的工作原理。现在,子模块是 Git 存储库,因此您可以 将子模块放到分支上。但是 superproject Git 会立即将其从分支中拉回,因为 这就是子模块的工作方式。理解为什么会这样。
为什么要分离子模块
首先,让我们再次注意子模块是 Git 存储库。 使它成为子模块的唯一原因是其他 Git 存储库不时控制它。我们将另一个 Git 存储库称为 superproject。超级项目正在执行控制命令,子模块正在服从它们。除此之外,它们是两个 独立 Git 存储库:如果子模块具有 master 和 develop 以及其他任何分支,则它们独立于超级项目的 master 和 develop 以及任何其他分支否则,子模块和超级项目根本不必具有相同的一组分支名称。
还要注意,子模块可以成为另一个子模块的超级项目。这种情况变得特别令人困惑,因为现在说“the”子模块或“the”superproject 是模棱两可的。现在有 两个 超级项目、两个 子模块和 三个 Git 存储库,中间的 Git 存储库既是子模块(顶级 Git 的)和超级项目(底部的)。
设计做了一个巨大的假设,这是一个完全安全的假设,但可能很烦人。这个假设是:子模块是从您无法控制的存储库中克隆出来的。这个其他存储库可能会非常频繁地更新,或者几乎不会更新,但是它们的分支作为子模块的 origin/* 远程跟踪名称复制到您的克隆,以您不一定控制的方式进行更改。当您第一次克隆该子模块存储库时,git clone 会 创建一个名为master 的新分支,或者其他名称(如果他们这么说的话),并且通过检查这个名称所获得的提交,在他们 的控制之下,而不是你的.
出于这个原因,超级项目本身不需要——也几乎从不使用——子模块的任何分支名称。相反,超级项目记录了关于子模块存储库的一些无法更改的内容。它无法更改的事实意味着,无论谁控制您的克隆源对子模块 做了什么,他们都无法破坏您对该子模块的依赖。
(当然,这不是 100% 正确的。例如,他们可以完全删除源存储库。或者他们可以丢弃您所依赖的提交或标记,但一般来说,人们不会删除存储库。丢弃提交是很少见,并且丢弃已发布的标签及其相应的提交尤其罕见。)
你的超级项目记录的不能改变的是提交的原始哈希ID,超级项目将告诉子模块git checkout <em>hash</em>。此记录的信息将进入您的超级项目中的每个提交! (嗯,每个使用子模块的提交。)
运行git checkout <em>hash</em> 时会出现分离的 HEAD
如果您使用 Git 的时间足够长,那么您应该对此很熟悉。通过哈希 ID 检查任何历史提交,Git 进入分离 HEAD 模式。
超级项目通过其不可更改的哈希 ID 检查历史提交。所以子模块最终处于分离的 HEAD 模式。这就是如何以及为什么这一切都有效。超级项目记录哈希 ID,并命令子模块:检查那个,现在您处于分离 HEAD 模式。
这对于只读历史提交非常有用。 每个你的超级项目中的提交记录了正确的子模块哈希 ID,对于那个超级项目提交。当你签出那个提交时,你告诉 Git 同步你的子模块,你会得到正确的子模块提交,以便您可以构建和使用您的项目。
这会妨碍新工作
首先,我们都需要就定义达成一致:现有提交与新工作无关。提交是只读的,永远冻结。您不能更改任何现有提交的任何内容。 (这就是子模块提交的哈希 ID 对超级项目有用的原因:它被及时冻结,并且可能永远是好的。)每个提交都包含您所有文件的快照,以及一些元数据,例如谁制作了它,何时以及为什么 -日志消息。
提交就像这样被冻结,但我们认为新工作是一件令人向往的事情。因此,任何 Git 存储库——嗯,除了 --bare 一个——都提供了一个可以执行此操作的地方。那个地方就是work-tree(或工作树或任何数量的类似拼写)。 Git 将压缩的、只读的、仅 Git 格式的文件从某个提交中复制到工作树中,在那里它们采用普通的日常形式,因此您可以查看和处理/处理您的文件。
引用子模块的提交是通过 Git 所谓的 gitlink 来实现的。 gitlink 实际上是mode 160000 的提交文件(普通文件是mode 100644 或mode 100755),其中文件的“内容”只是超级项目应该命令子模块Git 到git checkout 的哈希ID .
因此,提交中的子模块条目(gitlink)告诉git checkout:您正在充当超级项目。当你到达工作树中的这个位置时,子模块应该在这个提交上,而不是在这里提取一个文件,作为一个分离的 HEAD。 如果你使用git checkout --recurse-submodules,Git 就是这样做的。如果你使用git checkout --no-recurse-submodules,Git 会推迟这样做——它会留下子模块,它毕竟是一个单独的 Git 存储库。
现在,Git 使 new 提交不是来自工作树中的内容,而是来自 index 中的内容。索引包含每个文件的副本......当提交具有 gitlink 时,索引包含该 gitlink 的副本。 索引中的 gitlink 条目决定了下一次提交的内容。因此,下一次 superproject 提交使用超级项目索引中的任何内容。
有时您希望子模块在另一个提交上。由于子模块是一个 Git 存储库,您可以进入它并git checkout 任何您喜欢的。如果您使用分支名称,现在将附加子模块的HEAD。就超级项目 Git 而言,这无关紧要:对超级项目重要的是实际的哈希 ID。如果子模块的 git rev-parse HEAD 仍然产生与超级项目索引的 gitlink 中相同的哈希 ID,那么一切仍然匹配。如果它产生其他一些哈希 ID,则由您来解决。 由于您想要不同的提交,您现在应该更新超级项目的索引的 gitlink。
-
PERSON: 但是,嘿,Git:我告诉过你要记住超级项目中的一个分支名称,用于这个特定的子模块。怎么样,Git,命令控制子模块的 Git 到
git checkout 那个分支 name?
你可以做到这一点。但是这里的假设是您的 Git 从您无法控制的上游存储库中克隆了您的子模块 Git,并且您没有 git push 到。所以这还不够,Git 提供的是命令:
git submodule update --remote
在这种情况下,超级项目 Git 将:
- 进入子模块
- 命令子模块Git运行
git fetch
- 等待
origin/master 或其他任何东西在子模块中得到更新,因为git fetch
- 根据上游找到子模块
origin/master(或其他)的新哈希ID(您无法控制,但您只是从中获取)
然后将子模块 Git git checkout that 哈希 ID ...再次作为分离的 HEAD!
如果您确实控制了子模块,并想在其中进行新的提交,您需要 cd 进入 子模块,只需 git checkout 任何分支名称你想要,然后在那里做你的工作。毕竟,这个子模块是一个普通的旧 Git 存储库。你可以做任何你喜欢的工作,然后运行git add 将任何更新的工作树文件复制到索引中——git commit 将使用索引中的内容——然后运行git commit 进行新的提交。
然后,完成所有这些后,您可以立即将提交推送到上游,或者等待并 cd 回到超级项目中。无论哪种方式,您现在都可以执行超级项目中需要的任何工作,git add 任何修改过的文件和子模块的名称。您不仅要更新超级项目索引中的文件,还需要更新超级项目中的 gitlink。现在您已经完成了所有这些,您可以在超级项目中运行git commit,以进行新的提交,以存储更新的文件和更新的 gitlink。
现在您有一个新的超级项目提交,您可以在某处git push 新的超级项目提交。 如果您已经在子模块中使用了git push,这就是您需要做的所有事情。但如果没有,你应该先git push子模块。原因很明显,一旦你考虑一下:新的超级项目提交说:当你到达这个子模块时,阅读这个 gitlink 并提取提交 a987654... (或任何哈希 ID)。对于其他人else 这样做,他们需要git fetch 上游子模块,您必须已经拥有git pushed,以便get 提交@ 987654369@ 进入他们的子模块 Git!
请注意,这与 git submodule update --remote 操作匹配:它们将转到其子模块的上游,git fetch 更新的分支,然后 git checkout 适当的 origin/<em>branch</em> 哈希 ID,在本例中为 a987654...,作为为其子模块分离 HEAD。
这不是有史以来最顺畅的过程,但它就像 Git 本身可以做到的一样简单。 git submodule update 可以做更多的事情,但它们都以这种思路开始:子模块存储库是从其他地方克隆的,主要是 其他地方 为子模块。