我无法真正回答“好处”问题,但我可以描述为什么 rebase 会按原样运行以及如何获得所需的结果。
Rebase,无论是否交互,都接受“三个类似分支或提交”的参数,它称之为 newbase、upstream 和 branch:
git rebase [-i | --interactive] [options] [--exec <em>cmd</em>] [--onto <em>newbase</em>]
[upstream [branch]]
如果您将其中的部分或全部排除在外,rebase 仍会使用它们,它只是自行找到它们:
-
branch 默认为当前分支,或HEAD(包括分离的头部)。
-
upstream 默认为当前分支的上游,由git branch --set-upstream-to 或类似设置。无论您在此处指定什么或默认值,都会将其提供给git rev-list,以使其停止接受提交,即,将被重新定位的提交是由git rev-list <em>upstream</em>..HEAD 打印的提交。
-
newbase 默认与 upstream 具有相同的提交 ID。
(如果你在一个足够新的 git 中提供 --fork-point 以拥有 --fork-point,这些都会有所修改,但这里的一般想法仍然适用:要重新设置的提交是根据上游的“之后”选择的,向上到并包括 HEAD,默认情况下。)
当您让git pull 为您运行git rebase 时,它会提供,作为upstream,当前分支的实际上游(同样,在较新的 gits 中由 fork-point 计算修改,但这应该没有如果您从中获取的实际上游没有完成自己的变基,则效果)。 (--onto 通常也与上游相同。在这种情况下,它等于git rebase [options] --onto origin/master origin/master master。)
如您所见(通过运行git rev-list origin/master..master),这意味着“请将B 和C 重新设置为D”。添加--preserve-merges 只需使用交互式机制并根据请求保留合并,在根据请求重新设置两个提交之后。
如果你只是简单地运行git fetch,你将像往常一样获得提交D,并将你绘制的图形作为第一个:
D origin/master
/
| C master
| |\
| | B
\|/|
A :
|
:
您现在可以尝试手动运行git rebase -p 以变基提交C,将B 指定为其“上游”,以便变基不会复制提交B,并指定D 作为 --onto 提交,以便 C 重新基于 D:
$ git rebase -p --onto origin/master master^2 master
不幸的是,这会产生一个新的合并提交,其两个父级是origin/master(提交D)和原始master^(提交A)。 (这不是很明显的原因,尽管它显然与交互保留模式在内部的工作方式有关。)
因此,在这种情况下,诀窍是进行自己的合并:
$ git branch -m master old-master
$ git checkout master
(这使得新的master 指向提交D),然后:
$ git merge old-master^2
(这会对未修改的B 进行新的合并)。
不过,您还提到:
...这是有问题的,因为B合并中的所有冲突都必须重新解决。
不幸的是,您无法真正避免这种情况,因为D 与A 可能存在很大差异,以至于实际的合并冲突解决方案也不同。
如果您确定它们(分辨率)不应该(不同),您也可以通过从旧提交 C 获取合并结果来回避这个问题。例如,假设冲突发生在dir1/file1 和dir2/file2 中,而A 和D 之间的差异都在README.txt 或类似的地方。然后,当您执行上述合并时,您可以非常简单地“解决”冲突:
$ git checkout old-master -- dir1/file1 dir2/file2
提取这些文件的提交C 的版本,更新您的索引和工作树以恢复您之前的合并分辨率。你的冲突现在已经解决了(和以前一样),你可以git commit结果合并。