为什么GitHub允许分支在前面和后面提交但不包含要合并的更改?

时间:2018-06-27 19:10:50

标签: git github git-diff

最近合并了一个包含3行修订的分支。第二天,提交了一个分支请求请求,其中包含3行修复程序的2行。它是原始合并分支后面的多个提交。那棵树看起来像...

A1--A2--A3-------B3--C4
     \   \      /   /
      \   A3--B3   /
       \          /
        A2------C3

因此,B3将包含3行,而C3将包含2行。当我去检查C4时,即使技术上没有,它也会显示提交的差异。实际上,在进行交互式基准时,该基准将停止并说这是一个空提交。

我知道git可以让您合并空的提交,但我感到困惑的是:

  1. 为什么GitHub允许您合并(在某种程度上为git),而在没有任何更改的情况下通常不允许您合并。我想这与前/后问题的提交有关,但只是想知道根本原因是什么
  2. 为什么git在没有差异的提交中显示diff(意为双关语)?

1 个答案:

答案 0 :(得分:0)

TL; DR

合并会影响提交图,因此根据定义,必须始终允许合并。一些请求合并的命令行命令可以作为非合并 fast-forward 操作完成,但是GitHub clicky按钮在这些情况下会强制进行真正的合并。

您尚未回答我的评论问题,因此我在这里至少要含糊不清或冗长。另外,我现在意识到您的图表中有两次A2A3B3,这意味着您的图表是错误的,我们应该有例如:

A1--A2--A3-------B5--C4
     \   \      /   /
      \   B3--B4   /
       \          /
        C2------C3

或者也许:

A1--A2--A3-------B4--C4
     \   \      /   /
      \   ----B3   /
       \          /
        --------C3

最后,在我开始之前,将这些东西标记为AnBnCn的序列从Git的角度来看并没有什么用处,因为它是无关紧要的在查看和合并提交时,哪个分支包含提交。重要的是提交者本身及其父母/子女关系-分支名称仅在此处用于人类。 :-)因此,我将其重新绘制为:

...--A--B--C------H--I   <-- branchname
         \  \    /  /
          \  D--E  /
           \      /
            F----G

其中HI是合并提交。 (要 make H,必须有人跑:

git checkout branchname

branchname指向C时,然后:

git merge --no-ff <identifier-selecting-E>

或类似的东西。 --no-ff强制此操作为真正的合并,而不是快进的非合并“合并”。请注意,GitHub“合并”按钮可以执行此操作,即使用--no-ff。)

  

当我去检查[在我的图表中,最终的合并提交现在称为I –编辑。]时,即使技术上没有,它也显示了提交的差异...

这是使事情变得有趣的地方。如您现在已经知道的那样,每个提交都是其所有文件的完整快照-提交包含与先前提交的区别,它们仅包含文件的快照。

当我们有一个普通的非合并提交,例如E时,该提交具有一个父提交,因此Git可以通过提取快照轻松显示提交E其父提交D的快照,然后为E本身提取快照,并比较两者。对于在这两个快照中不同的每个文件,Git尝试产生一个最小的编辑序列,该序列会将D更改为E

如果您运行git commit,并且您的索引(Git生成新提交的区域)与当前提交匹配,则Git通常只会告诉您“没有任何提交”,并且拒绝进行提交。添加--allow-empty会告诉Git:创建新快照,即使它与当前快照相同。不是快照本身为空,而是上一次提交的更改为空。因此,没有特别的理由来打扰新快照。

合并提交不同。在某种程度上,它们,就像普通的提交一样:它们拥有快照。区别在于它们有两个或更多的 1 父母,每个父母都拥有快照。这意味着当涉及至少三个快照时,很难显示两个快照之间的差异。

某些命令,例如git log,默认为不打扰。也就是说,他们看到他们正在显示的提交是合并提交,他们只是不费心地提取任何父级和计算差异。

git show命令在默认情况下会做一些额外的事情:它提取父母的快照和子合并提交的所有快照,然后将子文件中的每个文件与 all 该文件的父母版本。然后,它会抛出与 any 父变量完全匹配的任何文件,并且仅对于与双方父变量不同的某些文件,显示Git称为组合差异。组合的diff输出格式记录在several Git manual pages including that for git diff中。

在这些命令中添加-m标志使Git进行 split 合并为多个虚拟提交,每个父提交一个。也就是说,除了显示IH G的组合差异之外,Git还将显示H-vs-的一个差异I,然后是G-vs-I的第二个差异。这些差异包含更多信息:例如,如果文件README.txtH-vs-I中是相同的,而foo.txt是不同的,则您会看到{{1 }}。如果foo.txt在{{1}-vs-README.txt中与G相同,则您会看到I。但是,如果您得到了一个组合差异,那么Git就会同时抛出 foo.txtREADME.txt的匹配项README.txt的) { {1}}(与H匹配的I),所以您根本看不到任何一个文件。

现在,即使所有在输入中都完全匹配的文件都提交了,即foo.txtG中的快照完全匹配了,Git仍会进行合并提交,因为合并提交I本身(其两个父哈希ID指向HG)记录了合并。这将为以后的合并设置新的合并基础。 Git使用图为以后的I操作计算合并基础,因此我们需要连接这两个节点的图节点。在您的示例中不是这种情况,但是值得注意。


1 Git称两个以上的父母合并为章鱼合并。但是,它们不会做任何更明显的成对合并所无法实现的事情。其他一些VCS仅支持成对合并。


  

实际上,当进行交互式重新设置时,重新设置将停止并说[合并是]空提交。

Rebase通过复制提交来工作。这需要将每个提交变成一个变更集,将提交与其父级进行比较。根据定义,合并至少有两个父对象,因此不能以这种方式复制。 2 Rebase解决此难题的方法是完全删除 合并提交。但是:

  

...因此[H将包含3行,而[G]将包含2行。

让我们绘制第二个分支名称,指向git merge,用于重新定级,而完全不进行合并提交E

G

我们可以先运行G,然后运行I(有或没有...--A--B--C------H <-- branchname \ \ / \ D--E \ F----G <-- feature )。 Git将从git checkout feature开始并向后工作,从git rebase branchname开始并向后工作,以枚举--interactive无法到达的提交。这两个“后退工作”步骤在提交feature处同时出现,因此{em> not 不会被复制branchname,更早的提交也不会被复制。这样就可以按顺序复制提交GH

B的副本可能会起作用,给出:

B

(请注意,rebase与分离的F一起使用,直接指向某些提交)。

现在,Git准备复制G中的更改,这将更改已更改的三行中的两行,而剩下一行。 Git认为这是徒劳的(已经更改了两行),并且根据上下文声明了合并冲突(因此您必须清理所有内容并确定哪个是正确的最终版本),或者只是确定什么都没有改变。如果Git确实因合并冲突而停止,则将其修复,并使用提交F中三行更改的版本。您运行...--A--B--C------H <-- branchname \ \ / \ \ D--E F' <-- HEAD \ F----G <-- feature 使其继续运行,此时,Git说没有什么要提交的。您现在可以选择:使用HEAD提交,以便保留提交消息和单独的快照(与其父快照匹配),或完全使用G删除提交。

假设您要进行后者,Git通过将名称(F')指向其复制的最后一个提交,并设置git rebase --continue来记住--allow-empty过去的位置来完成重新配置点:

git rebase --skip

一切都完成了。


2 feature有一个附加标志ORIG_HEADfeature,声称保留合并。这是不正确的:实际上是重新执行合并。也就是说,它将复制足够的提交以设置合并条件,然后运行...--A--B--C------H <-- branchname \ \ / \ \ D--E F' <-- feature (HEAD) \ F----G <-- ORIG_HEAD 进行新的合并。如果您以前解决了冲突,则可能必须再次进行。即使您以前不必解决冲突,也可能必须在这一点上解决。如果您先使用git rebase -i进行合并,然后再编辑内容,然后依次编辑--preserve-p以产生所谓的 evil merge ,则Git不知道您执行了此操作,并进行了新的非邪恶合并,从而降低了所有的偷偷摸摸的感觉。如果尽管有邪恶合并这个术语,偷偷摸摸实际上是的事情,那么您将失去它。