我真的试过看过类似的话题,但我似乎无法掌握这个概念。
我分了一个回购,做了一些改变。加班,我也曾多次使用git fetch upstream
和git merge upstream/<branch>
。有一段时间,我解决并重新测试了一些冲突。
现在,当涉及到推动上游更改时,我想进行一次提交。我给出的指示是使用git fetch upstream
和git rebase -i upstream/<branch>
。
我不明白的是,我再次坚持处理冲突解决方案。我不明白为什么我需要解决冲突,因为我的fork是最新的。我本可以备份我所有修改过的文件,再次核对我的fork,fork,恢复备份,我没有冲突可以解决并准备提交。这个过程看起来很机械,我不明白为什么我必须努力(再次解决冲突)。
有人可以帮我理解吗?
答案 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
并最终重复过程
这假定:
upstream
&#39; s)分叉在同一个网络服务提供商上。upstream
来引用上游存储库(它们的分支),并使用短名称origin
来引用您自己的存储分区。网络服务提供商。 Web提供商提供了提取请求的clicky按钮。理解所有这些有几个关键,其中大部分都与Git的提交图的工作方式有关。其余部分与Git分发存储库的(单个)魔术技巧有关,以及各种命令 - git merge
,git rebase
和git merge --squash
- 实际执行,git fetch
和git push
真正做了什么。
我真的不想写另一篇关于此的大文章(......太晚了:-)),所以让我们通过注意这些观点进行总结。它们的顺序不是很好,但我不确定是一个伟大的订单:有很多交叉引用的项目。
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 upstream
和git merge upstream/<branch>
。有一段时间,我解决并重新测试了一些冲突。
你有:
...--o--*--o...o...o--T <-- upstream/branch
\ \
A--B---M---C <-- branch (HEAD), origin/branch
此处,commit *
是您和upstream
启动的公共基本提交。您在自己的分支上进行了一些提交,例如A
到C
。您还至少进行了一次合并提交M
。我假设您在不同的点上运行git push origin branch
,因此您自己的存储库中的origin/branch
会将您的fork的分支名称branch
记录为指向您的提示提交C
git rebase -i upstream/branch
1}}。 (如果不这样做,这并不重要,因为我们下面没有使用它。)
如果您现在运行A
,则会列出提交B
,C
和M
,但不提交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
完成使用合并机制合并提交T
和C
并使用此*
作为合并基础时,它会停止并让您运行{{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
中的合并,或者如果他们压缩合并,他们的upstream
与S'
基本相同,但具有不同的哈希ID和不同的提交者:无论如何你&# 39; ll必须再次S
才能获得提交。
您可以简单地强制您的分支名称git fetch upstream
指向最新的提交,而不是删除并重新创建。让我们说他们将你的壁球压缩到他们的分叉中,这样他们就有了branch
的新提交S'
,而S
和git 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/branch
或git 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
如果我们停止绘制所有被遗弃的提交,那么一切看起来都干净整洁,这可能就是所有这一切的重点。 : - )