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

时间:2017-11-11 07:56:37

标签: git squash git-squash

我真的试过看过类似的话题,但我似乎无法掌握这个概念。

我分了一个回购,做了一些改变。加班,我也曾多次使用git fetch upstreamgit merge upstream/<branch>。有一段时间,我解决并重新测试了一些冲突。

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

我不明白的是,我再次坚持处理冲突解决方案。我不明白为什么我需要解决冲突,因为我的fork是最新的。我本可以备份我所有修改过的文件,再次核对我的fork,fork,恢复备份,我没有冲突可以解决并准备提交。这个过程看起来很机械,我不明白为什么我必须努力(再次解决冲突)。

有人可以帮我理解吗?

1 个答案:

答案 0 :(得分:3)

最好的答案&#34;为什么你需要重新解决所有问题&#34;是不是&#34;你不是&#34;。但是这意味着放弃了你给出的指示(我不清楚谁给了你这些指示)。

作为ElpieKay commented,您可以使用git rerere自动执行重新以前重新有线重新解决方案。通过这种方式,你 重新解决所有问题,但让Git去做,而不是手工完成。

但是,如果你的意图是制作一个新的&#34;壁球合并&#34;在当前的上游提示顶部提交(即,不合并,只是一个普通的提交)来发出拉取请求,没有必要经历任何这一点。您只需执行以下命令序列(请注意以下假设):

git fetch upstream
git checkout -b for-pull-request upstream/branch
git merge --squash branch
git commit
git push origin -u for-pull-request

然后使用origin的网络服务上的clicky网页按钮发出拉取请求。

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

这假定:

  • 您的fork由两个存储库组成:您的存储库 - 您自己的计算机/笔记本电脑/其他存储库 - 以及GitHub等Web服务提供商的另一个存储库。
  • 他们的(upstream&#39; s)分叉在同一个网络服务提供商上。
  • 在您的计算机上,在您的存储库中,您使用短名称upstream来引用上游存储库(它们的分支),并使用短名称origin来引用您自己的存储分区。网络服务提供商。 Web提供商提供了提取请求的clicky按钮。
  • 也许最重要的是,这假设你愿意放弃那个本地分支,转而支持上游接受的拉取请求,如果发生这种情况。

理解所有这些有几个关键,其中大部分都与Git的提交图的工作方式有关。其余部分与Git分发存储库的(单个)魔术技巧有关,以及各种命令 - git mergegit rebasegit merge --squash - 实际执行git fetchgit push真正做了什么。

我真的不想写另一篇关于此的大文章(......太晚了:-)),所以让我们通过注意这些观点进行总结。它们的顺序不是很好,但我不确定一个伟大的订单:有很多交叉引用的项目。

  • Git通常会添加新的提交。添加新提交会导致当前分支名称(存储在HEAD中)指向新提交,而新提交则指向HEAD提交的任何内容。
  • 但是git rebase通过现有提交复制到新提交,然后放弃原始提交。它使用&#34;分离的HEAD&#34;进行复制,其中HEAD直接指向提交,而不是使用分支名称。 (&#34;添加新提交&#34;仍然照常工作,除了它只是直接更新HEAD,而不是名称不再存储在HEAD中的分支。)复制的提交不包括任何合并提交。在某种程度上,这意味着您所做的任何冲突解决方案都会大量丢失(除非您启用了git rerere)。 (它实际上因各种原因丢失了,我还没弄清楚如何解释好。)
  • 每个副本的制作方式与git cherry-pick相似,有时通过字面运行git cherry-pick
  • 每次挑选都会导致合并冲突,因为挑选提交运行合并机制,合并为动词,因为我喜欢称之为。

    此操作的合并基础是被挑选的提交的父级,即使这不是一个非常合理的合并基础。 --ours提交一如既往地是当前或HEAD提交,另一个提交 - --theirs提交 - 是被复制/挑选的提交。在成功的樱桃选择结束时,新提交是作为普通的非合并提交。

  • 相比之下,运行git merge有点复杂,除了有各种不同的情况。

    有时Git认为毕竟不需要合并,可以进行快进操作:更改当前分支指向的提交,以便分支点到目标提交。

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

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

  • 正在运行git merge --squash是另一种特殊情况。与实际合并一样,它使用常规合并机制来计算所涉及的两个分支提示的 true 合并基础。

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

    然而,最终的提交可以回到樱桃挑选的想法:最终的提交是合并提交。它只是一个普通的提交。 是由merge-as-a-verb计算的,但是commit是常规提交,而不是merge-as-a-noun。 (没有特别好的理由,--squash标志也会打开--no-commit标志,迫使你自己运行git commit。可能有一个原因 - 可能它最方便谁写了最初的--squash代码才能提前退出 - 但今天没有理由这样做。)

我们将这些事实添加到这里:

  • 您的fork最初是某个其他存储库的克隆。所以它开始时upstream具有相同的提交(相同的历史记录)。从那以后,你和他们有点分歧,但同时你通过git push来自你自己的本地存储库的提交更新你的fork。

  • 当您git fetch upstream时,您从他们的分叉中选择他们的提交,将它们放入您自己的本地存储库中。这些跟踪名称为upstream/master,依此类推。

    当你git fetch origin(如果你需要)时,你从你的分叉中获取提交,将它们放入你自己的本地存储库中。这些跟踪名称为origin/master,依此类推。

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

    这意味着您的本地存储库 - 您自己计算机上的存储库 - 具有完整联盟&#34;他们的分叉&#34;,&#34;您的分叉&#34;和& #34;你的提交&#34;。您可以随时将自己的提交推回到自己的分支。

  • 您可以轻松制作&#34;拉取请求&#34;使用你的分叉,因为你的提供者(例如,GitHub)会记住你的分叉&#34;你的分叉&#34;和&#34;他们的叉子&#34;。

因此,让我们绘制您在存储库中提供的 - 提交图的简化版本,结果如​​下:

  

我分了一个回购,做了一些改变。加班,我也曾多次使用git fetch upstreamgit merge upstream/<branch>。有一段时间,我解决并重新测试了一些冲突。

你有:

...--o--*--o...o...o--T    <-- upstream/branch
         \      \
          A--B---M---C   <-- branch (HEAD), origin/branch

此处,commit *是您和upstream启动的公共基本提交。您在自己的分支上进行了一些提交,例如AC。您还至少进行了一次合并提交M。我假设您在不同的点上运行git push origin branch,因此您自己的存储库中的origin/branch会将您的fork的分支名称branch记录为指向您的提示提交C git rebase -i upstream/branch 1}}。 (如果不这样做,这并不重要,因为我们下面没有使用它。)

如果您现在运行A,则会列出提交BCM,但提交T,作为复制提交(&#34; pick&#34;)。副本的目标是提交upstream/branch,这是branch的提示,您的Git会从upstream上的分支 A'-B'-C' <-- branch (HEAD) / ...--o--*--o...o...o--T <-- upstream/branch \ \ A--B---M---C <-- origin/branch 记住这些提示。

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

origin

然后您可以将此推送(或强制推送)到A'-B'-C',或者您现在可以(此时没有合并冲突)将S序列折叠为单个&#34;压扁&# 34;提交 S <-- branch (HEAD) / ...--o--o--o...o...o--T <-- upstream/branch \ \ A--B---M---C <-- origin/branch

origin

再次将其推送或强制推送到您的前叉branch作为分支名称*

(注意,一旦它不再是一个有趣的基础提交,我就停止标记基础提交git rebase -i upstream/branch。当我们考虑运行branch时,它特别有趣,因为它是{ {1}}和upstream/branch永久重新加入。这决定了必须为git rebase操作复制的提交集。)

但是,如果我们创建一个名为for-pull-request的新分支,指向提交T并检查出来,那该怎么办呢?

...--o--o--o...*...o--T    <-- for-pull-request (HEAD), upstream/branch
         \      \
          A--B---M---C   <-- branch, origin/branch

现在我们运行git merge --squash branch。这将调用合并机制,使用当前提交T作为HEAD,另一个提交为C。我们将找到合并基础,现在是我标记为*的提交:它是第一个(或&#34;最低&#34;) common 祖先,而不是rebase使用的最后一个不相交的祖先A。也就是说,从提交C和提交T开始并向后工作,Git发现提交&#34;最接近&#34;两个分支提示,这是您上次合并的提交。

这意味着您将看到的唯一冲突是您在C(最后一次合并M之后)所做的任何事情与*以来所做的任何事情相冲突。

git merge --squash branch完成使用合并机制合并提交TC并使用此*作为合并基础时,它会停止并让您运行{{1手动。当你这样做时,你得到一个新的普通提交,我们可以为Squash调用git commit

S

现在,除了这个名为 S <-- for-pull-request (HEAD) / ...--o--o--o...o...o--T <-- upstream/branch \ \ A--B---M---C <-- branch, origin/branch 的事实外,如果我们执行了for-pull-request,这就是相同的图表你被给予了那些指示。此外,如果我们正确解决任何合并冲突,提交git rebase -i upstream/branch具有相同的我们会得到另一种方式 - 但我们会有更少的,如果有的话,合并冲突首先解决。

您现在可以将此名称(并提交S)推送到您的提供程序(GitHub?)上的fork中,然后执行合并请求。如果他们接受了,您可以删除您的分支S,然后创建一个指向branch的新分支branch(或S中的合并,或者如果他们压缩合并,他们的upstreamS'基本相同,但具有不同的哈希ID和不同的提交者:无论如何你&# 39; ll必须再次S才能获得提交。

您可以简单地强制您的分支名称git fetch upstream指向最新的提交,而不是删除并重新创建。让我们说他们将你的壁球压缩到他们的分叉中,这样他们就有了branch的新提交S',而Sgit fetch:{ / p>

                        S   <-- for-pull-request (HEAD), origin/for-pull-request
                       /
...--o--o--o...o...o--T--S'  <-- upstream/branch
         \      \
          A--B---M---C   <-- branch, origin/branch

您现在可以运行git branch -f branch upstream/branchgit checkout branch && git reset --hard upstream/branch,让您的Git拥有此功能:

                        S   <-- for-pull-request, origin/for-pull-request
                       /
...--o--o--o...o...o--T--S'  <-- branch, upstream/branch
         \      \
          A--B---M---C   <-- origin/branch

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

一旦git push --force origin branch将更新的branch发送到您的提供商的分支并删除for-pull-request(在您的仓库和分支中),您将有效地放弃{{1}提交系列,给出:

origin/branch

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