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

Posted

技术标签:

【中文标题】允许在 git rebase 中合并不相关的历史【英文标题】:Allow merging unrelated histories in git rebase 【发布时间】:2017-08-17 19:23:02 【问题描述】:

当你想重新设置一个保持合并提交的分支时,你传递--preserve-merges 标志。当您在 git 中合并不相关的历史记录时,您需要传递 --allow-unrelated-histories 标志。

如果你正在做git rebase --preserve-merges,当现有合并来自不相关的历史时,它会失败:

致命:拒绝合并无关的历史

如果你尝试git rebase --preserve-merges --allow-unrelated-histories,它会失败:

错误:未知选项“允许无关历史”

还有其他方法可以告诉 rebase 允许合并吗?


编辑:这是一个最小的复制:https://github.com/vossad01/rebase-unrelated-merge-reproduction

要重现结帐master 然后执行:

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

【问题讨论】:

您能否向我们详细介绍一下您试图通过合并不相关的历史记录来解决什么问题? @Schwern 我已经将它用于各种事情,但我最近遇到的几次案例是将项目的文档从 GitHub Wiki 移动到 GitHub Pages(当网站已经存在时)。保留历史记录(在这种情况下)的原因是 (1) 归功于贡献者和 (2) 保留修订历史记录。 我无法使用不相关的历史记录重现您的问题,我的测试存储库可能不够复杂。对于您的情况,rebase 可能是不合适的,这是假装一个代码库一直是在另一个代码库之上开发的。他们不是,你确实有平行发展,如果你保留它,历史会更有意义。正常的合并会更合适,或者subtree merge。 @Schwern 我添加了一个可以克隆的复制存储库。 我将举一个例子说明为什么这可能有用。我目前有一个曾经是几个回购的回购。我通过合并历史来组合它们。后来,我决定实际上不应该合并其中一个存储库。无论出于何种原因,当我试图从历史记录中删除它时,filter-branch 并没有为我工作。所以我想做一个基本排除合并的提交的变基。由于在那之后合并了其他项目,它将涉及不相关历史的变基合并。也许解决方案是让 filter-branch 工作。 【参考方案1】:

git rebase 合并失败时,它不会中止变基,因此您有机会手动干预。

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

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

理想情况下,Git 有一种无需人工干预即可处理此问题的方法。

【讨论】:

有趣的是,这不适用于通过git-subtree add 创建的合并。为了让它工作,git 需要记住使用了哪种合并策略,以及与此相关的冲突解决是如何发生的。听起来很棒。【参考方案2】:

蛮力方法是强制一个共同的根——因为你试图重新定位根,没有内容历史,做一个 nonce 空提交并告诉 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

【讨论】:

这让我大吃一惊,它确实有效!在简化的语言中,它使用grafts 人为地创建与空父/根提交相关的所有分支。当 Git 认为一切都是相关的时,你会做 rebase。然后你移除移植物以恢复原状。 考虑使用git replace --graft 代替this answer 到How do git grafts and replace differ? (Are grafts now deprecated?)。 @sampablokuper git replace --graft 使用的方法在这里是不可取的。 @jthill,如果您可以扩展您的答案来解释为什么 git replace --graft 在这种情况下不适合(或不太适合),那就太好了。谢谢! @sampablokuper git replace --graft 使用重写的祖先进行新提交,而不是简单地为现有提交指定临时祖先,因此旨在将新子代添加到现有提交的变基将改为添加替换提交的孩子。【参考方案3】:

要重现 checkout master 然后执行:

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

git-rebase 文档说不要合并 -i--preserve-merges

[--preserve-merges] 在内部使用 --interactive 机制,但将其与 --interactive 选项明确结合通常不是一个好主意,除非您知道自己在做什么(请参阅下面的错误)。

但即使没有-i,它仍然会因fatal: refusing to merge unrelated histories 而失败。

部分问题是HEAD~2origin/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]

您可以使用git log --topo-order 按拓扑顺序(8、7、6、5、3、1、4、2)单独查看它们,也可以按日期顺序(8、7、6、 5, 4, 3, 2, 1),git log 默认值。像 gitk 或 GitX 这样的历史可视化工具会同时显示两个订单。

在另一个之上重新建立一个基础是一个谎言:我们在网站上工作,然后我们在文档上工作,然后在某个时候(你必须找到一个点)并且出于某种原因我们工作网站和文档在一起。

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

这会丢失重要信息,并令人费解为什么某些更改在未来变得更加困难。

进行合并,这是正确的。

【讨论】:

我同意这两个存储库历史应该通过合并加入。有问题的变基是在合并完成之后发生的,并且没有试图线性化不同的历史(因此--preserve-merges)。 预期结果是 2 会有父母 3 和 4。 @vossad01 抱歉,我不理解您的演示存储库如何表示合并两个不相关分支的结果。哪个提交应该代表两个不相关的历史? 2? 3 是合并之前存在的分支吗?我也不明白你为什么要在 合并之后重新定位,那是为了完成什么?您是否正在尝试更新合并之前存在的分支?这就是3代表的意思吗? 是的。 2是不相关分支的原始合并。 3 在合并时不可用(否则合并最初会在它之上完成)。是的,它正在尝试更新沿袭,以便看起来合并发生在 3 之后(在这种情况下,它代表原始/基础存储库的最新状态)。 @vossad01 在这种情况下,将origin/a-prime 和任何其他未完成的分支重新设置为新的mastergit checkout origin/a-prime; git rebase -p master。您可能必须指定分支的开始和结束,例如git rebase --onto master origin/a origin/a-prime,它表示将以origin/aorigin/a-prime 开头但不包括所有提交重新设置为master【参考方案4】:

同步两个分叉的分支的唯一方法是将它们重新合并在一起,从而产生额外的合并提交和包含相同更改的两组提交(原始的,以及来自您的重新定位分支的)。不用说,这是一个非常令人困惑的情况。

所以,在运行 git rebase 之前,请始终问自己:“还有其他人在看这个分支吗?”如果答案是肯定的,请把手从键盘上移开,开始考虑一种非破坏性的方式来进行更改(例如,git revert 命令)。否则,您可以随意重写历史记录。

参考:https://www.atlassian.com/git/tutorials/merging-vs-rebasing#the-golden-rule-of-rebasing

【讨论】:

以上是关于允许在 git rebase 中合并不相关的历史的主要内容,如果未能解决你的问题,请参考以下文章

Git rebase使用

git mearge和git rebase的区别和应用场景

merge和rebase的区别

分布式版本控制系统Git-----7.Git 使用git rebase合并多次commit

【Git】rebase 用法小结

Git~rebase