我找不到任何文章/教程来解释当尝试cherry-pick 多次提交时究竟发生了什么。
在这种情况下,cherry-pick 代码使用 Git 的 sequencer,它也用于 git am 和 git revert(在最新版本的 Git 中,某些情况下是 git rebase —git rebase 如您所读,部分使用git cherry-pick 实现,尽管它也部分使用git am 实现:您获得哪一个取决于您提供给git rebase 的标志)。请注意,在内部,git revert 和 git cherry-pick 是相同的命令(由 builtin/revert.c 构建)。
排序器只是对一系列提交运行重复的“一次提交一次”Git 子命令,如果单次命令失败,则可以选择跳过任何单个提交。通过运行git rev-list,通常(尽管并非总是)收集各个提交哈希 ID。所以你的“2”的第一部分。和“3”。可以通过运行git rev-list 找到问题(尽管结果对人类不是特别有用:-) 因为git rev-list 旨在产生对其他 Git 命令有用的输出)。
那么,让我们按顺序来:
What CHERRY_PICK_HEAD ref is?
当排序器在 one 提交上运行以进行樱桃挑选或恢复时,它notices, writes the commit ID to CHERRY_PICK_HEAD or REVERT_HEAD, and invokes the code to do a single pick/revert。 (点击 GitHub 上实际 Git 源的链接以获取更多详细信息。)否则,它会执行 rev-list walk 来构建提交列表,将它们写入序列器目录(或者如果存在正在进行的操作,则立即失败并拒绝您的尝试顺序操作),然后一次执行一个选择或恢复。这会调用do_pick_commit(),这是一个相当复杂的函数,但你可以看到,在第 1118 行,它还会将当前提交的哈希 ID 写入CHERRY_PICK_HEAD,如果我们正在挑选樱桃,我们将停止一段时间原因。
因此,每当任何单独的cherry-pick 失败并以未合并的索引停止,或者由于使用--no-commit 而在成功后停止时,CHERRY_PICK_HEAD 包含当时被挑选的提交的哈希 ID命令已停止。
然后您可以解决问题并运行git cherry-pick --continue。这个特定的调用检查是否存在定序器目录;如果它在那里,它假定您已经解决了问题并尝试继续现有的、正在进行的樱桃挑选序列。
2--3--4 <-- dev
/
1
\
5--6--7 <-- master (HEAD)
通过运行git cherry-pick 2^..4,git 执行的操作顺序是什么以及git 使用diff 的确切操作顺序是什么?
如果你运行:
git rev-list 2^..4
(将2 和4 替换为实际的哈希ID,或使用名称dev 来标识提交4)您会看到这列出了哈希ID 4、3、2(在那个命令)。但是,在执行git cherry-pick 时,Git 专门对每个“..”样式的选择使用 reversed 顺序,以便实际的提交哈希是 2,然后是 3,然后是 4。 p>
因此,排序器将这三个哈希 ID 写入排序区域,然后在每一个上运行 do_pick_commit。从1043 和1088 行开始仔细观察,您会发现说Git 在每次提交的父子节点之间运行diff 实际上有点误导。事实上,它运行一个 merge 操作(“合并为动词”,我喜欢这样说),合并基础是每个提交的父级,而要合并的提交是--theirs 提交。 (--ours 提交一如既往地是当前或HEAD 提交。)
然而,合并操作本身确实,实际上,在合并基础和两个分支提示中的每一个之间运行git diff。由于合并基础是被挑选的提交的父级,因此2^(或1)与2 的差异作为--theirs 端的输入。它还将2^ 与HEAD 区分为--ours 端的输入,然后进行合并。
默认情况下(没有-n / --no-commit),如果合并成功,Git 将提交此合并的结果,作为单父、非合并提交。因此,虽然这个特定的樱桃挑选执行合并,它使一个普通的提交。此新提交的提交消息是来自原始提交的提交消息的副本,即来自提交 2 的消息的副本(如果您使用 -x 请求,则添加一行保存原始提交哈希)。
如果一切顺利,sequencer 继续提交 3。3 的父节点是 2,所以 sequencer 调用合并机制来合并提交 3 和(由上一步新创建的)HEAD 使用提交 2 这次作为合并基地。这意味着 Git 将 diff 2 vs 3,以及 2 vs HEAD,合并 diff,如果一切顺利,进行一个新的普通(非合并)提交,成为 HEAD。
如果 进展顺利,则排序器继续执行提交 4,其行为方式相同。
最终结果是:
2--3--4 <-- dev
/
1
\
5--6--7--2'-3'-4' <-- master (HEAD)
其中2' 是2 的一种副本,3' 是3 的一种副本,4' 是4 的一种副本。
2---3---4
/ \
1--5--6--7--8 <-- dev
\
9 <-- master (HEAD)
通过运行git cherry-pick 1..8,git 会做什么?
在这里我们将遇到多个问题。
首先,cherry-pick 调用序列器代码,因为您已指定提交范围 2^..8。这个特定的子范围被颠倒了:
git rev-list --reverse 2^..8
此列表以一些的顺序提交了 2、3、4、5、6、7 和 8,但具体的顺序是什么?我们要求所有可从提交 8(包括 8 本身)到达的提交,不包括可从提交 2^(即提交 1)到达的所有提交。当然,没有--reverse,我们会首先看到commit 8,这意味着使用--reverse,我们会最后看到commit 8。但是 8 有 两个 父母,即 4 和 7。Git 可以在此处选择任何一个。
如果没有--topo-order,Git 会首先选择具有最新时间戳的那个。假设两个时间戳使它首先选择 7。我们会得到 8,然后是 7(所以在反转之后我们会得到 7,然后是 8,最后)。现在又有两个提交可以选择下一个:6 和 4。假设这两个时间戳使 Git 选择下一个 4。我们现在将有两个提交可供选择:6 和 3。此过程重复进行,直到图中的两条腿在提交 1 处重新收敛(无论如何我们都不会选择)。
--reverse 表示我们得到了一个以提交 8 结尾的线性化列表,但 2、3、4、5、6 和 7 的顺序由时间戳决定(特别是 commit时间戳,而不是作者时间戳)。因此,在不查看提交时间戳或运行git rev-list 的情况下,要知道哪个每个提交将被精心挑选,并不是很容易。
在任何情况下,sequencer 仍然会一次挑选每个提交,无论它们从git rev-list --reverse 中出现的顺序如何。但最终我们会挑选所有的 2/3/4/5/6/7,它们是不是合并,然后挑选提交 8,这 是 合并。无论哪种方式,我们都会通过the code at lines 967–990。对于不合并的提交,git cherry-pick 将要求我们不提供-m 选项。对于是合并的提交——提交 8——git cherry-pick 将要求我们做提供-m 选项。
所以这个樱桃选择肯定会失败。为了使其正常工作,您必须避免挑选合并,并且您应该挑选每个单独的范围 2^..4 和 5^..7(以任意顺序) .