繁体   English   中英

Git-恢复与一个分支的历史合并

[英]Git - Revert merge with history of one branch

我有出色的工作来清理功能强大的git存储库。 过去有人将整个linux内核src合并到存储库中(包含所有650k提交)。 我知道合并和父提交的提交ID。 当然,在将linux与masterbranch合并之间的时间有所变化,因此此刻树看起来与此类似。

-x-x-x-x-LinuxMerge-x-x-x-x-x-x-x-x-x-today

我想要的是还原LinuxMerge commit incl。 历史。 这可能吗?如何?

您可以通过重新定标来撤销此操作。

如果您从这里开始...

-x-x-x-x-LinuxMerge-x-x-x-x-x-x-x-x-x-today

...那么您可能正在谈论的是:

-x-x-x-x-x-x-x-x-x-x-x-x-x-today
        /
-linus-/

让我们标记更多的提交:

-x-x-x-prev-merg-post-x-x-x-x-x-x-x-today
             /
     -linus-/

因此,您想将prevpost粘合在一起,并丢弃merg 该命令是:

git rebase merg today --onto prev

(请注意,在命令中,我们提到merg ,而不是post ;这是在git声明提交范围的典型“ + -1”问题)。

此rebase命令将添加新的提交行,并更改today分支以指向新的尾部:

          post'-y-y-y-y-y-y-y-today'
         /
-x-x-x-prev-merg-post-x-x-x-x-x-x-x-today
             /
     -linus-/

而且,如果您只是忽略了较旧的内容,则可以扩展为:

-x-x-x-prev-post'-y-y-y-y-y-y-y-today'

重新设置还将更改“ today分支,以指向此ASCII图片中标记为“ today'的提交。

请注意, post'y提交(以及today' )都将具有与原始提交不同的哈希值,它们不是“相同”的提交。

如果没有其他标签或分支指向导致linus的历史记录,那么最终将通过git垃圾收集清除这些提交和相关对象(您可以使用git gc强制确保此操作)。

我认为这个问题引起了一些困惑,因为您将其表述为想“恢复”错误-“恢复”意味着特定于git的东西。 我知道您的意思不是git这个词的意思,因为git的意思不是“恢复历史记录”之类的词。

因为您想完全撤消更改,所以第一步是重写历史记录。 AnoE的答案显示了执行此操作的一种方法,假设只有一个ref可以实现错误合并,并且没有ref与错误合并之间的合并提交。

如果有多个裁判,则您需要做更多的事情。 例如,如果您有

x -- x -- x --- ML -- x -- A -- x <--(master)
               /            \
(linux history)              o <--(branch_one)

完成变基会给你

            x' -- A' -- x' <--(master)
           /
x -- x -- x --- ML -- x -- A -- o <--(branch_one)
               /
(linux history)

然后,您需要移植o commit,例如

git rebase --onto A' A branch_one

(用提交ID或其他命名适当提交的表达式替换AA' )。

如果存在需要重写的合并,则问题更大。 默认情况下, rebase命令将尝试写入线性历史记录。 您可以使用--preserve-merges选项告诉您要保留合并拓扑,但是它可能无法正常工作。 如果合并提交有冲突,则必须重新解决它。 更糟糕的是,如果合并提交没有冲突,但是最初不是使用默认合并结果完成的,那么rebase不会正确地重新创建合并(或其任何子级)。

因此,重新设置基准的唯一安全方法是分段处理,在遇到合并时手动重新生成合并。

另一种选择是使用git filter-branch而不是rebase ; 但这也很棘手。 仅当您可以编写脚本删除合并引入的所有内容时,它才可行。 例如,如果Linux历史比你自己的工作不同的路径,这样你可以清理由内容的给定实例rm荷兰国际集团某些路径,那么你可以使用filter-branch

(由于这是一个可能对您而言可行或不可行的选项,因此,我现在将不详细说明详细步骤filter-branch文档可以填补空白。基本上,您将使用parent-filter绕过合并提交(通过将以下提交重新绑定到第一个父提交上),再加上index-filtertree-filter以从后续提交中删除linux文件。)

一种或另一种方式是,一旦清除了历史记录,您仍将所有这些历史记录存储在存储库的数据库中。 至少您需要确保没有任何内容引用该历史记录。 然后,它将最终被gc清除(或者您可以强迫这种情况尽快发生)。

通常,这意味着您必须找到可以达到linux历史记录的所有引用。 由于重写操作移动了“您的”引用,因此这很可能包括随Linux历史记录本身引入的所有引用(分支或标签)。 因此,您只想删除它们。

还有一些reflog可以(间接地)到达linux历史记录,并且gc无法删除以这种方式可以到达的历史记录。 坦率地说,在这一点上,最简单的操作可能是重新克隆存储库(因为新的克隆应该只获取当前引用及其历史记录),并用结果替换origin。

如果您想修复现有的仓库而不是出于任何原因而重新克隆,那么下一步将是清除reflog(我通常只是rm -r .git/logs ),然后运行一个激进的gc (请参阅gc文档)

您在这里有几个选择。

选项1:重写历史记录

如果您能够重写 master分支的历史记录而不会产生任何后果,则实现所需目标的最快方法是简单地使用git rebase --onto完全删除合并提交:

git checkout master
git rebase --onto <SHA-1-of-the-linux-merge>^ <SHA-1-of-the-linux-merge>

这意味着:“ 从合并提交本身开始,在合并提交的第一个父节点之上重新设置master服务器 ”。 这将有效地删除合并提交,并将所有后续提交应用到其第一个父级之上。 您可以在此处阅读有关git rebase --onto如何工作的更多信息。

选项2:还原合并

如果要避免重写历史记录,可以始终使用git-revert “ LinuxMerge”提交:

git revert --mainline-parent 1 --no-commit <SHA-1-of-the-linux-merge>

--mainline-parent选项告诉Git您要将合并提交还原哪个父提交。 在这种情况下,您想还原为第一个父节点,即Linux内核被合并的提交。

文档中

通常,您无法还原合并,因为您不知道合并的哪一侧应被视为主线。 此选项指定主线的父级编号(从1开始),并允许还原以相对于指定的父级撤消更改。

请注意,以这种方式还原合并将导致以后同一分支的合并排除由还原的合并最初带来的提交:

还原合并提交将声明您永远不需要合并带来的树更改。 结果,以后的合并将仅引入由不是先前还原的合并的祖先的提交引入的树更改。 这可能是您想要的,也可能不是。

但是,在这种情况下,听起来您很快就不会再合并Linux内核。

至于--no-commit选项,它使您可以进行试运行以查看是否在未实际创建提交的情况下在工作目录中遇到任何冲突。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM