究竟是什么导致了 git 中的合并冲突?

What exactly lead into merge-conflicts in 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) 现在A push变成master了。然后B也试着推。

B会怎样?任何合并冲突或没有合并冲突?原因?

当B尝试push时,会报错return,因为A已经push更改到master了。 那你应该做,

git fetch
git rebase origin/master

在做rebase步骤的时候,没有发生冲突,因为A改了文件的第3行,B改了文件的第5行。 那么你可以做,

git commit -m ""
git push

当 git 不知道如何处理代码同一部分中的两个未按顺序进行的更改时,就会发生合并冲突。在您的情况下,已更改的行是不同的,因此 git 将能够合并提交而不会发生冲突。但是,一旦 A 中的提交被推送,B 中的提交必须在 A 之上进行 rebase 才能被推送。

在这种情况下你不能推 B:

----M-----A
     \----B

如果您转到 B 并键入并使用:

git rebase A

您的代码在您的本地存储库中将如下所示:

----M-----A-----B

你也可以推送提交 B

最简单的答案是:git 如果两个开发人员更改了同一文件(和同一分支)中的同一行代码,则假设存在冲突。

谁先提交和推送谁就是"winner"。第二个必须处理冲突。在他从源中提取所有更改并解决冲突之前,他不能推送。

您的问题暗示并非如此。

具体来说,git push 仅推送 现有提交 。它不合并任何东西,事实上,它甚至从未尝试合并任何东西。

在您的问题中,涉及三个实体(人员和存储库)。 A(我们称她为Alice),B(我们称他为Bob),C(我们称他为Central-Server,他实际上只是一台机器,而不是一个人,虽然这并不重要)。

Alice 和 Bob 都从中央服务器获取某个存储库的副本(克隆)开始。他们得到 完全相同的 克隆(这就是他们被称为 "clones" 的原因):Alice 的克隆匹配 Bob 的克隆匹配中央服务器上的克隆。他们通过 运行 git clone <url> 来做到这一点,其中 <url> 指向中央服务器(git 集线器或其他任何东西),并且他们的 git 保存了 url 在名字 origin 下(我们很快就会再次看到这个名字)。

让我们绘制所有三个实体现在拥有的 git 提交图(的一部分):

... - C7 - C8   <-- master

现在 Alice 和 Bob 都进行了更改,但是他们进行了 不同的 更改。爱丽丝提交她的更改:

... - C7 - C8 - A   <-- master

然后 Alice 运行 git push origin 将她的工作推送回中央服务器。中央服务器查看她的请求,上面写着 "add commit A to the end of the chain at C8, and make master point to A"。此操作 将新提交 添加到链中,因此被允许,因此中央服务器回复 "OK" 给爱丽丝,她就完成了。 Central-Server 的存储库现在看起来也和 Alice 的一样,因为它们在旧提交 C8 之后都有新的提交 Amaster 指向提交 A(并且提交 A 指向旧的 C8).

与此同时,Bob 进行了更改并添加了一个新提交,他的提交图现在如下所示:

... - C7 - C8 - B   <-- master

Bob 不知道 Alice 已经提交 A,也没有成功将其推送到中央服务器。他去了 git push origin,但这次中央服务器收到一个请求 "add commit B to the end of the chain at C8, then make master point to B"。 如果中央服务器这样做,效果将是:

                A
              /
... - C7 - C8 - B   <-- master

也就是说,提交 A 将保持浮动状态,没有任何指向它的内容。 (分支 master 将指向 B,而 B 将指向 C8,没有任何内容指向 A。)这通常是一种糟糕的情况git 拒绝它,因此中央服务器告诉 Bob:

rejected (non-fast-forward)

请注意,没有合并也没有变基。

现在 Bob 的工作是进行合并(或变基)。他应该这样做:

  1. 从拥有更新的人那里获取更新。谁有它? Alice 有它,但 Central-Server 也有。他只是要求推送到 Central-Server,Central-Server 告诉 Bob "no",所以他还不如从 Central-Server 获取。

  2. 执行合并(或变基),解决任何冲突。

  3. 重试推送。

如果 Bob 选择 "merge" 并进行了适当的合并,这是他的新提交图:

... - C7 - C8 - A - M   <-- master
              \   /
                B

注意新的合并提交 M。现在 Bob 可以重新尝试推送到中央服务器,中央服务器当前的链以 A 结尾。这次 Central-Server 将看到一个让 master 指向 M 的请求,并且由于 M 指向 A,该请求将被允许(现在是 "fast-forward").

当然,如果 Alice(或 Dave、Emily 或 Frank)先发制人,在 A 之后添加新的提交并将它们发送回中央服务器,Bob 将不得不合并(或变基) ) 再试一次。

鲍勃是如何管理这一切的?

由 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 所做的:它从中央服务器带来最新版本(或者如果 Bob 直接从 Alice 获取,从她那里带来它,因为她有相同的提交),然后使一些标签指向那个犯罪。标签以 origin/... 开头,因为这是我们允许 git clone 使用的名称:它只是将 origin/ 贴在另一个名称(在本例中为 master )前面,以便我们可以区分它们。

如果 Bob 选择 rebase 而不是合并,他将 git copy 他的提交 B 到一个新的提交 B':

                A       <-- origin/master
              /   \
... - C7 - C8       B'  <-- master
              \
                B

Bob 的原版 B 怎么了?答案是:废弃了。它会在存储库中保留一段时间(默认 30 天)以防 Bob 需要它,保存在 Bob 的 reflogs 中,但除非您(或 Bob)明确要求 git看那里,您看不到这些提交,所以它们 似乎 消失了。

如果鲍勃选择合并,他会得到:

                A       <-- origin/master
              /   \
... - C7 - C8       M   <-- master
              \   /
                B

这是我们在上面绘制的 同一张图 ,我们刚刚将 A 节点抬起,这样我们就可以有一个指向它的箭头(标记为 origin/master).

无论哪种情况,Bob 现在都可以尝试推送,因为他的新提交——B'M——指向回提交 A,因此他只是在询问中央服务器添加新提交而不是忘记或放弃提交A

合并本身呢?

Git 将尝试帮助 Bob,方法是将 Alice 所做的更改(添加一行 100)与 Bob 所做的更改(添加一行 200)进行比较.如果 git 决定这些更改 不会 相互冲突,它将保留这两个更改。如果它确定这两个更改影响文件的相同部分,它将给 Bob 一个合并冲突,标记文件的更改区域,并让 Bob 决定如何组合它们。

Bob 可以使用他喜欢的任何东西来实现组合结果。由 Bob 来确保结果是正确的,然后 Bob 应该告诉他 git 到 git add 文件的最终版本并 git commit 提交更改。1 如果他用来合并更改的命令是 git merge,这将进行合并提交。如果它是 git rebase,这将生成新副本 B'


1如果Bob选择git rebase,他可以只使用git rebase --continue,它会为他提交。 Bob 先做 git commit 然后再做 git rebase --continue 是安全的(在 git 1.5 左右的日子里,确实必须手动完成提交部分,在继续变基之前)。

关于 git pull

的最后说明

我鼓励新的 git 用户从 git fetch 开始,然后做他们自己的 git mergegit rebase。许多文档告诉您以 git pull 开头,但我认为这是一个错误。 git pull 命令是为了 方便 :它运行 git fetch 然后运行 ​​git mergegit rebase,但这有几个缺陷。 None 已经非常严重了,但是这些对新用户来说并不好:

  1. 它选择merge vs rebase before你甚至可以看看变化。

  2. 默认是合并,这对于新用户来说通常是错误的答案,他们通常可能应该变基。您可以更改默认值,但新用户并不知道提前这样做。你可以添加 --rebase 来告诉它变基,但是你可以忘记包含这个标志。

  3. 它进行的合并是 :它的父提交方向错误。

  4. 与手动合并相比,参数令人困惑:git pull origin <branch>git merge origin/<branch>。 (当然,如果你想避免狐步舞合并,你也不能使用后者,但你可能应该重新设置基址。)

  5. 如果您提供太多参数 (git pull origin master develop),它会生成 章鱼合并 ,这不是新用户应该考虑的事情. :-)

  6. 它曾经有许多破坏工作的错误案例。我相信它们都是固定的,但是使用 git fetch 后跟一个单独的 git mergegit rebase 总能避免这些错误。

  7. 最后,这里发生了太多的魔法。它应该方便(对于那些git的老手来说,它很方便)但它只是变得晦涩难懂。