git rebase/squash - 为什么我需要再次解决冲突?

som*_*ser 5 git squash git-squash

我确实尝试过浏览类似的主题,但我似乎无法掌握这个概念。

我分叉了一个仓库,做了一些更改。加班的时候,我也用过git fetch upstreamandgit merge upstream/<branch>几次。有时,我会解决并重新测试一些冲突。

现在,当涉及到将更改推送到上游时,我想进行一次提交。我得到的指示是使用git fetch upstreamgit rebase -i upstream/<branch>

我不明白的是,我再次陷入解决冲突的困境。我不明白为什么当我的叉子与它的起源相同时我需要解决冲突。我可以备份所有修改过的文件,核对我的分叉,再次分叉,恢复备份,这样我就没有冲突需要解决并准备提交。这个过程似乎很机械,我不明白为什么我必须走这条艰难的路(再次解决冲突)。

有人可以帮我理解吗?

tor*_*rek 5

“为什么你需要重新解决所有问题”的最佳答案是“你不需要”。但这意味着放弃给你的指示(我不清楚是谁给你这些指示的)。

\n\n

正如ElpieKay 评论的那样,您可以使用它git rerere来自动重用以前记录的解决方案。通过这种方式,你重新解决了所有问题,但是让 Git 来做,而不是必须手动做。

\n\n

但是,如果您的意图是在当前上游提示之上进行一个新的“挤压合并”提交(即,不是合并,只是普通提交)以从中发出拉取请求,则无需执行通过这一切。您可以只执行以下命令序列(请注意下面的假设):

\n\n
git fetch upstream\ngit checkout -b for-pull-request upstream/branch\ngit merge --squash branch\ngit commit\ngit push origin -u for-pull-request\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后使用 Web 服务上的可点击 Web 按钮发出拉取请求origin

\n\n

最终,他们接受您的拉取请求,此时您将删除 branch,放弃所有这些工作,转而采用他们接受的新的单一提交。(请参阅末尾以了解如何重命名/重置。)或者,他们不接受它,此时您可以删除for-pull-request,并可以继续处理branch并最终重复该过程。

\n\n

这假设:

\n\n
    \n
  • 您的 fork 由两个存储库组成:您的存储库\xe2\x80\x94(您自己的计算机/笔记本电脑/无论什么\xe2\x80\x94 上的存储库)和 Web 服务提供商(如 GitHub)上的另一个存储库。
  • \n
  • 他们的 ( upstream\'s) 分叉位于同一个网络服务提供商上。
  • \n
  • 在您的计算机上的存储库中,您使用短名称upstream来引用上游存储库(它们的分支),并使用短名称origin来引用存储在 Web 服务提供商上的您自己的分支。Web 提供商提供了发出拉取请求的点击按钮。
  • \n
  • 也许最重要的是,这假设您愿意放弃该本地分支,转而支持上游接受的拉取请求(如果发生这种情况)。
  • \n
\n\n

理解这一切有几个关键点,其中大部分都与 Git提交图的工作方式相关。其余部分与 Git 分发存储库的(单个)魔术有关,以及各种命令\xe2\x80\x94 git mergegit rebasegit merge --squash\xe2\x80\x94 实际执行的操作以及 和git fetchgit push实际操作。

\n\n

我真的不想再写一篇关于这个的巨篇文章(……太晚了:-)),所以让我们通过注意这些要点来总结一下。它们的顺序不是很好,但我不确定是否有很好的顺序:有很多交叉引用的项目。

\n\n
    \n
  • Git 通常只添加新的提交。添加新提交会导致存储在 \xe2\x80\x94 中的当前分支名称 \ HEADxe2\x80\x94 指向新提交,而新提交又指向该HEAD提交。
  • \n
  • git rebase通过将现有提交复制到新提交,然后放弃原始提交来工作。它使用“分离的 HEAD”进行复制,其中HEAD直接指向提交,而不是使用分支名称。(“添加新提交”仍然照常工作,只是它只是HEAD直接更新,而不是名称不再存储的分支HEAD。)复制的提交不包括任何合并提交。在某种程度上,这意味着您随后所做的任何冲突解决都将很大程度上丢失(除非您已git rerere启用)。(实际上由于不同的原因丢失了,我还没想好如何解释。)
  • \n
  • 每个副本都像是通过运行而制作的git cherry-pick,有时甚至是通过运行而制作的git cherry-pick
  • \n
  • 每个cherry-pick都可能导致合并冲突,因为cherry-picking提交会运行合并机制,merge作为动词合并作为我喜欢称之为的

    \n\n

    此操作的合并基础是精心挑选的提交的父级,即使这不是一个非常明智的合并基础。一如既往,提交是当前提交或提交,而另一个提交\xe2\x80\x94提交--ours\ xe2\x80\x94 是正在复制/挑选的提交。在成功的选择结束后,新的提交将作为普通的非合并提交进行。HEAD--theirs

  • \n
  • 相比之下, Running 则git merge不太复杂,只是存在各种单独的情况。

    \n\n

    有时Git发现根本不需要合并,可以进行快进操作:更改当前分支指向的提交,使分支指向目标提交。

    \n\n

    有时需要合并,或者(使用--no-ff)你告诉 Git 不要进行快进,即使可以。在这种情况下,Git 使用合并机制来进行合并。此合并的合并基础是两个提示提交的实际合并基础。与所有合并一样,这里可能存在合并冲突。最后进行的最终提交是合并提交:合并为形容词或名词

    \n\n

    提交图中存在合并提交意味着将来的合并将找到一个新的、更好的合并基础。这将避免重新解决冲突。

  • \n
  • 跑步git merge --squash是另一个特殊情况。就像实际的合并一样,它使用正常的合并机制来计算真实的合并所涉及的两个分支提示的

    \n\n

    当然,要合并的两个提交是一如既往的HEAD( --ours) 和您在命令行中命名的提交。由于合并基础是真正的合并基础,它使用现有的合并作为名词合并(合并提交)来最小化新的合并冲突,因此可以获得相对无冲突的合并结果。

    \n\n

    然而,最终提交又回到了樱桃选择的想法:最终提交不是合并提交。这只是一个普通的提交。该是通过 merge-as-a-verb 计算的,但提交是常规提交,而不是 merge-as-a-noun。(没有特别好的理由,该--squash标志也会打开该--no-commit标志,迫使您git commit自己运行。可能有一个原因曾经\xe2\x80\x94可能对于编写初始代码的人来说是最方便的--squash提早退出可能是最方便的\xe2 \x80\x94但今天没有理由这样做。)

  • \n
\n\n

我们添加以下事实:

\n\n
    \n
  • 您的分叉最初是其他存储库的克隆。所以它从相同的提交(相同的历史)开始upstream。从那时起,你和他们有了一些分歧,但同时你更新了你的分叉git push从您自己的本地存储库中提交来更新您的分叉。

  • \n
  • 当您这样做时,您可以从他们的git fetch upstream分支中获取他们的提交,并将它们放入您自己的本地存储库中。这些的跟踪名称等等。upstream/master

    \n\n

    当您git fetch origin(如果需要)时,您可以从分支中获取提交并将它们放入您自己的本地存储库中。这些的跟踪名称origin/master等等。

    \n\n

    您的存储库有自己的分支(当然)。

    \n\n

    这意味着您的本地存储库\xe2\x80\x94(您自己的计算机上的存储库\xe2\x80\x94)具有“他们的分叉”、“您的分叉”和“您的提交”的完整联合。您可以随时将自己的提交推回自己的分支。

  • \n
  • 您可以使用您的分叉轻松发出“拉取请求”,因为您的提供商(例如 GitHub)会为您记住“您的分叉”和“他们的分叉”之间的链接。

  • \n
\n\n

因此,让我们绘制存储库中的\xe2\x80\x94 或\xe2\x80\x94 提交图的简化版本,结果如​​下:

\n\n
\n

我分叉了一个仓库,做了一些更改。加班的时候,我也用过git fetch upstreamandgit merge upstream/<branch>几次。有时,我会解决并重新测试一些冲突。

\n
\n\n

你有:

\n\n
...--o--*--o...o...o--T    <-- upstream/branch\n         \\      \\\n          A--B---M---C   <-- branch (HEAD), origin/branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这里,提交*是您开始的公共基础提交upstream。您在自己的分支上进行了一些提交,例如A通过。C您还至少进行了一次合并提交M。我假设您git push origin branch在不同的点运行,以便origin/branch在您自己的存储库中将您的分支的分支名称记录branch为指向您的提示提交C。(它是否存在并不重要,因为我们下面不使用它。)

\n\n

如果您现在运行 ,git rebase -i upstream/branch这将列出提交ABC,但不会列出 commit M,因为提交是复制(“pick”)。副本的目标将是 commit T,这是 的提示,您的 Git 从上的upstream/branch分支中记住它branchupstream分支中记住的提示。

\n\n

如果您忍受重做所有合并冲突,并将三个提交复制到三个新提交,您将得到:

\n\n
                        A\'-B\'-C\'   <-- branch (HEAD)\n                       /\n...--o--*--o...o...o--T    <-- upstream/branch\n         \\      \\\n          A--B---M---C   <-- origin/branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后,您可以将其推送(或强制推送)到origin,或者您现在可以(这次没有合并冲突)将序列折叠A\'-B\'-C\'为单个“压缩”提交S

\n\n
                        S   <-- branch (HEAD)\n                       /\n...--o--o--o...o...o--T    <-- upstream/branch\n         \\      \\\n          A--B---M---C   <-- origin/branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

origin并再次将其作为分支名称推送或强制推送到您的分支branch

\n\n

(请注意,*一旦基本提交不再是有趣的基本提交,我就停止对其进行标记。当我们考虑运行 时,它特别有趣,因为它是和永久重新加入的git rebase -i upstream/branch点。这决定了必须被标记的提交集复制以进行操作。)branchupstream/branchgit rebase

\n\n

但是,如果我们创建一个名为 的新分支for-pull-request,指向 commitT并检查它:

\n\n
...--o--o--o...*...o--T    <-- for-pull-request (HEAD), upstream/branch\n         \\      \\\n          A--B---M---C   <-- branch, origin/branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们跑git merge --squash branch。这会调用合并机制,使用当前提交TasHEAD和另一个提交 being C。我们将找到合并基础,它现在是我标记的提交*:它是第一个(或“最低”)共同祖先,而不是Arebase 使用的最后一个不相交祖先。也就是说,从提交C和提交开始T并向后工作,Git 会找到“最接近”两个分支提示的提交,即您上次合并的提交。

\n\n

这意味着您将看到的唯一冲突是您在C(上次合并之后M)所做的任何事情与他们此后所做的任何事情发生冲突*

\n\n

git merge --squash branch完成使用合并机制来合并提交T并将C*用作合并基础时,它会停止并让您git commit手动运行。当你这样做时,你会得到一个新的普通提交,我们可以将其称为SSquash:

\n\n
                        S   <-- for-pull-request (HEAD)\n                       /\n...--o--o--o...o...o--T    <-- upstream/branch\n         \\      \\\n          A--B---M---C   <-- branch, origin/branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在,除了它被命名为 之外for-pull-request,如果我们按照您所给出的说明进行操作,我们将得到与我们得到的图表相同的图表。git rebase -i upstream/branch此外,如果我们正确解决任何合并冲突,提交的来源与我们以其他方式获得的来源S相同\xe2\x80\x94,但我们首先要解决的合并冲突(如果有的话)会少得多。

\n\n

您现在可以将此名称(并提交S)推送到提供商(GitHub?)上的分支,并执行合并请求。如果他们接受它,您可以删除您的分支branch,然后创建一个branch指向S(或将其合并到)的新分支upstream,或者如果他们压缩合并,则它们的S\'分支基本上相同S,但具有不同的哈希ID和不同的提交者:无论如何你都必须git fetch upstream再次获得他们的提交)。

\n\n

branch您可以简单地强制分支名称指向最新的提交,而不是删除并重新创建。假设他们将你的挤压合并到他们的分叉中,这样他们就有了新的提交,S\'这是 的副本S,而你git fetch

\n\n
                        S   <-- for-pull-request (HEAD), origin/for-pull-request\n                       /\n...--o--o--o...o...o--T--S\'  <-- upstream/branch\n         \\      \\\n          A--B---M---C   <-- branch, origin/branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

您现在可以运行git branch -f branch upstream/branch,或者git checkout branch && git reset --hard upstream/branch,让您的 Git 具有以下功能:

\n\n
                        S   <-- for-pull-request, origin/for-pull-request\n                       /\n...--o--o--o...o...o--T--S\'  <-- branch, upstream/branch\n         \\      \\\n          A--B---M---C   <-- origin/branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

(这些分支中的哪一个是HEAD取决于您使用的命令序列)。

\n\n

一旦您git push --force origin branch将更新发送branch到提供商的分叉并删除for-pull-request(在您的存储库和分叉中),您将有效地放弃一系列origin/branch提交,从而:

\n\n
                        S   [abandoned]\n                       /\n...--o--o--o...o...o--T--S\'  <-- branch, origin/branch, upstream/branch\n         \\      \\\n          A--B---M---C   [abandoned]\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果我们停止绘制所有废弃的提交,一切看起来都干净整洁,这可能就是这一切的重点。:-)

\n