这里发生的情况是,您的子模块存储库与超级项目中记录的哈希 ID 不同。您在超级项目中运行的 git status 告诉您这一点,没有更改它,而您的 git add -A 显然也没有更改它。
这最后一部分似乎是错误的。当我做类似的事情,然后使用git add -A,我得到:
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: [submodule path]
如果我再运行两个命令,它会按预期返回:
$ git reset
Unstaged changes after reset:
M [submodule path]
$ git submodule update
Submodule path [path]: checked out '[hash]'
$ git status
On branch ...
nothing to commit, working tree clean
(我怀疑您在子模块中做了一些更改,但从未在那里提交。)
发生了什么,细粒度的细节可以让您诊断问题
我们有一个 Git 存储库,称为 superproject,它控制着第二个存储库,称为 submodule。超级项目实际上有三个单独的控制旋钮,每个提交中都有一个,因此也可以在 index 中找到(因为索引控制将进入 next 提交)。
其中一个控制旋钮就是您提到的文件.gitmodules。如果子模块还不是git cloned,它会告诉超级项目如何克隆子模块。一旦子模块被克隆,它的主要工作就完成了。
第二个是您的.git/config 文件。它包含从.gitmodules 文件复制的信息,如果.gitmodules 文件不完全适合您自己的目的,您可以根据需要更新这些信息(这可能与负责.gitmodules 文件的人不同) . .git/config 中的任何设置都会覆盖.gitmodules 中的设置。否则这两个放置设置的地方基本上是等价的。
最后一个是导致问题的原因。为了使子模块签出到您的工作树中,从而对您有用,控制超级项目的 Git 启动了第二组 Git 命令。一般来说,您可能会运行:
git submodule update --init
检查子模块(如果您使用git clone --recursive,Git 会为您执行此操作)。
此时,超级项目 Git 已经创建了一个几乎为空的目录,并且路径正确。 (该目录包含一个.git 文件,命名克隆存储库的路径,或者在过去或使用旧样式向后兼容模式,包含实际的.git 目录本身。)将超级项目Git chdirs 放入这个目录并告诉子模块 Git:
- 运行
git checkout <em>hash</em>
一旦发生这种情况,路径中就会充满从 ID 为 hash 的提交中提取的文件,这主要使外部 Git(超级项目)“完成”了这些文件。但是有一个副作用,因为子模块本身就是一个完整的 Git 存储库,这意味着一切。
特别是,子项目有自己的HEAD。这个HEAD 现在分离,并且子模块的存储库的当前提交是hash,所以它在子模块的索引和工作树中,它是当然是我们想要的:子模块的工作树是超级项目中所有子模块文件所在的路径。
但有一个有趣的问题需要回答:超级项目 Git 从哪里获得哈希 ID? 答案是:它存储在 每个快照中——嗯,每个快照使用子模块——在超级项目中,每个快照都有每个文件的完整副本。为了实现这一点,超级项目的索引包含一个 gitlink 类型的特殊条目。
每当超级项目告诉子模块 Git 时,超级项目索引中的这个 gitlink 条目会告诉超级项目将哪个哈希 ID 提供给子模块 Git:签出一些特定的提交。
如果您手动导航到子模块,并 git checkout 分支名称或任何其他按哈希 ID 提交的提交,则子模块存储库的 HEAD 会更改。它要么附加到分支名称,要么指向另一个提交,仍然处于 detached-HEAD 模式。
此时,子模块和超项目不同步。超级项目 Git 还没有对此做任何事情。你可以控制,你可以选择你想要的提交。你甚至可以做出新的提交并 git push 将它们提交到上游。一旦你完成了所有你想要的提交和git checkout-ing,并且一切都安排好了,你应该爬出子模块工作树回到你的超级项目。
现在git status 和git diff 默认情况下——这里也有很多控制旋钮——告诉你超级项目正在调用一些哈希H,但子模块有一些其他散列 S 已签出。 (如果您为此设置了控制旋钮,他们可能会或可能不会告诉您子模块本身是否需要提交。)如果您希望记录下一个超级项目提交,在该子模块的 gitlink 中,这个新的提交哈希 S,你跑:
git add path-to-submodule
(或git add -A应该做同样的事情,这就是为什么这令人费解)。这将更新索引中的 gitlink 以记录哈希 ID S 而不是 H,以便下一个超级项目提交将在 git submodule update 命令上告诉子模块 Git : 签出提交 S,作为你分离的 HEAD.
一旦超级项目中的索引与实际签出子模块中的HEAD 匹配,子模块将不会列在更改未暂存以进行提交部分。如果索引中 gitlink 中的哈希与 HEAD 中 gitlink 中的哈希不匹配,git status 将在要提交的更改中列出子模块的路径。