首页 > 解决方案 > 在超级项目中提交和推送更改时,如何强制使用 git 子模块上的特定分支?

问题描述

我想这样做,所以在超级项目中推送发布分支时,我可以在子模块中强制发布分支

标签: gitgit-submodules

解决方案


TL;博士

子模块分支名称仅在您在该子模块中进行实际工作时才重要,即使那样,您也可以轻松地git push从该子模块中进行操作。否则,只有子模块内的实际原始哈希 ID 很重要。Git 通常会使用“分离的 HEAD”来设置子模块。虽然您可以(在较新版本的 Git 中)存储分支名称以与子模块一起使用,但 Git 在超级项目中工作时确实不会使用该名称。它只使用提交哈希 ID。

可以git checkout使用基于该子模块上游名称的名称将超级项目获取到子模块中的(分离的 HEAD)提交。当您不处理子模块但其他人这样做时,这有时很有用:您可能想要获取他们的最新提交,查看它是否适用于您的项目,如果是,则切换您的项目以使用他们的最新提交。在这里,分支名称有点用处,因为git submodule update --remote可以使用它让您的子模块克隆调用他们的Git,查看他们的 Git 的分支名称,并为您获取哈希 ID。但这实际上是针对开发过程,而不是发布过程。

简短的回答主要是“你不能直接这样做”,但也主要是“没关系”。

事实是,对于一个超级项目,即使子模块中是否存在分支也无关紧要,更不用说那些分支名称可能是什么以及它们对应的哈希 ID 可能是什么。 超级项目中的每个提交都记录了要在子模块中使用的提交的原始哈希 ID。

让我们让这个例子更具体一点。假设我们有一个名为P的项目。这个存储库——我们称之为RepoP——有一些提交。这个存储库有一些分支名称,例如masterfeature/f、也许release/v1等等。它也可能有标签名称,例如v1.0哪个是分支release/v1中代表版本 1.0 的特定版本。

这些名称中的每一个(<code>master、feature/f、等)都在 RepoP 中v1.0命名了一个特定的提交。分支名称和标签名称之间的区别在于分支名称会随着时间而变化:今天,master可能是 commit a123456...,明天可能是b789abc...。一个标签名永远不应该改变:它总是代表同一个提交。

当您git checkout在 RepoP 中执行操作时,无论您使用分支名称、标签名称还是仅使用原始提交哈希,Git 都会首先确定这是哪个提交。也就是说,Git 会找到目标提交的实际哈希 ID。然后,Git 将该提交(其快照中的所有文件)提取到 Git 的索引/暂存区和 RepoP 的工作树中。

如果这个提交有一些子模块,它们也会进入索引。他们还没有进入工作树——还没有!但是 Git 会创建一个空目录,当它需要这样做时(这只是片刻)。

假设您使用了a123456...在 RepoP 中选择提交的名称。让我们进一步说,提交a123456...调用两个子模块,分别被提取到path/to/s1s2(没有前导目录路径,只是./s2)。在 commita123456中,你会发现 Git 称为gitlinks 的两个实体:它们拥有path/to/s1一个原始哈希 ID,以及s2第二个原始哈希 ID。Git 也会将所有 gitlink 读入索引,以便在您进行下一次提交时将它们永久保存。

假设 gitlink forpath/to/s1具有 hash ID5100000...并且 gitlink fors2具有 hash ID 5200000...。(这些不太可能,但不太可能比a123456...ora6496b61...或其他。事实是,如果此提交调用这两个子模块,将有两个哈希 ID,无论它们是什么。)

现在提交a123456在你的工作树中,现在Git 可以开始填充子模块了。您可能需要运行:

git submodule update --init

手动,在这一点上,让它做所有的事情,或者你可能已经完成git checkout了让 Git 自动做这件事的选项,但是你现在以一种或另一种方式指示 Git 填充子模块。

Git 现在将进入目录path/to/s1,如有必要克隆子模块存储库,然后运行:

git checkout 5100000...

要在子模块的子目录中签出该特定提交path/to/s1, . (请记住,我们说过 gitlink 条目path/to/s1现在在您的索引中说use commit 510000...with this commit。这就是 Git 所做的。)

Git 将进入子目录s2,如果需要克隆子模块,然后运行:

git checkout 5200000...

在子模块的子目录中签出该特定提交s2

请注意,路径和提交哈希 ID 来自超级项目。 Git 从不查看子模块中的任何分支名称。子模块的分支名称根本不重要。1

Git使用存储在superproject中的提交哈希 ID将每个子模块存储库置于分离的 HEAD状态。 您只需要将正确的哈希 ID 存储到超级项目中。 好吧,也就是说,您只需要这样做确保在子模块被克隆时这些提交存在于子模块中。


1除了,即在git clone这个子模块的原始过程中:Git 需要查找提交,而要查找提交,Git 需要一些名称。这是因为哈希 ID 看起来完全随机。没有办法知道哪些提交是最新的,除非您有一个这样的分支名称release/1.0,其中包含该分支的最新哈希 ID release/1.0


那么子模块分支名称何时重要

假设您要在 RepoP 工作树中对项目 P 做一些工作。

您需要做的工作包括修复错误或向子模块添加功能s2。因此,在此窗口或另一个窗口或其他窗口中,您输入s2.

您现在位于子模块 Git 存储库中。 git status表明您有一个分离的 HEAD,因为超级项目 Git 告诉子模块 Git:签出提交5200000...作为分离的 HEAD,它确实做到了。

您现在可以修改代码并进行提交,但如果您这样做,它们将只是在这个分离的 HEAD 上。很难将它们推送到其他 Git。所以现在你可能希望进入一个分支。

较新版本的 Git 允许您让超级项目 Git 引导子模块 Git 按名称签出分支。 您必须在此超级项目中发出命令以使其执行此操作。2 但是如果你要在s2自己内部做一些工作,你可以在git checkout里面发出一个简单的命令s2,同时在子模块上工作:

git checkout release/1.0

例如。现在您可以照常工作并提交,这将使新提交更改存储在子模块分支名称中的哈希 ID release/1.0。然后,您可以git push将结果提交到子模块origin以将其添加到存储库release/1.0

假设,为了具体起见,这个新提交5200001...在你提交时以某种方式分配了哈希 ID。现在您的新提交和您的新功能已存在于子模块中并已推送到origin,您只需返回超级项目并使用git add更新超级项目 Git 中的索引/暂存区域:

git add s2

您在 RepoP 中进行的下一次提交会说:使用此提交时,告诉 Git for submodules2签出 commit 5200001...使用新提交的 任何人都通过其分离的 HEAD 原始哈希 ID 使用它。分支名称与超级项目无关。您只需要在子模块中工作时使用它,这样您就可以将新提交推送到origin,以便克隆子模块的每个人也将获得提交5200001...


2我相信较新版本的 Git 在这里获得了一些更高级的控件,这可能会使您的工作稍微轻松一些,尤其是在有多个子模块需要处理的情况下。但实际上,先做git submodule newsubcommand release/1.0之后cd s2再做你的工作,cd s2然后再做你的工作要容易多少git checkout release/1.0


推荐阅读