如果我从一个分支中挑选了一个提交,然后稍后合并整个分支,那么Git历史记录会发生什么?

10

我有一个这样的情况,只想要branchA中的一个特定提交,因为该分支中的其他提交还没有准备好合并。所以,如果我将commitX从branchA 挑选到 master 中,然后稍后将branchA与master合并(其中有一些提交在其中),那么就git历史而言,commitX会发生什么?它会被忽略,因为它已经存在于master中,还是会出现某种重复?

2个回答

9
“git merge”操作并不会查看所谓的“合并路径”上的单个提交。相反,它只查看起始和结束快照。请注意,然而,有两个结束快照(和一个起始快照)。
...--o--B--o--...--o--L
         \
          o--...--o--R

在这里,B是合并基础提交,LR分别是左侧(或本地或--ours)和右侧(或其他、远程或--theirs)提交。同时,每个回合的o代表Git甚至不查看的某些提交。1 Git实际上只执行以下操作:

git diff --find-renames B L    # figure out what we did
git diff --find-renames B R    # figure out what they did

这对于您的git cherry-pick意味着“我们所做的”和“他们所做的”可能包含一些相同的更改,涉及相同的文件。但这没问题,因为在获取这两个en-masse差异后,git merge执行的步骤是将更改合并,仅保留左右“侧面”中出现的任何更改的一份拷贝。
但是,假设您挑选了一个提交,并沿着一个路径再次撤销它,例如:
...--o--B--X'--!X'--L
         \
          X--o--R

X'带有勾号的原因是它是提交X的一个副本:当您挑选了一个提交时,新复制将获得不同的哈希标识符。)
在这里,您为分支(图中下方的线)编写了提交X,将其挑选到主线(上方的线),意识到它并不准备好或已损坏,进行了还原(添加了!X'),然后在主线中进行了最终提交L。现在,在合并时,提交X的一个副本进入然后又出现的事实是看不见的:Git将BL进行比较,并且完全没有X'--!X'序列的迹象。因此,它仍然需要提交X引入的更改的一份副本。
如果提交X在以R结尾的分支中已成为准备就绪状态,则这是正确的操作。
如果提交X仍然存在问题,则这是错误的操作,但正确的治疗方法可能是在合并之前还原分支上的X
除了找到B外,也就是说:Git 必须从 LR 开始并向后遍历图形以找到合并基础。这意味着 Git 必须遍历一些其他不相关的提交节点。

可能还需要提一下,原始提交与挑选的提交是完全不同的提交。 - Chris Rasys
@ChrisRasys,这就是我想知道的。如果提交ID不同,那么Git历史记录确实会包含两个相同更改的提交。一个是挑选的提交,然后是稍后合并的原始提交? - user2494770
@ChrisRasys:完成了。@sreya:是的;但如果您通过其他方式复制提交,例如再次进行相同的更改,也会发生这种情况。显示提交及其图形线(如gitk所做的那样,大多数GUI都会这样做,当然我上面也是这样做的)的查看器将向您显示这两个提交位于稍后合并的不同开发线中。 - torek

1

没有特别的事情发生,但让我们来分解一下。

         M
-o---o---o master
  \
   \----o--o--o branchA
           X

现在你需要进行cherry-pick操作。然后情况会变成:

         M  MX
-o---o---o--o master
  \
   \----o--o--o branchA
           X

到目前为止还不错。然后你再提交几次...
         M  MX
-o---o---o---o---o---o---o master
  \
   \----o---o---o---o---o---o branchA
            X

现在合并...
         M  MX
-o---o---o---o---o---o---o-----o master
  \                           /
   \----o---o---o---o---o---o branchA
            X

所有这些都只是一如既往的业务。Git不存储提交MX是樱桃拣选的结果,也不需要。樱桃拣选操作与合并的区别在于,选择的提交X和新提交MX之间没有任何关系。它们也不能有,因为(通过合并)“父子”关系的语义是最终master包含branchA的所有历史记录,而不仅仅是单个提交引入的更改。
实际上,文件级别上的实际更改就像您手动编辑它们一样工作。也就是说,如果樱桃拣选引入的更改在master中保持不变,则Git将注意到这一点(在简单情况下,在相关行上合并时不会注意到任何差异),然后进行合并。
编辑:关于您在评论中的问题... 如果我的提交X有一条消息“Foobarbaz”,那么在这种情况下,我现在是否在我的主分支中有两个提交消息为“Foorbarbaz”的提交,还是只有一个?
每个提交都有一个提交级别的“消息”和一些文件级别的“内容”。挑选一个樱桃只在内容层面上起作用;也就是说,它从一个提交中获取文件更改,并将其应用于当前工作目录中的任何内容。可能令人困惑的是,命令“git cherry-pick”确实在应用该更改后为您创建一个新的提交(在此示例中为“MX”)。但是,这个新提交只是普通的提交——它与原始提交“X”没有任何关系,除了“git cherry-pick”将复制旧的提交消息(您可以编辑)作为方便。
为了澄清,您可以执行“git cherry-pick -n”,避免“git”代替您进行提交——这会给您编辑樱桃选择操作之前的机会,然后再自己提交。
因此,樱桃选择实际上只是一种方便的方法,就像您自己编辑更改并自己提交一样。新提交消息与旧提交消息相似或相同的事实对于合并而言对git来说并不重要。

在文件级别上同意更改,这很有道理。但是假设我的commitX有一条消息“Foobarbaz”。在这种情况下,我的主分支现在会有两个提交记录,其中包含提交消息“Foorbarbaz”,还是只有一个?我知道,至少从功能上来说,我提出的建议不会造成任何伤害,但我仍然很好奇历史记录会是什么样子。从您的图表中看来,主分支只存在一个提交记录。 - user2494770
@sreya:分支中的提交是指从分支名称“可达”的那些提交。要查看我们是否可以到达提交,我们必须跟随每个合并提交的所有父级。因此,从master开始,我们回顾了顶部行上的两个提交(因为在该方向上从合并返回连接)和底部行上的两个提交(因为在那里有第二个连接)。因此,我们发现MX和X都包含在master中。这使得Git与许多其他版本控制系统根本不同,在Git中,包含提交的分支集合会更改 - torek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接