有没有适用于忽略子模块更新的git合并策略,可以在将提交或分支合并到目标分支时使用?

14
我的使用情况是: 我有一个包含子模块的仓库的两个分支。 我想设置这两个分支的自动集成。但是每当这两个分支的子模块更新时,我的自动集成脚本会因为子模块冲突而失败。有没有办法让Git在合并过程中忽略子模块指针的更改? 或者为给定的子模块提供类似于 merge=ours 的东西?
我已经在这里阅读了这个文档。 我尝试按照上面页面上的合并策略来处理子模块文件夹(但它只适用于文件)。
submodule-name merge=ours

你是要将这两个分支合并在一起,还是其他什么操作? - Tim Biegeleisen
"git checkout branch1" 然后 "git merge branch2" (branch2 有新的提交,其中包含子模块更新) - Aila
哇,这个问题已经五年没有得到解答了...今天我遇到了完全相同的问题,但是我还没有找到任何线索来解决它(除了很多手动解决冲突的建议,但这不是一个选项!) - SvenS
2个回答

4
说实话,我也感到惊讶,但是文档很快就解释了发生了什么事情:

如果子模块的历史记录已经分叉,并且在超级项目中提交到不同的分支中,则...Git 将不会为您尝试甚至微不足道的合并。

我想回答“为什么合并策略不起作用?”的问题是:因为不同子模块版本之间不存在合并过程。 如果需要进行实际合并,则文档建议的方法仍然不那么复杂,但在您的情况下,还更加“容易”。
假设您在 master 分支上,并想在其中合并 my_branch 。有三种情况:

git merge my_branch

这在我已经链接的文档中已经解释得非常清楚,所以我没有理由重复它。 我仍然建议你阅读一下,因为你可能会遇到意外的情况。 我在回答末尾提到它。

git merge -Xours my_branch

我不知道这是否是解决此问题的正确方法,但是有一种非常简单的快捷方式可以清除子模块上的任何冲突。 因为 Git 不会对子模块版本执行任何合并操作,它只会告诉您有关不同版本之间的差异。 因此,如果您查看索引,仍然可以找到来自三路合并的三个版本。
git ls-files -s 的输出)
100644 acbc19aafbf0c14e67f9a437d465351a7e96388b 0   .gitmodules
100644 3da4fcc7b3a9bc886b50977dc35e10f48a42416b 0   your_files
160000 e826e1b762a17dbc7225b36db4a9f7f6c08774ad 1   submod
160000 fef2abfb901d20ba1f4d1023ba384bbc6afbc392 2   submod
160000 cd5caa8674fe078e0fb875861bb075ccb60cfee0 3   submod

第一个(e826e1b)是合并基础,但您对索引2的那个感兴趣。不幸的是,我认为您无法在将其添加到索引时使用通用的子模块修订版本 :2:./submod ,因为它不是实际路径。我告诉过您的简便方法是这样的:

git add submod

然后您可以提交commit。它会自动保留master子模块版本,但这当然不是最好的方法,因为行为可能会在某些未来版本的Git中发生改变。我将向您展示下一个场景的另一种方式。

git merge -Xtheirs my_branch

在这里事情变得复杂:我们无法使用上述技巧(简单的add,暂时默认为master子模块修订版本),因为现在我们需要索引3的修订版本,即:3:./submod ,该修订版本不能与add命令一起使用。

您还可以使用plumbing命令update-index更新索引,并传递原始缓存信息条目,例如:

git update-index --cacheinfo 160000,cd5caa8674fe078e0fb875861bb075ccb60cfee0,submod

160000 模式用于子模块(更具体地说是Git链接),cd5caa86... 是我们想要添加到索引的对象,而 submod 则是其路径。奇怪的是,将子模块名称放在这里也可以工作。 如果您需要编写脚本,则显然不能在此处放置硬编码对象,但可以使用以下命令检索它:

git rev-parse :3:./submod

最终,这是要运行的命令,以保持子模块的theirs版本:
git update-index --cacheinfo 160000,$(git rev-parse :3:./submod),submod

相反,为了保留ours版本,请将:3:替换为:2:

Git不会合并子模块,但可以重用合并

虽然参考文献已经很好地解释了这部分内容,但您需要非常小心。总的来说,git不会尝试合并您的子模块,除非:

  • 合并可以使用快进策略解决,在这种情况下,它使用最近的提交,或者
  • 有关于冲突子模块修订的合并已经存在于子模块存储库中,因此git将只添加该合并提交到索引中(而不是ourstheirs)。

在我看来,git团队应该添加一个选项,以便我们更多地控制如何处理这些情况。顺便说一句,您始终可以允许提交,然后使用--amend进行修改,或者在提交之前使用git merge --no-commit进行修复。在这两种情况下,您都不能使用索引修订(例如:2:./submod),而是在第一种情况下使用HEAD^1HEAD^2,在第二种情况下使用MERGE_HEAD。如果这是一个非常不可能的情况,那就跳过它,否则我会提交并使用--amend

结论

我希望有一种更简单的方法来合并具有不同子模块修订的分支,因此不要将此答案视为唯一的可能性。此外,在合并之后,您可能需要deinit并重新初始化(或重新同步)您的子模块,但只要这是可脚本化的,这就不是真正的问题。如果我是您,我会创建一个包含所有这些操作的别名。


1
请注意,如果您正在寻找的是这个问题,请不要立即接受此答案(或奖励悬赏),因为有许多 Git 专家可以在这个主题上做出贡献。很可能我过于复杂化了任务。 - Marco Luzzara
还想补充一点,这基本上是将my_branch重置为masterreset --hard操作,这实际上就是我的脚本所做的事情,直到有人运行它却没有强制推送权限,因此不允许更改历史记录;) 上述结构复制了reset --hardmaster的效果,并用一个新提交代替旧提交,因此可以使用更少的权限来完成。 - SvenS
@SvenS 很高兴你成功了。你采用的策略(-s ours)并不意味着真正的合并,因为你只是重用 HEAD 树来创建新的提交树,最终也会丢弃 my_branch 中任何没有产生冲突的更改。如果这正是你所需要的,那么这是一个非常聪明的解决方法 :) - Marco Luzzara
1
是的,这就是为什么我写了基本上是一个 reset --hard,因为它会丢弃 my_branch 上的所有提交。解决方案非常特定于我的用例,因为 my_branch 只包含由相同脚本进行的更改,该脚本现在正在尝试根据最新的 master 进行类似的更改。这也是为什么我的解决方法没有得到奖励的原因 ;) - SvenS
@SvenS 哈哈,有时候使用子模块并不容易。正如我在 Git does not merge submodules, but could reuse a merge 段落中所说的那样,如果你想做一些不同的事情,这两种情况都很烦人。顺便说一下,是的,如果你想要一个干净的合并,子模块合并应该首先完成。 - Marco Luzzara
显示剩余2条评论

0

虽然这是一篇旧帖子,但我只是为像我这样的懒人留言 :P

基本上,如果您的子模块历史记录与您想要合并的分支不同,则会出现此问题。

尝试以下方法,它可能也能帮助您:

  1. 使用您想要的分支更新(或合并)您的子模块分支。
  2. 在主模块中提交并推送所有已更新的子模块引用。
  3. 现在尝试合并,应该可以了。

由于子模块历史记录不匹配,请尝试通过拉取/还原/切换到其他分支来匹配它们,并提交这些子模块引用,这对我有用。希望对您也有用。


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