我在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.0
和release-2.0.0
将在完成后合并到master
中。
请分享您的想法。我不确定这是否是正确的问题。但是我看到其他类似的问题here
答案 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可以在您要求时计算这些文件中的更改,从而向您显示该提交中的更改。
要从单个提交中进行更改,可以使用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
,看看我们做了什么 ,该变化拾取了在I
和J
中所做的更改,并且比较H
-vs-L
来查看它们发生了什么变化,从而找出在提交K
和L
中所做的更改。
合并过程组合两组更改,将合并的更改应用于提交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将比较L
和M
来看看我们所做的更改-毕竟这是由于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
作为合并基础,并将H
与L'
进行比较来查看我们所做的更改,并像以前一样使用H
与K
与K
进行比较。这次,当Git结合这些更改时,我们已经有了L
-vs- D--X--E--F <-- redesigned
/
...--B--C
\
G--H--Y--I <-- somebranch
的更改,但是Git通常 足够聪明,只说 >哦,我看到我们和他们俩都做过同样的事情,所以我只需要复制一份副本。。
1 差异包括以下事实:提交者时间戳是“现在”,而您要复制的提交的提交者时间戳大概是过去的某个时间。但是也有这样的情况:新提交的父级是曾经是您选择进入的分支上的最后一个提交的提交。您选择的提交的父代是不同的。因此,即使您设法在完全相同的一秒钟内选择原始提交,新提交也至少会稍有不同,并且产生完全不同的哈希ID。
将来会有一个重要的版本,它在设计和架构上都有重大变化...
要了解这是如何成为问题的,请坐下来为自己绘制一张简化图:
C
假设提交X和Y进行了相当大的更改。然后,在两个分支上的提交C
在两个分支上完全相同,因为它实际上只是一个提交X
。您显然不希望将提交Y
或F
复制到 other 分支(这些是主要的重新设计),因此您肯定不要希望以任何方式合并提交I
和git cherry-pick
。
您可以很容易地G
提交H
或redesigned
到C
,因为这些提交是用来提交C
本身或直接派生的东西来自git cherry-pick
。您可以D
将somebranch
提交到C
,因为该提交将应用于E
本身。但是,如果您尝试选择F
,I
或E
,那么,这些都是在之后进行的主要重新设计。它们不太容易申请。
如果提交F
,I
和/或E
中的内容从不必须移至“其他分支”,那就很好了。但是,如果您在F
或I
中所做的某件事对 D--X--E--F <-- redesigned
/
...--B--C
\
G--H--Y--I <-- somebranch
很重要,那么现在您就遇到了问题。
这里没有皇家之路,但请注意。假设您有一个 fix ,用于在任何重大更改提交之前 的提交中发生的问题:
B
假设提交C
,D
,G
,H
和/或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
其中L
和X
是合并提交。这使我们看到该修复程序已应用于两个分支。有问题的提交Y
和redesigned
在分支somebranch
和C
上仍然仅是 。但是,共享提交J
之后是共享提交G
。
应该{{1}},也许还需要修复,需要进入redesigned
,我们可以创建一个新分支,直接指向提交G
,对此进行修复,然后将其合并到redesigned
中。结果图太纠结,我无法尝试在此处绘制,但是所有内容都会记录在Git中,以备以后提取。
这些合并中的每一个都可能会带来一些困难(由于结构性重写的提交),并且常常很想在每个分支提示中使用单独的修订。也不存在任何内在的错误,尤其是如果您事先不知道两个分支都可能需要此修复程序。