Git:在合并时意外丢失文件后,撤销已推送的合并

4
一位经验不足的用户更改了release,然后合并到了dev。糟糕,出现了合并冲突!合并状态显示了他们自己的有冲突的文件以及其他团队成员的所有修复。作为一个多疑和谨慎的用户,他们说:

那些不是我的文件,我是否意外地改变了它们?啊哈,我应该discard它们!

现在,合并提交仅包含他们的更改,放弃了分支中的所有其他更改(可能是大量的工作)。他们将dev推送到上游,直到另一个团队成员注意到有问题才发现错误。
大多数指导建议:
  1. 如果分支还没有被推送: git reset
  2. 撤销合并:git revert -m 1 <commitId>。但这只会还原数据(也就是只还原了不幸用户的更改),而不是撤消历史记录。任何未来尝试进行merge的操作都会忽略丢失的更改,因为历史记录意味着它们已经被集成了。
  3. 重写历史,使用rebasereset,然后跟随git push origin -f。这意味着团队的其他成员必须将强制dev分支同步。如果团队很大,或者错误没有被快速发现,或者存在复杂的CI工具-这将成为一个非常痛苦的练习。
在我看来,这是Git设计中一个关键的疏忽。几乎没有工具可以识别或从这种情况中恢复。git diff不显示被放弃的更改,git revert也无法撤销那些被放弃的更改。有没有更好的方法来防止和解决这个问题?
1个回答

4
如Linus所述(https://mirrors.edge.kernel.org/pub/software/scm/git/docs/howto/revert-a-faulty-merge.txt):
撤销普通提交只会有效地撤销该提交所做的更改,而且相当简单。但是,撤销合并提交还会撤销提交所更改的数据,但它对合并对历史记录的影响完全没有任何作用。
因此,合并仍将存在,并且仍将被视为连接两个分支的合并,未来的合并将看到该合并作为最后共享状态,撤销合并带来的还原不会对此产生任何影响。
因此,“还原”撤消了数据更改,但在某种意义上非常不是一个“撤消”,因为它不会撤消提交对存储库历史记录的影响。
好的,这解释了为什么“还原”不是一个有效的策略,但我们可以做些什么呢?让我们考虑以下内容:
p---Q---r---s---M---t---u---   dev
     \         /
      A---B---C-------D---E    feature
  • ABC是功能/发布分支的工作
  • M是错误的合并,AB的更改被丢弃了,但C被保留了下来
  • DE是在feature上的后续工作
  • 其余的是主线分支dev上无关的更改

只要M存在于dev上,它就假定ABC的历史已经集成了,即使AB的增量缺失。为了在不改变dev的历史记录的情况下恢复它们,我们需要在替代历史中重新创建增量(即新的提交ID)。

如果只有几个提交,您可以逐个将每个提交用cherrypick复制到dev中,因为cherrypicking会将数据复制到新的提交ID中。然而,这对于大型或复杂的分支历史不太可行。

下一个选项是使用rebase --no-ff从中重新创建一个新的feature分支,可以从中合并丢失的更改。

git checkout E
git rebase --no-ff Q

这将创建以下内容:

      A'--B'--C'-------D'--E'    feature-fixed
     /                      \
p---Q---r---s---M---t---u---M2   dev
     \         /
      A---B---C--------D---E     feature

原始合并 M 可能只是由于合并冲突而成为问题。这种方法的一个问题是,不仅必须正确解决 ABC 中的原始冲突,而且现在您还必须应对 DE 和 TU 中可能出现的新冲突源。在紧急情况下,弄清楚发生了什么可能会很棘手。

首选解决方案:

p---Q---r---S-------M---t---u-----M3      dev
     \       \     /              /
      A---B---\---C----D---E     /        feature
               \   \            /
                ----M2----------          fix

一种更简单的策略是使用您可能熟悉的工具,通过正确重新创建合并使用 squash 提交(M2)。这将创建一个新的提交 ID(新历史记录),因此可以成功地将 AB 的增量集成回主线。该方法还隔离了可能导致合并冲突的源,允许您首先纠正错误,然后处理上游更改。
方法:
在坏合并(M)到达之前从 dev 分支出。
git checkout -b fix S

现在你可以从头开始执行一个修正合并。Squash标志将这些更改压缩成一个单独的提交,但更重要的是它将生成一个新的提交ID。

git merge --squash C

很可能在这一点上,您需要解决冲突,但是M2现在表示M应该最初包含的所有数据。您现在可以像往常一样将其合并到dev中。
git checkout dev
git merge fix

再次提醒,合并冲突可能会出现,但在此时刻(M3)之后,您已经恢复了丢失的数据。您现在可以按照正常方式继续操作,例如您可以自由地将来自feature的更改合并到dev或进行其他任何常规操作。这也意味着没有其他团队成员需要重置他们的本地dev分支,因为当他们下次执行pull操作时,它将恢复。


尽管我已经找到了一个解决方案,但我仍然对在服务器端钩子上检测和警告此错误的方法感兴趣。 - Peter Cardwell-Gardner
但是如果我们使用“压缩”(squash)的话,那么会破坏基于行的 git blame 历史记录,除非我们进行进一步的诡计,对吧? - Mark VY

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