问题描述
我正试图加深我对git命令的理解(和交流)。
这样说是否正确
git checkout A
git rebase B
完全等同于
git checkout B
git cherry-pick <all_commits_from_common_ancestor_of_<A>_and_<B>_to_<A>>
如果没有,它们在什么情况下会发散?
解决方法
这不是很正确。
这里有几个绊脚石。首先,可能没有一个共同的祖先,或者可能有一个以上的祖先。 (这是很小的事情:这意味着所有提交都将被复制,或者所有共同祖先都将被忽略。)其次,我们可能不会在此处签出 B
指定的提交。第三,可以省略某些提交,并且根据rebase的形式,这可能会变得有些复杂。最后,复制是在分离式HEAD 模式下进行的,此后,重新定位分支名称(就像通过git checkout -B
或git switch -C
或git branch -f
git checkout
或git switch
)。
要枚举的实际提交取决于rebase的 upstream
参数,可以通过以下方式指定该参数:
git rebase --onto <target> <upstream>
或:
git rebase <upstream>
如果省略了--onto <target>
选项,则 target
与 upstream
相同。这是被检出的提交(在分离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中的新功能){{1 }}选项。
实际上,这是Git在-r
模式下为git rev-list
使用三点语法的原因。 1 基本的三点语法:
--left-right
枚举所有 提交都可以访问的提交,但 提交都不能到达的提交。用图的理论术语来说,这是对称差。 the gitrevisions documentation中对此进行了简要描述。这迫使修订版本代码检查可从git rev-list upstream...HEAD
到达的提交,而不检查 HEAD
和可从 upstream
到达的提交,而不是upstream
。在这样做的同时,Git在每次提交时执行一个git patch-id
。这样,HEAD
可以定位“相同”的提交(根据它们的更改),因此,如果它们已经被挑选到上游分支,则可以忽略它们。
那么,假设您有:
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
的补丁ID,以及来自C
,D
,{{ 1}}和E
。鉴于提交B'
是您的提交F
的副本,它可能 2 具有相同的补丁ID。因此,Git将从要复制的提交列表中省略 B'
。
对B
模式的介绍有些斜,但是您应该首先注意,在某些情况下B
是默认选项,而在其他情况下是--fork-point
是默认选项。叉点模式的工作方式是使用您的引用日志。有关更多信息,请参见Git rebase - commit select in fork-point mode。
有一个相对较新的--fork-point
选项确实可以进行合并基础计算。您可以使用三点语法直接调用它,也可以使用--no-fork-point
选项将其打开。
最后,由于Git从字面上不能复制复制,所以发生了合并提交的遗漏(除了--keep-base
或应避免使用的--keep-base
选项之外)。合并的省略基本上与运行-r
时使用-p
选项相同。当您使用--no-merges
选项时,Git将枚举合并并记下它们,并将使用更新颖的交互式脚本模式重新执行。也就是说,给定这样的图形片段:
git rev-list
一个-r
将产生:
...--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
上实际运行git merge
产生的。如果您使用B'
将C'
设为evil merge,那么在重新合并过程中,邪恶将会消失。其余带有主要后缀(D
等)的提交是通过使用底层的Cherry-pick机制进行复制来完成的。 3
1 在过去,git merge --no-commit
是几个shell脚本,其中一个确实确实像这样运行A'
,尽管它使用了git rebase
,据我回忆。从那时起,它已经用C重写,现在……变得更加复杂。 :-)
2 它是否具有相同的补丁ID,取决于是否有人在复制时必须对其进行修改。有关详细信息,请参见the git patch-id
documentation。
3 在旧版本的Git中, default 实际上是使用git rev-list
和--right-only --cherry-pick
或内部等效项。这实际上也无法复制合并,并且错过了Cherry-pick检测到的一些重命名案例。在添加新的git format-patch
选项期间,所有设置均已设置为将默认值切换为使用Cherry-pick,最近(2.25ish?)变成了新的默认值。