在Git中解决合并冲突时新建文件



我是Git的新手,所以如果这听起来像是一个微不足道的问题,我很抱歉。我昨天在我的特色分支Git中提交了一些代码。今天,当它被审查和批准时,我正试图合并到开发和获取合并冲突中。

今天我在开发分支,它是最新的,有一些新的文件由另一个开发人员提交。当我从development切换到我的功能分支时,我看到development分支中的所有新文件都是IDE中的新文件。因此,我应该只提交有冲突的文件,还是在解决冲突后也必须提交所有新文件。

这个问题似乎是关于如何使用IDE的一半,但请注意,Git本身将所有文件存储在每个快照中。这包括合并快照。

因此,如果您在提交之前确实从合并中删除了文件,则合并结果将不会有新文件。实际上,您的合并会声称合并这些文件的正确方法是删除它们。因此,它们应该保留为"新文件",除非合并它们的正确方法是删除它们。

更多详细信息

在使用Git时,同时记住很多事情是很重要的。(这是Git很难入门的原因之一。)以下是一些需要了解的重要项目的列表:

  • Git并不是真正的文件,甚至不是真正的分支。Git就是关于提交。这意味着你需要确切地知道什么是承诺和做什么。

  • 每个提交包含两件事:它的主要数据,以所有文件的快照的形式;以及描述此提交的一些元数据或有关数据的数据。元数据包括谁、何时以及——对人类来说很重要,尽管与Git本身无关——为什么你(或任何人)做出了承诺。它们还包括一些提交哈希ID。这些哈希ID对Git来说非常重要,尽管你自己可能并不那么关心它们。

  • 每个提交都会得到一个唯一的哈希ID。实际上,每个提交的"真实名称"都是其哈希ID。这些哈希ID是Git查找提交的方式。如果你想从Git的中获取文件,你会使用哈希ID,即使它被一个名称所掩盖:像masterdevelop这样的分支名称,或者像v2.1这样的标记名称,或者其他什么。

    这些散列ID又大又丑,人类无法处理。它们需要又大又丑,因为每个提交都有一个唯一的哈希ID。显而易见的方法是,只按顺序对它们进行编号(提交#47将在提交#46之后,等等)可能会起作用,但Git是分布式的。没有一个中心的Git提交编号分配器,每个人都可以去,以获得下一个编号。

    由于它们又大又丑,我们通常不会去看它们。我们使用名称,稍后我们将详细介绍。

  • 每个提交——嗯,几乎每个提交——都有一个父级提交。父项是在此之前的提交。这就是这个额外的元数据的作用:提交存储其父母的哈希ID。合并提交在一个方面很特别:它们存储多个父级,即在这个父级之前有多个提交。

    (某人进行的第一次提交没有父级,因为没有更早的提交。这种提交被称为根提交。每个非空存储库都至少有一个,而且有多个是不寻常的,尽管可以进行新的根提交,也可以通过其他方式获取。)

  • 分支名称,如master,只记住分支中最后一次提交的原始哈希ID。

  • 因此,分支通过添加新提交而增长。添加一个新的提交包括制作一个新快照,即每个文件的新副本,其父级设置为当前提交。然后Git使分支名称记住提交的哈希ID。

因此,我们可以将提交绘制为一个链,最新的提交位于右侧(或git log --graph的顶部),如下所示:

... <-F <-G <-H   <--master

在这里,每个大写字母代表一些丑陋的提交散列。最新提交的哈希是H,而H包含其父级G的哈希ID。Git可以使用H中的哈希ID来查找G。CommitG包含其父F的哈希ID,因此Git可以找到FF包含其父级的散列ID,依此类推

但是我们如何找到H呢?这就是分支名称的作用:分支名称只包含最后一次提交的哈希ID。

因此,为了向master添加提交,Git:

  • 写出提交,包括父散列IDH
  • 它计算一个新的唯一散列ID,我们称之为I
  • 然后Git将其填充到分支名称master中,给出

    ... <-F <-G <-H <-I   <--master
    

提交是快照,但您查看更改

当我们有这样的线性提交链时:

...--F--G--H   <-- master

当我们要求Git显示我们提交H时,我们看到的更改,而不是快照。但这是因为很有用,所以Git实际上提取GH提交到临时区域(实际上是内存中的),然后对它们进行比较

两个提交GH是两个快照。两者都保存着你所有的文件。GREADME.md的副本和H中的副本可能有不同,在这种情况下,当向您显示H时,Git会向您显示在G中的副本和在H中的副本之间的差异

当然,在两个提交中可以有不同的文件。也许GH都有README.md——也许它们在两个提交中都是相同的——但也许Hfile.py,而G中根本没有。在这种情况下,GH显示一个新文件

请注意,G中也可以有不在H中的文件;在这种情况下,与Git一样,进行比较,告诉文件被删除。它仍然存在于提交G中,作为一个完整的快照。只是H中没有。

多个分支

当你有多个分支机构名称时,你可以这样画:

I--J   <-- master
/
...--G--H

K--L   <-- other

两个名称masterother通过散列ID选择两个提交。提交Jmaster的提示(最后一个),提交Lother的提示。

现在我们有了两个分支名称,我们需要一种方法来记住我们实际使用的是哪一个。Git为此使用了特殊名称HEAD,并将其附加到存储库中的一个分支名称上:

I--J   <-- master (HEAD)
/
...--G--H

K--L   <-- other

请注意,直到H(包括CCD_50)的提交都在两个分支上。(Git在这里很不寻常;大多数版本控制系统都不是这样工作的。)提交IJ只在master上,并且——至少现在——提交K-L只在other上。

合并就是合并工作

当你在你的分支上,并且你正在合并其他人在其他分支上所做的工作时,你不想只按照他们最近提交的文件原样处理,也不想只按你最近提交的原样处理你的文件。您希望将您所做的更改与它们所做的更改组合起来。

但是,由于Git只存储快照,Git将如何发现更改?我们已经看到Git如何将提交与其父级进行比较。但假设你有:

I--J   <-- master (HEAD)
/
...--G--H

K--L   <-- other

我们如何将您对master所做的更改与他们对other所做的修改进行比较?Git对此的回答是:找到最好的共享提交,这在两个分支上这里,这显然是提交H。所以Git现在将H中的所有文件与J:中的所有文档进行比较

git diff --find-renames <hash-of-H> <hash-of-J>   # what we changed

然后,Git将H中的所有文件与L:中的全部文件进行比较

git diff --find-renames <hash-of-H> <hash-of-L>   # what they changed

Git可以合并的更改。无论我们做了什么,Git都可以对H中的文件做同样的事情,但也可以对CCD-64中的文件执行

合并冲突

然而,有时,在试图将这些更改组合在一起时,Git会遇到问题。例如,如果我们更改README.md的第42行,他们也更改了README.md的第42列,但我们做出了不同的更改,会怎么样在这种情况下,Git会尽其所能进行合并,然后以合并冲突停止。

您现在的工作是解决这些冲突。Git解决冲突的能力是有限的,但它提供了一系列不同质量的工具来提供帮助,并允许您在上面添加自己的工具。很多IDE添加了很多不同质量的工具,我不能对其中的大多数说什么,因为我不使用它们。

不过,在解析过程中和/或之后运行git status的可能性很大。这个git status根据您在解决过程中所处的位置说明不同的内容。在这里,我假设您已经完成了解决--git status表示所有已解决的冲突,或者不表示任何关于未合并文件的信息。(精确的输出取决于你的Git年份;2.x系列之前的旧Git在这里几乎没有那么好,任何超过1.8.4的版本都不好。)

当您在这一点上使用git status时,Git将比较您提出的下一次提交,这将是一次合并提交——我们尚未绘制或描述——与当前提交

I--J   <-- master (HEAD)
/
...--G--H

K--L   <-- other

但是,在表上有一个提议来进行新的提交MM中的快照将不同于现在存在于J中的快照git status将告诉您这一点,就像Git可能会向您展示J和这个建议的M之间的差异一样。

现在,假设在HL的差异中,Git发现他们添加了一些新文件。这些文件不在H中,而在L中。这些文件可能不在IJ中。

因此,Git从L中获取了这些新文件,它们现在位于您提议的下一次提交

中。如果您现在使合并提交M,那么这些文件将存在。将J与本方案进行比较,这些文件是新文件

所以git status会告诉你他们添加的文件是新的!您可能想要保留它们。如果您现在删除,它们将从您提议的新提交中删除。

无论你是否认为自己已经完成了,你仍然处于"解决"的阶段。你已经告诉Git,Git抱怨的每一个冲突文件都已经完成了。它们已解析的版本已准备好进入新提交,git status会将J中的文件与提议的新提交中的文件进行比较,并表示它们不同(如果不同的话)。但是,在这种状态下,您可以继续进行更多的更改——这些更改不是来自提交JL

做更多的改变很少是个好主意。人们将此时所做的更改称为邪恶合并。看到邪恶在git中融合了吗?了解更多信息。你可以去做,如果你觉得有充分的理由去做,也许你终究应该去做。请记住,当您进行新的合并提交时,您有机会解释您这样做了,以及为什么这样做。但您可能希望将新文件作为新文件保留在此处。

在任何情况下,您现在都可以使用命令行Git完成合并,其中包含:

git merge --continue    # or git commit, if your Git does not have --continue

这使得最终合并提交。正如我们前面提到的,合并提交有两个父级(从技术上讲,是两个或更多,但你自己可能不会遇到这些所谓的章鱼合并)。合并的第一个父级是通常的父级。第二个是你告诉Git合并的提交:

I--J
/    
...--G--H      M   <-- master (HEAD)
    /
K--L   <-- other

新提交的M现在有两个父级,还有一个像任何提交一样的快照,以及一个作者、日期和时间戳以及日志消息等等。第一个父级是J,第二个是L,因为您说git merge otherother的名称为提交L

查看合并提交不同

当您稍后查看此提交时,默认情况下,Git不会显示更改内容。这是因为Git不知道该与哪个父级进行比较。Git是否应该提取JM,并将两者进行比较?还是应该提取LM,并进行比较?git log -p命令是惰性的,只是不执行任何一个命令。

还有其他Git命令和其他查看更改的方法,可以让您选择使用哪个父级。最简单的方法是将-m添加到git log -p。也就是说:当您执行合并提交时,为每个父级运行一个diff也就是说,git log现在将首先比较J-与-M,并显示;然后比较L-与-M。但你必须要求这个。

您应该知道,git show将使用Git所称的组合diff来显示合并提交。但是组合diff故意省略了许多细节。大多数情况下,它们试图显示合并冲突发生或可能发生的区域。

最新更新