git 合并与 git 合并冲突场景的变基
git merge vs git rebase for merge conflict scenarios
我想确保我看到的是正确的。
当我执行 git 合并导致冲突时,我看到存在冲突的文件为:
<<<<<<<<<HEAD
my local changes first
=============
The remote github changes here.
>>>>>>>>>>
而当我 运行 由于 git 变基而陷入冲突时,我看到了相反的情况:
<<<<<<<<<
The remote github changes here.
=============
my local changes first
>>>>>>>>>>
我在这里遗漏了什么吗?
当您执行合并时,Git 将目标更改视为您的本地源分支,并且这些更改首先出现在顶部。另一方面,由于 rebase 发生的顺序,被合并的分支首先发生,你的改变被重新应用到它。因此,在这种情况下,您的作品出现在底部。一个简单的图表将有助于解释变基期间发生的事情。
remote: -- A -- B
\
local: C
在这里,您已经从远程分支并进行了一次提交 C
,而远程自分支点以来也有一次新提交 B
。现在执行变基:
remote: -- A -- B
\
local: B -- C'
请注意,重新应用 C
提交的步骤是 在 之后,即您的本地分支已经有 B
提交。因此,从 Git 的角度来看,您的本地 C
提交是来自外部的新提交。
说你的好友,你使用当时的时间作为提交消息进行了如下更改。
你:下午 1 点、下午 3 点、下午 5 点、晚上 7 点等等。
你的好友:下午 2 点、4 点、6 点、8 点等等。
现在看看 git merge 和 rebase 之间的区别,当你在你的分支上应用你的伙伴更改时。
合并:
git merge <otherLocal/remoteBranch> ## Always current branch changes takes top
执行(1PM、3PM、5PM、7PM.. + 2PM、4PM、6PM、8PM..)并显示是否有冲突。
变基:
git rebase <hisBranch> <yourBranch> ## His branch changes takes top
做 (2PM, 4PM, 6PM, 8PM) + (1PM) 显示是否有冲突,否则继续变基。
做 (HEAD + 3PM) 显示是否有冲突 else 继续变基,等等。
git rebase <yourBranch> <hisBranch> ## Your branch changes takes top
做 (1PM, 3PM, 5PM, 7PM) + (2PM) 显示是否有冲突,否则继续变基。
做 (HEAD + 4PM) 显示是否有冲突 else 继续变基,等等。
是对的,但我画的图有点不同。在您自己的(本地)Git 存储库中,您在开始时有一系列这样的提交:
...--G--H <-- origin/somebranch
\
I--J <-- somebranch (HEAD)
也就是说,您已经做出了一个或多个自己的提交——在这里,我将它们标记为 I
和 J
;他们的真名是一些丑陋的大哈希 ID,而您的分支名称 somebranch
指向(包含哈希 ID)您所做的这些新提交中的最后一个。
您然后 运行 git pull --rebase somebranch
,或者(我的首选方法)两个单独的命令 git fetch
,然后是 git rebase origin/somebranch
。 two-step 序列是 git pull
为你做的:它 运行 有两个 Git 命令,第一个总是 git fetch
,第二个是命令你提前选择,before 你会看到 git fetch
做了什么。 (我喜欢看看 git fetch
做了什么,然后决定:我是变基、合并、等待,还是完全做其他事情?)
git fetch
步骤选择了其他人所做的新提交,给你这个:
...--G--H------K--L <-- origin/somebranch
\
I--J <-- somebranch (HEAD)
同样,大写字母代表一些真实的哈希 ID,无论它是什么。
当您使用 git merge
、Git 时, 合并 。可以稍微不同地绘制图形以使其更清晰:
I--J <-- somebranch (HEAD)
/
...--G--H
\
K--L <-- origin/somebranch
合并的共同起点是提交H
;您的提交 HEAD
是提交 J
;他们的提交当然是 L
。因此,如果存在冲突,您将在 in-progress 合并中看到的 HEAD
是您来自 J
的代码,而您将在 L
中看到的是他们的代码].如果将 merge.conflictStyle
设置为 diff3
,您将看到的基数是 H
.1
中的内容
请注意,合并有 三个 输入。 提交 H
是 合并基础,提交 J
和 L
(HEAD
和他们的)是涉及的两个分支提示。在这里做一个完整的合并操作的最终结果将是一个新的 merge commit M
,它将指向它的 both两个直接输入:
I--J
/ \
...--G--H M <-- somebranch (HEAD)
\ /
K--L <-- origin/somebranch
merge M
中的 snapshot 是将 combined 更改应用于 commit [=30] 中的快照的结果=].即 Git 找到:
- 从
H
到J
的区别:你改变了什么;
- 从
H
到L
的区别:他们改变了什么;
并尝试将它们单独组合起来。 Git 在合并它们时遇到了问题——合并冲突——然后放弃并强迫 you 合并它们。一旦你这样做了,并使用 git merge --continue
完成了这个过程,Git 从合并的结果中得到了 M
。
(提交M
不记得直接提交H
。Git可以re-discover合并基础H
稍后,如有必要,使用与这次查找相同的过程。2)
1我喜欢设置这个选项。这样,您不仅可以看到您输入的内容和他们输入的内容,还可以看到合并基础提交中最初存在的内容。这在您或他们删除有问题的代码时特别有用。
2这实际上是一种错误,因为您可以 运行 git merge
使用修改内容的选项,包括——在一些相对罕见的情况下- 使用的合并基础。合并命令应记录您使用的选项,以使合并真正可重复。
但是,当您使用 git rebase
时,Git 复制 您现有的每个提交——在本例中为两个——一次一个。此复制过程使用 "detached HEAD",其中 HEAD
直接指向提交。 Git 首先检出 他们的 提交 L
作为分离的 HEAD,像这样:
...--G--H------K--L <-- HEAD, origin/somebranch
\
I--J <-- somebranch
现在,从技术上讲,cherry-pick 是合并的一种形式,或者我喜欢这样说,合并为动词: 合并过程,实际上没有进行合并提交。也就是说,您仍在做 会 用 git merge
做的所有相同工作。不同之处在于对合并的输入提交,当你完成时,最终提交 不是 合并提交:它只是一个常规的、普通的、日常的提交,与一个 parent.
所以,既然 Git 已经完成了 git checkout --detach origin/somebranch
,那么 他们的 提交 L
就是你的 当前 提交,它执行 git cherry-pick <hash-of-I>
复制提交 I
。 cherry-pick 开始合并过程。此特定合并的三个输入是:
- 合并基础,即提交 Git 的 parent 被告知 cherry-pick:那是
H
;
--ours
提交,它始终是 HEAD
,在本例中是提交 L
:他们的 提交;和
--theirs
提交,即 Git 提交给 cherry-pick:那是 I
,也就是 你的提交。
所以te --theirs
合并操作的提交是 你的 提交,合并操作的 HEAD
或 --ours
提交是他们的 提交 L
! 这就是这个 apparent 逆转的来源。 Git 正在做一个 cherry-pick, 是合并的一种形式。 --ours
输入是他们的提交,--theirs
输入是您的提交。
解决任何合并冲突后,您将 运行 git rebase --continue
。 (如果你有 运行 和 git cherry-pick
你自己,你会 运行 git cherry-pick --continue
;git rebase
会为你做这件事。)这将有 cherry-pick 完成,它通过 ordinary commit:
I' <-- HEAD
/
...--G--H------K--L <-- origin/somebranch
\
I--J <-- somebranch
分离的 HEAD 现在直接指向这个新的普通提交,即原始提交 I
的副本 I'
。请注意,提交 I'
是 "just like" 提交 I
除了:
- 它有一个不同的 parent 提交,
L
;和
- 它有一个不同的 快照。
I'
中的快照是将 H
与 I
之间的差异(即您更改的内容)与 合并 之间的差异与H
和 L
之间的区别。
唉,因为这是 git rebase
而不是 git merge
,我们还没有完成。现在我们也必须复制提交 J
,就像 git cherry-pick <hash-of-J>
一样。我们的情况仍然是分离的 HEAD
指向新的提交 I'
。 this 合并的三个输入是:
- 合并基础:
J
的parent,即I
;
- HEAD 提交为
--ours
:提交 I'
,我们刚刚创建的那个;和
- to-be-copied 提交为
--theirs
:提交 J
,即您的第二次提交。
对于合并,Git 将合并基础中的快照与两个提示提交中的每一个进行比较。所以 Git:
- 将您的
I
快照与您自己的 I'
快照进行比较,看看您更改了什么:那是您通过提交 [=] 引入的 他们的 代码33=]。如果存在冲突,那将显示在 <<<<<<< HEAD
中。
- 将您的快照从您的
I
与您自己的 J
进行比较,以查看 "they" 发生了什么变化:那是 您的 发生的变化J
。如果存在冲突,这就是 >>>>>>> theirs
中显示的内容。
这次,HEAD
只是他们的代码,现在是他们的代码和你的代码的混合体, 在冲突的 --ours
一方。同时,任何冲突的 --theirs
方面仍然是他们的代码。解决冲突并使用 git rebase --continue
后,Git 将进行新的普通提交 J'
,如下所示:
I'-J' <-- HEAD
/
...--G--H------K--L <-- origin/somebranch
\
I--J <-- somebranch
这里 J'
是 J
的 cherry-picked 副本。
因为这些是必须复制的所有提交,Git 现在通过将名称 somebranch
从提交 J
中拉出来并将其附加到新的来完成 rebase提交 J'
,然后 re-attaching HEAD
到名称 somebranch
:
I'-J' <-- somebranch (HEAD)
/
...--G--H------K--L <-- origin/somebranch
\
I--J [abandoned]
变基完成。 运行 git log
将显示您的新副本,并且不再显示原始提交 I
和 J
。原始提交最终将被回收和销毁(通常是在 30 天后的某个时间)。
这就是使变基从根本上比合并更棘手的原因。变基涉及重复 cherry-picks,每个 cherry-pick 是一个合并。如果您必须复制十次提交,那么您正在执行 ten 合并。 Git 通常可以自动完成它们,并且 Git 通常可以正确完成它们,但是 每个 合并只是 Git 愚蠢地应用一些简单的 text-difference-combining 规则,因此 每个 合并都是出错的机会。您必须仔细检查 and/or 测试结果。理想情况下,您应该检查 and/or 测试所有十份副本,但如果最后一份是好的,可能其他所有副本也是如此。
我想确保我看到的是正确的。
当我执行 git 合并导致冲突时,我看到存在冲突的文件为:
<<<<<<<<<HEAD
my local changes first
=============
The remote github changes here.
>>>>>>>>>>
而当我 运行 由于 git 变基而陷入冲突时,我看到了相反的情况:
<<<<<<<<<
The remote github changes here.
=============
my local changes first
>>>>>>>>>>
我在这里遗漏了什么吗?
当您执行合并时,Git 将目标更改视为您的本地源分支,并且这些更改首先出现在顶部。另一方面,由于 rebase 发生的顺序,被合并的分支首先发生,你的改变被重新应用到它。因此,在这种情况下,您的作品出现在底部。一个简单的图表将有助于解释变基期间发生的事情。
remote: -- A -- B
\
local: C
在这里,您已经从远程分支并进行了一次提交 C
,而远程自分支点以来也有一次新提交 B
。现在执行变基:
remote: -- A -- B
\
local: B -- C'
请注意,重新应用 C
提交的步骤是 在 之后,即您的本地分支已经有 B
提交。因此,从 Git 的角度来看,您的本地 C
提交是来自外部的新提交。
说你的好友,你使用当时的时间作为提交消息进行了如下更改。
你:下午 1 点、下午 3 点、下午 5 点、晚上 7 点等等。 你的好友:下午 2 点、4 点、6 点、8 点等等。
现在看看 git merge 和 rebase 之间的区别,当你在你的分支上应用你的伙伴更改时。
合并:
git merge <otherLocal/remoteBranch> ## Always current branch changes takes top
执行(1PM、3PM、5PM、7PM.. + 2PM、4PM、6PM、8PM..)并显示是否有冲突。
变基:
git rebase <hisBranch> <yourBranch> ## His branch changes takes top
做 (2PM, 4PM, 6PM, 8PM) + (1PM) 显示是否有冲突,否则继续变基。
做 (HEAD + 3PM) 显示是否有冲突 else 继续变基,等等。
git rebase <yourBranch> <hisBranch> ## Your branch changes takes top
做 (1PM, 3PM, 5PM, 7PM) + (2PM) 显示是否有冲突,否则继续变基。
做 (HEAD + 4PM) 显示是否有冲突 else 继续变基,等等。
...--G--H <-- origin/somebranch
\
I--J <-- somebranch (HEAD)
也就是说,您已经做出了一个或多个自己的提交——在这里,我将它们标记为 I
和 J
;他们的真名是一些丑陋的大哈希 ID,而您的分支名称 somebranch
指向(包含哈希 ID)您所做的这些新提交中的最后一个。
您然后 运行 git pull --rebase somebranch
,或者(我的首选方法)两个单独的命令 git fetch
,然后是 git rebase origin/somebranch
。 two-step 序列是 git pull
为你做的:它 运行 有两个 Git 命令,第一个总是 git fetch
,第二个是命令你提前选择,before 你会看到 git fetch
做了什么。 (我喜欢看看 git fetch
做了什么,然后决定:我是变基、合并、等待,还是完全做其他事情?)
git fetch
步骤选择了其他人所做的新提交,给你这个:
...--G--H------K--L <-- origin/somebranch
\
I--J <-- somebranch (HEAD)
同样,大写字母代表一些真实的哈希 ID,无论它是什么。
当您使用 git merge
、Git 时, 合并 。可以稍微不同地绘制图形以使其更清晰:
I--J <-- somebranch (HEAD)
/
...--G--H
\
K--L <-- origin/somebranch
合并的共同起点是提交H
;您的提交 HEAD
是提交 J
;他们的提交当然是 L
。因此,如果存在冲突,您将在 in-progress 合并中看到的 HEAD
是您来自 J
的代码,而您将在 L
中看到的是他们的代码].如果将 merge.conflictStyle
设置为 diff3
,您将看到的基数是 H
.1
请注意,合并有 三个 输入。 提交 H
是 合并基础,提交 J
和 L
(HEAD
和他们的)是涉及的两个分支提示。在这里做一个完整的合并操作的最终结果将是一个新的 merge commit M
,它将指向它的 both两个直接输入:
I--J
/ \
...--G--H M <-- somebranch (HEAD)
\ /
K--L <-- origin/somebranch
merge M
中的 snapshot 是将 combined 更改应用于 commit [=30] 中的快照的结果=].即 Git 找到:
- 从
H
到J
的区别:你改变了什么; - 从
H
到L
的区别:他们改变了什么;
并尝试将它们单独组合起来。 Git 在合并它们时遇到了问题——合并冲突——然后放弃并强迫 you 合并它们。一旦你这样做了,并使用 git merge --continue
完成了这个过程,Git 从合并的结果中得到了 M
。
(提交M
不记得直接提交H
。Git可以re-discover合并基础H
稍后,如有必要,使用与这次查找相同的过程。2)
1我喜欢设置这个选项。这样,您不仅可以看到您输入的内容和他们输入的内容,还可以看到合并基础提交中最初存在的内容。这在您或他们删除有问题的代码时特别有用。
2这实际上是一种错误,因为您可以 运行 git merge
使用修改内容的选项,包括——在一些相对罕见的情况下- 使用的合并基础。合并命令应记录您使用的选项,以使合并真正可重复。
但是,当您使用 git rebase
时,Git 复制 您现有的每个提交——在本例中为两个——一次一个。此复制过程使用 "detached HEAD",其中 HEAD
直接指向提交。 Git 首先检出 他们的 提交 L
作为分离的 HEAD,像这样:
...--G--H------K--L <-- HEAD, origin/somebranch
\
I--J <-- somebranch
现在,从技术上讲,cherry-pick 是合并的一种形式,或者我喜欢这样说,合并为动词: 合并过程,实际上没有进行合并提交。也就是说,您仍在做 会 用 git merge
做的所有相同工作。不同之处在于对合并的输入提交,当你完成时,最终提交 不是 合并提交:它只是一个常规的、普通的、日常的提交,与一个 parent.
所以,既然 Git 已经完成了 git checkout --detach origin/somebranch
,那么 他们的 提交 L
就是你的 当前 提交,它执行 git cherry-pick <hash-of-I>
复制提交 I
。 cherry-pick 开始合并过程。此特定合并的三个输入是:
- 合并基础,即提交 Git 的 parent 被告知 cherry-pick:那是
H
; --ours
提交,它始终是HEAD
,在本例中是提交L
:他们的 提交;和--theirs
提交,即 Git 提交给 cherry-pick:那是I
,也就是 你的提交。
所以te --theirs
合并操作的提交是 你的 提交,合并操作的 HEAD
或 --ours
提交是他们的 提交 L
! 这就是这个 apparent 逆转的来源。 Git 正在做一个 cherry-pick, 是合并的一种形式。 --ours
输入是他们的提交,--theirs
输入是您的提交。
解决任何合并冲突后,您将 运行 git rebase --continue
。 (如果你有 运行 和 git cherry-pick
你自己,你会 运行 git cherry-pick --continue
;git rebase
会为你做这件事。)这将有 cherry-pick 完成,它通过 ordinary commit:
I' <-- HEAD
/
...--G--H------K--L <-- origin/somebranch
\
I--J <-- somebranch
分离的 HEAD 现在直接指向这个新的普通提交,即原始提交 I
的副本 I'
。请注意,提交 I'
是 "just like" 提交 I
除了:
- 它有一个不同的 parent 提交,
L
;和 - 它有一个不同的 快照。
I'
中的快照是将H
与I
之间的差异(即您更改的内容)与 合并 之间的差异与H
和L
之间的区别。
唉,因为这是 git rebase
而不是 git merge
,我们还没有完成。现在我们也必须复制提交 J
,就像 git cherry-pick <hash-of-J>
一样。我们的情况仍然是分离的 HEAD
指向新的提交 I'
。 this 合并的三个输入是:
- 合并基础:
J
的parent,即I
; - HEAD 提交为
--ours
:提交I'
,我们刚刚创建的那个;和 - to-be-copied 提交为
--theirs
:提交J
,即您的第二次提交。
对于合并,Git 将合并基础中的快照与两个提示提交中的每一个进行比较。所以 Git:
- 将您的
I
快照与您自己的I'
快照进行比较,看看您更改了什么:那是您通过提交 [=] 引入的 他们的 代码33=]。如果存在冲突,那将显示在<<<<<<< HEAD
中。 - 将您的快照从您的
I
与您自己的J
进行比较,以查看 "they" 发生了什么变化:那是 您的 发生的变化J
。如果存在冲突,这就是>>>>>>> theirs
中显示的内容。
这次,HEAD
只是他们的代码,现在是他们的代码和你的代码的混合体, 在冲突的 --ours
一方。同时,任何冲突的 --theirs
方面仍然是他们的代码。解决冲突并使用 git rebase --continue
后,Git 将进行新的普通提交 J'
,如下所示:
I'-J' <-- HEAD
/
...--G--H------K--L <-- origin/somebranch
\
I--J <-- somebranch
这里 J'
是 J
的 cherry-picked 副本。
因为这些是必须复制的所有提交,Git 现在通过将名称 somebranch
从提交 J
中拉出来并将其附加到新的来完成 rebase提交 J'
,然后 re-attaching HEAD
到名称 somebranch
:
I'-J' <-- somebranch (HEAD)
/
...--G--H------K--L <-- origin/somebranch
\
I--J [abandoned]
变基完成。 运行 git log
将显示您的新副本,并且不再显示原始提交 I
和 J
。原始提交最终将被回收和销毁(通常是在 30 天后的某个时间)。
这就是使变基从根本上比合并更棘手的原因。变基涉及重复 cherry-picks,每个 cherry-pick 是一个合并。如果您必须复制十次提交,那么您正在执行 ten 合并。 Git 通常可以自动完成它们,并且 Git 通常可以正确完成它们,但是 每个 合并只是 Git 愚蠢地应用一些简单的 text-difference-combining 规则,因此 每个 合并都是出错的机会。您必须仔细检查 and/or 测试结果。理想情况下,您应该检查 and/or 测试所有十份副本,但如果最后一份是好的,可能其他所有副本也是如此。