如何git cherry-pick一系列提交?

时间:2017-11-04 10:59:29

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

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

我没有找到任何文章/动词来解释在尝试cherry-pick多次提交时究竟有什么好处。

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

  1. CHERRY_PICK_HEAD参考是什么?
  2. 通过运行git cherry-pick 2^..4,git执行的操作顺序是什么,确切地提交git使用diff
  3. enter image description here

    1. 通过运行git cherry-pick 1..8,git会做什么?
    2. enter image description here

2 个答案:

答案 0 :(得分:3)

Cherry cherry n提交与其中一个提取樱桃相同(使用不同的git调用)。不涉及分支或其他任何其他内容,只需在当前分支上为您创建新的提交。

更多细节

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

  

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

让我们选择那个陈述:

  

改变

这有时候也被称为"差异"。这是git diff HEAD somecommit输出的内容。有时它也被称为"补丁" (事实上​​,git diff的相同输出可以应用于通常的patch实用程序 - 当然还有git apply,但这不是重点。

所以,"改变"是指示gitpatch这样的工具如何修改文本文件以最终获得新的已更改文本文件的内容。

您可以通过在两个文件上运行标准diff实用程序来创建两个文件之间差异的类似文本表示。事实上,这就是git内部采用樱桃选择的方式(当然也有自己的差异实现);即这只是一个双向差异,而不是git merge操作中的三向差异。

  

每个人都介绍

当你有这种状态时:

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

然后git cherry-pick def更改是提交abcdef之间的双向差异(对于所有不同的文件,当然,在逐个文件的基础上) ,因为这是def"介绍"。

  

应用更改

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

  

记录新提交

git merge将更改应用于索引和工作目录,并提交。除非双向合并和3向合并在没有冲突的情况下没有成功,在这种情况下,直接来自帮助页面并附带我的一些评论:

  1. 当前分支和HEAD指针停留在最后一次成功提交。 [即,只是简单的HEAD。]
  2. CHERRY_PICK_HEAD ref设置为指向引入难以应用的更改的提交。 [上面的图片中git。]
  3. 干净地应用更改的路径在索引文件和工作树中都会更新。 [即,如果在提交中更改了许多文件,那些可以干净地应用的文件就是。]
  4. 对于冲突路径,索引文件最多可记录三个版本,如" TRUE MERGE" git-merge [1]的一节。工作树文件将包括由通常的冲突标记<<<<<<<<<<<<<<<和>>>>>>>。 [即,与合并冲突相同,其中一些"凭空捏造而来#34;共同的祖先。]
  5. 不做任何其他修改。
  6. 最后,句子的其余部分:

      

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

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

    您的问题

      

    CHERRY_PICK_HEAD参考是什么?

    git cherry-pick sha1..sha2

    如果它尝试选择提交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

      

    通过运行git cherry-pick 2 ^ .. 4,git执行的操作顺序是什么,并且确实在哪个提交git使用diff?

    如上所述:

    1. 选择提交2,即
      • Git计算1和2之间的差异。
      • 如果可能,该差异将作为双向合并应用于HEAD并提交。
      • 如果不可能,则将该差异作为3向合并应用于HEAD并提交。
      • 如果无法(即合并冲突),那么您将像往常一样手动解决冲突,并等待您发出def
    2. 选择提交3,即......相同。
    3. 选择提交4,即......相同。
    4.   

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

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

      (未选择&#34;第一个&#34;提取范围的事实是通常的行为,例如git cherry-pick --continuegit log 2^..4将输出相同的提交 - 事实上这将在git log 1..8下的cherry-pick帮助页面中进行描述,包括git如何进行修订的链接,以及所有细节。这不是<commits>的属性,而是如何这些git cherry-pick范围有效。)

答案 1 :(得分:1)

  

我没有找到任何文章/动词来解释在尝试cherry-pick多次提交时究竟有什么好处。

在这种情况下,挑选代码使用Git的音序器,它也用于git amgit revert(以及最新版本的Git,git rebase - git rebase的一些案例,正如您所读,部分是使用git cherry-pick实现的,尽管它也部分使用git am实现:你是哪一个get取决于您提供给git rebase的标志。请注意,在内部,git revertgit cherry-pick是相同的命令(从builtin/revert.c构建)。

序列发生器只是重复运行&#34;一次一次提交&#34; Git子命令在一系列提交上,如果单次命令失败,可以选择跳过任何单独的提交。通常通过运行git rev-list来收集各个提交哈希ID,但并不总是这样。所以你&#34; 2的第一部分。&#34;和&#34; 3。&#34;通过运行git rev-list可以找到问题(虽然结果对人类没有特别的用处:-)因为git rev-list意味着产生对其他Git命令有用的输出。)

所以,让我们按顺序采取这些:

  

What CHERRY_PICK_HEAD ref is?

当序列发生器在一个提交上运行以进行cherry-pick或revert时,它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来构建提交列表,将它们写入sequencer目录(或者立即失败并拒绝您的尝试,如果有&#39 ; s是一个正在进行的顺序操作),然后一次一个樱桃挑选或恢复。这会调用do_pick_commit(),这是一个相当复杂的函数,但是你可以看到,在第1118行,它还会将当前提交的哈希ID写入CHERRY_PICK_HEAD,如果我们正在挑选我们将因某种原因停止。

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

然后,您可以解决问题并运行git cherry-pick --continue。此特定调用检查sequencer目录是否存在;如果它在那里,它假定你已经解决了问题,并试图继续现有的,正在进行的樱桃挑选序列。

  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),您将看到这列出了4的哈希ID,那么3,然后是2(按此顺序)。但是,在执行git cherry-pick时,Git会在每个&#34; ..&#34; -style选项中专门使用颠倒顺序,以便实际的提交哈希值为2,然后是3,然后是4。

因此,定序器将这三个散列ID写入排序区域,然后在每个散列区域上运行do_pick_commit。仔细查看第1043行并再次1088,您可以看到Git在父级之间运行 diff 实际上有点误导和每个提交的孩子。实际上,它运行 merge 操作(&#34;合并为动词&#34;,因为我喜欢它),合并基础是每个提交的父级,并且 - 作为--theirs提交被合并提交。 (--ours提交一如既往地是当前或HEAD提交。)

但是,合并操作本身 实际上在合并库和两个分支提示之间运行git diff。由于合并基础是被挑选的提交的父级,因此2^(或1)与2的区别作为--theirs方的输入。它还将2^HEAD区分为--ours方的输入,然后进行合并。

默认情况下(没有-n / --no-commit),如果成功,Git将提交此合并的结果,作为单父,非合并提交 。因此,虽然这个特定的挑选执行合并,但它使成为普通提交。此新提交的提交消息是来自原始提交的提交消息的副本,即来自提交2的消息的副本(如果使用-x请求保留原始提交哈希,则添加一行)。

如果一切顺利,序列发生器继续提交3. 3的父级是2,所以序列发生器调用合并机制合并提交3和(由前一步骤新创建)HEAD使用commit 2 这次是合并基地。这意味着Git将diff 2 vs 3,并且2 vs HEAD,组合差异,如果一切顺利,则进行一个新的普通(非合并)提交,变为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,我们首先会看到提交8,这意味着对于--reverse,我们最后会看到提交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的情况下,知道哪些订单将被挑选出来,这不是一件容易的事。

在任何情况下,音序器仍然会按照它们从git rev-list --reverse出来的顺序一次挑选每个提交一个。但最终我们将挑选所有2/3/4/5/6/7,其中合并,然后挑选提交8,其中是< / em>合并。无论哪种方式,我们都会经历the code at lines 967–990。对于合并的提交,git cherry-pick将要求我们 not 提供-m选项。对于 的提交,合并提交8 - git cherry-pick将要求我们提供-m选项。

所以这个樱桃选择肯定会失败。为了使其正常运行,您必须避免挑选合并,并且应该挑选每个单独的范围2^..45^..7(在任何一个顺序)。