你有点问题。没有既简单又好的解决方案(反正还没有)。这也是一些将 Git 子模块称为 sob -modules 的众多原因之一。 ?
您必须删除文件才能签出特定的提交。之后,您可以再次删除文件,添加并提交结果,然后添加相同的子模块以取回文件。这是一种痛苦,但它是一次性的痛苦,除了当它重复时,将来会重复一次(没有真正好的方法来总结这一点;请参阅下面的详细解释)。
长:发生了什么
Git repository 大致是commits 的集合。它与文件无关,尽管提交包含文件;这与分支无关,尽管分支名称可以帮助我们找到提交。这都是关于提交。
每次提交是:
-
编号。提交的唯一编号是它的 hash ID。这个数字对于这个提交是唯一的——不仅仅是这个提交在这个存储库中,而是这个提交在每个存储库中。每个具有此哈希 ID 的 Git 存储库中都有此提交;每个缺少此哈希 ID 的 Git 存储库都缺少此提交。也就是说,哈希 ID 是全局唯一 ID (GUID) 或通用唯一 ID (UUID)。这就是两个 Git 存储库在它们聚在一起交换提交时如何分辨哪个有哪些提交。他们此时不查看提交的内容,只查看 ID:它们是唯一的,因此仅从 ID 就可以判断。
-
只读:任何提交的任何部分都不能更改。 (这是哈希 ID 工作所必需的,因为 Git 本身是分布式的。如果您可以更改提交,两个 Git 可以聚在一起,交换提交并共享其哈希 ID,然后一个 Git 可以更改提交,以便它与其他 Git 存储库的副本不匹配,这是不允许的。)
-
一个包含两个部分的容器:
-
所有文件都有快照。这些文件以压缩和去重的方式存储。只有 Git 甚至可以读取这些文件,一旦它们被写出(因为它们具有相同类型的 UUID 哈希 ID),就没有任何东西可以写入它们。
-
有一些元数据,或关于提交本身的信息:例如,谁提交、何时提交、为什么提交(日志消息)。元数据包括一个previous 提交哈希 ID 的列表,通常只有一个条目长。这个先前提交的列表提供了边(传出 arcs),它们与提交及其哈希 ID 一起形成 Directed Acyclic Graph 或 DAG,除了哈希 ID 本身及其神奇的唯一性之外,它是 Git 工作的原因。
因为提交是只读的,否则对于完成任何实际工作毫无用处,我们必须提取一个提交——使用git checkout 或git switch——来处理或使用它。当我们这样做时,Git 会提取提交中的所有文件。1提取的文件进入一个工作区,Git 将其称为我们的工作树 或工作树。
总的来说,这一切都很好,2直到我们引入子模块。
1sparse checkouts 的工作正在进行中,可以避免这种情况,并且最终可能成为处理这个子模块问题的一种方法。不过,目前还没有面向用户的方法。
2至少对于“工作得很好”的一些定义。许多人可能会对此提出异议。 ?
子模块
Git 子模块只是 对 Git 存储库(或 2 个或更多组),其中:
- 至少一个存储库,称为(或一个)超级项目,作为其“文件”之一存储对单个提交in 另一个 Git 存储库;和
- 至少有一个存储库,称为(或一个)子模块,其签出文件位于其超级项目的工作树中。
超级项目将子模块的名称和路径 (path/to/sub) 存储在超级项目的每个(新)commit 中。这个提交包含一个伪文件条目,Git 称之为 gitlink,它存储哈希 ID 以在 in 子模块中使用。超级项目应该还存储一个名为 .gitmodules 的文件:该文件将包含 Git 运行 git clone 以创建包含子模块的 Git 存储库所需的指令。 Git 不强制存在这个.gitmodules 文件,并且一旦克隆完成,也不会费心查看.gitmodules 文件,所以很容易不小心造成损坏 缺少.gitmodules 文件的子模块。
在您第一次创建子模块时尤其如此,因为您经常拥有子模块存储库,因为您只是制作初始子模块存储库。你现在不需要clone它,你只需要制作一些网络托管站点store它(例如,在 GitHub 上创建一个新的存储库,然后使用git push在那里发送提交)。这也导致了一点chicken-vs-egg problem,因为在您应该运行git submodule add 来创建.gitmodules 文件和/或条目时,您可能还没有创建GitHub 存储库(或其他任何东西)。解决方案是创建 Web 托管存储库,以便您确定其 URL,以便您可以运行 git submodule add;即使还没有要克隆的内容,您也可以执行 git submodule add,因为您已经拥有克隆,Git 不会尝试克隆它,甚至不会检查您的 URL放入。再一次,Git 懒得看。只要确保你做对了,你就没事了。
哪里出了问题
在这种特殊情况下,您有一些现有的提交,其中lib/some_module/ 是一堆现有文件的前缀在这些提交。这意味着每次您签出(或切换到)其中一个现有提交时,Git 都必须提取所有这些文件。
例如,这些文件位于 lib/some_module/some_file.ext 中。但目前,lib/some_module 是您的子模块的工作树。所以lib/some_module/some_file.ext 是从lib/some_module/ 存储库的提交in 签出的文件。它不是超级项目中的一个文件。它位于超级项目的工作树中,因此根据定义,它是一个未跟踪的文件,它确实会被覆盖。
换句话说,Git 不会检查旧的提交,因为子模块在路上。解决方案很简单:移动或移除子模块。它是一个克隆,只要它是一个可以通过重新克隆获得其所有文件的克隆,完全删除它是安全的。3
也许它的一些文件并不安全。在这种情况下,您有其他选择:
-
提交这些文件(在子模块中)。现在他们承诺了。确保 git push 新提交,以便它可用。请记住,superproject 存储库记录了正确的子模块提交,而您刚刚进行了 new 子模块提交,所以转到超级项目和@987654343 @子模块并提交(如果需要,还可以推送)以在超级项目中记录新的提交哈希ID!
-
将所有内容保存在某处。移动整个子树(使用您的常规操作系统“移动文件树”操作),以便它们都安全地远离超级项目,以便在超级项目中运行 Git 命令 in没有可以破坏的子模块文件。将文件移开后,现在根本没有子模块,可以安全地检查其中一个旧提交。
如果您愿意,您可以使用这两种技术(腰带和吊带/belt-and-braces)。这里的重点是在单个存储库中做容易做的事情:确保文件可以恢复,如果你做了一些覆盖或删除它们的事情。使用单个存储库,git status 会告诉您这一点;对于子模块,您必须在子模块中运行git status 才能找到。4
请记住,每次在子模块中进行新的提交时,都必须更新超项目中的gitlink。超级项目 Git 是进入每个子模块,并使用 detached-HEAD 样式 git checkout 或 git switch 使 submodule Git check out 提交 hash ID 为 记录在超级项目中。所以你必须更新超级项目,以记录新的哈希 ID。
(通常,即使使用花哨的更新模式,您也需要这样做,在这种模式下,您让超级项目在子模块中运行两个 Git 命令:git fetch,然后是 git merge 或 git rebase。这些花哨的模式很棘手,因为它们在分离的 HEAD 上/内部工作,所以有点松鼠。您可以通过进入子模块并使用 git switch 来强制子模块具有 附加 HEAD,这让它不那么松散——更像是运行 git pull 会——但它仍然很复杂和毛茸茸:sob -modules。)
3如果你不确定确定是否所有的文件都可以通过重新克隆获得,就假设他们不能 并使用其中一种替代方法。
4请注意,超级项目中的git status 实际上会为您执行此操作(可选)并对其进行总结,因此您可以研究the git status documentation 并确保按照您想要的方式对其进行配置。还有来自超级项目的git submodule status。