git rebase 未应用提交

git rebase is not applying a commit

我有一个看起来像这样的文件:

line 1
line 2
line 3
line 4

我的提交完成了 3 件事:

  1. 添加第 5 行 line 5
  2. 将第 2 行中的 L 大写
  3. 在第 2 行输入数字 22

我在 3 个单独的提交中完成了此操作,因此我有以下图表:

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

问题是在 rebase 之后,我完全失去了提交 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 应用。出了什么问题,我该如何解决?

git 文档中有一个关于 rebase 的警告,即 preserve-merges 选项不应与交互模式一起使用。文档中还针对此组合提到了一个错误:

BUGS The todo list presented by --preserve-merges --interactive does not represent the topology of the revision graph. Editing commits and rewording their commit messages should work fine, but attempts to reorder commits tend to produce counterintuitive results.

For example, an attempt to rearrange...

https://git-scm.com/docs/git-rebase

之前的评论和答案(包括我的评论)就您正在做的事情可能不起作用的原因而言实际上是正确的,但就有用的答案而言可能不是很好。

首先,虽然答案 "it's a documented bug" 可能不太令人满意,但我要指出的是,您示例中重新排序的 TODO 列表实际上并没有告诉 git 您的拓扑结构'试图重现。这是您想要做的事情不会那么容易的第一个线索,因为 将如何 您告诉 git 您想要什么拓扑?你会如何向 git 解释你的意思是 CD 都将父级从 B 更改为 F,而这种方式也不能读作 "put F before C, so that F' and D' each have B as parent";或 "put F before D, so that F' and C' each have B as parent"?当然,结果是 git 做了 none 那些事情,我想这是错误的一点。 (也就是说,我敢打赌它确实重写了 F,但随后使 F'C'D' 的父级都变成了 B,这使 F' 最终无法访问。)

无论如何,我想你的意思是问:你能从

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

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

无需手动重新解决 E 处的合并冲突。

答案是,你可以;但它不会像找到正确的 rebase 选项并发出一条命令那么简单。

事实是 rebase 旨在产生线性历史;它的支持者倾向于坚持线性历史是 "easier to read" 或 "cleaner",并且这比 "accurate" 或 "made up of commits that have been tested" 更重要。这些陈述在客观上并不普遍,但这正是 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)。此命令的目的是记录合并冲突的解决方案,以便如果在不同的合并中看到相同的冲突可以重新应用它,我认为它可以在这里应用。 (也就是说,我从不使用它,因为我不同意导致通常需要它的工作流程的前提,所以我不能说它在这里的服务器效果如何。)

我应该补充一点 if 合并是非冲突的(并且非 "evil";即默认合并策略和选项产生所需的结果),并且在所需的重新排序下保持不冲突,则以下过程将代替上述过程:

    // 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 到位的情况下,这种(概念上更简单的)方法即使在冲突的情况下也可能有效。