Git:Wrong Merge,然后恢复合并。现在无法再次合并分支

时间:2015-07-10 04:59:19

标签: git version-control merge

我和git有点麻烦。

这就是我所做的。

  1. 我将最新的主人合并到我的分支并推送
  2. 后来意识到此合并已损坏并还原了合并
  3. 现在我想再次合并大师,它说它是最新的。
  4. 我可以知道如何强制将master的所有更改合并到我的分支中吗?

    提前致谢。

2 个答案:

答案 0 :(得分:2)

让我们说你的功能分支featuremaster就像这样开始:

master:  A -> B -> C
feature: A -> D

master合并到功能后,事情就是这样:

master:  A -> B -> C
feature: A -> D -> M        # M is a merge commit

接下来,通过执行feature恢复 git revert分支中的合并。这意味着您告诉Git 添加新提交以撤消合并的结果。现在这是两个分支的状态:

master:  A -> B -> C
feature: A -> D -> M -> R   # R is a revert commit

当您尝试将master拉入feature时,Git会告诉您,您已经是最新的,因为您已经是!恢复提交在功能上解除了合并期间发生的任何事情,但现在您的feature分支中有两个新的提交。

要回到您在feature分支中进行错误合并之前所处的状态,您可以在feature中修改两个合并并恢复提交。完全按照以下步骤操作:

git checkout feature        # switch to your feature branch
git reset --hard HEAD~2     # nuke the 'M' and 'R' commits

在此之后,您可以尝试再次与master合并,一切都应该没问题。当然,请确保正确进行合并。

请注意,此选项涉及通过nuking提交重写 feature分支的历史记录,如果分支是,则可能不应该使用此选项您已经使用合并提交推送了分支。如果您属于此类别,请参阅@torek的答案以获取其他选项。

答案 1 :(得分:1)

您至少有三个选项。其中一个请参见Tim Biegeleisen's answer:它是最简单的,如果您没有其他提交且未推送合并(或合并并恢复),则可以使用它。

如果剩下的是TL; DR,只需使用其他答案。 : - )

让我以这种方式重新绘制他的master - 和 - feature图表:

A - B - C   <-- master
  \                         (pre-merge)
    D       <-- feature

A - B - C     <-- master
  \      \                  (post-merge)
    D --- M   <-- feature

使合并提交成为合并提交的事情是它有两个父母:&#34;主线&#34; parent(在这种情况下为D)和已合并的提交(C)。

合并如何运作

当你要求git执行合并时,git首先找到一个共同的祖先,在这种情况下提交A。然后它会做两个差异:A vs D(主线上发生了什么,feature)和A vs C(分支上发生的事情是合并,master)。然后它组合了这两个差异集,以便它可以创建一个新的提交,其中包含两行中每个更改的一个副本。 1 如果一切顺利,它会自动提交结果;如果没有,它会因合并冲突而停止,让你解决问题,并提交最终结果。无论哪种方式,最终提交M都会将提交CD列为其(两个)父提交,而不仅仅是一个父提交。在git中,这是一个合并的定义:一个提交有两个 2 父母。

上次合并后

为了便于说明,我们暂时在feature上添加一个新的普通提交:

A - B - C         <-- master
  \      \
    D --- M - E   <-- feature

如果在分支feature上你要求git再次合并master,会发生什么?

Git必须首先找到featuremaster(又名&#34;合并基础&#34;)的最新共同祖先(提示)。提交E的祖先,feature的提示,按照距离的顺序 - E本身(零退步); M(后退一步); CD(两个退步:两者都是M的父母);和AB(后退三步)。 (还有另一种方法可以到达A后退四步但我们采用更短的路线。)提交C的祖先,master的提示,从C本身开始(零退步)。所以这是合并基础,并且为了合并master,git应该将C(合并基础)与C区分开来......但显然那里没有变化,所以什么都没有合并,你得到了最新的&#34;消息。

但是还原呢?好吧,就git而言,恢复只是一个普通的提交。使其成为回归的原因是它所适用的变化&#34;撤消&#34;来自先前提交的更改。也就是说,要进行恢复,git基本上会执行git diff(对于合并的任何一方),但是然后在差异所说的任何地方&#34;添加一行&#34;,git 删除< / em>该行代替,或者说#34;删除行&#34;,git 放回已删除的内容。 (当你恢复合并时这有点棘手,因为git必须避免撤消某些东西,当你完成合并时,已经在两个方面完成了;但是git做得对。)

重新合并

回到手头的问题,即重新进行合并:您可以使用建议的方法,即(实际上)删除不需要的还原不需要的合并。使用git reset --hard会做到这一点,让你回到原来的合并前状态:

A - B - C   <-- master
  \
    D       <-- feature

(额外提交MR仍然存储在存储库中,但是从视图中隐藏,最终将被垃圾收集。)现在您可以重新进行合并,大概是获取这次是对的。

什么时候做更棒的事

最简单方法的一个缺点是,当您进行新的(更正的)合并时,它将具有新的且不同的基础SHA-1 ID:

A - B - C      <-- master
  \      \
    D --- M2   <-- feature

就其本身而言,这是必要的,甚至是可取的。但是,如果您已经推送(或以其他方式发布)合并提交M某处(有或没有推动还原R),其他人可能拥有该原始错误合并的副本。推送新feature将需要使用--force来重写历史记录&#34;。任何选择错误合并M的人都可能依赖它,重写历史会让他们感到困难。

(你当然可以选择对他们说&#34;运气不好&#34;让他们处理你的重置和重新合并。这是简单的方法,有时甚至是最好的方式。但如果不是,我会按下。)

如果你从糟糕的提交中做了一些好的提交,你可能不想这样做的另一个原因是:

A - B - C                 <-- master
  \      \
    D --- M - E - R - F   <-- feature

让我们说提交M是错误的合并,R是它的回归,但EF都是好的和可取的。如果您使用git reset --hard清除M,则还会清除EF。那么现在呢?

发烧友要做的事情

Git是git,有很多方法可以解决这个问题。你必须选择:

  • 你是否想要一个快速前进的&#34;历史
  • 是否要完全重新进行合并,或者只是修复它
  • 您希望如何保存您的&#34; good&#34;提交

让我们先解决最后一个问题。您可以使用git format-patch将这些提交保存为修补程序。这可以让你将其删除(使用git reset --hard),然后重新应用它们(使用git am)。或者,您可以将它们保留在存储库中,以便您可以使用git cherry-pick复制它们,或将它们保留在原始的git提交链中,以便您可以获得快速前进的历史记录。

让我们看看下一个快进项目。这基本上意味着每个发布的提交(您通过git pull或其他任何方式推送或提供给其他人的每个提交)必须保持不变:您必须只有添加到此类历史记录中。这真的就是它的全部(当然这意味着将你的错误留在那里供其他人查看)。

最后,让我们看一下中间项目:你真的想重新进行合并,或者只是采取什么措施并修复它?实际上第三个选项,起初看起来有点愚蠢:你可以完全重新进行合并,然后(使用那个)修复破坏的那个。这种方法的优势在于它可以让您轻松地生成快速前进的历史记录。

重新执行合并而不重置

实际上有几种方法可以做到这一点,但我只会特别展示一种方法。你希望git看到它看到的相同的设置&#34;预合并&#34;即使有合并。我将再次绘制相同的图形,但稍微伸出一点:

A - B - C                   <-- master
  \      \
    D     \
     \     \
      ----- M - E - R - F   <-- feature

现在让我们轻松做一些git,即检查提交D。我们可以找到其ID(78cefa3或其他),或者在feature上重新计算提交:F为0,R为1,E为2回,M回到3,D ......好吧,它有点棘手:DC都回来了4,但是{{1在&#34;主线&#34;上,D将命名提交feature~4

无论哪种方式,我们都会检查出来...但是我们为它制作了一个新的分支标签,让我们称之为D

feature-alt

现在我们有了这张图:

$ git checkout -b feature-alt 78cefa3   # by ID

(我在A - B - C <-- master \ \ D ....\................ <-- feature-alt \ \ ----- M - E - R - F <-- feature s中显示.直接指向提交feature-alt)。现在,即使有一个D分支,让我们暂时忽略,并再次绘制图形:

feature

这应该看起来非常熟悉:它是我们进行错误合并时的图表。现在,当我们在A - B - C <-- master \ D <-- feature-alt 时,我们只需运行:

feature-alt

这次正确合并(如果你愿意,可以使用$ git merge master ,以防止git提交自动合并结果,所以你可以先修复它。)

一旦合并完成并提交,我们就会有:

git merge --no-commit

我在此处添加了A - B - C___ <-- master \ \ \ D ----\-- M2 <-- HEAD=feature-alt \ \ ----- M - E - R - F <-- feature ,以表明我们仍然使用新的合并提交HEAD= feature-alt。请注意,M2包含父级MD(按此顺序),C也是如此,M2内容 (推测)是不同的(这次合并是正确的)。

此时,您只需M2提交git cherry-pickE,然后删除分支F并将feature重命名为feature-alt,并使用feature推送结果。这给了你一个干净的历史与正确的合并和你的两个良好的提交。它不能快进,但会保留您在--forceE中所做的更改。

或者,如果F是正确的合并结果,并且您需要快速前进的历史记录,那么您现在需要的是将M2中的更改应用于现有分支的提示{ {1}}。由于M2还原了feature,您现在可以执行此操作:

R

这就像任何其他樱桃选择一样,除了你必须指定合并的主线(就像M)。这实际上说,&#34;比较$ git checkout feature $ git cherry-pick -m 1 feature-alt (第一个父母)和git revert并将这些更改应用到当前分支D&#34;的提示:这是,把正确的合并,而不是错误的合并。

如果一切顺利,你现在就拥有这个:

M2

其中featureA - B - C___ <-- master \ \ \ D ----\-- M2 <-- feature-alt \ \ ----- M - E - R - F - M3 <-- HEAD=feature 的副本(但是是常规的非合并提交)。

如果一切顺利,您现在可以完全删除M3分支。错误合并在历史记录中保留,其恢复也是如此,并且合并的更正版本看起来好像通过魔法作为普通提交。

如果所有这些都太复杂而您不想希望重复合并,那么您只需还原合并的恢复,即可恢复错误的合并:

M2

变为:

feature-alt

其中A - B - C <-- master \ \ D --- M - E - R - F <-- feature 撤消A - B - C <-- master \ \ D --- M - E - R - F - R2 <-- feature 。现在,您可以进行所需的任何修补,并R2调整R(甚至只需git commit --amend而不R2只需在顶部添加其他修补程序,然后使用{{ 1}}在推进之前将其压扁。)这样做的目的是使git commit--amend完全相同,git rebase -i是精选合并的R2。您避免重新进行合并,但当然,您必须弄清楚如何解决错误的合并。

在任何情况下,所有这一切的最终结果都可以在没有M3的情况下推送,因为它只是在所有以前的上添加提交。

1 例如,如果M2 - 至 - -f将一行文字添加到AD - 至 - {{ 1}}向README.txt添加不同的文本行,新的提交A将每行添加到每个文件中。但是,如果C - 到 - main.c相同的行添加到M,在同一个地方,git将知道只添加一个新的副本行,而不是每组更改中的一行。新的A只会添加一行,而不会重复。

2 真的是两个或更多,但是多臂&#34;章鱼&#34;合并基本上以同样的方式工作。