说我有以下情况:
A -- B -- C -- D -- H master
\ /
E -- F -- G topicA
\
I -- J -- K topicB
topicA
使用 - squash 开关合并到master
,这意味着master
不知道{{1}的历史记录}}
如果我现在将topicA
合并到master
然后执行topicB
,则差异会混乱,并且包含许多不应该在那里的更改或撤消。< / p>
在这种情况下,我通常会将diff master...topicB
合并到master
,然后topicA
合并到topicA
,然后再执行前一段中的说法。但是,有时它是不可能的(例如分支被删除)并且我以很多冲突结束。
在这种情况下我该如何处理?我有任何误解吗?
topicB
是正确的解决方案吗?
答案 0 :(得分:4)
作为DavidN said in a comment,您的计划看起来足够健全。
(剩下的时间太长而且很乱;它是在其他任务之间写的。)
由于H
创建git merge --squash
,因此绘图错误。它应该是:
A -- B -- C -- D -- H master
\
E -- F -- G topicA
\
I -- J -- K topicB
关键区别在于提交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)
请注意,我绘制了一个真正的合并,而不是假的,不是合并的#34;壁球合并&#34;。正如我们将要看到的,这确实很重要。
当Git进入 make 这个新提交L
时,它必须合并提交H
和K
。要做到这一点,它必须找到他们的合并基础(在某些情况下可以有几个合并基础,但这里只有一个)。
任何两个提交的合并基础都是最低公共祖先:也就是说,最接近两个起始提交(H
和K
)的提交可以从< em>两个的那些开始提交。
让我们先从H
和K
开始。 H
可以从H
(当然)访问,但不能从K
访问。 K
可以从K
(当然)访问,但不能从H
访问。现在我们可以检查D
vs H
和K
:D
是否可以从H
开始,但不能从K
开始。现在我们可以检查J
,但无法从H
访问。现在我们考虑C
和I
,F
和E
,但直到我们一直回到提交B
我们发现可以从H
和K
找到提交。提交A
也可以,但距离H
和K
更远,因此提交B
是合并基础。
然后合并以两个差异开始:
git diff B H
和
git diff B K
第一个差异显示我们从B
更改为H
的内容。当然,H
包含我们在C
和D
中更改的内容,以及我们在E
,F
和G
中更改的内容。第二个差异显示我们从B
更改为K
的内容。当然,K
包含我们在E
中更改的内容,以及我们在I--J--K
中更改的内容。
这包括我们在E
两次中更改的内容,但Git通常并非总是如此,但通常 - 可以很好地注意到这一点变化只有一次。因此,提交L
可能包含每次提交的所有内容,只执行一次。
然后执行
diff master...topicB
请注意,这是使用三 -dot ...
语法,而不是两个 -dot ..
语法。我不确定你打算在这里做什么,但三点语法本质上意味着&#34;找到(或一个)合并基础&#34;。因此,让我们再次进行此练习:master
仍指向提交H
,topicB
现在指向新的合并提交L
,我们发现合并H
和L
的基础,现在我们有一个真正的合并(没有这个愚蠢的&#34;壁球合并&#34; 假的为我们合并的东西,没办法!)。
所以,让我们先从H
和L
开始。 L
可以从L
(当然)访问,但不能从H
访问。 H
可以从H
(当然)也可以从L
到达。这意味着H
和L
的合并基础为H
:master
和topicB
的合并基础为主。
...
diff master...topicB
由于master
位于三点的左侧,因此将其替换为合并基,即提交H
。三点的右侧被解析为其提交,即提交L
。然后,差异会显示H
和L
之间的差异。
在这种情况下,效果与git diff master..topicB
相同,这意味着与git diff master topicB
相同:比较提交H
和{{1按顺序。
这应该是一个非常明智的差异,尽管我们最初做的L
可怕的假壁球合并。 真实的合并修复了这种情况,至少对H
vs H
进行了修复。
让我们再次画出这个东西,但这一次使用假的非合并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)
我们需要再次找到diff master...topicB
和H
之间的合并基础,但现在L
并未指向两者 {{1} } 和 L
,但仅限于K
。 H
和K
都不是合并基础。 H
和L
都不会工作:我们无法从D
向后走J
,我们无法向后走D
来自L
。实际上,合并基础提交再次提交J
,因此这意味着:
H
这个差异将完全不同。
我不知道你对你的差异有什么期望,所以我无法解决这个问题:
差异混乱,包含很多变化或撤消,不应该存在。
现在让我们回到问题:
在这种情况下,我通常会将
B
合并到git diff B L
,然后master
合并到topicA
,然后再执行前一段中的说法。但是,有时它是不可能的(例如分支被删除)并且我以很多冲突结束。
请注意,删除分支名称对其提交没有立竿见影的效果。它做的是停止保护这些提交。也就是说,因为每个分支名称使提交可达,所以这些提交对 Grim Collector 是安全的......呃... 死神垃圾集电极。为了找到合并基础,我们多次做了这个可达性的事情;不过,Git更经常地在GC期间找到保留和承诺丢弃的提交;承诺在topicA
和topicB
期间转移;等等。如果提交受到其他方式的保护 - 通过真实合并的可达性,或来自另一个分支或标签名称的可达性,或者其他任何 - 它们都会保留。如果您可以通过哈希ID找到它们,则可以将它们带回来。
更重要的是,对于push
案例,如果您可以fetch
找到它们,则可以将其删除。我们马上就会看到这一点。
因为壁球&#34;合并&#34;根本不是合并,他们不会保护另一个提交链,并且 - 这通常是未来合并冲突的关键 - 他们不提供 future 与更新的合并库合并。这意味着未来的合并必须检查巨大的差异,而不是小的差异,然后Git的自动化&#34;冗余变化&#34;检测失败。
这在实践中的意义取决于你如何使用这些壁球而不是合并&#34;合并&#34;。当您使用它们进行一系列开发并将其减少到一次提交时,完全停止使用其他开发线可能是一个好主意。您可以保存它(使用分支或标记名称,甚至是分支和标记名称空间之外的其他引用,以便您通常不会看到它,从而使提交链不被GC编辑)或者只是让它它得到了收获,但无论哪种方式,你可能不应该继续使用它,并且包括你从它上面的一些提交分叉的任何其他分支。
rebase
git log
是正确的解决方案吗?
使用git rebase
,您可以将这些其他链 - 您的rebase --onto master topicA topicB
(在本例中)复制到新链,然后将标签(git rebase
)指向复制链的顶端。您要复制的提交是那些未被压缩的提交:这里是topicB
链。使用topicB
作为I--J--K
的topicA
参数将选择正确的提交集。请注意<upstream>
到达提交git rebase
,topicA
,G
,F
和E
,而B
到达{{1} }},A
,topicB
,K
,J
等等;因此,使用I
作为F
会切断E
背面的所有内容,但需要您提供的显式topicA
。
如果标签<upstream>
被删除,你仍然可以做这个rebase,它变得更加棘手。您需要做的是通过其哈希ID指定提交F
或--onto
,以便切断提交topicA
和更早。 G
的哈希ID是难以找到的任何地方(GC没有将其删除但是无法从任何实时引用中删除)到不存在(GC已将其删除)。但是,F
的ID就在F
链中:G
的父级是F
,topicB
&#39;父级是K
,J
的父级是J
。问题是没有简单的方法来确定提交I
是否在早期I
处理的链中的提交集中。
(这与我刚才加粗的早期评论有关,但并不完全相同。)