撤消已发布的合并并重新应用原始更改而不重写历史记录

Posted

技术标签:

【中文标题】撤消已发布的合并并重新应用原始更改而不重写历史记录【英文标题】:Undo Published Merge and Re-Apply Original Changes Without History Rewrite 【发布时间】:2021-09-01 14:12:40 【问题描述】:

晚上好,

在我们的一个实习项目中,我们遇到了以下 git 问题。我们有两个分支,mainfeat/smth,它们在开发过程中都出现了分歧。从feat/smthmain 创建了一个拉取请求,显示了这些冲突。这些冲突是使用 GitHub 的图形工具解决的,基本上是执行从 mainfeat/smth 的合并以解决冲突。此合并错误,导致文件损坏,暂时没有注意到。

因此,feat/smth 分支被合并(使用 GitHub 的拉取请求)到发现错误的main。为了恢复更改,学生们在 main 上的合并提交上运行了 git revert 并推送了所有这些更改。现在,问题是我们希望从我们的feat/smth 分支中获得更改并将它们“再次”合并到main。显然,这不起作用,因为提交已经合并,因此出现在 main 上。

假设我们不想强制推送,因为许多其他开发人员已经检查了代码并继续工作。我的第一个想法是使用git reset -m <commit_sha> 在本地恢复两个合并并重新执行合并。在合并期间,我从第一次合并尝试中“避免”了错误。我已经隐藏了文件,feat/smth 分支并应用了我的隐藏。使用这种方法,我们从合并中得到了正确的更改,但是分支上的所有其他更改都不再应用,因为它们仍然在主分支上恢复。我们feat/smth 分支的提交已经存在于main 上并且被还原。我们使用此尝试引入的唯一更改是来自合并的正确更改。

这就是我基本上被卡住的地方。为了撤消推送到 main 的合并并重新应用分支中的原始更改,正确的方法是什么?我的第一个想法是 cherry picks,尽管这可能会因为大量提交而变得很麻烦(并且可能导致很多合并冲突?)或者获取正确版本的“硬拷贝”分支,然后将它们复制粘贴到主分支上。但是,这也可能导致不需要的更改。如果已经对 main 进行了更改,后一种方法可能会变得棘手。作者必须仔细检查所有更改,并区分来自feat/smth 分支的更改和来自main 分支的更改。此外,copy paste 感觉它不是正确的“git 方式”。有任何想法吗?我真的不知道如何解决“git方式”。

我已经绘制了 git 历史图,也用于说明目的。

非常感谢!我很好奇这些方法。

【问题讨论】:

有一个著名的文件关于我认为是这个问题,你可能想看看:github.com/git/git/blob/master/Documentation/howto/… 【参考方案1】:

更新:我最初误读了您的问题,以为您想添加一个提交来修复合并,但实际上您似乎需要从原始分支中删除一个。在这种情况下,我只需将您的功能分支(如图所示的 4 次提交,没有合并提交)重新设置为 main 并解决当时的冲突。那么在合并到main 之前就不需要合并提交了。事实上,这就是我首先要做的,而不是合并main 来解决。 (我更喜欢 rebase 而不是合并的原因之一正是因为发生了什么;大多数人发现在提交本身中检测到错误的合并工件比在合并提交中更容易。)

话虽如此,如果您不喜欢 rebase 工作流程,并且希望为这 4 个提交保留原始合并基础并重做合并,那么您将需要执行下面 #2 中描述的重写。首先检查您的功能分支并重置为合并提交之前的提交(在图表中#1 标记的右侧)。然后rebase --no-ff,然后合并main,然后创建您的 PR。

注意:在您的具体情况下,您执行下面的 #1 是没有意义的。

原始答案: 更一般的情况是您后来决定仍希望原始分支中的所有提交。在这种情况下,您通常有两种选择:

    还原已还原的合并提交。这样你就可以再次合并你在功能分支上的提交。我建议每当您恢复任何提交时,尤其是当您恢复一个恢复时,在提交消息的详细信息中解释您为什么这样做,并包括被恢复的提交的提交 ID。 使用git rebase --no-ff [merge-base-commit] 重写您的功能分支(不是master)。 (请注意,合并基本提交 ID 是 git merge-base main feat/smth,这是图表中 main 底部的第二次提交。)也许您听说过 --no-ff 用于合并,但对于 rebase 也存在相同的选项。类似于合并、变基的含义,它只是意味着:“即使你可以快进,也要重写提交”。更改提交 ID 后,您将能够合并所有这些提交,以及包含修复的新提交。

顺便说一句,选项 2 几乎就是您在按顺序挑选提交列表时所想的,除了变基是自动化的并且不太容易出错。只要您不更改父提交 ID,就不可能与cherry-picks 或 rebase 发生合并冲突。

至于走哪条路,我想说这是一个品味问题。如果您不了解正在发生的事情,revert 的 revert 会让人感到困惑。但是我认为如果这是唯一的更改,我可能会倾向于还原还原(例如,您决定不想要合并,然后在不添加新提交的情况下改变了主意)。如果您经常有新的提交,如果我没有太多会重复的提交,我可能更喜欢rebase --no-ff。也许我会重写 5 而不是 50。

【讨论】:

以上是关于撤消已发布的合并并重新应用原始更改而不重写历史记录的主要内容,如果未能解决你的问题,请参考以下文章

如何撤消对特定文件的已提交更改而不进行变基?

git revert 回到某个提交而不更改历史记录并创建一个新的提交,如 git revert

markdown 撤消提交,重写历史记录

重新加载 IFRAME 而不添加到历史记录

如何将Tkinter Text小部件的撤消/重做历史记录复制到另一个小部件中

git:清理 git 历史记录并仅在 master 中保留合并的提交