Git rebase未应用提交

3

我有一个文件,其内容如下:

line 1
line 2
line 3
line 4

我有三个提交做了以下三件事:

  1. 添加第五行,内容为line 5
  2. 将第二行中的L大写
  3. 将第二行中的数字改为22

我已经在三个单独的提交中完成了这些操作,所以我有以下的提交记录:

*   F - add line 5
*   E - Merge branch 'branch' into 'master'
|\
| * D - change 2 to 22
* | C - capitalize line 2
|/
*   B - adding line numbers
*   A - Initial commit

我的文件现在看起来像这样:

line 1
Line 22
line 3
line 4
line 5

很明显,在E处合并时发生了冲突,我手动解决了它。我想将F移动到B之后(最终将它们压缩在一起,但这是一个单独的问题)。因此我运行了git rebase -i --preserve-merges A命令,编辑器打开后,我进行了以下操作:

pick B adding line numbers
pick F add line 5
pick C capitalize line 2
pick D change 2 to 22
pick E Merge branch 'branch'

问题是在变基之后,我完全丢失了提交 F。现在我的图形看起来像这样:
*   E - Merge branch 'branch' into 'master'
|\
| * D - change 2 to 22
* | C - capitalize line 2
|/
*   B - adding line numbers
*   A - Initial commit

F已经不存在了。它没有被rebase应用。出了什么问题,我该如何解决?


我尝试过,如果不使用--preserve-merges,它可以正常工作。这不是一个答案,但在这里有一个相当深入的关于如何在--preserve-merges的上下文中使用rebase的概述。 - larsks
如果我不使用“--preserve-merges”,那么我就必须手动再次合并,对吧?这里并不是很大的问题,但在具有更大、更复杂历史记录的仓库中则是一个大问题。 - ewok
@larsks 所以在过去,当我没有使用--preserve-merges时,合并提交就会丢失,我不得不手动重新合并。在这个历史上下文中,如果我执行 git rebase -i A 且不使用 --preserve-merges,即使我没有对历史记录进行任何更改,重置也会因冲突而失败。 - ewok
变基不会“失败”。确实存在冲突,需要解决,但这很正常。 - larsks
1
@ewok - "保留合并"选项并不意味着冲突解决工作被保留,它只是表示rebase尝试保留提交的拓扑结构。此外,文档中明确指出,“保留合并”选项与“交互式”选项在重新排序提交时不兼容。 - Mark Adelsberger
显示剩余2条评论
2个回答

2
先前的评论和答案(包括我的评论)在说明为什么您正在做的事情可能行不通方面是事实正确的,但可能并不是有用的答案。
首先,虽然答案“这是已知的错误”可能并不令人满意,但我想指出,在您的示例中重新排序的TODO列表实际上并没有告诉git您要复制哪种拓扑结构。这是您想要做的事情不那么容易的第一个线索,因为您如何告诉git您想要什么拓扑结构呢?您如何向git解释,您的意思是让C和D都从B更改为F的父级,而不会被理解为“将F放在C之前,以便F'和D'各自具有B作为父级”或“将F放在D之前,以便F'和C'各自具有B作为父级”?当然,结果是git没有执行任何这些操作,这也许就是有缺陷的部分。(话说回来,我敢打赌它确实重写了F,但然后将F'、C'和D'的父级都设为B,这使得F'最终无法访问。)
总之,我认为您的问题只是:可以从哪里开始。
A -- B - C - E -- F <--(master)
      \     /
       - D -

to

A -- B -- F' - C' - E' <--(master)
            \      /
             - D' - 

不需要手动重新解决合并冲突,就可以在 E 处完成。

答案是可以的,但不像找到正确的 rebase 选项并发出单个命令那么简单。

事实上,rebase 的设计目的是生成线性历史记录;其支持者倾向于坚持线性历史记录“更易于阅读”或“更清晰”,并且这比“准确”或“由已测试过的提交组成”更重要。这些陈述并非客观普遍存在,但这就是 rebase 工具的设计目的。 preserve-merges 选项试图让您既能拥有优点又能克服缺点,但它有很多问题(不仅是使用 -i 时的错误)。它可能适用于某些简单情况,但通常尝试通过您想要保留的合并进行变基最好只会带来麻烦。

那么应该怎么做呢?以下是一种方法...请注意,当引用提交时,我已经给出了一个表达式,以在此示例中解析到正确的提交,而不仅仅是使用占位符字母。

git checkout master
git branch temp
git rebase -i master~4
// in the editor, move F after B and remove D

此时,您应该已经具备了

       F' -- C' <--(master)
      /
A -- B - C - E -- F <--(temp)
      \     /
       - D -

然后您需要变基D。(在这种情况下,由于只有一个提交,您可能可以使用cherry-pick代替。)

git checkout temp^^2
git checkout -b branch
git rebase --onto master^ master branch

目前为止,您已经

         D' <--(branch)
        /
       F' -- C' <--(master)
      /
A -- B - C - E -- F <--(temp)
      \     /
       - D -

您需要重新合并。有几种方法可以解决这个问题,但简化观察是您的最终内容应与原始历史记录中的 F 相匹配。因此,有效的解决方案是:

git checkout master
git reset --hard $(git commit-tree -p master -p branch -m "Merging branch into master" temp^{tree})

(对于-m,你可以使用原本在E处的任何提交消息。) 然后你可以删除temp分支,并且如果完成了,则删除branch分支。

在更复杂的情况下,从现有提交中获取合并结果TREE可能不起作用,因为可能没有现有提交匹配所需的状态。在这种情况下,我认为你最好尝试一下git rerere(文档在https://git-scm.com/docs/git-rerere)。此命令的目的是记录解决合并冲突的结果,以便在不同的合并中看到相同的冲突时可以重新应用它,我想它也可以在这里应用。(话虽如此,我从不使用它,因为我不同意需要常规使用它的工作流程的前提条件,所以我无法确切地说它在这里的表现如何。)

我还应该补充说明,如果合并没有冲突(并且非“邪恶”;即默认合并策略和选项产生所需结果),并且保持在所需的重新排序下没有冲突,则以下步骤将代替上述步骤:

    // first rewrite F
git checkout master
git checkout -b temp
git rebase --onto master~3 master^
    // now move master to exclude the original F
git checkout master
git reset --hard HEAD^
    // now move the rest of the history
git rebase --preserve-merges temp

因此,在使用rerere的情况下,即使在存在冲突的情况下,这种(概念上更简单的)方法也可能起作用。


1
在git文档中,关于rebase的警告指出,--preserve-merges选项不应与交互模式一起使用。文档中还提到了这种组合存在的一个bug:
BUGS 通过--preserve-merges --interactive呈现的待办列表并不代表修订图的拓扑结构。编辑提交和修改它们的提交消息应该可以正常工作,但尝试重新排序提交往往会产生令人难以理解的结果。
例如,尝试重新排列...
请参见https://git-scm.com/docs/git-rebase

1
那么,是否有可能像这样进行合并提交,并将提交“F”移动到合并之前,而不会丢失合并的事实呢?例如,如果我删除“--preserve-merges”,那么“F”就会出现,但我会失去合并提交,而只得到一个单一的历史记录。 - ewok
1
@ewok,我认为这应该可以工作。但是在rebase时可能会出现冲突。如果发生这种情况,您可以解决它,而不会在历史记录中获得合并提交或类似的内容。 - snap
@ewok为什么?难道在rebase之后的新历史不更容易理解吗? - snap
1
@ewok 也许在合并之前你应该重置你的主分支。将两个分支(在你的情况下只有一个分支)进行变基,然后再次将其合并到主分支上。这是更加手动化的工作,但它应该会得到符合你要求的历史记录。 - snap
1
没错,但你仍然可以从F中挑选更改,因为旧的提交和结构仍然存在。或者你可以先将分支进行变基,然后重置并合并主分支,以避免需要挑选更改。 - snap
显示剩余4条评论

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