git 如何挑选一系列提交?

How git cherry-pick a range of commits?

我看到 rebasecherry-pick 一系列提交之间存在联系。

我没有找到任何 article/toturial 来解释当一个人尝试 cherry-pick 多次提交时到底发生了什么。

一些问题(我能想到的)是:

  1. CHERRY_PICK_HEAD ref 是什么?
  2. 根据 运行 git cherry-pick 2^..4,git 执行的操作顺序是什么,在哪些提交之间 git 使用 diff ?

  1. 运行git cherry-pick 1..8,git会做什么?

Cherry picking n 提交与 cherry picking 一个接一个地提交相同(具有不同的 git 调用)。不涉及分支或其他任何内容,只是在当前分支上为您创建的新提交。

更多详情

帮助页面 https://www.git-scm.com/docs/git-cherry-pick.html 说:

Given one or more existing commits, apply the change each one introduces, recording a new commit for each.

让我们把那句话分开:

the change

这有时也被称为"the diff"。这就是 git diff HEAD somecommit 会输出的内容。有时它也被称为 "patch"(事实上,git diff 的相同输出可以用通常的 patch 实用程序应用 - 当然还有 git apply,但那不是点这里)。

因此,"change" 指示 gitpatch 等工具如何修改文本文件以生成新的、已更改的文本文件。

您可以通过 运行 标准 diff 实用程序在两个文件上创建两个文件之间差异的相似文本表示。事实上,这就是 git 在内部使用 cherry-pick 所做的(当然,它有自己的 diff 实现);也就是说,这只是一个 2 向差异,而不是像 git merge 操作中那样的 3 向差异。

each one introduces

当你处于这种状态时:

...----+----+----...
   abc  def          

然后 git cherry-pick def 变化是提交 abcdef 之间的双向差异(对于所有不同的文件,当然,逐个文件基础),因为这就是 def "introduces".

apply the change

这意味着采用 HEAD 和 "the change"(即差异、补丁等)并创建一组新的文本文件。原则上,您可以将其视为双向合并(就像 patch 实用程序所做的那样),除非它不是,即如果 diff 输出中的上下文信息不匹配现在 HEAD。在这种情况下,git 作弊以找到能够进行三向合并的共同祖先,您可以阅读 中的详细信息。但从用户的角度来看,它仍然不能真正与 git merge 相比,因为它在结构上将以单亲提交结束,而不是像 git merge 这样的双亲提交。

recording a new commit

git 将更改应用于索引和工作目录,然后提交。除非 2 向合并和 3 向合并没有冲突,在这种情况下,直接从帮助页面和我的一些评论:

  1. 当前分支和 HEAD 指针停留在最后一次成功提交。 [即,只是简单的 HEAD。]
  2. CHERRY_PICK_HEAD ref 设置为指向引入难以应用的更改的提交。 [也就是我上图中的def。]
  3. 在索引文件和您的工作树中都更新了干净应用更改的路径。 [即,如果在提交中更改了很多文件,那些可以干净地应用的是。]
  4. 对于冲突的路径,索引文件最多记录三个版本,如 git-merge[1] 的 "TRUE MERGE" 部分所述。工作树文件将包括由通常的冲突标记 <<<<<<< 和 >>>>>>> 括起来的冲突描述。 [即,与合并冲突相同,具有一些 "conjured out of thin air" 共同祖先。]
  5. 没有进行其他修改。

最后,剩下的句子:

Given one or more existing commits, apply ... recording a new commit for each.

如果你给它多次提交,可能像 git cherry-pick sha1 sha2 sha3... 中那样显式或隐式地 git cherry-pick sha1..sha2,那么上面只是在一个简单的循环中运行,在最后一次选择之后停止,或者当有发生合并冲突。

您的问题

What CHERRY_PICK_HEAD ref is?

2. The CHERRY_PICK_HEAD ref is set to point at the commit that introduced the change that is difficult to apply.

如果它尝试选择提交 def,并且发生合并冲突,那么 CHERRY_PICK_HEAD 将指向 def

By running git cherry-pick 2^..4, what is the sequence of actions git does and exactly between which commits git use diff?

如上所述:

  1. 提交 2 被选中,即
    • Git 计算 1 和 2 之间的差异。
    • 如果可能,将该差异作为双向合并应用到 HEAD 并提交。
    • 如果不可能,该差异将作为 3 向合并应用到 HEAD 并提交。
    • 如果 that 不可能(即合并冲突),那么您将像往常一样手动解决冲突,它将等待您发出 git cherry-pick --continue.
  2. 提交 3 被选中,即...相同。
  3. 选择了提交 4,即...相同。

By running git cherry-pick 1..8, what git will do?

相同,但这次它将选择提交 2、3、4、8。

(未选择范围内的 "first" 提交的事实是通常的行为,例如 git log 2^..4git log 1..8 将输出相同的提交 - 实际上是相同的这将被选中。这在 <commits> 下的 cherry-pick 帮助页面中进行了描述,包括指向 git 如何进行修订的链接,了解所有详细信息。这不是 属性 的 git cherry-pick 但是这些 .. 范围是如何工作的。)

I have failed to find any article/toturial which explain what exactly happands when one try to cherry-pick multiple commits.

在这种情况下,cherry-pick代码使用Git的sequencer,它也用于git amgit revert(在 Git 的最新版本中,git rebase 的某些情况下——git rebase 部分是使用 git cherry-pick 实现的,尽管它也部分是使用 git cherry-pick 实现的git am:你得到哪一个取决于你提供给 git rebase 的标志。请注意,在内部,git revertgit cherry-pick 是相同的命令(从 builtin/revert.c 构建)。

排序器简单地运行s重复"one-commit-at-a-time"Gitsub-commands一系列提交,如果single-shot 命令失败。 运行ning git rev-list 经常(尽管不总是)收集各个提交哈希 ID。所以你的“2”的第一部分。和“3”。可以通过 运行ning git rev-list 找到问题(尽管结果对人类不是特别有用 :-) 因为 git rev-list 旨在产生对其他 Git 命令有用的输出).

所以,让我们按顺序进行:

What CHERRY_PICK_HEAD ref is?

当 sequencer 在 运行 上 提交 cherry-pick 或还原时,它 notices, writes the commit ID to CHERRY_PICK_HEAD or REVERT_HEAD, and invokes the code to do a single pick/revert. (Follow the link to the actual Git source on GitHub for further details.) Otherwise, it does the rev-list walk to build the list of commits, writes them to the sequencer directory (or immediately fails and rejects your attempt if there's an ongoing sequenced operation), and then does one cherry-pick or revert at a time. This calls do_pick_commit(),这是一个相当复杂的函数,但是你可以看到,在第 1118 行,它还会将当前提交的哈希 ID 写入 CHERRY_PICK_HEAD,如果我们正在 cherry-picking 并且由于某种原因我们将要停止。

因此,每当任何个人 cherry-pick 失败并以未合并的索引停止,或由于使用 --no-commit 成功后停止,CHERRY_PICK_HEAD 包含提交的哈希 ID命令停止时正在拾取。

然后您可以解决问题 运行 git cherry-pick --continue。这个特定的调用检查 sequencer 目录是否存在;如果存在,则假定您已解决问题并尝试继续现有的 on-going cherry-pick 序列。

  2--3--4  <-- dev
 /
1
 \
  5--6--7   <-- master (HEAD)

By running git cherry-pick 2^..4, what is the sequence of actions git does and exactly between which commits git use diff?

如果你运行:

git rev-list 2^..4

(用实际哈希 ID 替换 24,或使用名称 dev 来标识提交 4)你会看到这列出了 4 的哈希 ID,然后是 3,然后是 2(按此顺序)。但是,在执行 git cherry-pick 时,Git 在每个“..”样式选择上专门使用 reversed 顺序,因此实际的提交哈希值是 2 , 然后 3, 然后 4.

排序器因此将这三个哈希 ID 写入排序区域,然后 运行s do_pick_commit 每个。从行 1043 and again at 1088 开始仔细观察,您会发现说 Git 运行 是 diff 实际上有点误导 [=每个提交的 215=] 和 child。事实上,它 运行 是一个 merge 操作("merge as a verb",我喜欢这么说),合并基础是每个的 parent提交和 to-be-merged 提交作为 --theirs 提交。 (--ours 提交一如既往地是当前或 HEAD 提交。)

但是,合并操作本身 确实 ,实际上,合并基础和两个分支提示中的每一个之间 运行 git diff。由于合并基础是 cherry-picked 提交的 parent,因此 2^(或 1)与 2 作为 [= 的输入存在差异43=] 边。它还将 2^HEAD 作为 --ours 端的输入进行区分,然后进行合并。

默认情况下(没有-n/--no-commit),Git将提交合并的结果,如果合并成功,作为single-parent, non-merge 提交。因此,虽然这个特定的 cherry-pick 执行 合并,但它 使 成为普通提交。此新提交的提交消息是原始提交的提交消息的副本,即来自提交 2 的消息的副本(如果您使用 -x 要求添加一行,则包含原始提交哈希) ).

如果一切顺利,定序器继续提交 3。3 的 parent 是 2,因此定序器调用合并机制来合并提交 3 和(上一步新创建的)这次 HEAD 使用提交 2 作为合并基础。这意味着 Git 将区分 2 vs 3,以及 2 vs HEAD,合并差异,如果一切顺利,进行一个新的普通(non-merge)提交,成为 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)

By running git cherry-pick 1..8, what git will do?

这里我们将遇到多个问题。

首先,cherry-pick 调用定序器代码,因为您指定了一系列提交,2^..8。这个特定的 sub-range 被颠倒了:

git rev-list --reverse 2^..8

这列出了提交 2、3、4、5、6、7 和 8 一些 顺序,但顺序到底是什么?我们要求从提交 8(包括 8 本身)可访问的所有提交,排除可从提交 2^(即提交 1)访问的所有提交。当然,如果没有 --reverse,我们将首先看到提交 8,这意味着使用 --reverse 我们将最后看到提交 8。但是8有两个 parent,即4和7。Git可以在这里选择一个。

没有--topo-order,Git先选择最近的time-stamp。假设两个时间戳让它先挑7。我们将得到 8,然后是 7(这样在反转之后我们将得到 7,然后是 8,最后)。现在又有两个提交可供选择:6 和 4。假设这两个时间戳使得 Git 接下来选择 4。我们现在有两个提交可供选择:6 和 3。此过程重复,直到图中的两条腿 re-converge 在提交 1(我们无论如何都不会选择)。

--reverse表示我们得到一个以commit 8结尾的线性化列表,但是2、3、4、5、6、7的顺序是由时间戳决定的(具体来说commit 时间戳,而不是作者时间戳)。因此,如果不查看提交时间戳或 运行ning git rev-list,要知道每个单独提交的 哪个 顺序并不是很容易 cherry-pick编辑

在任何情况下,排序器仍将 cherry-pick 每个人一次提交一个,无论它们从 git rev-list --reverse 出来的顺序如何。但最终我们将 cherry-pick 所有 2/3/4/5/6/7, 合并,然后 cherry-pick 提交 8,这合并。无论哪种方式,我们都会通过 the code at lines 967–990。对于 not 合并的提交,git cherry-pick 将要求我们 not 提供 -m 选项。对于 合并的提交——提交 8——git cherry-pick 将要求我们 do 提供 -m 选项。

所以这个cherry-pick肯定会失败。为了使其正常工作,您必须避免cherry-pick合并,并且您应该cherry-pick每个单独的范围2^..45^..7(任意顺序)。