为什么我与git rebase互动遇到合并冲突?

时间:2019-02-22 15:01:21

标签: git rebase

我还在学习git。

我有一个名为names.txt的文件。带有此文字。

enter image description here

这是我的提交历史记录

enter image description here

第一次提交已添加文件。 第二个提交添加了第一行Mary。 第三次提交添加了第二行约翰。

git show 7bdb5ef

enter image description here

git show 80384aa

enter image description here

我想以此为基础并编辑提交Mary以将文本更改为Mary Shelly

I do git rebase -i 4a5244b

接下来,我将提交Mary设置为编辑并运行变基。

enter image description here

调整停在这里。

enter image description here

现在name.txt的值是Mary commit的值。 enter image description here

我将其更改为Mary Shelly并进行登台。

我跑步

git commit --amend 

跟着

git rebase --continue

现在我遇到了合并冲突。

enter image description here

enter image description here

我不明白为什么会这样。提交John仅更改文件的第二行。当我们编辑提交Mary时,我们仅更改文件的第一行。 这怎么引起冲突?

3 个答案:

答案 0 :(得分:3)

问题在于存在 合并冲突,而chepner's comment是理解原因的关键。好了,还有 commit graph ,还有git rebase由重复的git cherry-pick运算组成的事实。交互式rebase允许您在每个git cherry-pick之间添加自己的命令,甚至可以将Cherry-pick更改为其他内容。 (最初的命令表以全{pick命令开始,每个命令都表示进行摘樱桃。)

您的提交历史记录是您的提交图的摘要-本质上是从某个特定终点(当前分支的尖端)开始并工作的访问该提交图 中的每个提交的结果向后。如果您使用git log --graph,则会得到一些潜在的重要信息,而这些信息在没有--graph的情况下会被忽略,尽管在这种特殊情况下,很容易看到图形是线性的。因此,您只有三个提交:

A <-B <-C   <-- master (HEAD)

其中A实际上是4a5244bB代表7bdb5ef,而C代表80384aa(如果我已经转录了图片正确)。每次提交都具有文件names.txt的完整副本。当然,副本在提交ABC中是不同,在A中是空的。在B中,它是一行读取Mary的行;在C中,两行分别显示MaryJohn

该图本身源自以下事实:提交C80384aa在{{内的中包含提交B7bdb5ef的哈希ID。 1}}本身。这就是为什么我从C拔出一个指向C的箭头。 Git将此称为B parent 提交。 Git以名称C记录C的哈希ID,然后在名称master上附加特殊名称HEAD 知道master应该从这里开始,并且git log是您现在要进行的工作。

当您运行C时(选择提交git rebase -i 4a5244b作为新基础),Git指出这意味着副本提交AB ,因此会将其哈希ID放入C命令列表中。然后,它将在命令表上打开您的编辑器。您将pick更改为pick,这会告诉Git:在执行操作的中间,执行“摘樱桃”操作,然后退出rebase。

您没有强制重新建立真实副本。 (为此,使用edit-f--no-ff都是同一意思。这在这里也没有关系,在大多数情况下也是如此。)因此,Git看到有一条指令复制--force-rebase,使它出现在B 之后,并意识到:嘿,等等,A已经在B之后。我会把它留在那里。 Git做完了然后停下来,让你保持这种状态:

A

请注意,A--B <-- HEAD \ C <-- master 不再附加到HEAD:现在它直接指向提交master。提交B仍然存在,并且C仍然指向它,但是Git已停止在“分离的HEAD”模式下,允许您进行编辑。

您对文件mastergit add进行了更改。这会产生一个 new 提交-我们可以将其称为git commit --amendB',通常我使用D,因为通常它与B'很像,但是这次已经足够不同了,所以让我们使用B。新的提交将D作为其父项,A就是这样做的。 Git更新--amend以指向新提交。现有的提交HEAD保持不变。所以现在您有了:

B

D <-- HEAD / A--B \ C <-- master 中的文件names.txt的新行读为D

您现在运行Mary Shelly,因此Git继续使用说明表中的内容。这由git rebase --continue组成,这使Git运行pick <hash-of-C>来复制git cherry-pick。该副本需要在当前提交C之后进行。现有的提交D不需要,因此Git这次必须真正完成这项工作。

樱桃采撷是一种合并-至少作为动词合并

要执行合并操作(要合并),操作-Git需要三个输入。这三个输入是 merge base 提交,当前或C提交(有时也称为 local ,尤其是--ours),以及其他或git mergetool提交(有时称为远程)。对于常规合并,基址通常有点远:这是两行提交分开的地方。对于“挑剔”和“还原”而言,该基准就在提交旁边。此操作的合并基础是--theirs的父提交C

合并的实际操作包括在整个提交中运行两个B命令:

  • git diff:我们做了哪些更改?
  • git diff --find-renames hash-of-base hash-of-ours:它们发生了什么变化?

因此Git现在将基础提交git diff --find-renames hash-of-base hash-of-theirs与您当前的提交B进行对比。该差异影响文件D并说:将说玛丽的一行更改为两行:一读玛丽·雪莱,另一读约翰。然后,Git对比names.txt与{ {1}},看看“他们”(您,之前)做了什么。差异影响文件B并说:在读取文件的末尾,在读取Mary的行之后,添加读取John的行。

这就是Git在合并冲突部分中向您显示的内容:一个文件说用Mary Shelly代替Mary ,另一个文件说 keep Mary并添加John 。如果愿意,您可以告诉Git在合并冲突部分保留更多信息。为此,请将C设置为names.txt。 (如果未设置,则默认值为diff.conflictStyle。)

使用diff3设置,您会看到 base 内容(由merge标记)是一行diff3,而这两行来自冲突提交的文件已分别用|||||||Mary +新行Mary Shelly替换了该基础。我发现这种合并冲突更清晰,更易于手动合并。

无论如何,此时您的工作是想出正确的结果(无论是什么结果)并将其写出并将其复制到索引插槽零中。通常,您只需要编辑Git在工作树中留下的混乱Mary,将正确的内容放入其中,然后运行John

恢复

已解决冲突,请运行names.txt以恢复停止的任何操作-在这种情况下,请重新设置基准,但这也发生在Cherry-pick和merge中。 Git将使用您用git add names.txt更新的索引内容进行新的提交,它是git whatever --continue的副本:

git add

到达命令表的末尾,C现在通过将名称 D--C' <-- HEAD / A--B \ C <-- master 从提交git rebase中移出并将其粘贴到master上来完成,这是最后一个复制它,然后重新附加C

C'

答案 1 :(得分:0)

文件级合并操作(即,Git需要协调对文件的两组更改的操作)试图允许您在不引起太多冲突的情况下四处移动代码,因此为了尝试找到正确的位置,进行更改后,也会考虑 context -周围的线条集。

在这里,重新应用提交John会造成麻烦:原始提交John添加到行Mary的旁边。现在,Git试图重新应用提交,但是引用行Mary不再存在-仅有一行引用Mary Shelly ...请记住,Git无法理解目的和/或文件的含义,因此在这种情况下,不会有任何机会将其作为冲突呈现给您,以便您可以进行检查。

使用JohnMary之间的许多其他行再次尝试相同的操作,这些行将保持不变-您会发现不会发生冲突。

答案 2 :(得分:0)

问题在于,实际上,在添加的相邻行上也很有可能需要对原始行进行更改,以使合并成功而不会引起人为判断。我使用的示例是

<<<<<<<<<<<<<
    if ( g->tag == mark 
      || g->tag == error ) {
||||||||||||||
    if ( tag == mark
      || tag == error ) {
==============
    if ( tag == mark 
      || tag == release
      || tag == error ) {
>>>>>>>>>>>>>>

其中一个更改将g->添加到了两行,而另一个更改在中间添加了release