【问题标题】:How git cherry-pick a range of commits?如何 git cherry-pick 一系列提交?
【发布时间】:2018-04-17 00:35:01
【问题描述】:

我看到rebasecherry-pick 之间有一个提交范围。

我找不到任何文章/教程来解释当尝试cherry-pick 多次提交时究竟发生了什么。

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

  1. CHERRY_PICK_HEAD ref 是什么?
  2. 通过运行git cherry-pick 2^..4,git 执行的操作顺序是什么以及git 使用diff 的具体操作顺序是什么

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

【问题讨论】:

标签: git git-rebase git-cherry-pick


【解决方案1】:

樱桃采摘n 提交与樱桃采摘其中一个是相同的(具有不同的 git 调用)。不涉及分支或其他任何内容,只需在当前分支上为您创建新提交即可。

更多详情

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

给定一个或多个现有提交,应用每个提交的更改,并为每个提交记录一个新提交。

让我们把那句话分开:

变化

这有时也被称为“差异”。这是git diff HEAD somecommit 将输出的内容。有时它也被称为“补丁”(事实上,git diff 的相同输出可以与通常的 patch 实用程序一起应用 - 当然也可以与 git apply 一起应用,但这不是重点)。

因此,“更改”是指示 gitpatch 之类的工具如何修改文本文件以最终生成新的已更改文本文件的东西。

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

每人介绍

当你有这个状态时:

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

那么git cherry-pick def 的变化是提交abcdef 之间的双向差异(对于所有不同的文件,当然,在逐个文件的基础上),因为这就是@ 987654338@“介绍”。

应用更改

这意味着采用HEAD 和“更改”(即差异、补丁等)并创建一组新的文本文件。原则上,您可以将其视为 2 路合并(就像 patch 实用程序一样),除非它不是,即如果 diff 输出中的上下文信息与 HEAD 中的内容不匹配现在。在这种情况下,git 会欺骗找到一个共同的祖先来进行 3 路合并,您可以在 What are the three files in a 3-way merge for interactive rebasing using git and meld? 中阅读血腥细节。但从用户的角度来看,它仍然无法与git merge 相比,因为它在结构上最终会是单亲提交,而不是像git merge 这样的双亲提交。

记录一个新的提交

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

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

最后,剩下的句子:

给定一个或多个现有的提交,应用 ... 为每个记录一个新的提交。

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

您的问题

CHERRY_PICK_HEAD 参考是什么?

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

通过运行 git cherry-pick 2^..4,git 执行的操作顺序是什么,以及 git 使用 diff 的具体操作顺序是什么?

如上所述:

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

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

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

(未选择范围内的“第一个”提交的事实是通常的行为,例如git log 2^..4git log 1..8 将输出相同的提交 - 实际上与将选择的相同。这已描述在 <commits> 下的 cherry-pick 帮助页面中,包括指向 git 如何遍历修订的链接,以获取所有详细信息。这不是 git cherry-pick 的属性,而是这些 .. 范围如何工作的属性。)

【讨论】:

  • 很抱歉您的回答令人反感。我假设您没有找到该帮助页面,因为在我看来它回答了您的问题。我将使用更多详细信息编辑答案。 @StavAlfi
  • "git 作弊找到一个共同的祖先来进行 3 路合并" - 你能指定 是当cherry-pick 不能的共同祖先干净地应用路径?
  • @StavAlfi:无法比链接中更好地解释它stackoverflow.com/questions/36993683/… ...
  • 感谢您的精彩回答,让我了解diff,patch,format-patch,blob,tree,apply,am,@987654366 是什么@,3-way-merge,cherry-pick。正如您现在可以理解的那样,我很难理解手册页。总而言之,我花了 6 天时间才完全完全理解您的答案。所以感谢你的功课和你的知识。希望下次再见;-)
【解决方案2】:

我找不到任何文章/教程来解释当尝试cherry-pick 多次提交时究竟发生了什么。

在这种情况下,cherry-pick 代码使用 Git 的 sequencer,它也用于 git amgit revert(在最新版本的 Git 中,某些情况下是 git rebasegit rebase 如您所读,部分使用git cherry-pick 实现,尽管它也部分使用git am 实现:您获得哪一个取决于您提供给git rebase 的标志)。请注意,在内部,git revertgit 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

(将24 替换为实际的哈希ID,或使用名称dev 来标识提交4)您会看到这列出了哈希ID 4、3、2(在那个命令)。但是,在执行git cherry-pick 时,Git 专门对每个“..”样式的选择使用 reversed 顺序,以便实际的提交哈希是 2,然后是 3,然后是 4。 p>

因此,排序器将这三个哈希 ID 写入排序区域,然后在每一个上运行 do_pick_commit。从10431088 行开始仔细观察,您会发现说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^..45^..7(以任意顺序) .

【讨论】:

    猜你喜欢
    • 2012-11-05
    • 2021-01-18
    • 2012-05-21
    • 2013-03-09
    • 2016-10-24
    • 2016-04-11
    • 1970-01-01
    • 2018-04-16
    • 2016-01-08
    相关资源
    最近更新 更多