允许在git rebase中合并不相关的历史记录

23
当您想要保留合并提交进行变基时,需要传递--preserve-merges标志。 当您在git中合并不相关的历史记录时,需要传递--allow-unrelated-histories标志。
如果您正在执行git rebase --preserve-merges时,如果现有的合并来自不相关的历史记录,则会失败:
致命错误:拒绝合并不相关的历史记录
如果您尝试git rebase --preserve-merges --allow-unrelated-histories,则会失败,并显示以下内容:
错误:未知选项“allow-unrelated-histories”
是否有其他方法告诉rebase允许合并?

编辑:这是一个最简化的复现示例:https://github.com/vossad01/rebase-unrelated-merge-reproduction

要复现,请先检出 master,然后执行以下操作:

git rebase --preserve-merges --onto origin/a-prime HEAD~2

您能向我们展示一些关于通过合并不相关的历史记录来解决问题的详细信息吗? - Schwern
@Schwern 我已经用它做了很多事情,但最近遇到的几次情况是将项目文档从GitHub Wiki移动到GitHub Pages(当网站已经存在时)。在这种情况下保留历史记录的原因是(1)归功于贡献者和(2)保留修订历史记录。 - vossad01
我无法使用不相关的历史记录重现您的问题,我的测试仓库可能不够复杂。对于您的情况,rebase 可能不合适,因为这意味着一个代码库一直在另一个代码库之上开发。它们并没有,您确实进行了并行开发,并且如果保留这种情况,历史记录会更有意义。正常合并或子树合并可能更合适。 - Schwern
@Schwern 我已经添加了一个可以克隆的复制存储库。 - vossad01
我来举个例子说明为什么这可能很有用。我目前有一个曾经是几个repo的repo。我通过合并历史记录将它们合并了起来。后来,我决定其中一个repo实际上不应该被合并。由于某种原因,当我尝试从历史记录中删除它时,filter-branch并没有奏效。因此,我想进行一次重新基础,基本上排除了合并该提交的地方。由于在那之后合并了其他项目,这将涉及到无关历史记录的合并基础。也许解决方案是让filter-branch起作用。 - NateW
就我的经验来说,我似乎刚刚通过执行git rebase --rebase-merges master来使这个工作正常了。这将以 master 为基础重新应用与不相关仓库的合并(就像在后者的早期时刻一样)。 - underscore_d
4个回答

17

git rebase在合并时失败时,它不会中止重置,因此您有机会手动介入。

如果您愿意手动解决此问题,可以按照以下步骤完成合并:

git merge --allow-unrelated ORIGINAL_BRANCH_THAT_WAS_MERGED --no-commit
git commit -C ORIGINAL_MERGE_COMMIT
git rebase --continue

理想情况下,Git应该有一种处理这个问题的方式,而无需手动干预。


2
有趣的是,这在通过 git-subtree add 创建的合并中不起作用。为了使其起作用,Git需要记住使用了哪种合并策略,以及与此相关的冲突解决方式。听起来很糟糕。 - nishantjr
对于那些想要使用章鱼合并来完成此操作的人,应该将此解决方案与Git octopus merge with unrelated repositories相结合。 - Didier L

10
暴力方法是强制一个公共的根 -- 因为你正在尝试重新定位根,没有内容历史,创建一个空提交并告诉 Git 那是你正在合并的历史的父提交:
git rev-list --all --max-parents=0 \
| awk '{print $0,empty}' empty=`:|git mktree|xargs git commit-tree` \
> .git/info/grafts
git rebase here
rm .git/info/grafts

3
这让我有点惊讶,而且它还有效!简单来说,它使用移植来人为地使所有与空父/根提交相关的分支。在Git认为一切都相关的情况下进行变基。然后您删除移植以恢复原状。 - vossad01
考虑使用 git replace --graft 替代,参考 这个答案 中提到的 How do git grafts and replace differ? (Are grafts now deprecated?) - user82216
@sampablokuper 这里不适合使用 git replace --graft 方法。 - jthill
@jthill,如果您能扩展您的答案并解释为什么在这种情况下git replace --graft不太合适,那将是非常好的。谢谢! - user82216
@sampablokuper git replace --graft 会创建一个新的提交记录,而不是仅仅指定现有提交记录的临时祖先,因此,如果重置了一个旨在向现有提交记录添加新子节点的变基操作,则会将该子节点添加到替换提交记录中。 - jthill
@sampablokuper ... 我看到的情况并不是这样,我只是发现实际上创建替换对象和引用,然后浏览目录并读取该目录中的每个文件以查看替换ID,然后必须读取对象数据库并解析每个替换对象以查看父项是如此过度设计(你的意思是如果我想使用嫁接,现在我必须积极地保护我的存储库免受其他人远程推送任意重写吗?真的吗?)我错误地认为它也会在这里失败。 - jthill

3
要复现,请先检出主分支,然后执行以下命令:
git rebase --preserve-merges --onto origin/a-prime HEAD~2 -i
Git-rebase文档建议不要同时使用-i和--preserve-merges选项。但即使没有-i选项,它仍然会以“拒绝合并无关的历史记录”的错误信息失败。问题的一部分是HEAD〜2直接是origin / a-prime的祖先。你的测试存储库如下:
1 [master]
|
2
|\
| |  3 [origin/a-prime]
| |  |
| 4 / [origin/b]
|  /
| /
|/
5 [origin/a]

masterHEAD~2是5。origin/a-prime是3。你的命令等同于:

git rebase -p --onto 3 5

5是3的直接祖先,因此该命令没有多大意义。如果能够工作,它将执行一些奇怪的操作。


我最近遇到了几次情况,即将项目文档从GitHub Wiki移动到GitHub Pages(当网站已经存在时)。

这是不适当使用rebase。Rebase将并行历史转换为线性历史,基本上假装一组更改始终在另一组更改之上完成。这对于保持功能分支在被开发过程中保持最新状态很有用,如果您没有一堆中间合并提交只是更新分支,那么记录和审查就更容易了。对于未来阅读代码和提交历史的任何人来说,这些只是噪音。

但是当您拥有两个真正不同的历史时,最好将它们保持为不同的历史。将它们合并告诉正确的故事:网站和文档是分别开发的,但然后合并成一个单元。

1 - 3 - 5
         \
  2 - 4 - 6 - 7 - 8 [master]

你可以按照拓扑顺序(8、7、6、5、3、1、4、2)使用git log --topo-order命令单独查看它们,或者按日期顺序交替查看它们(8、7、6、5、4、3、2、1),这是 git log的默认方式。像gitk或GitX这样的历史可视化工具将同时显示这两个顺序。
将一个提交变基到另一个之上会说谎:我们在网站上工作,然后我们在文档上工作,然后在某个时间点(你需要找到这个时间点),由于某种原因我们一起在网站和文档上工作了。
1 - 3 - 5 - 2 - 4 - 6 - 7 - 8 [master]

那样会丢失重要信息,未来很难推断为何做出某些更改。
进行合并是正确的做法。

1
我同意将这两个代码库的历史记录通过合并进行合并。所讨论的变基是在此合并完成后进行的,并且不会尝试使分歧的历史记录线性化(因此使用了“--preserve-merges”选项)。 - vossad01
预期结果是2将拥有3和4作为其父级。 - vossad01
@vossad01 抱歉,我不明白你的演示存储库如何代表合并两个不相关分支的结果。哪个提交应该代表两个不相关历史记录的汇合?是2吗?3是在合并之前存在的分支吗?我也不明白为什么你在合并之后进行变基,这样做有什么目的?你是想将在合并之前存在的分支更新到最新状态吗?这就是3所代表的吗? - Schwern
是的。2 是无关分支的原始合并。3 在合并时不存在(否则合并最初将建立在其之上)。是的,它正在尝试更新谱系,以便看起来合并发生在 3 之后(在这种情况下,3 代表原始/基本存储库的最新状态)。 - vossad01
@vossad01 在这种情况下,将origin/a-prime和任何其他未完成的分支变基到新的master上。 git checkout origin/a-prime; git rebase -p master。您可能需要指定分支的起点和终点,例如git rebase --onto master origin/a origin/a-prime,它表示将从origin/a(但不包括)开始的所有提交重新应用到master上,直到origin/a-prime - Schwern
显示剩余2条评论

0
唯一同步两个分支的方法是将它们合并在一起,这会产生一个额外的合并提交和两组包含相同更改的提交(原始提交和您的变基分支的提交)。不用说,这是非常令人困惑的情况。
因此,在运行git rebase之前,请始终问自己,“是否有其他人正在查看此分支?”如果答案是肯定的,请将手从键盘上拿开,并开始考虑一种非破坏性的方法来进行更改(例如git revert命令)。否则,您可以放心地随意重写历史记录。
参考:https://www.atlassian.com/git/tutorials/merging-vs-rebasing#the-golden-rule-of-rebasing

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