Git合并策略

时间:2020-07-31 10:32:54

标签: git

我在git中创建了一个名为release-1.0.0的分支,我一直在其中提交代码。现在,有一个主要的未来版本,该版本在设计和体系结构更改上有重大更改,称为release-2.0.0。此新分支是根据release-1.0.0创建的。这将对release-1.0.0进行一些更改,但由于设计差异,无法合并该分支中的某些更改。

将在release-1.0.0中完成的更改移至release-2.0.0的正确策略是什么?合并是正确的做法吗?还是应该手动将代码复制粘贴到release-2.0.0?或者我们甚至应该为此创建一个单独的存储库:-O

最后,release-1.0.0release-2.0.0将在完成后合并到master中。 请分享您的想法。我不确定这是否是正确的问题。但是我看到其他类似的问题here

1 个答案:

答案 0 :(得分:2)

如果对此类问题有一个正确的答案,那么每个人都将使用它,这将是众所周知的。没有。但是我们可以说一些一般的事情:

或者我们甚至应该为此创建一个单独的存储库:-O

一个单独的存储库只不过是一个分支-分支的内容其他人看不到,除非他们有权访问该单独的存储库。 (从技术上讲,这是Git中的一整套分支,正弦分支名称在存储库中是本地的。)在Git中创建分支的成本非常低,因此,如果有帮助,无论您是否要这样做,都是一件好事是否将其放在单独的存储库中。

我们可以肯定地说是这样的:

  • Git本质上是关于 commits 的全部内容。

  • 每个提交都有编号。这些数字不是简单的序号(它们不累加,1、2、3等),而是看似随机的哈希ID,但它们仍是唯一编号。

  • 哈希ID的计算对于使Git正常工作至关重要:这里的秘密在于,每个地方的 每个Git都会为计算相同的hash ID 。 em>相同的提交内容。因此,这意味着两个Git在彼此交谈时仅需要比较哈希ID 即可查看它们是否具有相同的提交。 (您不必为当前的问题而在意,这只是一件有用的事情。)

  • 提交的内容分为两个部分:

    • 每个提交都有每个文件的完整快照。这些文件采用特殊,只读,仅Git,压缩和重复数据删除的格式,通常只有Git可以读取。 (重复数据删除意味着,由于大多数提交大部分都重复使用了较早提交中的文件,因此新提交几乎不会占用任何额外的空间。即使每个提交都有每个文件的完整副本,这些提交实际上也是 share < / em>单个副本。)

    • 与快照一起,每个提交都具有一些 metadata 或有关提交本身的信息。元数据包括进行提交的人的姓名和电子邮件地址,一些日期和时间戳记以及他们进行该提交的为什么的日志消息。元数据的一部分专门用于Git本身,由Git自己维护:每个提交记录其 parent 的哈希ID(即提交编号)(或者,对于合并,父级,复数)

这最后一部分是像master这样的分支名称仅存储一件事的方式和原因: last 提交的哈希ID。提交本身就是并存储了项目的历史。

请注意,提交不会存储更改。他们存储快照。但是由于每个提交都记住其前一个提交(即其父级),因此Git可以执行任何提交并向后退一步,查看其父级。在父目录中,大多数文件可能是 same ,并且实际上是通过重复数据删除来共享的。因此,Git可以跳过这些文件,而仅麻烦比较两个提交之间不同的文件。通过比较不同的文件,Git可以在您要求时计算这些文件中的更改,从而向您显示该提交中的更改。

樱桃采摘vs合并

要从单个提交中进行更改,可以使用git cherry-pick。在内部,这实际上使用了Git的合并机制,但是简化的描述很有意义:

  • Git将提交与父提交进行比较,以查看提交中发生了什么更改

  • 然后,Git将相同的更改应用于当前提交。

如果应用程序运行顺利,您只需进行相同的更改,Git就会自行进行提交。当然,进行新提交的人当然是 you ,但是 message 也会从原始提交中复制。从新提交到其父级的差异与从经过挑选的提交到其 父级的差异相同。但是新的提交与原始的 1 不同,因此它具有不同的哈希ID(提交编号)。

这与合并非常不同。使用git merge时,您会告诉Git:*找到两个特定提交的最佳共享祖先。比较共享祖先对两个分支技巧中的每个技巧。作为说明,请考虑以下相对简单的分支历史记录:

          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2

在这里,我们在分支branch1上,如附加的特殊名称HEAD (HEAD)所示。我们运行git merge branch2,告诉Git这两个 commits 是提交J(我们当前的提交)和提交L。 Git自行找到最佳共享提交 H。 Git将此称为合并基础。然后,Git比较H-vs-J,看看我们做了什么 ,该变化拾取了在IJ中所做的更改,并且比较H-vs-L来查看它们发生了什么变化,从而找出在提交KL中所做的更改。

合并过程组合两组更改,将合并的更改应用于提交H(即合并基础)的快照。如果一切顺利的话,由此产生的合并更改将正确应用,并且Git自行生成新的 merge commit M

          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \    /
          K--L   <-- branch2

因为我们在branch1上,所以Git将新合并提交的哈希ID写入名称 branch1,自动更新该名称,以使 last branch1上的em> commit现在为M。由于M两个父母,而不是只有一个,因此将所有事物联系在一起。如果我们在branch2上进行更多提交,则返回branch1,如下所示:

          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \    /
          K--L----N--O   <-- branch2

并要求Git再次合并,这次最好的 shared 提交不是H而是L(commit L在两个分支上) 。因此,这次Git将比较LM来看看我们所做的更改-毕竟这是由于H -vs-J而进行的更改,然后比较L-vs-O,看看它们在branch2上发生了什么变化。 Git将合并这些更改,将其应用于L中的快照,并产生新的合并:

          I--J
         /    \
...--G--H      M-------P   <-- branch1 (HEAD)
         \    /       /
          K--L----N--O   <-- branch2

现在提交P将从N-O中获取更改,以后的合并将使用提交O作为新的合并基础。

如果我们回过头来将其与摘樱桃进行比较,我们会发现它们有很大的不同:

          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2

假设我们现在通过提供其哈希ID或使用名称git cherry-pick在提交L上运行branch2。 Git会将提交K的快照与提交L的快照进行比较,将这些更改应用于提交J,并进行新的提交,我们将其称为L'-表示它是L的副本-将以J作为其(单)父对象:

          I--J--L'  <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2

我们没有从提交K获得任何更改。

如果我们在 this 点运行git merge branch2,Git仍将找到H作为合并基础,并将HL'进行比较来查看我们所做的更改,并像以前一样使用HKK进行比较。这次,当Git结合这些更改时,我们已经有了L-vs- D--X--E--F <-- redesigned / ...--B--C \ G--H--Y--I <-- somebranch 的更改,但是Git通常 足够聪明,只说 >哦,我看到我们和他们俩都做过同样的事情,所以我只需要复制一份副本。


1 差异包括以下事实:提交者时间戳是“现在”,而您要复制的提交的提交者时间戳大概是过去的某个时间。但是也有这样的情况:新提交的父级是曾经是您选择进入的分支上的最后一个提交的提交。您选择的提交的父代是不同的。因此,即使您设法在完全相同的一秒钟内选择原始提交,新提交也至少会稍有不同,并且产生完全不同的哈希ID。


重大的结构变化使Git变得困难

将来会有一个重要的版本,它在设计和架构上都有重大变化...

要了解这是如何成为问题的,请坐下来为自己绘制一张简化图:

C

假设提交X和Y进行了相当大的更改。然后,在两个分支上的提交C在两个分支上完全相同,因为它实际上只是一个提交X。您显然不希望将提交YF复制到 other 分支(这些是主要的重新设计),因此您肯定不要希望以任何方式合并提交Igit cherry-pick

您可以很容易地G提交HredesignedC,因为这些提交是用来提交C本身或直接派生的东西来自git cherry-pick。您可以Dsomebranch提交到C,因为该提交将应用于E本身。但是,如果您尝试选择FIE,那么,这些都是在之后进行的主要重新设计。它们不太容易申请。

Git无法完成的工作变成了必须完成的工作

如果提交FI和/或E中的内容从不必须移至“其他分支”,那就很好了。但是,如果您在FI中所做的某件事对 D--X--E--F <-- redesigned / ...--B--C \ G--H--Y--I <-- somebranch 很重要,那么现在您就遇到了问题。

这里没有皇家之路,但请注意。假设您有一个 fix ,用于在任何重大更改提交之前 的提交中发生的问题:

B

假设提交CDGH和/或fix123中存在缺陷。进一步假设我们可以通过在出现缺陷的位置创建一个 branch 来修复缺陷。为简单起见,让我们使用C来创建一个指向git checkout -b fix123 hash-of-C的分支,现在指向 D--X--E--F <-- redesigned / ...--B--C <-- fix123 (HEAD) \ G--H--Y--I <-- somebranch

C

现在,通过进行新的提交J,来修复出现在 提交 D--X--E--F <-- redesigned / ...--B--C--J <-- fix123 (HEAD) \ G--H--Y--I <-- somebranch 中并在两个分支共享的错误:

git checkout redesigned; git merge fix123

这使我们能够运行git checkout somebranch; git merge fix123 D--X--E--F--K <-- redesigned / / ...--B--C-----------J <-- fix123 \ \ G--H--Y--I--L <-- somebranch 将修订合并到两个分支中。这样做之后,我们得到以下结果:

K

其中LX合并提交。这使我们看到该修复程序已应用于两个分支。有问题的提交Yredesigned在分支somebranchC上仍然仅是 。但是,共享提交J之后是共享提交G

应该{​​{1}},也许还需要修复,需要进入redesigned,我们可以创建一个新分支,直接指向提交G,对此进行修复,然后将其合并到redesigned中。结果图太纠结,我无法尝试在此处绘制,但是所有内容都会记录在Git中,以备以后提取。

这些合并中的每一个都可能会带来一些困难(由于结构性重写的提交),并且常常很想在每个分支提示中使用单独的修订。也不存在任何内在的错误,尤其是如果您事先不知道两个分支都可能需要此修复程序。