6

目标:我需要为上游项目的先前版本制作自定义补丁,并且我希望能够将这些补丁应用于更高版本。

问题:从维护分支变基到更高版本会与维护分支中未修改的文件产生冲突。

怀疑:我正在为我想要完成的事情错误地应用合并或变基。

示例:这是我想要完成的逻辑示例,但要了解标记版本 v1.0 和 v2.0 之间的提交历史可以是数百次之间的提交。

我 fork 具有多个标记版本(v1.0 和 v2.0)的上游存储库,其主提交历史 A 到 D。B 是标记 v1.0 的最后一次提交。C 是标签 v2.0 的最后一次提交。D是持续开发。

$ git clone git@server:repo

A<--B(v1.0)<--C(v2.0)<--D Master

我分支了一个较早的发布标签 (v1.0) 以进行一些自定义修改,如下所示:

$ git checkout v1.0 -b v1.1

     E<--G<--H v1.1
    /
A<--B<--C<--D Master
        \
         F v2.1

我现在要做的是分支出更高版本的标签(v2.0)并将我在 v1.1 分支(G' 和 H')中所做的补丁提交应用到 v2.0 分支。我需要在 v2.0 日志中保留我在 v1.0 中所做的各个提交。

     E<--G<--H v1.1
    /
A<--B<--C<--D Master
        \
         F<--G'<--H' v2.1

问题:应用这些更改并避免冲突的正确工作流程是什么?我尝试了许多合并和变基的组合(包括 --onto),但都失败了。git rebase 希望默认为 3 路合并并与不相关的文件发生冲突。

$ git rebase v2.1 v1.1
Falling back to patching base and 3-way merge
...
Failed to merge in the changes.

$ git checkout v2.1
$ git rebase v1.1
Falling back to patching base and 3-way merge
...
Failed to merge in the changes.
4

2 回答 2

5

我建议您编辑您的问题以包含您尝试重新设置 --onto 的确切命令。这应该是您完成您正在尝试的方式的方式,但您可能已经以这种方式运行命令以触发比实际需要更多的变基。

如果你的 rebase 命令重写了v1.0和之间的所有内容v.2.0,那么如果该历史记录包括通过非快进合并解决的冲突,这可能会导致很多不必要的痛苦。

为了清楚起见,我已将有关合并冲突和变基的解释移至此答案的底部。rebase --onto但是,该部分只是推测,查看您尝试的示例会很有帮助。现在没有可用的,我会提供我认为你应该做的。话虽如此,让我们开始您的解决方案。


解决方案


变基--onto

我喜欢向后阅读 --onto 的论点以更好地理解。向后阅读,--onto <1> <2> <3>读为 - 将任何提交在 上<3>,未在 上<2>,并将它们应用于<1>. 提交不是“移动”的,而是“克隆”的,因此您的旧提交仍然在原处 - rebase --onto 只是创建它们的副本并在<1>.

重要的是要知道,在执行 a 之后,rebase --onto您最终可能会处于无头状态。如上所述应用新提交,但它们不会立即更改分支的状态。这在该过程中增加了一个额外的步骤,但也为您提供了额外的安全性,即知道变基不会破坏您的分支 - 您将有机会在将这些更改应用于您的分支之前查看更改的历史记录。

从这张图开始。

     E<--G<--H v1.1
    /
A<--B<--C<--D Master
        \
         F v2.1

根据您的描述,要获取 onlyGH遵循F而不包括,那么您应该尝试以下命令。E

git rebase --onto F G^ v1.1

如果你的情况,我写了上面的假设尽可能少。

这将立即处理提交v1.1存在G的任何提交,并在之后应用它们F。由于实际被重写的唯一提交是Gand H,因此没有理由让您遇到与这两个提交更改的内容无关的任何冲突。


无头状态

如上所述,您最终可能会处于无头状态。这意味着您不再在您的分支中。您此时的图表实际上看起来像这样......

     E<--G<--H v1.1
    /
A<--B<--C<--D Master
        \
         F v2.1
          \
           G'<--H' (currently checkout out headless state)

如您所见,分支 v2.1 仍位于F,但您已经创建了A<--B<--C<--F<--G'<--H'. 这是您想要的,但它不在您的v2.1分支中。因此,现在查看您的历史记录并确认它是您想要的。如果你愿意,甚至可以测试它。验证后,您只需签出 v2.1 并运行以下命令...

git checkout v2.1
git merge H'

这假设您在 v2.1 上没有新的提交,这些提交不在H'. 为确保,您可能希望--ff-only在合并上使用标志,以便它将拒绝合并而不是创建合并提交。

就像我上面所说的,这是一个需要注意的额外步骤,但因此您可以重置确保git rebase --onto不会在您的实际分支上弄得一团糟。如果您发现 rebase 没有按预期工作 - 您可以简单地检查v2.1并查看没有造成任何伤害。


结果

在您的快进合并完成后,您将拥有如下所示的历史记录...

     E<--G<--H v1.1
    /
A<--B<--C<--D Master
        \
         F<--G'<--H' v2.1


Cherry-picking vs Rebase --onto

不会详细介绍樱桃采摘,但我想明确以下..

git checkout v2.1
git cherry-pick G^..H

也完全等价...

git rebase --onto v2.1 G^ H
git checkout v2.1
git reset --hard <hash> <-- were hash is the commit the rebase kicks you into.

Cherry pick 的步骤更少,rebase 可以在不检查“base”的情况下完成,在这两种情况下都是v2.1. 同样如前所述, rebase --onto 不会直接影响您的分支,如果出现问题,则更容易从中恢复。两者都“克隆”了他们带到基础上的提交,而原件保持不变。


问题


以上是关于如何实现您要求做的事情的一般解释。以下是我对您为什么遇到您所描述的问题的怀疑。


非快进冲突解决和变基

我的猜测是,在 v1.0 和 v2.0 之间,您有一些用于解决冲突的非快进合并。在非快进合并期间解决冲突时,该冲突的解决方案将存储在合并提交中,而不是存储在有问题的提交本身中。合并提交发生在合并点的历史后期,而不是冲突提交本身。

当您变基时,git 会单独执行每个提交并重新提交它 - 结果,您将重温由非快进合并引起的所有冲突,但该冲突的解决方案直到合并发生的历史后期才可用。使用非快进合并解决的冲突不利于您将来重新设置分支的能力,除非您愿意一一重新解决所有这些冲突。


你可能犯的错误

如果我对您的问题的猜测是正确的,那么您可能已经做了以下事情......

git rebase --onto v1.1 F v1.1

这个或这个的一些变体将导致获取所有F未打开 的提交v1.1并将它们附加到v1.1. 如上所述,这将导致每次提交之间B并被F一一重新提交。如果其中存在通过非快进合并解决的冲突 - 那么您将通过这些提交重新体验这些冲突中的每一个作为 rebase 步骤。


合并而不是变基

您的问题标题表明您可能愿意简单地合并这些历史。如果您不关心线性历史,您可能只想将 v1.1 合并到F. 这不应该导致任何奇怪的冲突,但它会严重混淆你的历史。

于 2013-04-15T18:41:09.700 回答
2

虽然它可能没有 rebase 的细粒度控制或合并的便利性,但将一系列提交传递给git cherry-pick似乎更适合在旧分支上进行单独更改并将它们播放到当前分支上。

因此,如果 G 和 H 是最后两个提交,v1.1您应该能够通过以下方式挑选它们v2.0

git cherry-pick v1.1~1

(或手动提供提交哈希)

如果您已经尝试过,并且有缺点,请告诉我。我仍在尝试自己完善这种工作流程:)

于 2013-04-12T15:09:04.713 回答