【问题标题】:Git: Wrong Merge, then reverted the merge. Now not able to merge the branch againGit:错误的合并,然后恢复了合并。现在无法再次合并分支
【发布时间】:2015-07-10 04:59:19
【问题描述】:

我的 git 有点麻烦。

这就是我所做的。

  1. 我将最新的 master 合并到我的分支并推送它
  2. 后来意识到这个合并已损坏并恢复了合并
  3. 现在我正在尝试再次合并 master,它说它是最新的。

我可以知道如何强制将 master 的所有更改合并到我的分支吗?

提前致谢。

【问题讨论】:

  • 当你说“revert”时你是使用git revert还是回滚合并提交?
  • 我做了一个 git revert :-(
  • 请查看我的回答,它会告诉您如何回到做出错误提交之前的状态。

标签: git version-control merge


【解决方案1】:

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

如果其余的是 TL;DR,请使用其他答案。 :-)

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

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

和:

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

使合并提交成为合并提交的原因在于它有两个父级:“主线”父级(在本例中为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 (也称为“合并基础”)的(提示)的最近共同祖先。提交E 的祖先,feature 的尖端,按距离顺序是E 本身(后退零步); M(退一步); CD(都退后两步:都是 M 的父母);和AB(都退了三步)。 (还有另一种到达A 的方法,即后退四步,但我们采取更短的路线。)提交C 的祖先,master 的尖端,从C 本身开始(后退零步)。所以这是合并基础,要合并master,git 应该区分C(合并基础)与C ......但显然那里没有变化,所以没有什么可以合并,你得到“最新”消息。

但是,还原呢?好吧,就 git 而言,revert 只是一个普通的提交。使它成为一个回归的原因是它应用的更改“撤消”了先前提交的更改。也就是说,要进行还原,git 本质上会执行 git diff (对于合并的任一侧),但是无论差异显示“添加一行”,git deletes 代替该行,或者它说“删除一行”,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 来“重写历史”。任何选择了错误合并 M 的人都可能会依赖它,重写历史会让他们感到困难。

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

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

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

假设提交 M 是错误的合并,R 是它的反转,但 EF 都是好的和可取的。如果你使用git reset --hard 消灭M,你也消灭了EF。那么现在呢?

更有趣的事情要做

Git 是 git,有很多方法可以处理这个问题。您必须选择:

  • 您是否想要“快进”历史记录
  • 您是想完全重新进行合并,还是只是修复它
  • 您希望如何保留“好的”提交

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

接下来让我们看一下可快速前进的项目。这基本上意味着每个已发布 提交(您曾经推送过的每个提交,或通过git pull 提供给其他人的每个提交)必须保持不变:您只能添加 到这样的历史。这就是它的全部内容(当然,这意味着将你的错误留给其他人查看)。

最后,让我们看看中间的那一项:你真的想重新进行合并,还是只取现有的东西并修复它?实际上还有第三种选择,起初看起来有点愚蠢:您可以完全重新进行合并,然后然后(使用那个)修复损坏的那个。这种方法的优势在于它可以让您非常轻松地生成可快速转发的历史记录。

在不重置的情况下重新合并

实际上有几种方法可以做到这一点,但我只特别展示一种。即使有合并,您也希望 git 看到与“合并前”相同的设置。我将再次绘制相同的图表,但将其拉长一点:

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 后退,但 D 在“主线”上,所以 feature~4 将命名为 commit 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 merge --no-commit,以防止 git 提交自动合并结果,所以你可以先修复它)。

一旦合并完成并提交,我们将得到:

A - B - C___                <-- master
  \      \  \
    D ----\-- M2            <-- HEAD=feature-alt
     \     \
      ----- M - E - R - F   <-- feature

我在此处添加了HEAD=,以表明我们仍在使用feature-alt,并带有新的合并提交M2。请注意,M 有父级 DC(按此顺序),M2 也是如此,但 M2内容 (可能)不同(合并为这次更正)。

此时,您可以简单地git cherry-pick 提交EF,然后删除分支feature 并将feature-alt 重命名为feature,并使用--force 推送结果。这为您提供了正确的合并和两次良好提交的干净历史记录。它不能快进,但它保留了您在 EF 中所做的更改。

或者,如果M2 是正确的合并结果,并且您想要一个可以快速转发的历史记录,那么您现在需要将M2 中的更改应用到现有分支feature 的尖端。由于R 还原了M,您现在可以这样做:

$ git checkout feature
$ git cherry-pick -m 1 feature-alt

这与任何其他樱桃选择一样,只是您必须指定合并的主线(就像git revert 一样)。这实际上是说“将D(第一个父级)与M2 进行比较,并将这些更改应用到当前分支feature 的尖端”:也就是说,放入正确的合并,而不是错误的合并.

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

A - B - C___                     <-- master
  \      \  \
    D ----\-- M2                 <-- feature-alt
     \     \
      ----- M - E - R - F - M3   <-- HEAD=feature

其中M3M2 的副本(但是是常规的非合并提交)。

如果一切顺利,您现在可以完全删除 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(或者甚至只是 git commit 而没有 --amend 只是在顶部添加另一个修复,然后可能使用 git rebase -i 在稍后推送之前将其压扁)。它的作用是使R2M3 得到的完全相同,M2 的精挑细选副本,更正的合并。您避免重新进行合并,但当然,您必须弄清楚如何修复 bad 合并。

无论如何,所有这一切的最终结果都可以在没有-f 的情况下推送,因为它只是在所有先前的提交之上添加了次提交。


1例如,如果 A-to-D 将一行文本添加到 README.txtA-to-C 将不同的文本行添加到main.c,新的提交 M 会将每一行添加到每个文件中。但是,如果 A-to-Csame 行添加到 README.txt,在同一个地方,git 会知道只添加该新行的一个副本,而不是每个副本组的变化。新的README.txt 将只添加一行,不再重复。

2实际上是两个或更多,但多臂“章鱼”合并的工作方式基本相同。

【讨论】:

  • 这极大地加深了我对git的理解!
【解决方案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 合并,一切都会好起来的。当然,请确保正确执行合并。

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

【讨论】:

  • 这假设您没有将提交推送到远程。
  • @BenWheeler 好点,torek 已经做了一个,但无论如何我都更新了我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-18
  • 2013-12-27
  • 2017-08-03
  • 1970-01-01
  • 1970-01-01
  • 2012-07-06
相关资源
最近更新 更多