理解git冲突

3
我觉得我可能对git处理事情的方式有所误解,因此遇到了一些相当烦人的冲突。
我从主分支开始创建一个新的A分支,并开始创建新文件。最终,我从A分支创建B分支,并开始在B上工作。A分支继续开发,B需要A分支中的更改,因此我将A合并到B中,并继续在两者上工作。
时间过去了,A分支继续开发,直到它被合并到主分支。此时,我认为现在已经将其合并到主分支,我可以简单地将主分支合并到B中,并获得来自A和其他人的所有更改。
问题是,现在我尝试这样做时,我会在我没有更改的文件上得到多个“Both added”、“Both modified”等冲突-但是我完全理解我更改的文件中的冲突-我引起了它们,我很清楚。

我觉得我的解释可能会让人困惑,所以我进入了Google幻灯片,并创建了这个“惊人”的图画来说明我的情况(箭头代表合并,例如“将主分支合并到A”,除非它们指向分支中的第一个提交 - 这意味着“从这里分支出去”;也就是说,这是相当标准的git符号,除了箭头方向 - 我不确定):

er
关于其他人修改的文件,我不明白为什么它们被列为冲突。也就是说,有一个文件在我创建A分支时已经被更改了,当我尝试将主分支合并到B分支时,同一个文件又被更改了。当然,分支B仍然具有该文件的旧版本,但是,Git难道不应该识别出它们在我的提交中没有被更改,并只是覆盖它们或者做其他操作吗?我在这里错过了什么吗?
编辑:仅澄清,我是A和B两个分支的唯一贡献者。

你使用了merge-with-rebase吗?还是A/master上的一些文件也被更改了,这些是冲突吗? - Lasse V. Karlsen
不,只有纯 git 合并,没有任何其他选项或内容。我不确定你问题的第二部分。我的意思是,这些文件是由其他人的提交更改的,这些提交已合并到 A 和 B 中。我想这应该不是一个问题... - Lucas Lima
如果您在两个不同的分支中添加了一个文件(当我说“一个文件”时,我指的是存储库中相同子目录中命名相同的文件),然后合并,您将遇到“两者都添加”的冲突,除非这两个文件是相同的,否则就会出现问题。 - Lasse V. Karlsen
你是A和B的唯一贡献者,但由于你将master拉入其中,并试图将master与你的分支合并,其他人为master做出的贡献内容将成为合并的一部分,并可能成为冲突问题的一部分。 - Lasse V. Karlsen
如果 A 和 B 都没有更改文件,而该文件在主分支上被更改,则应该干净地合并。 - Lasse V. Karlsen
显示剩余2条评论
2个回答

4

你的图形绘制非常漂亮,但它有些误导人。

让我们从一个问题开始:蓝色提交位于哪个分支?

这是一个诡计问题。它们不在一个分支上,而是在多个分支上,复数形式。如果你说它们在分支A,那是对的,但它们master上,大部分还同时在分支B上。

在您的图中,每个提交没有标识符。这使得它们很难谈论:我可以说“从左边数第二个灰色提交”或“最右边的蓝色提交”,但这有点难以掌握,所以让我将图形重新绘制为文本,并在每个提交中使用单个大写字母。我在此处也不会使用箭头,因为在文本中太难了。

A--B--C--D--E--F--G--H--I--J--K--L   <-- master
    \        \            /
     M--N--O--P--Q---R---S   <-- branch-A
         \        \
          T--U--V--W--X--Y--Å--Ø--Z   <-- branch-B

也就是说,master 分支的 tip commit 是提交记录 Lbranch-A 分支的 tip commit 是提交记录 Sbranch-B 分支的 tip commit 是提交记录 Z
在 Git 中,分支名(比如 master)总是指向一个单独的提交记录。你可以选择任何一个提交记录作为你的目标,被你这样选中的提交记录就成为了该分支的 tip commit 。 从该 tip commit 开始往前(在这些图示中就是向左)沿着由提交到提交的箭头,我们会先到达 K,再从 KJ。但是 J 是一个合并提交,它有两个父提交记录,而不是一个。因此,从 J 我们同时到达了 SI。从这两个节点,我们分别继续到达 HR,然后到达 GQFP,最后到达 E。虽然有两种到达 E 的方式,但我们仍然只访问了一次。从这里,我们可以继续前往 ODNCMB,最后到达 A。因此,这些提交记录的列表就是属于 master 分支的提交记录集合。
(注意,我们不能从 Q 到达 W,虽然我们可以从 W 到达 Q:所有箭头都是单向指向向后的。) branch-A 上的提交记录集合从 S 开始向后(在这里是向右)延伸,经过 RQPEODN 等等。所有这些提交记录也都属于 master 分支。

branch-B上的提交从Z开始,穿过ØÅYXW,然后捡起PV,接着是EOU等。因此,在branch-B上有四个提交不在任何其他分支上。


1技术上,Git内部的所有箭头都指向后面。所以你画箭头时其实是反向的。这种情况发生是因为提交是完全只读的:父提交无法事先知道它的子提交的哈希值,但子提交在创建时确实知道它的父提交的哈希值。因此,箭头必须指向后面。

(为了向前移动,您要告诉Git您想要到达哪里,然后它会从那里向后移动,以确保可以从某个其他起始点到达那里。)


git merge如何工作,大大简化

当您运行以下命令:

git checkout master
git merge branch-B

Git必须找到提交记录L和Z的merge base提交。为了做到这一点,像大多数Git操作一样,它向后工作,从这些分支尖端提交开始,找到最佳共享提交:一个可从两个分支尖端到达的提交,并且在两个分支上,但是“最接近尖端”。 在这种情况下,虽然这可能不是立即显而易见的,但那就是提交Q。 从L开始,通过K回到J,然后转到S,然后转到R,最后转到Q。 同时,从Z开始,回到W再上升到Q。提交P,E,O等都在两个分支上,但提交Q更好,因为它是最后一个这样的提交:它是所有这些提交的后代。
现在,Git将在内部运行两个git diff命令。 这将比较合并基础-提交Q与两个分支尖端:
git diff --find-renames <hash-of-Q> <hash-of-L>   # what we changed
git diff --find-renames <hash-of-Q> <hash-of-Z>   # what they changed

在这两个不同的清单中,如果某个文件在两个分支末端看起来是新创建的,则该文件将出现一个add/add冲突。该文件不在Q中,但它在L Z中。
对于所有三个提交中的文件,Git将尝试组合在两个diff集中显示的任何更改。对于这些更改不重叠(并且也不“接触边缘”)的地方,Git可以将它们组合在一起。如果它们重叠但这些重叠完全相同-覆盖Q中的相同原始行,并进行相同更改-Git可以通过只取一份更改的副本来组合它们。所有其他重叠都会导致合并冲突。
你的工作现在是解决所有的冲突,任何你喜欢的方式。例如,在add/add整个文件冲突中,如果两个文件匹配,只需选择其中一个文件的内容。如果不匹配,以某种方式组合这两个文件。完成后,使用git add将每个有冲突的文件的最终内容写入索引。这标记了索引冲突为“已解决”,现在你可以运行git merge --continuegit commit来完成合并。2

2git merge --continue检查你是否在完成一个合并。如果没有,它会出错。如果是的话,它只运行git commit-因此两者之间没有实际区别,除非你实际上没有完成一个有冲突的合并。


1
感谢您抽出时间回复 - 有趣的是,您的图形是基于文本的,绘制速度更快,比我的花哨且误导性的混乱图形更有用。活到老学到老哈哈。感谢您的解释。非常有启发性。最终,我在每个我知道想要保留的文件中都加入了一个新行,将它们存储起来,硬重置我的分支到之前的某个合并之前,并进行新的合并 - 然后再将存储的更改弹出。这是一种极其手动的解决方案,但它起作用了,而且冲突甚至没有出现。我想我以后会更加小心地合并。谢谢。 - Lucas Lima

1

git不应该认识到它们在我的提交中没有被更改,只是覆盖它们吗?

不。

这并不重要。Git的头脑里并没有关于谁创建了提交或谁优先的信息。无论是主分支还是b分支,在某种程度上都不存在谁更好的问题。

关键只是masterb所知道的文件以不同的方式存在差异,无法自动协调。

显然,Git可以自动协调一些差异,例如如果一个文件在一个分支中不同,因为第1行已更改,并且在另一个分支中由于第1000行更改而不同。但有时它只会放弃并让您解决。这不是因为谁修改了文件,而是因为差异的性质。

关于差异是什么,我认为阅读https://stackoverflow.com/a/56053884/341994将有所帮助——这是我读过的关于合并和它如何工作的最好的文章。这完全是关于LCA以及从它到分支末端的差异。在您的图表中,LCA是最左侧第二个提交,一直回到A首次分支的点。为了合并,必须考虑到该提交与master末端之间的所有差异,以及该提交与b末端之间的所有差异。好吧,git告诉你对于这个文件它做不到。如果您考虑一下从那里到master末端以及从那里到b末端该文件如何更改,您就会明白为什么了。

无论是谁更改了文件或者你如何绘制图表,这都不重要。重要的是有三种状态:在早期提交时的事物状态、master结束时的事物状态和b结束时的事物状态。即使在你的脑海中描述了发生过什么的历史路径,也不重要。


这很有道理。困扰我的是如何处理它。我的意思是,我将主分支合并到我的分支中时,“text”文件包含一些信息。当我继续在我的分支上工作时,其他人可能会更改该文件,但只要我的分支不修改它,我应该能够将主分支合并到我的分支中而不会发生冲突,对吗?这似乎是一个不太容易理解的问题。为什么在通过合并时只存在于我的分支中的文件中会出现冲突 - 也就是说,该分支不以任何方式更改它们? - Lucas Lima
此外,“论文”是对那个答案的相当好的描述。人们像你和其他人一样花时间真正地帮助别人,这真是令人惊叹。感谢您的时间和努力。 - Lucas Lima

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接