相似的git rebase案例的结果不一致

时间:2018-07-26 15:31:04

标签: git github

我观察到,当在不同分支中的同一文件中进行更改时,然后尝试重新设置基础并将这些分支一个接一个地合并到主线中时,它有时会通过或有时会失败。结果不一致。例如 情况1): - 我在主线有一个文件- test.txt

a
b
c
d

我从主线创建了两个分支b1和b2 在分支b1中,我编辑并提交了这些更改-test.txt

a
b1
c

在分支b2中-> test.txt

a
b
c1
d

然后,我在b1上执行了“ git rebase mainline”,在主线上​​做了“ git merge b1” 到目前为止很好。现在,当我在分支b2上执行“ git rebase mainline”时,它失败并要求我首先解决冲突。

情况(2):- 我在主线中有文件test2.txt。

MEMBER xe-1/1/1 REMPORT xe-1/1/1
MEMBER xe-2/2/2 REMPORT xe-2/2/2
MEMBER xe-3/3/3 REMPORT xe-3/3/3
MEMBER xe-4/4/4 REMPORT xe-4/4/4

MEMBER xe-11/11/11 REMPORT xe-11/11/11
MEMBER xe-21/21/21 REMPORT xe-21/21/21
MEMBER xe-31/31/31 REMPORT xe-31/31/31
MEMBER xe-41/41/41 REMPORT xe-41/41/41

我已经从主线创建了分支c1和c2。在c1中,我将test2.txt编辑并提交为:-

MEMBER xe-1/1/1 REMPORT xe-1/1/1:1
MEMBER xe-2/2/2 REMPORT xe-2/2/2:1

MEMBER xe-11/11/11 REMPORT xe-11/11/11
MEMBER xe-21/21/21 REMPORT xe-21/21/21
MEMBER xe-31/31/31 REMPORT xe-31/31/31
MEMBER xe-41/41/41 REMPORT xe-41/41/41

在分支c2中,我将test2.txt编辑并提交为:-

MEMBER xe-1/1/1 REMPORT xe-1/1/1
MEMBER xe-2/2/2 REMPORT xe-2/2/2
MEMBER xe-3/3/3 REMPORT xe-3/3/3
MEMBER xe-4/4/4 REMPORT xe-4/4/4

MEMBER xe-11/11/11 REMPORT xe-11/11/11:1
MEMBER xe-21/21/21 REMPORT xe-21/21/21:1
MEMBER xe-31/31/31 REMPORT xe-31/31/31:1
MEMBER xe-41/41/41 REMPORT xe-41/41/41:1

然后,我在c1上执行了“ git rebase mainline”,在主线上​​做了“ git merge c1”。 到目前为止很好。现在,当我在c2上执行“ git rebase mainline”时,它会通过。 “ git merge c2”之后主线上的最终内容如下:-

MEMBER xe-1/1/1 REMPORT xe-1/1/1:1
MEMBER xe-2/2/2 REMPORT xe-2/2/2:1

MEMBER xe-11/11/11 REMPORT xe-11/11/11:1
MEMBER xe-21/21/21 REMPORT xe-21/21/21:1
MEMBER xe-31/31/31 REMPORT xe-31/31/31:1
MEMBER xe-41/41/41 REMPORT xe-41/41/41:1

这符合我的期望,但是我试图理解为什么Case(2)通过时Case(1)失败。这里的“ git rebase”算法是什么?

1 个答案:

答案 0 :(得分:1)

要正确了解此处发生的情况,您需要了解几件事:

  1. Rebase本质上是重复的摘樱桃操作。
  2. 选择一个(单个)提交确实是一种三向合并。更准确地说,它是合并的合并形式-合并为动作,动词。 (Git的git merge实现了很多事情,包括要合并的步骤,然后执行产生 merge commit 的提交,即将合并视为一个形容词。 -pick操作永远不会产生合并提交,但会利用合并即动词过程。)
  3. 作为动词进行合并,进行三步合并,包括将(单个)合并基础提交与两个感兴趣的提交进行比较。

考虑原始设计时,合并操作最有意义。假设有两个人修改了某些内容,为简单起见,假设某物在这种情况下只是一个文件。我们称两个人为A(爱丽丝)和B(鲍勃)。

它们以该文件的通用基本版本开头。爱丽丝对文件进行了一些更改,鲍勃对文件进行了一些更改。最终,有人-爱丽丝(Alice),鲍勃(Bob)甚至是第三人C(卡罗尔(Carol))必须结合他们的更改。

在Git中,为了合并这些更改,我们让Git找出它们都是从哪个文件开始的,然后比较该基本版本为两个最新版本。基本版本是进入基本提交的文件的版本:

             o--o--A   <-- Alice
            /
...--o--o--*
            \
             o--o--B   <-- Bob

Git可以简单地运行两个git diff命令:

git diff --find-renames <hash-of-*> <hash-of-A> > /tmp/alice
git diff --find-renames <hash-of-*> <hash-of-B> > /tmp/bob

Git然后可以提取提交*的内容,应用Alice的更改,应用Bob的更改,并将最终结果用作合并结果。请注意,此操作可以处理 all all 文件(一次一个文件)的更改。

如果Alice和Bob触摸任何一个文件中的 same 行,则当然存在合并冲突。这意味着我们必须定义行“相同”的含义,但这往往很清楚。如果您进行了简单的合并,这就是您在“案例1”中看到的内容:爱丽丝更改了:

a
b
c
d

收件人:

a
b1
d

因此她删除了bc两行,并在b1之前a之后添加了一行d

同时,

鲍勃将相同的原始输入更改为:

a
b
c1
d

也就是说,鲍勃保留 b,删除了c,并在保留的c1和保留的{{1}之间添加了b }。这些变化是重叠的,因此它们显然是冲突的。

对于d来说,上面的内容很好,也很不错,该git merge进行动词合并,然后进行形容词合并提交(或者在这种情况下,由于合并冲突而停止,并且让您完成工作)。但是摘樱桃呢?

要了解变基及其选择,请再次开始,绘制提交图。我们有一系列提交,名称mainline指向这些提交:

...--A--B--C   <-- mainline (HEAD)

然后您创建一个新分支b1,指向提交C

...--A--B--C   <-- mainline, b1 (HEAD)

然后更改文件并提交新快照:

...--A--B--C   <-- mainline
            \
             D   <-- b1 (HEAD)

您还创建了指向提交b2的分支C,并更改了一个文件并提交了新的快照:

             E   <-- b2 (HEAD)
            /
...--A--B--C   <-- mainline
            \
             D   <-- b1

(在这些图形中,名称HEAD始终附加到当前分支。)

  

然后,我在b1上执行了“ git rebase mainline”,在主线上​​做了“ git merge b1”

也就是说,您运行了git checkout b1 && git rebase mainline。这根本不执行任何操作,因为对D的提交b1已经在正确的位置。然后您做了git checkout mainline && git merge b1。这会执行快进,实际上根本不是合并:它实际上等同于在移动名称D时检出commit mainline。结果是此图:

             E   <-- b2
            /
...--A--B--C
            \
             D   <-- b1, mainline (HEAD)
  

到目前为止很好。现在,当我在分支b2上执行“ git rebase mainline”时,它失败并要求我首先解决冲突。

如果您git checkout b2 && git rebase mainline,Git会发现提交E之后没有提交D。而是在提交C之后出现。因此,Git现在必须复制提交E到新的提交。此复制为git cherry-pick

Cherry-pick通常被描述为“将提交与父提交进行比较以获取补丁,然后将该补丁应用于当前提交”。对于简单的情况,这已经足够接近了-实际上,git cherry-pick一次完成了操作。但是,对于更复杂的情况,git cherry-pick的操作是运行 merging 操作,但是将合并基础设置为要选择的提交的父级。

在这种情况下,E的父级是C,因此这是“挑选”的“合并基数”。 Git现在运行两个git diff:一个将合并基础CD比较,另一个将合并基础CE比较。现在非常清楚,这就是我们合并爱丽丝和鲍勃的变更时的情况。这些更改重叠,因此Git引发了合并冲突。

解决合并冲突的过程与git merge相同:您在Git的索引中拥有每个冲突文件的所有三个版本,另外Git最好自行进行合并,并存储在工作树。此工作树版本具有合并冲突标记。您必须以任何您喜欢的方式解决冲突,并更新索引,以使其具有文件的最终版本。例如,您可以只将工作树文件编辑为形状,然后使用git add将工作树版本复制到索引中。

然后,您运行git rebase --continue以完成变基正在执行的选择,并让变基继续选择更多的提交(如果有)。在这种情况下,没有任何内容,因此rebase通过移动分支名称以指向复制的提交来完成其工作:

             E   [abandoned]
            /
...--A--B--C
            \
             D   <-- b1, mainline
              \
               E'  <-- b2 (HEAD)

您现在拥有所有工具来了解第二个基准。查看哪些提交被复制。注意哪个提交充当合并基础。运行两个git diff命令,以将合并基础提交与其他两个提交进行比较。两个diff输出中的更改是否冲突?如果不是,将这两个更改都应用于文件的基于合并的版本会得到什么结果?那和Git得到的一样吗?