用一个具体的例子来回答这个问题会更容易。 原则上很简单:git rebase 表示:复制提交,就像 git cherry-pick 一样。这里失败的是复制步骤。
我们从分支 R(用于变基)开始并选择一些新的目标分支 T(用于 Target)。然后我们确定一些要复制的提交:这些提交大部分是在 R 上而不是在 T 上的提交。 (在一个善意的谎言中,the git rebase documentation 暗示这些是由git log T..R 或git log T..HEAD 列出的提交。这基本上是正确的,除了添加了三个额外的小提琴,或者可能是“减去”这个词.)
然后,在列出要复制的提交哈希后,Git 对目标分支进行 detached-HEAD 签出,以便当前 (HEAD) 提交与分支 T 标识的提交相同.请注意,我们不是 on 分支 T,我们只是在同一个 commit 上。现在,对于我们保存其哈希 ID 的每个提交,Git 有效地(有时是字面上)运行git cherry-pick <hash-id>:
....... HEAD
v
...--o--o--o--o--o--o <-- T
\
A--B--C <-- R
我们运行git cherry-pick A 来复制提交A。如果我们调用新副本A',结果如下:
A' <-- HEAD
/
...--o--o--o--o--o--o <-- T
\
A--B--C <-- R
然后我们运行git cherry-pick B 将B 复制到B':
A'-B' <-- HEAD
/
...--o--o--o--o--o--o <-- T
\
A--B--C <-- R
我们重复直到我们复制了所有提交,然后作为最后一步,Git 从原始链“剥离分支标签”R 并将其粘贴到复制链的末尾:
A'-B'-C' <-- R (HEAD)
/
...--o--o--o--o--o--o <-- T
\
A--B--C <-- (only in reflogs and ORIG_HEAD)
冲突和其他问题
每个副本(每个挑选步骤)都是使用 Git 的 merge 机制 完成的,或者我称之为 to merge 的“动词形式”(与名词或形容词形式,a merge 或 merge commit——git merge 通常在做动词形式 to 之后进行 merge commit合并,我们只使用了前半部分)。例如,当您将添加新文件的提交与也添加相同新文件的更改合并时,您会遇到添加/添加冲突。
最有可能发生这种冲突的地方是在从A 复制A' 时,因为cherry-pick 操作的合并基础 是被复制提交的父提交.合并操作 - to merge 动词 - 将此合并基础与 两个 提交进行比较。在这种情况下,两个提交是提交A 本身,以及分支T 的最尖端提交,即T 指向的提交。让我们称之为X,并用*标记合并基础:
? [merge in progress] HEAD
/
...--o--o--*--o--o--X <-- T
\
A--B--C <-- R
如果同时提交A 和X,与* 相比,添加文件path/to/file.txt,您会遇到添加/添加冲突。樱桃挑选停止,留下正在进行的合并冲突。你必须解决它并告诉 rebase 恢复。
如果A 添加path/to/file.txt 而它在提交X 中不是 怎么办?通常,这没有问题:Git 只会创建文件并将其放入新的提交中。根本不会有添加/添加冲突,只是一个完美的副本A'(我们会继续做其余的变基)。
但也许文件path/to/file.txt由于某种原因存在于工作树中(例如,作为一个未跟踪文件,也可能被忽略)。在这种情况下,Git 不能只是从提交 A 中复制 path/to/file.txt,从而覆盖工作树版本。您会在此处收到错误消息,就像常规合并冲突一样。你必须解决问题并告诉 rebase 恢复。
对于位于索引/暂存区域(因此可以在各种提交中)但标记为 --assume-unchanged 或 --skip-worktree 的文件,这里还有一些其他情况。在较新版本的 Git 中,精确的错误消息会识别“将被覆盖”的文件是否真正未跟踪,或者像这样标记。 (这在旧版本的 Git 中可能也是如此,但我不记得手头也没有检查过。)这就是为什么一个具体的例子更好:这个特定问题有几个不同的原因。
为了完整性:我提到的减法
这些也(在某种程度上)在 rebase 文档中有所涉及,但值得一提的是提交 git rebase 故意不复制:
它不能复制任何合并。当使用--preserve 时,git rebase 将重新执行合并以尝试重建它们,但它无法复制原始合并。所以通常它甚至不会尝试。
它忽略了具有上游等效项的合并(例如,那些已经精心挑选的合并)。在原始图表中(使用T 和R),Git 检查A、B 或C 的the git patch-id value 是否与@987654372 上任何提交的git patch-id 值匹配@ 在合并基础提交的右侧。如果是这样,Git 会丢弃其补丁 ID 已在上游出现的 A、B 和/或 C 提交。
如果与--fork-point 一起使用,rebase 代码将运行git merge-base --fork-point 以尝试查找故意丢弃的上游提交。这通常适用于远程跟踪分支,如果您将自己的本地分支设置为您自己分支的上游,则通常效果较差。当使用自动机制基于上游重新定位时,使用--fork-point 是默认,因此在这里必须小心。更多信息请参见Git rebase - commit select in fork-point mode。