为什么 git rebase 会触发合并冲突?

Avi*_*ohn 7 git rebase git-merge-conflict

我已经克隆了一个远程存储库,创建了一个新分支b并开始工作并进行提交。我也推动了这个b分支,但我是唯一一个在这方面工作的人。

一段时间后,我想在远程主分支上重新设置本地分支的基础,只是为了与系统可能发生的更改同步。请注意,我确信我是唯一处理这些特定文件的人。

所以我做了

git fetch --all
git rebase origin/master
Run Code Online (Sandbox Code Playgroud)

然后 Git 通知我合并冲突。

现在,我可以轻松地手动解决冲突,但我很烦恼:为什么会发生合并冲突?

如果我没有记错的话,整个想法git rebase就是在指定分支的顶端“重放”当前分支的所有提交。我是唯一一个处理过这些特定文件或这个特定分支的人。

那么为什么会发生这种情况呢?我的做法有问题吗?

tor*_*rek 6

我认为,这有助于认识到这git rebase确实是一种重复运行的自动化方式git cherry-pick。但只有当您也意识到这git cherry-pick是一种合并形式时,这才会有帮助。这就是合并冲突的来源。

\n

查看常规合并时更容易理解这一点。让我们画一些提交图,用单个大写字母代表每个提交,如下所示:

\n
          I--J   <-- branch1 (HEAD)\n         /\n...--G--H\n         \\\n          K--L   <-- branch2\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们运行git merge branch2,Git 会发现三个提交:

\n
    \n
  • 一个提交\xe2\x80\x94(最后是#2)\xe2\x80\x94 始终是当前提交或HEAD提交。由于HEAD附加到 name branch1,当前提交是标识branch1: commit的提交J

    \n
  • \n
  • 最后的 #3 提交是命名的提交。通过使用该名称,branch2您可以告诉 Git 读取该名称并查看它是否指向 commit L

    \n
  • \n
  • #1 提交是 Git 自己找到的提交。Git 通过查找两个分支“上”的最佳提交来实现此目的。正在进行的提交branch1...-G-H-I-J. 正在进行的提交branch2...-G-H-K-L. 因此 commitG位于两个分支上,但它比 commit 更靠后H,后者也位于两个分支上。提交I-J仅在一个分支上,并且K-L仅在另一分支上。这意味着提交H最好的共享提交。

    \n
  • \n
\n

Git 现在可以执行合并。为此,Git实际上运行两个命令: git diff

\n
    \n
  • git diff --find-renames hash-of-H hash-of-J:这告诉 Git 公共起点和您的提交之间发生了什么变化,即您做了什么。

    \n
  • \n
  • git diff --find-renames hash-of-H hash-of-L:这告诉 Git 在相同的起点和提交之间发生了什么变化即他们做了什么。

    \n
  • \n
\n

合并命令现在的工作是合并您的更改和它们的更改:

\n
    \n
  • 对于没有人接触过的文件H,请保留这些文件。
  • \n
  • 对于您H已更改但未更改的文件,请采用您的版本。
  • \n
  • 对于他们H已更改而您未更改的文件,请采用其版本。
  • \n
  • 对于你们都接触过的文件,弄清楚是否可以合并你们的更改。
  • \n
\n

还有一些其他棘手的情况,例如如果您重命名文件和/或他们重命名文件,或者您删除文件而他们修改了它,等等会发生什么。但大多数情况下,当您都对某个文件进行了更改并且都更改了该文件的相同,或者进行了“接触”的更改时,就会发生合并冲突。如果你的更改和他们的更改没有“相互接触”,Git 会认为保留这两个更改是可以的。否则,您会遇到合并冲突。

\n

刚开始几次时,这会有点棘手,但过了一段时间后,你就会感觉很自然。如果 Alice 将“红球”更改为“蓝球”,而 Bob 将“红球”更改为“红砖”,Git 不知道该怎么做,并让选择正确的答案。

\n

输入樱桃选择

\n

git cherry-pick命令的作用是复制提交。也就是说,给定一些提交(代表所有文件的完整快照),我们想要弄清楚该文件中发生了什么变化。

\n

在 Git 中,将两个相邻的提交\xe2\x80\x94(两个紧随其后的快照\xe2\x80\x94)转换为一组更改是很容易的。我们所要做的就是要求 Gitgit diff在两个快照上运行。Git 会找出哪些文件是相同的,并且对这些文件什么也不说。它将找出哪些文件不同,并生成一个配方\xe2\x80\x94一组要添加和/或删除\xe2\x80\x94的行,这会将早期提交的文件更改为稍后提交的副本。如果我们询问--find-renames(从 Git 2.9 开始默认开启),Git 会判断左侧丢失的文件与右侧新文件相比是否也代表文件重命名操作。

\n

那么想象一下,我们有这样的情况:

\n
...--G--H--I--J   <-- main\n         \\\n          K--L   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们要求与H到的差异K,我们将看到与 相比发生了什么变化。例如,这可能会说“在第 72 行之后添加此行”。KHfile.py

\n

但是如果我们想将这些更改应用于 commit 该怎么办J?我们可以闭上眼睛,希望“在第 72 行之后添加这一行”有意义,但如果第72现在是第 75 行,或者可能更远呢?我们可以搜索上下文。但也许我们甚至可以做得更好。

\n

H如果我们首先获取 commit与 commit的第二个差异,而不是盲目地应用此更改或检查上下文,会怎么样J?这将告诉我们他们改变了什么。如果他们在第 72 行上方添加了三行,那么现在第 72 行肯定是第 75 行。这告诉我们将更改放在哪里。

\n

但等一下,这个“获取两个差异并将它们组合起来”的想法是什么git merge!事实上,这正是git cherry-pick工作原理:我们选择要复制的提交的父级,并假装它是合并基础。我们得到两个差异,一个从合并基础到我们正在复制的提交\xe2\x80\x94这些是“他们的”更改\xe2\x80\x94,另一个从合并基础到我们正在处理的提交,这是 commit J,这些是“我们的”更改。我们让 Git 将它们组合起来,使用与运行git merge.

\n

如果一切顺利,git cherry-pick请为我们做出新的承诺。该命令在 Git 所谓的分离 HEADgit rebase模式下完成所有这些操作,因此现在的图片如下所示:

\n
                K'  <-- HEAD\n               /\n...--G--H--I--J   <-- main\n         \\\n          K--L   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n

我们将调用新提交K'来表明它是原始提交的副本K。现在是时候挑选提交了L,所以现在 Git 将会比较KvsL来看看“他们”(实际上是我们)改变了什么,也会比较比较来K看看K'“我们”改变了什么(实际上,所有的事情,包括之前的樱桃)选择操作)已更改。然后 Git 将尝试组合这两组更改\xe2\x80\x94“ours”,来自K-vs-K'和“theirs”,来自K-vs- L\xe2\x80\x94 如果一切顺利,git cherry-pick将创建一个新的犯罪L'

\n
                K'-L'  <-- HEAD\n               /\n...--G--H--I--J   <-- main\n         \\\n          K--L   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n

如果事情进展顺利,在任一git cherry-pick步骤中,Git 都会停止并让我们解决冲突,就像处理git merge.

\n

复制所有提交后,git rebase还有最后一个技巧:它将名称 feature旧位置拉出并将其粘贴到HEAD现在指向的任何位置,然后“重新附加”HEAD到分支名称。在这种情况下,会产生:

\n
                K'-L'  <-- feature (HEAD)\n               /\n...--G--H--I--J   <-- main\n         \\\n          K--L   [abandoned]\n
Run Code Online (Sandbox Code Playgroud)\n

如果您现在使用 来查看提交git log,您将根本看不到原始K-L提交,而是会看到新K'-L'提交。之前的下一次提交L是现在J,并且功能分支已重新基于主分支。

\n

任何合并冲突都会发生,因为“您”和“他们”在合并与奇数合并基过程中生成的差异中接触了同一文件的相同或相邻行。当然,“他们的”提交实际上是您的提交\xe2\x80\x94,即您正在变基的\xe2\x80\x94,并且当您开始变基时,“您的”提交通常最初是其他人的。最终,“你的”提交是你和他们的混合,这非常令人困惑。

\n

(当我遇到这些合并冲突时,我喜欢设置merge.conflictStylediff3获取更多信息。)

\n


mat*_*att 0

问题在于“重播”的定义。变基的作用与合并的作用相同:它创建一个差异b(在本例中,从分支分离的点master到分支的尖端b)并尝试将其应用到 的末尾origin/master

\n

因此,调用分支 commit split

\n

现在,我们知道这origin/master不是\xe2\x80\x94,因为如果是,您首先split需要重新建立基址。因此,自 以来master已添加了一些提交。mastersplit

\n

嗯,就像合并一样,可能会出现冲突。split从到的差异和从到master的差异可以包含无法同时自动完成的事情 \xe2\x80\x94 例如,同一文件的同一区域在两个不同的地方编辑方式,或者文件在一个路径中被编辑但在另一个路径中被删除,等等。这是一个冲突。splitb

\n

请注意,冲突并不意味着发生了任何不好的事情!“冲突”这个词选得非常糟糕。这仅仅意味着git不想通过试图读懂你的想法而造成偏见;它需要您手动完成合并,因为如果它自动选择要做什么,它可能会做您不想要的事情。

\n