为什么`git checkout`不自动执行`git submodule update --recursive`?

58

请有人帮助我理解git中的子模块。我知道它们在互联网上受到了很多批评,但由于我认为git开发人员是聪明的人,所以当前行为必须有原因 - 也许有一种方法可以解决我的问题。

所以,我有一个项目和一些子模块。该项目有不同的分支,例如:

  • MyApp_version2
  • MyApp_version3
  • MyApp_version4
  • MyApp_liteversion
  • MyApp_development

我的子模块更新不那么频繁(可能每周一次),所以我对它们不会自动附加到子模块存储库的头部感到满意。

然而,当我检出旧分支 - 因为我需要修复旧版本软件中的错误时 - 我也需要更新子模块。

为什么我需要这样做呢?

我希望git能像svn一样工作。当我在主repo中提交我的工作时,我希望git能够思考以下内容:“好的,他现在想提交他的工作。我可以看到子模块当前处于版本abc,因此当他在将来某个时间回到这个提交时,他可能还想要子模块再次处于相同的版本。”
我无法看出任何情况下你会希望子模块停留在当前版本,而你在主代码库中回退3年。然而,肯定有实现这种方式的原因,对吧?
我真的很想知道你们中是否有人了解这背后的思路,但在任何情况下,我都希望得到解决方案。有没有一种方法可以告诉git:“我想提交这个工作和这些子模块。如果我在某个时刻回到这个状态,我也希望子模块被检出到正确的版本。”
澄清示例:
我的主代码库是一个需要使用SSL的应用程序,我找到了一个SSL库(libSSL),作为子模块添加。
在2010年10月31日,我在我的主代码库中创建了一个提交(2fd4e1),而子模块指向libSSL版本3(c67a2d)。

时间流逝,libSSL更新到34版本,我调整了我的代码,生活很美好。

2013年5月14日,我创建了一个新的提交(28fced),子模块指向最新版本的libSSL(849ee1)。

然而,如果我检出2fd4e1,我的子模块将停留在849ee1,即使原始提交是用c67a2d创建的。Git知道我使用c67a2d进行了原始提交,我不明白你为什么需要另一个子模块。


也许你只是想查看不同的提交以查看某些事情在不同时间点的外观 - 你真的想每次都等待所有子模块更新吗?虽然我同意可能有一个选项可以使git checkout也执行适当的子模块操作,但我不希望它默认启用... - twalberg
你可以为Git创建一个别名,它将自动更新子模块。请参阅https://dev59.com/VG445IYBdhLWcg3w9O3R#4611550。 - Colin D Bennett
我在发现如何进行子模块包含式检出方面遇到了困难。这个问题的标题解决了我的问题。我想知道为什么这么难以理解。 - Brent Bradburn
我认为所有的子模块 * 命令都应该被其他现有的命令所取代,例如:如果 foo 不是一个 URL,那么 git submodule init foo 应该类似于 git clone foo。如果用户想要执行比克隆或检出更复杂的操作,他可以只需 cd foo 并正常调用 git。这可能会使所有这些晦涩的命令变得直观和易于使用。 - VinGarcia
git checout 是一种本地操作。相比之下,git submodule update(这就是 --recurse-submodules 所暗示的)可能涉及到 fetch 甚至 clone。因此,默认情况下这样做会从根本上改变 checkout 的特性。我想这就是设计者们反对它的原因。您可以使用 git config --global submodule.recurse true 更改默认行为。 - Lutz Prechelt
3个回答

55
从 git v2.13 开始,使用新选项 --recurse-submodules 可以实现你想要的功能。在 git-checkout 的手册页中可看到:

--[no-]recurse-submodules

使用 --recurse-submodules 将更新所有已初始化的子模块内容,使其与超级项目记录的提交一致。如果子模块中的本地修改将被覆盖,除非使用 -f,否则将无法进行检出。如果不使用任何选项(或 --no-recurse-submodules),则不会更新子模块的工作树。

此外,可以参考这个有关该新选项的相关 Git 邮件列表消息

51
从2.14.0版本开始,您可以使用命令git config --global submodule.recurse true将其设置为默认行为。 - codehearts
2
^ @codehearts 那应该是它自己的答案! - netdigger

6
简化您的快捷方式/别名,使用“:”:
alias checkitout='git checkout $1; git submodule update --recursive'

-3

你基本上希望git可以递归地自动执行所有子模块的操作。这在像svn这样的集中式客户端-服务器模型中可能很简单。

但是git是分布式的。你的子模块可能来自完全不同的URL,使用完全不同的协议。最有可能的是,你甚至没有对子模块的origin repo进行push访问权限,而你对主repo却有。

因此,不能进行递归推送。

因此,设计者们可能决定避免在所有子模块中自动递归。这是一致的,但也非常痛苦。

在某个项目中,我们完全放弃了子模块,改用子树合并。


6
完全不是这样!我加了一个例子。这与推送到远程仓库无关。问题是:假设我正在开发Firefox浏览器。在Firefox 3版本中,我使用libSSL版本1(作为子模块)。后来,在Firefox 24中,我使用libSSL版本4。如果我再次切换回Firefox 3,为什么libSSL还停留在版本4,而Git知道它应该是版本1呢?因为Firefox 3版本是在创建原始提交后的多年之后发布的,所以并不知道libSSL版本4的存在。 - Markus
再说一遍,永远不要自动递归到子模块中。为了保持一致性。可能吧。谁知道呢。 - SzG
2
问题是关于结账方面的,没有理由不能进行递归结账。当然,推送是不可能的。问题是为什么你必须要做“git checkout someoldrev && git submodule update”,因为几乎从来没有留下以前的子模块的理由。 - Karl P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接