这不是相当正确的。
这里有几个绊脚石。首先,可能没有共同的祖先,或者可能不止一个。 (这是非常次要的:这只是意味着所有提交都被复制,或者所有共同的祖先都被忽略了。)其次,我们可能不会在这里检查 B 指定的提交。第三,一些提交可以省略,根据变基的形式,这可能会有点复杂。最后,复制发生在 detached HEAD 模式下,然后,rebase 将分支名称拉到周围(好像通过git checkout -B 或git switch -C,或git branch -f 后跟git checkout 或@987654332 @)。
要枚举的实际提交取决于 rebase 的 upstream 参数,可以这样指定:
git rebase --onto <target> <upstream>
或:
git rebase <upstream>
如果省略了--onto <target> 选项,则target 与upstream 相同。这是被检出的提交(在 detached-HEAD 模式下)。
The rebase documentation 首先建议要枚举的提交是:
upstream..HEAD
(当然是在结帐步骤之前,因为这会移动HEAD)。这并不完全正确,因此当前文档立即进行了一些更正:
这与git log <upstream>..HEAD 显示的提交集相同;或git log 'fork_point'..HEAD,如果--fork-point 处于活动状态(参见下面--fork-point 的描述);或git log HEAD,如果指定了--root 选项。
稍后,它添加了以下内容:
请注意,HEAD 中引入与 HEAD.. 中的提交相同的文本更改的任何提交都将被省略(即,将跳过已在上游接受的具有不同提交消息或时间戳的补丁)。
直到很久以后它才提到完全省略了 merge commits,除非您使用(现已弃用)-p 选项或(Git 2.18 中的新)-r 选项。
不过,这里真正发生的是,Git 使用 git rev-list 的三点语法和 --left-right 模式。1 基本三点语法:
git rev-list upstream...HEAD
枚举从 either 提交可到达的所有提交,但 not 从 both 提交可到达。在图论方面,这就是对称差。在the gitrevisions documentation 中有简要描述。这会强制修订遍历代码检查可从 HEAD 访问但不是 upstream 和 可从 upstream 访问的提交但不是HEAD。在执行此操作时,Git 会在每次提交时执行 git patch-id。这让git rebase 可以找到“相同”的提交(就它们的更改而言),因此如果它们已经被挑选到上游分支,则可以忽略它们。
那么,特别是,假设您有:
...--o--*--D--E--B'--F <-- their-branch
\
A--B--C <-- your-branch (HEAD)
然后你运行git rebase their-branch 来复制你的三个A-B-C 提交到F 之后。变基代码将计算来自A、B 和C 的补丁ID,以及来自D、E、B' 和F 的补丁ID。鉴于提交 B' 是您的提交 B 的副本,它可能2 具有相同的补丁 ID。所以 Git 会省略 B 从提交列表中复制。
--fork-point 模式的描述有些倾斜,但您应该首先注意 --fork-point 在某些情况下是默认选项,而--no-fork-point 在其他情况下是默认选项。分叉点模式的工作方式是使用您的 reflogs。有关更多信息,请参阅Git rebase - commit select in fork-point mode。
有一个相对较新的--keep-base 选项可以真正进行合并基础计算。您可以使用三点语法直接调用它,也可以使用--keep-base 选项将其打开。
最后,合并提交的遗漏(-r 或您应该避免的 -p 选项除外)发生是因为 Git 从字面上 不能 复制合并。省略合并与运行git rev-list时使用--no-merges选项基本相同。当您使用 -r 选项时,Git 将枚举合并并记下它们,并将使用更新颖的交互式脚本模式来重新执行合并。也就是说,给定这样的图形片段:
...--o--*-------F <-- their-branch
\
\ B
\ / \
A D--E <-- your-branch (HEAD)
\ /
C
git rebase -r 将产生:
B'
/ \
A' G--E' <-- your-branch (HEAD)
/ \ /
/ C'
/
...--o--*-------F <-- their-branch
\
\ B
\ / \
A D--E [abandoned]
\ /
C
其中新的合并提交G 是通过在提交B' 和C' 上运行git merge 生成的。如果您使用git merge --no-commit 将D 设置为evil merge,则在此重新合并期间将失去邪恶。剩余的提交,标有主要后缀(A' 等),通过复制完成,使用底层的cherry-pick 机制。3
1在过去,git rebase 是几个 shell 脚本,其中一个确实像这样运行git rev-list,尽管我记得它使用了--right-only --cherry-pick。从那时起它就被用 C 重写了,现在它......更复杂了。 :-)
2它是否具有相同的patch-ID取决于是否有人在复制它时必须对其进行修改。详情请见the git patch-id documentation。
3在旧版本的 Git 中,默认实际上是使用git format-patch 和git am,或内部等效项。这在物理上也无法复制合并,并且错过了cherry-pick 检测到的一些重命名案例。在添加新的-r 选项期间,所有设置都设置为将默认设置切换为使用cherry-pick,并且最近(2.25ish?)成为了新的默认设置。