为什么在合并git中更改的文件时没有合并冲突?

为什么在合并git中更改的文件时没有合并冲突?,git,github,git-merge,Git,Github,Git Merge,我对使用git作为开发的版本控制系统是完全陌生的,并且仍在学习过程中。 我对拉请求期间的合并冲突感到困惑。根据我的理解,合并冲突通常发生在文件的同一行 在两个不同的分支中是不同的。 我有一个具有以下树结构的git存储库,所以我从master创建了一个dev分支,然后从dev创建了D1和D2分支。 主控->开发->D1,D2 在dev分支中,我有一个文件名file1.txt,第一行是1111111111,在D1分支中,我有一个相同的文件file1.txt,第一行是aaaaaaaaaaaaaaaaa

我对使用git作为开发的版本控制系统是完全陌生的,并且仍在学习过程中。 我对拉请求期间的合并冲突感到困惑。根据我的理解,合并冲突通常发生在文件的同一行 在两个不同的分支中是不同的。 我有一个具有以下树结构的git存储库,所以我从master创建了一个dev分支,然后从dev创建了D1和D2分支。 主控->开发->D1,D2 在dev分支中,我有一个文件名file1.txt,第一行是1111111111,在D1分支中,我有一个相同的文件file1.txt,第一行是aaaaaaaaaaaaaaaaaaaaaaaaa。我创建了一个pull请求以将D1分支合并到dev,并预期会出现合并冲突,因为两个分支中的第一行file1.txt不同

但git显示了能够合并消息,表明没有合并冲突,并且开发分支更改被D1分支更改覆盖。 你知道我错过了什么吗

根据我的理解,当文件的同一行在两个不同的分支中不同时,通常会发生合并冲突

这可不是那么回事。当您在两个不同的分支中更改文件的同一行,然后尝试将它们合并在一起时,Git会检测到冲突

在您的示例中,您已经更改了分支D1中文件的第一行,但在开发分支中没有触及它,因此合并过程看起来像是从分支D1中获取第一行更改,并将其应用于开发分支中的第一行


相反,如果您或某人在创建D1之后更改分支DEV中的文件的第一行,然后在D1中更改该行,合并过程将看起来像:分支D1中的第一行和分支DeV中的第一行发生了变化。这就是合并冲突的样子。

这里有很多东西需要学习。一些人将Git的学习曲线称为学习墙。在我看来,在Git上添加像GitHub这样的web服务实际上会让事情变得更难:现在已经不可能分辨什么东西是在Git中还是由GitHub提供的了。拉取请求实际上属于GitHub提供的类别。但它们建立在Git的合并动词的基础上,并结合GitHub同时存储您的存储库和其他人的存储库这一事实

让我们暂时把这些放在一边,从Git本身开始。理解某些文件中是否以及何时会存在合并冲突的关键隐藏在某些文件后面

提交中有什么内容 首先,让我们提到提交保存文件。更准确地说,每次提交时都会保存所有文件的快照。但是每次提交都有关于提交的额外信息,比如谁做的、何时做的以及为什么要发送日志消息

在Git中,每个提交都由其哈希ID唯一标识。这是一个巨大、难看、大部分不可读、对人类完全无用的字符串,例如Git存储库中的Git实际提交。这些是Git查找提交的方式,不过除了复制粘贴或链接之外,我不建议您经常使用它们-

不过,重要的是要知道Git就是这样做的,因为几乎每个提交都会列出至少一个父提交的原始哈希ID。父级是在此提交之前的提交。确保不列出父级的提交是有史以来第一次提交,当然之前没有提交

这一切都意味着,从任何分支上的最后一次提交开始,Git可以向后工作,直到以前的提交。然后,Git有一个从人类可读的分支名称(如master)到原始提交散列ID的表。要向分支添加新提交,请执行以下操作:

Git从分支名称获取当前提交哈希ID。 Git将其作为新提交的父级放入新提交中。当然,Git还加入了新的快照,还有你的名字等等。这将生成一个新的、唯一的、丑陋的大哈希ID。 Git然后将新提交的哈希ID写入名称master。 当某个东西持有提交哈希ID时,我们说该东西指向提交。这样,分支名称总是指向分支上的最后一次提交。最后一个提交指向它的前一个提交,这又指向了另一个步骤,依此类推,这样Git就可以从那里向后工作。这一步后退,一次一个提交,就是Git如何保存您和其他人所做的一切的历史:每次提交都会及时记录一个快照,除了第一次提交之外,每次提交都有一个以前的记录,看起来像。。。历史链接

绘制图表 创建新分支时,Git会创建一个新的表条目,以便两个分支都指向同一个提交。出于绘图目的,让我在每次提交时使用单个大写字母,而不是一个大而难看的哈希ID。那么我们可能会有这样的结果:

... <-F <-G <-H   <-- master, develop (HEAD)
名称头附加到其中一个分支名称。这就是Git知道哪个b的原因 我们正在使用的ranch,因为当我们进行新的提交时,Git必须更新这个分支名称。现在让我们进行一次新的提交,并将其称为哈希I:

请注意,提交H和更早版本在两个分支上

作为动词合并 如果我们现在要求Git将develop(即提交i)合并回master(即提交J),Git将找到最好的公共祖先提交。不严格地说,这是Git通过从两个分支提示反向工作可以找到的第一个共享提交。在这种情况下,这很明显是commit H

在找到Git调用合并基Git的公共祖先提交之后,现在必须弄清楚我们改变了什么,以及它们改变了什么。也就是说,Git必须根据两个分支提示区分提交H(合并基):-我们的,这是提交J,-他们的,这是提交I。这实际上需要两个独立的Git diff命令:

git diff --find-renames <hash-of-H> <hash-of-J>   # what we changed
git diff --find-renames <hash-of-H> <hash-of-I>   # what they changed
Git现在可以组合这两组更改。我们碰了一些文件,他们碰了一些文件。当我们和他们接触相同的文件时,我们将一些行从合并基更改为提交,他们将一些行从合并基更改为提交

如果Git认为我们接触了相同的行,但对这些行做了不同的更改,就会发生冲突。同样的,如果我改变了第17行,他们改变了第17行,我们显然改变了同样的行。然而,这可能有点奇怪:例如,如果我们都在文件末尾添加了不同的文本,Git不知道将它们放在哪个顺序,因此这也是一个冲突

但只要我们触及不同的行或不同的文件,就不会有冲突:Git将两组更改应用于每个合并基文件,以获得合并结果。然后Git可以根据组合的更改进行新的提交

这种组合过程称为组合过程。另见

作为形容词或名词合并 这个新提交有一个特殊的属性;让我们通过绘制结果来显示它:

             J
            / \
...--F--G--H   K   <-- master (HEAD)
            \ /
             I   <-- develop

这里发生的是,新的提交K记住了之前的两次提交:首先,它自己的前一次提交是J。这是K的第一个父级。但是K也有第二个父级:它还记得它来自I。这允许Git在将来的合并中做更少的工作,因为它改变了以后的合并基础是哪个commit。这是图论中最复杂的部分,我们将跳过它。创建D1分支后,开发分支上的文件是否已更改?是,创建D1分支后。我将D1分支拉到我的本地回购,对D1分支中file.txt中的第一行进行了更改,然后将更改推送到远程存储库中的D1分支。不,您也在开发分支上进行了更改吗?否决了这个问题,因为阅读它需要一段时间,然后您意识到,哦,这只是OP所做的一个改变,并合并回来,这正是git的目的。对于未来的读者来说,这并不是很清楚,也不是解决一个共同的问题。OP也没有回应。
             J   <-- master (HEAD)
            /
...--F--G--H
            \
             I   <-- develop
git diff --find-renames <hash-of-H> <hash-of-J>   # what we changed
git diff --find-renames <hash-of-H> <hash-of-I>   # what they changed
             J
            / \
...--F--G--H   K   <-- master (HEAD)
            \ /
             I   <-- develop