究竟是什么导致了git中的合并冲突?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了究竟是什么导致了git中的合并冲突?相关的知识,希望对你有一定的参考价值。
1)有一个'master'分支,其中包含一个文件
1
2
3
4
5
2)A从'master'获取分支并编辑
1
2
100
3
4
5
3)B从'master'获取分支并编辑
1
2
3
4
200
5
4)现在推动变成主人。然后B试图推动。
B会发生什么?任何合并冲突或没有合并冲突?原因是什么?
最简单的答案是:如果两个开发人员在同一个文件(和同一个分支)中更改了相同的代码行,git将假定存在冲突。
无论是谁先提出并推动,都是“胜利者”。第二个必须处理冲突。他不能推动,直到他从原点撤下所有变化并解决冲突。
你的问题意味着事情并非如此。
具体来说,git push
只推动现有的提交。它没有合并任何东西,事实上,它甚至从未试图合并任何东西。
在您的问题中,涉及三个实体(人员和存储库)。 A人(让我们称她为Alice),B人(让他们称他为Bob)和C人(让他们称他为Central-Server,他实际上只是一台机器,而不是一个人,虽然这并不重要)。
Alice和Bob都是从Central-Server获取某个存储库的副本(克隆)开始的。它们得到完全相同的克隆(这就是为什么它们被称为“克隆”):Alice的克隆匹配Bob的克隆匹配Central-Server上的克隆。他们通过运行git clone <url>
来执行此操作,其中<url>
指向中央服务器(github或其他任何东西),并且他们的git将名称origin
保存下来(我们很快会再次看到此名称)。
让我们绘制一下所有三个实体现在拥有的git提交图(的一部分):
... - C7 - C8 <-- master
现在爱丽丝和鲍勃都做出了改变,但他们做了不同的改变。爱丽丝承诺改变:
... - C7 - C8 - A <-- master
然后Alice运行git push origin
将她的工作推回到Central-Server。 Central-Server查看了她的请求,其中说“在A
将链接C8
添加到链的末尾,并使master
指向A
”。此操作会向链中添加新提交,因此允许,因此Central-Server向Alice回复“OK”,并且她已全部完成。 Central-Server的存储库现在看起来和Alice的相同,因为它们都在旧提交A
之后有新的提交C8
,master
指向提交A
(并且提交A
指向旧的C8
)。
同时Bob已做出改变并添加了一个新的提交,他的提交图现在看起来像这样:
... - C7 - C8 - B <-- master
Bob不知道Alice已经提交了A
,也没有成功将它推送到Central-Server。他去了git push origin
,但这次Central-Server得到一个请求,说“在C8链接的末尾添加提交B
,然后让master
指向B
”。如果Central-Server这样做,效果将是:
A
/
... - C7 - C8 - B <-- master
也就是说,提交A
将保持浮动,没有任何指向它。 (分支master
将指向B
和B
将指向C8
,没有任何指向A
。)这通常是一种糟糕的状态,git拒绝它,所以Central-Server告诉Bob:
rejected (non-fast-forward)
请注意,没有合并,也没有变基。
现在鲍勃的工作是进行合并(或改组)。他应该这样做:
- 从拥有它的人那里获取更新。谁拥有它?爱丽丝拥有它,但Central-Server也是如此。他刚刚要求推送到Central-Server,后者告诉Bob“不”,所以他不妨从Central-Server获取它。
- 合并(或rebase),解决任何冲突。
- 重试推送。
如果Bob选择“合并”并做适当的合并工作,这是他的新提交图:
... - C7 - C8 - A - M <-- master
/
B
请注意新的合并提交M
。现在Bob可以重新尝试推送到Central-Server,后者目前拥有以A
结尾的链。这次中央服务器将看到使master
指向M
的请求,并且由于M
指向A
,该请求将被允许(现在它是“快进”)。
当然,如果Alice(或Dave或Emily或Frank)通过在A
之后添加新提交并将其发送回Central-Server而击败Bob,那么Bob将不得不再次合并(或重新绑定),然后再试一次。
How does Bob manage all this?
这是Bob的选择是合并还是变基。无论哪种方式,Bob都必须解决任何合并冲突 - 无论他使用哪种方法,他都会得到相同的合并冲突。并且,在任何一种情况下,他应该开始运行:
git fetch origin
(或只是git fetch
,它将自动使用origin
)。
让我们看一下在变基或合并之前Bob的提交图:
A <-- origin/master
/
... - C7 - C8
B <-- master
请注意,Bob的master
指向提交B
,Bob有另外一件事 - 这个origin/master
指向Alice的提交A
。这就是git fetch
所做的:它从Central-Server中带来最新版本(或者如果Bob直接从Alice获取,将其从她身上带过来,因为她有相同的提交),然后使一些标签指向该提交。标签以origin/...
开头,因为这是我们允许git clone
使用的名称:它只是将origin/
粘贴在另一个名称前面(在这种情况下为master
),以便我们可以区分它们。
如果鲍勃选择改变而不是合并,他将把他的git复制他的提交B
到新的提交B'
:
A <-- origin/master
/
... - C7 - C8 B' <-- master
B
鲍勃的原始B
会发生什么?答案是:它被废弃了。它保留在存储库中一段时间(默认30天),以防Bob需要它,保存在Bob的reflog中,但除非你(或Bob)明确要求git查看,否则你看不到这些提交,所以它们似乎消失了。
如果Bob选择合并,他会得到:
A <-- origin/master
/
... - C7 - C8 M <-- master
/
B
这是我们在上面绘制的图,我们刚刚提升了A
节点,以便我们可以指向它的箭头(标记为origin/master
)。
在任何一种情况下,Bob现在可以尝试推送,因为他的新提交 - B'
或M
-points返回提交A
,因此他只要求Central-Server添加新提交而不是忘记或放弃提交A
。
What about the merge itself?
Git将尝试通过比较Alice所做的更改(添加100
的一行)与Bob所做的更改(添加一行200
)来帮助Bob。如果git决定这些更改不会相互冲突,它将保留这两个更改。如果它确定这两个更改会影响文件的同一部分,则会给Bob一个合并冲突,标记文件的更改区域,并让Bob决定如何组合它们。
Bob可以使用他喜欢的任何东西来实现组合结果。 Bob应该确保结果是正确的,然后Bob应该告诉他的git到git add
文件的最终版本和git commit
来提交更改。如果他用来组合这些更改的命令是git merge
,这个将进行合并提交。如果它是git rebase
,这将使新的副本B'
。
1如果鲍勃选择git rebase
,他可以使用git rebase --continue
,它将为他做出承诺。虽然鲍勃首先做git commit
然后做git rebase --continue
是安全的(并且在git 1.5左右的时候,在继续变换之前,必须手动执行提交部分)。
A final note about git pull
我鼓励新的git用户从git fetch
开始,然后做自己的git merge
或git rebase
。许多文件告诉你从git pull
开始,但我认为这是一个错误。 git pull
命令是一个方便:它运行git fetch
,然后运行git merge
或git rebase
,但这有几个缺陷。没有一个是非常严重的,但这些对新用户不利:
- 在您甚至可以查看更改之前,它会选择合并与rebase。
- 默认是合并,这通常是新用户的错误答案,他们通常可能应该进行rebase。您可以更改默认值,但新用户不知道提前执行此操作。你可以添加
--rebase
告诉它改变,但你可以忘记包含这个标志。 - 它所做的合并是一个"foxtrot merge":它让父母提交错误的方向。
- 与手动合并相比,这些论点令人困惑:
git pull origin <branch>
vsgit merge origin/<branch>
。 (当然,如果你想避免foxtrot合并,你也不能使用后者,但你可能应该重新定位。) - 如果你提供了太多的参数(
git pull origin master develop
),它会使章鱼合并,这不是新用户应该考虑的事情。 :-) - 它曾经有许多破坏工作的bug案例。我相信它们都是固定的,但使用
git fetch
后跟一个单独的git merge
或git rebase
一直避免这些错误。 - 最后,这里发生了太多的魔法。它应该是方便的(对于那些使用git的老手,它很方便)但它只是模糊不清。
当B尝试推送时,会有错误返回,因为A已被推送更改为master。那你应该这样做,
git fetch
git rebase origin/master
在执行rebase步骤时,不会发生冲突,因为A更改了文件的第3行,B更改了文件的第5行。那你可以做到,
git commit -m ""
git push
当git不知道如何处理代码的相同部分中的两个更改(未按顺序进行)时,会发生合并冲突。在您的情况下,已更改的行是不同的,因此git将能够合并提交而不会发生冲突。但是,一旦A中的提交被推送,B中的提交就不能在没有在A之上重新定位的情况下被推送。
在这种情况下你不能推B:
----M-----A
----B
如果你去B并输入并使用:
git rebase A
您的代码在本地存储库中将如下所示:
----M-----A-----B
你也可以推送提交B.
以上是关于究竟是什么导致了git中的合并冲突?的主要内容,如果未能解决你的问题,请参考以下文章