假设我有以下情况:
A -- B -- C -- D -- H master
\ /
E -- F -- G topicA
\
I -- J -- K topicB
Run Code Online (Sandbox Code Playgroud)
topicA已master使用--squash开关合并到其中,这意味着master不知道的历史topicA。
如果我现在合并master成一个topicB,然后做一个diff master...topicB比较,那么它就变得混乱了,其中包含了许多不应该存在的更改或撤消。
在这种情况下,我通常会合并master成topicA再topicA成topicB做在前面的段落前说。但是,有时这是不可能的(例如,删除了分支),并且我遇到了很多冲突。
在这种情况下我应该如何进行?我有什么误解吗?
是rebase --onto master topicA topicB正确的解决方案?
正如DavidN在评论中所说,您的计划看起来足够合理。
(其余的内容太长且太粗俗;它是在其他任务之间/期间编写的。)
由于H是由创建的git merge --squash,所以绘图是错误的。它应显示为:
A -- B -- C -- D -- H master
\
E -- F -- G topicA
\
I -- J -- K topicB
Run Code Online (Sandbox Code Playgroud)
关键的区别是,承诺H是不相关的E--F--G序列,至少不会在任何的Git检测到的感觉。提交H的内容是由在不管发生什么事情影响E--F--G序列(和,当然,通过无论在发生的C--D顺序),而是尽可能的Git现在知道,一个人走过来,写H甚至不看的E--F--G。
我现在在这里有一个很大的题外话。
如果我现在合并
master到topicB
好,让我们将其绘制为提交图,以确保这意味着您的意图。我将使用通常的形式(稍微紧凑一些,将分支名称的箭头略微移到右边):
A--B--C---D----H <-- master (still points to H)
\ \
E--F--G \ <-- topicA (still points to G)
\ \
I--J--K--L <-- topicB (points to new L)
Run Code Online (Sandbox Code Playgroud)
请注意,我绘制的是真正的合并,而不是伪造的,并非全部合并的“南瓜合并”。正如我们将看到的,这确实很重要。
当Git 进行这个新的提交时L,它必须合并提交H和K。为此,它必须找到其合并基础(在某些情况下可以有多个合并基础,但是这里只有一个)。
任何两个提交的合并基础(s)为/是最低的共同祖先:即,最接近于两种起始提交(在提交H和K是从可到达的)两者的那些开始提交的。
让我们开始H和K第一自己。 H可以从H(当然)到达,但不能从到达K。 K可以从K(当然)到达,但不能从到达H。现在我们可以检查Dvs H和K:D可以从而H不是到达K。现在我们可以检查了J,但是从不能到达H。现在,我们考虑Cand和and I和Fand E,但是直到我们重新提交后B,我们才发现Hand和都可以到达一个提交K。提交A也可以使用,但是H与和都相距较远K,因此提交B是合并的基础。
然后,合并从两个差异开始:
git diff B H
Run Code Online (Sandbox Code Playgroud)
和
git diff B K
Run Code Online (Sandbox Code Playgroud)
第一个差异显示了从B到的变化H。当然,H有我们在改变C和D,再加上无论我们改变了E,F和G。第二个差异显示了从B到的变化K。当然,K我们有什么改变了E,加上我们有什么改变了I--J--K。
这具有我们E 两次更改的内容,但是Git通常(并非总是如此,但通常)很好地注意到了这一点,并且只接受了一次更改。因此,提交L 可能具有上一次提交中的所有内容,只需完成一次。
然后做一个
diff master...topicB
注意,这是使用3个 -dot ...语法,而不是2个 -dot ..语法。我不确定您在这里打算做什么,但是三点语法实际上意味着“查找(或)合并基础”。因此,让我们通过这个练习一次:master仍然指向承诺H和topicB现在指向新的合并提交L,我们发现的合并基础H和L,现在我们有一个真正的合并(没有这愚蠢的“南瓜合并” 假合并工具和我们,没有办法!)。
因此,让我们开始H和L第一个自己。 L可以从L(当然)到达,但不能从到达H。 H可以从H(当然)以及从到达L。这意味着的合并基础H和L是H:的合并基础master和topicB是主人。
...
diff master...topicB
由于master位于三点的左侧,因此将其替换为合并基础(即commit)H。三点组的右侧解析为其提交,即commit L。然后,差异会向您显示H和之间的区别L。
在这种情况下,效果与的效果相同git diff master..topicB,这表示与git diff master topicB:比较提交H和的L顺序相同。
尽管我们最初所做的是可怕的假壁球合并,但这应该是一个非常明智的区别H。在真正的合并排序的修复这一点,至少在HVS L。
让我们再次绘制该对象,但是这次使用伪造的非合并git merge --squash技术。我们新提交的内容与L完成一次真正的合并的内容相同,但是图形将有所不同:
A--B--C---D----H <-- master (still points to H)
\
E--F--G <-- topicA (still points to G)
\
I--J--K--L <-- topicB (points to new L)
Run Code Online (Sandbox Code Playgroud)
现在我们回到:
diff master...topicB
再次,我们需要找到合并基地之间H和L,但现在L没有指向回两 K 和 H,但只K。无论是H也不L是合并基础。既不D也不J工作之一:我们不能倒着走,以D从L,我们不能倒着走,以J从H。实际上,合并基础提交再次是commit B,因此这意味着与以下内容相同:
git diff B L
Run Code Online (Sandbox Code Playgroud)
这个差异会大不相同。
我不知道您希望从差异中得到什么,所以我无法解决这一部分:
差异被弄乱了,其中包含许多不应该存在的更改或撤销。
现在让我们回到问题:
在这种情况下,我通常会合并
master成topicA再topicA成topicB做在前面的段落前说。但是,有时这是不可能的(例如,删除了分支),并且我遇到了很多冲突。
请注意,删除分支名称对其提交没有立即影响。它的作用是停止保护那些提交。也就是说,由于每个分支名称都使提交可访问,因此可以从Grim Collector ... er ... Grim Reaper垃圾收集器安全地进行那些提交。我们做了几次这种可到达性的事情,以找到合并基础。但是,Git在GC期间更经常地查找要保留的提交和丢弃的提交。提交转让,期间push和fetch; 等等。如果提交受到其他某种方式的保护(通过真正的合并实现可访问性,或者通过另一个分支或标记名的可访问性,等等),它们将始终存在。如果可以通过哈希ID找到它们,则可以将它们带回。
对于您的rebase情况更重要的是,如果可以通过找到它们git log,则可以将其切断。我们稍后会看到。
由于壁球“合并”实际上根本不是合并,因此它们将无法保护其他提交链,而且- 这通常是将来发生合并冲突的关键 -它们不会为将来的合并提供更新的合并基础。这意味着那些将来的合并必须检查巨大的差异而不是小的差异,然后Git的自动“冗余更改”检测失败。
实际上,这意味着什么取决于您如何使用这些“非合并”壁球。当您使用它们进行一条开发线并将其缩减为单个提交时,最好完全停止使用另一条开发线。您可以保存它(使用分支或标签名称,甚至在分支和标签名称空间之外使用其他引用,以使您通常看不到它,从而防止提交链被GC编辑)或只是让它获取取得了收益,但是无论哪种方式,您都可能不应该继续进行此工作,这包括您拥有从其某些提交派生出来的任何其他分支。
git rebase是
rebase --onto master topicA topicB正确的解决方案?
使用git rebase,您可以将这些其他链topicB(在本例中为您)复制到新链,然后将标签(topicB)指向复制链的尖端。您要复制的提交是未压缩的:这里就是I--J--K链。使用topicA作为<upstream>参数git rebase 将选择正确的提交集。需要注意的是topicA下游的提交G,F,E,B,和A,而topicB达到K,J,I,F,E,等; 因此将背面的所有内容都topicA用作<upstream>印章F,但这需要--onto您提供的内容明确。
如果标签topicA被删除,您仍然可以重新设置标签,这将变得更加棘手。您需要做的是指定提交内容G或F通过其哈希ID 来指定,以便将提交内容F及更早的内容截断。的哈希ID G从难以找到(GC尚未删除它,但是从任何实时引用都无法访问)到不存在(GC已删除它)的任何地方。F但是,ID的ID 正好在topicB链中:K的父级是J,J其父级是I,而I父级是F。问题在于,没有简单的方法来确定提交F是否在较早git merge --squash处理的链中的提交集中。
(这与我加粗的较早的说法有关,但又不完全相同。)