主仓库的不同分支如何使用子模块的不同分支?

How can different branches of a primary repo use different branches of submodule?

假设我有一个主仓库 y 和一些子模块,例如 sub/x

还假设,对于主库和子模块库,master 是活动分支,并且主库的 .gitmodules 文件指定 branch = master

现在,假设除了 master 分支之外,主要 (y) 存储库还有一个分支 yA,同样,除了 its master 分支,子模块 repo (x) 有一个分支 xA.

我希望 y 存储库的 yA 分支“查看”/使用 x 存储库的 xA 分支。

这意味着在主仓库中 masteryA 分支之间的切换将导致子模块中 masterxA 分支之间的相应切换.

git对此有任何支持吗?


我尝试了以下方法:

  1. 切换到主仓库上的 yA 分支;
  2. 切换到子模块 repo 上的 xA 分支;
  3. master 替换为 xA 作为主存储库 .gitmodules 文件中分支参数的值;
  4. 在主仓库中,提交了由 (2) 和 (3) 产生的所有更改。

这并没有像我希望的那样工作:如果我切换到主仓库上的 master 分支,这对子模块仓库的活动分支设置没有影响(因此, master 分支没有干净的状态)。

Does git have any support for [what I want]?

有点,我想。您需要确保 .git/modules(在超级项目中)不会获取设置。

TL;DR

.gitmodules(在超级项目提交中)中使用不同的设置,并根据需要使用 git submodule update --remote。我没有对此进行测试,但请参阅详细说明。

我的总体建议:子模块的 branch 设置大多无用且无关紧要。忽略它。不过,我们稍后会介绍 mostly 部分,您可以看看是否可以使用它。

子模块被定义为 Git 存储库,其中一些 other Git 有时会进入该子模块并且 运行 一些 Git 命令。另一个Git 称为超级项目。

超级项目Git的主要操作是:

(cd $path && git checkout $hash)

此序列中没有任何分支名称出现。这就是 为什么 branch 设置无关紧要。

$path$hash 部分来自超级项目 Git 的 index,它们是从提交到超级项目中。该提交记录了子模块的路径和原始哈希 ID。这里也没有出现分支名称。

当你在超级项目中 运行 git checkoutgit switch 时,为了 select 一些分支名称和一些特定的提交,超级项目 Git 提取承诺其(超级项目的)索引和该超级项目的 work-tree。这会将正确的 ($path, $hash) 对放入超级项目的索引中。

不幸的是,默认情况下,它不会调用$(cd $path && git checkout $hash)部分来更新子模块。为此,您有多种选择:

  • 运行git submodule update。此命令正是这样做的(好吧,无论如何默认情况下:请参阅下面的详细信息)。
  • 运行 git checkout --recurse-submodules(或 git switch 的相同标志)。此命令使 git checkout 运行 更新, 传播到子模块 Git,因此当该子模块 运行s git checkout(或git switch),如果子模块是另一个子模块的超级项目,则该子模块将以其超级项目角色调用更新。这将(递归地)重复所有嵌套的子模块。 (我通常不使用它,但我不必过多处理递归子模块。由于递归,它非常强大。)
  • submodule.recurse 设为真。这会在多个命令上启用 --recurse-submodules 选项,包括 checkout/switch,但也会在 git fetchgit pull 上启用。 (我不喜欢这个:我认为它 强大。但是,您可以设置它,然后使用 push.recurseSubmodules 设置明确禁用递归推送。)

详细信息,或者,当 branch 设置重要时?

The git submodule documentation 有几段很长而且相当难以理解的段落来描述 git submodule update sub-command。 (我相信这表明子模块的整体设置存在缺陷,但我们必须使用现有的东西,至少在我们想出更好的东西之前。)让我在这里引用它:

update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--] [<path>...​]
Update the registered submodules to match what the superproject expects by cloning missing submodules, fetching missing commits in submodules and updating the working tree of the submodules. The "updating" can be done in several ways depending on command line options and the value of submodule.<name>.update configuration variable. ...

如您所见,有很多选项。为了避免这个答案变得更长,让我们只关注其中三个:--checkout--rebase--merge。还有两个 不是 选项,但您可以使用 子模块设置。<em>name</em>.update 变量,我们将在这里忽略。这些选项 - --checkout--rebase--merge - 设置更新将使用哪种操作,这与没有前导双连字符的选项名称相同。

checkout模式是默认的默认模式。也就是说,如果您没有设置明确的 submodule.<em>name</em>.update 设置,并且您没有指定 --rebase--merge,你得到 checkout。所以这就是每个人都使用的——主要是!这就是 mostly 这个答案顶部的总体一般建议中的意思。

现在,介绍三种模式。我将再次引用文档,然后进行一些小的格式更改和评论:

  • checkout
    the commit recorded in the superproject will be checked out in the submodule on a detached HEAD.

  • rebase
    the current branch of the submodule will be rebased onto the commit recorded in the superproject.

  • merge
    the commit recorded in the superproject will be merged into the current branch in the submodule.

因此,在默认模式下,任何地方都不会出现分支名称。只有 rebasemerge 模式实际上使用了分支名称。所以现在我们要问这个问题:which branch name?

文档说得很清楚:子模块中的当前分支。那不是 branch = 设置 子模块;它是 当前分支 子模块 .

但是子模块中当前的分支是什么?喜欢的可以了解一下

(cd $path && git rev-parse --abbrev-ref HEAD)

会告诉你,对于你通过的每条路径,哪个分支是当前的。如果子模块使用 detached-HEAD 模式,它会打印 HEAD,如果你有 运行 git submodule update --checkout,或者任何使用 [=45] 的 git submodule update =]模式。

如果你要预测当前分支,或者子模块是否在一个分离的HEAD 因此在任何分支上,您会预测什么?那么,你 运行 git submodule update 了吗?您最初必须执行 git submodule update --init,除非您执行递归模式结帐,在这种情况下 Git 会为您执行 git submodule update --init --checkout。所以你的子模块很可能处于 detached-HEAD 模式,因此没有当前分支。

换句话说,我们仍然有点不知所措。我们如何让子模块 Git 首先位于分支上?

有一种简单明了的方法:我们可以自己做 (cd $path; git checkout $branch),我们自己提供 $path$branch。这样,子模块就在我们想要的分支上,无论提交是什么。但是由于我们提供$branch,我们不需要设置。我们只是做:

(cd path/to/submodule; git checkout feature/foo)

直接。所以也不是。

如果我们向下滚动到文档中的 OPTIONS 部分,然后进一步向下滚动到 --remote 选项,我们最终会找到设置的地方实际使用:

--remote
This option is only valid for the update command. Instead of using the superproject’s recorded SHA-1 to update the submodule, use the status of the submodule’s remote-tracking branch. The remote used is branch’s remote (branch.<name>.remote), defaulting to origin. The remote branch used defaults to the remote HEAD, but the branch name may be overridden by setting the submodule.<name>.branch option in either .gitmodules or .git/config (with .git/config taking precedence).

This works for any of the supported update procedures (--checkout, --rebase, etc.). The only change is the source of the target SHA-1. For example, submodule update --remote --merge will merge upstream submodule changes into the submodules, while submodule update --merge will merge superproject gitlink changes into the submodules.

Alllllll-righty then!

说真的,这段文字真的很难读——但它说的是 git submodule update --remote 不会只使用来自超级项目的原始 SHA-1 哈希 ID。相反,它将使用从其他地方获得的原始 SHA-1 哈希 ID。准确地说,其他地方在哪里?

In order to ensure a current tracking branch state, update --remote fetches the submodule’s remote repository before calculating the SHA-1. If you don’t want to fetch, you should use submodule update --remote --no-fetch.

因此:当您将 --remotegit submodule update 命令一起使用时,超级项目将:

  • 第 1 步:(cd $path; git fetch),除非您添加 --no-fetch
  • 第 2 步:(cd $path; git rev-parse $(complicated)) 获取哈希 ID。

$(complicated) 部分很复杂,但它从 branch = 设置中获取分支名称,例如 branch = master,来自 .gitmodules.git/config.它将它变成 remote-tracking 名称,例如 origin/master,即步骤 1 刚刚更新。另见 VonC's answer to How can I specify a branch/tag when adding a Git submodule?.

特殊名称.表示使用超级项目中的分支名称—但是:

I would like the yA branch of the y repo to "see"/use the xA branch of the x repo.

除非拼写完全匹配,否则您无法使用 . 技巧。并且,如果子模块的分支名称已被复制到超级项目的 .git/config 中,它将保持设置为任何设置,但如果没有,超级项目 Git 将读取 branch = 设置来自 .gitmodules 文件。

如果 .gitmodules 文件在主存储库提交 $SHA_YA 中记录在分支名称 yA 中说 branch = xA,那么,当时你 运行 git submodule update --remote(有或没有 --no-fetch),超级项目 Git 应该在 origin/xA 上做一个 git rev-parse,假设子模块 xorigin 作为这里的遥控器。当超级项目 y 运行s (cd x; git checkout $hash).

时,这将成为超​​级项目 y 将传递给子模块 x 的原始哈希 ID 的来源

当你切换到其他提交时——注意分支名称在这里不相关;重要的是 提交哈希 ID,以及作为该提交一部分的 .gitmodules 文件——在超级项目中,超级项目中的 .gitmodules 文件可以有一些其他 branch = 设置。您的 git submodule update --remote 命令将找到 that 设置,并让子模块 Git 执行不同的 git rev-parse 以获取要传递给子模块的哈希 ID Git 当超级项目告诉子模块要检出什么时。

这一切非常复杂,有很多活动部件。这些部分必须在正确的时间排列。超级项目最终实际上只是使用原始哈希 ID。只需使用正确的原始哈希 ID 就可以减少 head-ache-invoking。一旦提交,就无法更改,这通常是正确的,因此您只需在提交之前确保它们是正确的。