【问题标题】:Git - Revert merge with history of one branchGit - 恢复与一个分支的历史合并
【发布时间】:2018-05-28 12:26:57
【问题描述】:

我有一项很棒的工作来清理一个搞砸的 git 存储库。过去有人将整个 linux 内核 src 合并到存储库中(包含所有 650k 提交)。我知道来自合并的提交 ID,也来自父级。当然在合并 linux 和 masterbranch 之间的时间是有变化的,所以现在的树看起来像这样

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

我想要的是恢复 LinuxMerge 提交,包括。这的历史。这可能吗?如何?

【问题讨论】:

    标签: git merge revert


    【解决方案1】:

    我认为这个问题引起了一些混乱,因为您将其表述为想要“恢复”错误 - 而“恢复”意味着特定于 git 的东西。我知道你的意思不是 git 那个词的意思,因为 git 的意思是像“恢复历史”这样的短语。

    因为您想完全撤消更改,所以第一步是重写历史记录。 AnoE 的回答显示了一种方法来做到这一点,假设只有一个 ref 可以到达错误的合并,并且在该 ref 和错误的合并“之间”没有合并提交。

    如果有多个 refs,您需要做更多的事情。例如,如果你有

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

    完成rebase会给你

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

    然后你需要移植 o 提交,类似于

    git rebase --onto A' A branch_one
    

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

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

    因此,唯一安全的 rebase 方法是分段,在遇到合并时手动复制它们。

    另一种选择可能是使用git filter-branch 而不是rebase;但这也很棘手。仅当您可以编写脚本删除合并引入的任何内容时,它才可行。例如,如果 linux 历史记录与您自己的工作位于不同的路径中,那么您可以通过 rming 某些路径来清理给定的内容实例,那么您可以使用 filter-branch

    (由于这对您来说可能可行也可能不可行,所以现在我不会详细说明步骤。filter-branch 文档可以填补空白。基本上你会使用@987654335 @ 绕过合并提交(通过将以下提交重新设置为第一个父提交),加上 index-filtertree-filter 从后续提交中删除 linux 文件。)

    不管怎样,一旦你清理了历史记录,你会仍然在你的回购数据库中拥有所有这些历史记录。至少您需要确保没有引用该历史记录。然后它最终会被gc 清除(或者你可以强制它更快发生)。

    这通常意味着您必须找到任何可以访问 linux 历史记录的 refs。由于重写移动了“你的”引用,这可能包括任何与 linux 历史本身一起拉入的引用(分支或标签)。所以你只想删除那些。

    还会有可以(间接)访问 linux 历史记录的 reflog,而 gc 无法删除以这种方式可访问的历史记录。老实说,此时最简单的做法可能是重新克隆 repo(因为新的克隆应该只获取当前 refs 及其历史记录)并用结果替换 origin。

    如果您想修复现有的 repo 而不是出于任何原因重新克隆,下一步是清除 reflogs(我通常只是 rm -r .git/logs),然后运行积极的 gc(参见 @987654341 @文档)

    【讨论】:

    • 感谢您的回答。我明白了,很难解决这样的问题......但是感谢您提供这些信息!
    【解决方案2】:

    您可以通过变基来撤消此操作。

    如果你从这里开始......

    -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'
    

    rebase 还将更改 today 分支以指向此 ASCII 图片中标记为 today' 的提交。

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

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

    【讨论】:

    • 不过,这仍然没有清理数据库 - 这有点讽刺,因为您为同一件事打电话给 Enrico 的答案。充其量,这个过程可能使历史最终符合 gc 条件,假设 linux 历史没有引入任何 refs,并且该来源在gc 不时发生的上下文中运行跨度>
    • 感谢@MarkAdelsberger,我会更新答案。
    • 感谢您的回答。我明白了,很难解决这样的问题......但是感谢您提供这些信息!
    • @alabamajack:不,这并不难,真的 - 解释有点长。 :) 实际上,它只是一个“变基”命令(前提是一切顺利......)。
    【解决方案3】:

    这里有几个选项。

    选项 1:重写历史记录

    如果您能够重写 master 分支的历史记录而不会产生任何后果,那么实现您想要的最快的方法就是完全删除合并提交git rebase --onto:

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

    这意味着:“rebase master 在合并提交的第一个父级之上,从合并提交本身开始”。这将有效地删除合并提交并将所有后续提交应用到其第一个父级之上。您可以阅读更多关于git rebase --onto 工作原理here 的信息。

    选项 2:还原合并

    如果您想避免重写历史记录,您可以随时使用git-revert 恢复“LinuxMerge”提交:

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

    --mainline-parent 选项告诉 Git 您要将合并提交的哪个父级恢复。在这种情况下,您想恢复到 first 父级,即 Linux 内核合并的提交

    来自documentation

    通常您无法恢复合并,因为您不知道合并的哪一侧应该被视为主线。该选项指定主线的父级编号(从 1 开始),并允许 revert 反转相对于指定父级的更改。

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

    还原合并提交表明您永远不会希望合并带来的树更改。因此,以后的合并只会带来提交引入的树更改,这些提交不是先前恢复的合并的祖先。这可能是也可能不是您想要的。

    但是,在这种情况下,您似乎不会很快再次合并 Linux 内核。

    至于--no-commit 选项,它可以让您进行试运行,以查看您的工作目录中是否有任何冲突,而无需实际创建提交。

    【讨论】:

    • 我知道这一点,但我认为我是假的。我做到了并得到error [...] hint: after resolving mark corrected paths [...] git status 显示:You are currently reverting commit 1234567 . (fix conflicts and run "git revert --continue") [...] Unmerged paths: (use "git reset HEAD &lt;file&gt;..." to unstage) (use "git add &lt;file&gt;..." to mark resolution) added by us: file.shgit add file.sh 之后我得到git status: (all conflicts fixed: run "git revert --continue") 但这样做总是说You are currently reverting commit 1234567. nothing to commit, working directory clean
    • 您是否包含了-m 1 选项?您收到的错误消息告诉您,合并之前所做的一些更改与之后所做的更改发生冲突。此时,您需要resolve the conflicts 才能继续还原。
    • 如果您使用--no-commit 选项运行git revert,您可以在解决冲突后简单地使用git commit 提交更改。
    • 这并不能解决问题。是的,git revert 将撤消更改,但只需添加一个“反转”所有文件更改的新提交。他们仍将有 650K 的提交记录在历史记录中,其中有数百兆字节的历史记录。
    • 恢复提交会导致很多问题。我个人会避免它。
    猜你喜欢
    • 2016-01-11
    • 1970-01-01
    • 2015-01-15
    • 1970-01-01
    • 1970-01-01
    • 2011-07-20
    • 2015-04-17
    • 1970-01-01
    • 2019-09-28
    相关资源
    最近更新 更多