我是Git的新手,所以如果这听起来像是一个微不足道的问题,我很抱歉。我昨天在我的特色分支Git中提交了一些代码。今天,当它被审查和批准时,我正试图合并到开发和获取合并冲突中。
今天我在开发分支,它是最新的,有一些新的文件由另一个开发人员提交。当我从development切换到我的功能分支时,我看到development分支中的所有新文件都是IDE中的新文件。因此,我应该只提交有冲突的文件,还是在解决冲突后也必须提交所有新文件。
这个问题似乎是关于如何使用IDE的一半,但请注意,Git本身将所有文件存储在每个快照中。这包括合并快照。
因此,如果您在提交之前确实从合并中删除了文件,则合并结果将不会有新文件。实际上,您的合并会声称合并这些文件的正确方法是删除它们。因此,它们应该保留为"新文件",除非合并它们的正确方法是删除它们。
更多详细信息
在使用Git时,同时记住很多事情是很重要的。(这是Git很难入门的原因之一。)以下是一些需要了解的重要项目的列表:
-
Git并不是真正的文件,甚至不是真正的分支。Git就是关于提交。这意味着你需要确切地知道什么是承诺和做什么。
-
每个提交包含两件事:它的主要数据,以所有文件的快照的形式;以及描述此提交的一些元数据或有关数据的数据。元数据包括谁、何时以及——对人类来说很重要,尽管与Git本身无关——为什么你(或任何人)做出了承诺。它们还包括一些提交哈希ID。这些哈希ID对Git来说非常重要,尽管你自己可能并不那么关心它们。
-
每个提交都会得到一个唯一的哈希ID。实际上,每个提交的"真实名称"都是其哈希ID。这些哈希ID是Git查找提交的方式。如果你想从Git的中获取文件,你会使用哈希ID,即使它被一个名称所掩盖:像
master
或develop
这样的分支名称,或者像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可以找到F
。F
包含其父级的散列ID,依此类推
但是我们如何找到H
呢?这就是分支名称的作用:分支名称只包含最后一次提交的哈希ID。
因此,为了向master
添加提交,Git:
- 写出提交,包括父散列ID
H
- 它计算一个新的唯一散列ID,我们称之为
I
然后Git将其填充到分支名称
master
中,给出... <-F <-G <-H <-I <--master
提交是快照,但您查看更改
当我们有这样的线性提交链时:
...--F--G--H <-- master
当我们要求Git显示我们提交H
时,我们看到的更改,而不是快照。但这是因为很有用,所以Git实际上提取并将G
和H
提交到临时区域(实际上是内存中的),然后对它们进行比较。
两个提交G
和H
是两个快照。两者都保存着你所有的文件。G
中README.md
的副本和H
中的副本可能有不同,在这种情况下,当向您显示H
时,Git会向您显示在G
中的副本和在H
中的副本之间的差异。
当然,在两个提交中可以有不同的文件。也许G
和H
都有README.md
——也许它们在两个提交中都是相同的——但也许H
有file.py
,而G
中根本没有。在这种情况下,G
与H
显示一个新文件。
请注意,G
中也可以有不在H
中的文件;在这种情况下,与Git一样,进行比较,告诉文件被删除。它仍然存在于提交G
中,作为一个完整的快照。只是H
中没有。
多个分支
当你有多个分支机构名称时,你可以这样画:
I--J <-- master
/
...--G--H
K--L <-- other
两个名称master
和other
通过散列ID选择两个提交。提交J
是master
的提示(最后一个),提交L
是other
的提示。
现在我们有了两个分支名称,我们需要一种方法来记住我们实际使用的是哪一个。Git为此使用了特殊名称HEAD
,并将其附加到存储库中的一个分支名称上:
I--J <-- master (HEAD)
/
...--G--H
K--L <-- other
请注意,直到H
(包括CCD_50)的提交都在两个分支上。(Git在这里很不寻常;大多数版本控制系统都不是这样工作的。)提交I
和J
只在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从git status
时,Git将比较您提出的下一次提交,这将是一次合并提交——我们尚未绘制或描述——与当前提交I--J <-- master (HEAD)
/
...--G--H
K--L <-- other
M
。M
中的快照将不同于现在存在于J
中的快照,git status
将告诉您这一点,就像Git可能会向您展示J
和这个建议的M
之间的差异一样。H
和L
的差异中,Git发现他们添加了一些新文件。这些文件不在H
中,而在L
中。这些文件可能不在I
和J
中。L
中获取了这些新文件,它们现在位于您提议的下一次提交M
,那么这些文件将存在。将J
与本方案进行比较,这些文件是新文件。
所以git status
会告诉你他们添加的文件是新的!您可能想要保留它们。如果您现在删除,它们将从您提议的新提交中删除。
无论你是否认为自己已经完成了,你仍然处于"解决"的阶段。你已经告诉Git,Git抱怨的每一个冲突文件都已经完成了。它们已解析的版本已准备好进入新提交,git status
会将J
中的文件与提议的新提交中的文件进行比较,并表示它们不同(如果不同的话)。但是,在这种状态下,您可以继续进行更多的更改——这些更改不是来自提交J
或L
。
做更多的改变很少是个好主意。人们将此时所做的更改称为邪恶合并。看到邪恶在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 other
和other
的名称为提交L
。
查看合并提交不同
当您稍后查看此提交时,默认情况下,Git不会显示更改内容。这是因为Git不知道该与哪个父级进行比较。Git是否应该提取J
和M
,并将两者进行比较?还是应该提取L
和M
,并进行比较?git log -p
命令是惰性的,只是不执行任何一个命令。
还有其他Git命令和其他查看更改的方法,可以让您选择使用哪个父级。最简单的方法是将-m
添加到git log -p
。也就是说:当您执行合并提交时,为每个父级运行一个diff也就是说,git log
现在将首先比较J
-与-M
,并显示;然后比较L
-与-M
。但你必须要求这个。
您应该知道,git show
将使用Git所称的组合diff来显示合并提交。但是组合diff故意省略了许多细节。大多数情况下,它们试图显示合并冲突发生或可能发生的区域。