git - Git 合并冲突(假设场景)
问题描述
嗨,我对使用 git 很陌生,但我想知道如何解决以下假设示例:
有两个人(A 和 B)为一个分支(主)上的远程仓库做出贡献。我们将假设 B 与 repo 是最新的,但 A 不是。A 刚刚修改了一行新代码,并且刚刚尝试推送到 repo,并被告知他需要拉取最新版本。拉动时,他遇到了合并冲突,并且能够解决它。完成后,A 成功推送到 repo。现在 B 意识到 A 的更新很重要,并决定他需要从 repo 中拉取,但他已经在本地进行了更改,所以他暂时只是 stash。在提取并应用存储之后,B 意识到他遇到了与 A 之前遇到的相同的合并冲突。
非常感谢您的帮助。
解决方案
这种情况永远不会发生。原因是合并操作的输入之一已自动更改。但是,人 B(我将在下面称为 Bob)可以看到合并冲突。
请记住,在 Git 中merging是一个涉及提交的操作。也就是说,当我们进行合并时——动词形式,合并——我们会找到一些提交并完成我们的工作。还要注意,每个提交都代表一个完整的快照:所有文件,在特定提交时及时冻结。如果您想查看您的文件在昨天、去年或其他什么情况下的样子,它们就在当天提交的提交中。
合并操作有三个输入!我们作为运行 Git 的人,直接指定其中之一:
$ git merge origin/develop
例如。该名称origin/develop
解析为某个特定的提交 ID——一个大而丑陋的哈希 ID 字符串,例如5d826e972970a784bd7a7bdf587512510097b8c7
,这是我们在存储库中的一个提交,我们之前获得的,可能是通过运行git fetch
. (请记住,这git pull
本质上是一个方便的命令,意思是:为我运行git fetch
,然后一旦完成,运行第二个 Git 命令,通常是git merge
,为我。)
合并的第二个输入是当前提交是什么。也就是说,在我们运行之前git merge
(可能通过git pull
),我们运行了一个git checkout
命令:
$ git checkout develop
这会选择分支上的最新提交,因为分支名称的定义是“分支上的最新提交”。这意味着分支名称会随着时间的推移不断变化。要查看master
现在是哪个提交,或者现在develop
是哪个提交,您可以运行git rev-parse master
or git rev-parse develop
—that 将吐出他当前的哈希 ID。稍后再次运行,当有更多提交时,您将获得不同的哈希 ID。
绘制历史
因此,考虑到这一点,绘制提交图总是值得的。提交图是存储库中的历史,因为 Git 中的历史只不过是提交。
如果您之前没有绘制提交图,这需要一些练习。有很多方法可以在浏览器或 GUI 中绘制或查看它们,尽管我对 GUI 有一种健康的不信任,因为它们中的很多都在撒谎(通常有充分的理由与表现良好有关,但仍然是恼人的)。有很多方法可以做到这一点,但我喜欢对 StackOverflow 帖子横向进行:
o--o--o <-- develop (HEAD)
/
...--o--*
\
o--o <-- origin/develop
在这些图中,最近的提交位于右侧,时间向后移向左侧。分支名称指向最右边(最新)的提交,因为根据定义,名称是其分支中的最后一个提交。我们——和 Git——必须从头开始,然后回到更早的提交,看看事情进展如何。
从这张图中,我们可以看到我们的develop
三个提交——圆形o
节点——不在共享历史上,而我们origin/develop
的两个提交不在共享历史上。
共享历史从提交开始,*
并在时间上向后继续。*
在此图中,Commit是提交develop
和origin/develop
. 当 Git 进行真正的合并时——执行合并过程的合并部分——<strong>三个输入是合并基础、HEAD
提交 ( --ours
) 和另一个提交 ( --theirs
)。
Git 进行合并的方式现在实际上很容易看到。因为每个提交都是一个完整的快照,Git 首先将合并基础快照提取到一个临时区域。然后它提取我们的提交——嗯,它已经存在了,真的——和他们的提交,现在它运行两个单独的比较,我们可以自己使用一次复制一个git diff
。我们首先找到提交的哈希 ID *
(也许通过查看图表),然后运行:
git diff --find-renames <hash-of-*> HEAD # what we changed
git diff --find-renames <hash-of-*> origin/develop # what they changed
现在git merge
要做的就是结合这些变化。从基础文件开始。然后,无论我们在哪里更改了文件,如果他们没有更改该文件,请使用我们的。无论他们在哪里更改了文件而我们没有,都使用他们的。无论我们在哪里更改同一个文件,合并更改。
如果 / 当两个变更集的组合尝试更改相同文件的相同行时,就会发生合并冲突。在这种情况下,Git 会保留文件的所有三个原始副本(隐藏在 Git 的index中),并尽最大努力将更改以及一些冲突标记组合到工作树中。
如果 Git 根本没有遇到任何冲突,Git 就会进行新的提交。或者,在您手动修复了 Git 在发生冲突时留下的混乱之后,您可以对结果进行最终提交。这个提交在我们的图表中看起来有点奇怪,因为它之前有两次提交,而不是通常的一次。我会给这个新的提交一个字母ID(不是真正的哈希ID,只是一个替身)M
,:
o--o--o--M <-- develop (HEAD)
/ /
...--o--* /
\ /
o----o <-- origin/develop
这种提交是一个合并,或作为名词合并:一个双亲提交,两个父母是旧的HEAD
(父母#1)和另一个提交(#2)。
现在假设你的人 A (Alice) 和 B (Bob) 继续工作
您建议 Alice 进行了合并然后运行git push
。当 Alice 这样做时,假设她git push
成功了,那将获得另一个 Git 存储库来调用M
它的 develop
. 也许这第三个存储库位于 GitHub 上,例如:
o--o--o
/ \
...--o--o M <-- develop (HEAD)
\ /
o-----L
我画的有点不同,但它是相同的一组提交。这里没有,只是导致两个先前提交origin/develop
的合并提交。M
我还取消了旧的共享点的标记,并L
为 Bob目前的提交写了一封信,因为我们很快就需要讨论它。
现在 Bobgit fetch
在他的存储库中运行,以获取 Alice 的工作。虽然 Bob 还没有自己的新提交,但他获得了新的提交。请注意,他develop
已经存在并且当前指向 commit L
:
o--o--o
/ \
...--o--o M <-- origin/develop
\ /
o-----L <-- develop (HEAD)
请注意,鲍勃的develop
分支是他的分支!如果他有未提交的更改,他可以运行git stash
以将它们保存在一个提交中——实际上是两个提交,但我们现在假设只有一个——那是在任何分支上:
o--o--o
/ \
...--o--o M <-- origin/develop
\ /
o-----L <-- develop (HEAD)
\
S [stash]
现在 Bob 可以运行git merge --ff-only
(或者git pull
,它将执行另一个什么都不做的提取,因为没有什么比 更新M
,然后执行快进非真正合并“合并”)以使Bob的develop
点提交M
:
o--o--o
/ \
...--o--o M <-- develop (HEAD), origin/develop
\ /
o-----L
\
S [stash]
Bob 现在可以应用并删除(或“弹出”)存储以将更改放入他的工作树中。如果这可行,他们将准备好添加和提交。然后 Bob 进行新的提交,我们可以将其绘制为N
:
o--o--o
/ \
...--o--o M--N <-- develop (HEAD), origin/develop
\ /
o-----L
的内容N
已经包含M
, 因为git stash apply
take的内容S
,将其与其父级进行了比较,然后对中的任何内容进行了相同的更改M
。
当 Bob 可以看到冲突时
Bob 可以看到冲突的点不是在获取和快速转发期间,而是在 Bob 运行时git stash apply
(或git stash pop
以运行开始git stash apply
)。实际上,这运行了另一个合并!但它运行的合并不会以合并提交结束。这只会合并动作的动词部分。
此合并将提交S
作为其最后一个“其他提交”输入。中间的输入是HEAD
——commit——M
和往常一样是当前的提交,第一个输入是两者的合并基数,即 commit L
:
o--o--o
/ \
...--o--o M <-- develop (HEAD), origin/develop
\ /
o-----L
\
S [stash]
Git 比较(差异)L
并M
找出“我们”做了什么。(当然,这实际上是爱丽丝所做的,或多或少,但 Git 不在乎。)它比较L
看S
“他们”(真的,鲍勃)做了什么。然后它结合或尝试结合这两个变更集,就像常规合并一样。
如果一切顺利,就git stash
到此为止。还没有新的提交。如果进展不顺利,git stash
则以合并冲突停止,让 Bob 解决合并冲突。Bob 完成后,Bob 的下一次提交将是普通提交,而不是合并提交。
如果 Bob 使用git stash pop
并且存在合并冲突,Git 在该apply
步骤之后停止,并且 Bob 仍然有存储:他必须git stash drop
在修复混乱后运行以丢弃它。但是如果 Git 认为合并进行得很顺利,并且 Bob 运行了git stash pop
,Git将为git stash drop
Bob 运行。所以必须小心一点git stash
:有时它会同时进行应用和删除,但有时它会在应用后失败并且不执行删除部分。
(我主要建议避免git stash
使用它,但如果您要使用它,请分别执行 apply 和 drop,以避免在Git认为是干净的方式应用时意外丢弃存储,但实际上是您的错误。这真的确实会发生,即使对于 Git 专家也是如此,并且从中恢复是很痛苦的。)