推送到远程失败,因为“当前分支的尖端位于其远程对应部分之后”
Posted
技术标签:
【中文标题】推送到远程失败,因为“当前分支的尖端位于其远程对应部分之后”【英文标题】:Pushing to remote fails because "tip of your current branch is behind its remote counterpart" 【发布时间】:2020-10-18 23:33:45 【问题描述】:$ git config pull.ff only
$ git pull
Already up to date
$ git checkout EditReadMe1
Switched to branch 'EditReadMe2'
$ git rebase master
Current branch EditReadMe2 is up to date
$ git push myremote EditReadMe2
To https://github.com/[redacted repo]-playground
! [rejected] EditReadMe2 -> EditReadMe2 (non-fast-forward)
error: failed to push some refs to 'https://github.com/[redacted repo]-playground'
hint: Updates were rejected because the tip of your current branch is behind
hint: it's remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details
我的朋友正试图帮助我学习解决拉取请求中的冲突。他创建了一个存储库。我克隆了它,并创建了一个名为“EditReadMe2”的分支。我将“EditReadMe2”推送到存储库,他创建了一个冲突。
我最初运行了以下命令
git checkout master
git pull
git checkout EditReadMe2
git rebase master
它警告我解决了一个冲突,但是当我尝试推送 EditReadMe2 时,它给了我错误。
我再次输入命令以在所附图像中显示我的终端,因为我不知道当我第二次拉动和变基时分支如何可能落后,它告诉我一切都取决于日期,但它仍然失败。
强制推送解决了这个问题,但我想知道如何在不使用--force
的情况下做到这一点。
【问题讨论】:
【参考方案1】:命令序列不完整。
在git checkout EditReadMe2
之后,您需要执行另一个git pull
。
git pull
更新当前分支的工作索引,而不是您拥有的所有 本地 分支。
当您发出 rebase 命令时,您将更新的 master
重新定位到您的“旧”EditReadMe2
分支中。
无论如何git rebase
可以这样使用。
提示:
如果您要切换到master
、git pull
ing、切换回EditReadMe2
仅出于变基的目的,您可以使用以下顺序并保存几个命令:
假设你在EditReadMe2
git pull
git rebase origin/master
【讨论】:
【参考方案2】:变基通常会产生这种结果——这需要使用--force
——因为变基用新的和改进的1 提交替换一些现有的提交。要真正理解其工作原理,您需要了解 Git 如何使用和查找提交,以及 git push
和其他命令如何工作。这有点棘手!首先,看看my long answer 到How to delete all unpushed commits without deleting local changes,了解一下绘图是什么样的:
...--G--H <-- master
\
I <-- feature (HEAD)
可能意味着。特别是,您需要记住这些字母如何代表原始哈希 ID,每个提交如何向后指向其父提交,以及分支名称如何指向 / 包含的 latest 提交那个分支。
1至少,我们希望他们有所改进。 ?
设置
现在让我们假设我们有一系列本身没有缺陷的提交——我们实际上并不需要修复它们中的任何东西——但它们是在较早之前进行的,如下所示:
...--G--H <-- master
\
I--J <-- feature
(没有附加HEAD
表示我们不关心在此之前签出的是哪一个)。我们运行git checkout master
或git switch
master,然后运行git pull
或类似的,并获得一个新的master
提交,给我们这个:
...--G--H--K <-- master (HEAD), origin/master
\
I--J <-- feature, origin/feature
我们还添加或更新了这些远程跟踪名称,origin/master
和 origin/feature
。它们是我们的 Git 对一些 other Git 的 branch 名称的记忆。我们的名称origin/master
标识提交K
,我们自己的分支名称master
现在也是;我们的名字origin/feature
告诉我们,在origin
上,他们有我们的分支名称feature
的副本,它也标识了提交J
,就像我们的feature
一样。 (也许他们在早些时候我们运行 git push origin feature
时得到了它。)
下一部分很重要:提交哈希 ID(这些大写字母所代表的大而难看的字母和数字字符串)在两个存储库中相同。 分支名称不必是,尽管在这种特殊情况下,它们现在也是。
Rebase 通过复制提交来工作
在这个设置中,我们认为我们的功能的缺陷是它基于提交H
,而最新的提交现在是提交K
。我们希望我们的feature
分支基于提交K
。为此,我们运行:
git switch feature # or git checkout feature
给我们:
...--G--H--K <-- master, origin/master
\
I--J <-- feature (HEAD), origin/feature
接着是:
git rebase master
rebase 命令列出了那些在分支feature
但不在 在master
上的提交的原始哈希 ID。在这种情况下,这是提交 I
和 J
的哈希 ID。 (请注意,H
和更早的提交都在 both 分支上。)然后,Git 使用其特殊的“分离 HEAD”模式开始使用提交 K
,在 master
的尖端:
...--G--H--K <-- HEAD, master, origin/master
\
I--J <-- feature, origin/feature
Git 应用我们在提交 I
中所做的,并从中进行新的提交。这个新的提交有一个新的、不同的哈希 ID,但是重新使用了来自I
的作者姓名和日期和时间戳,并重新使用了来自I
的提交消息,所以提交看起来很糟糕很像提交I
。换句话说,它是提交I
的副本。2我们将这个新副本称为I'
:
I' <-- HEAD
/
...--G--H--K <-- master, origin/master
\
I--J <-- feature, origin/feature
在成功将I
复制到I'
之后,Git 现在以同样的方式复制J
,结果是:
I'-J' <-- HEAD
/
...--G--H--K <-- master, origin/master
\
I--J <-- feature, origin/feature
复制过程现已完成——没有更多要复制的提交——所以 rebase 执行它的最后一步,即将名称 feature
从它曾经命名的提交中拉出并使其指向最后复制的提交,在本例中为 J'
:
I'-J' <-- feature (HEAD)
/
...--G--H--K <-- master, origin/master
\
I--J <-- origin/feature
如图所示,在最后一步中,Git 重新附加 HEAD
,以便我们回到正常模式,使用附加的 HEAD
,在分支上。
请注意,这里的两个原始提交不再使用名称 feature
找到。如果我们没有名称 origin/feature
记住 other Git 的 feature
,我们将完全放弃这两个提交。但是我们的 Git 记得 他们的 Git 记得使用 他们的 名称 feature
提交 J
。
无论哪种情况,请注意我们所做的。 我们抛弃或至少尝试抛弃旧提交,转而支持这些新的和改进的提交。我们仍然可以通过我们的 origin/feature
名称访问旧提交,因为我们是记住 origin
上的 Git 正在通过 its 分支名称 feature
记住提交 J
。
2如果您愿意,您可以使用git cherry-pick
自己复制任何提交。 rebase
所做的是分离你的 HEAD,然后执行一组自动挑选樱桃,然后是这个分支名称动作,类似于 git reset
或 git branch -f
。在旧版本的 Git 中,rebase 可以默认为不真正运行 git cherry-pick
的替代策略,但这些细节通常无关紧要。
git push
的工作原理
git push
命令通过让您的 Git 调用其他 Git 来工作。另一个 Git 也有提交和分支名称。他们的分支名称不必与您的分支名称匹配,但如果不匹配,事情就会变得非常混乱,因此大多数人在这里将他们的分支名称设为相同。
他们的 Git 为您的 Git 列出了他们的 分支名称和提交哈希 ID。3 这可以让您的 Git 确定您有哪些提交而他们没有,他们需要。然后,您的 Git 通过它们的哈希 ID 将这些提交发送到他们的 Git。除了这些提交,您的 Git 还会发送 Git 需要的任何其他内部对象。
发送正确的对象后,您的 Git 现在会发送一个或多个礼貌的请求或命令。礼貌请求的形式如下:如果可以,请将您的名字 ______(填写分支或标签名称)设置为 ______(填写哈希 ID)。 命令有两种形式之一: 我认为你的名字 ______(填写分支或标签名称)设置为 ______(填写哈希 ID)。如果是这样,请将其设置为 ______! 或者:将您的姓名 ______ 设置为 ______!
礼貌的请求表单将要求他们将feature
设置为提交J'
,我们将J
的副本用作@ 的新改进版本987654394@。 他们,然而,不知道这是一个新的和改进的副本——他们只能说我们是在要求他们扔掉 em> 提交I
和J
,并让他们的名字feature
记住提交J'
。他们说不!他们说如果我这样做,我会丢失一些提交。
这就是我们希望他们做的事情:丢失提交 I
和 J
,用新的和改进的提交替换它们。为了让他们这样做,我们必须向他们发送命令。
如果我们使用git push --force-with-lease
,我们将向他们发送条件命令:我认为您的feature
标识提交J
;如果是这样,让它识别J'
。如果他们接受这个命令并执行,我们和他们将提交I'-J'
,我们现在可以像这样绘制我们的存储库:
I'-J' <-- feature (HEAD), origin/feature
/
...--G--H--K <-- master, origin/master
\
I--J [abandoned]
--force-with-lease
选项通常是执行此操作的正确方法如果完全允许这样做。 这样做会强制使用 @987654408 的其他任何人@分支,在另一个 Git 存储库中,使用新的和改进的提交来更新 他们的 分支。一般来说,让每个人都同意feature
可能会以这种方式重新定位是一个好主意,然后再开始以这种方式重新定位它。你需要做的是弄清楚谁是“每个人”。如果那只是你自己,你只需要同意你自己。如果是你和六位同事,请先征得同事的同意。
使用git push --force
,而不是--force-with-lease
,省略了安全检查:它只是将命令set your feature
发送到另一个Git,而没有任何条件“我认为”部分。如果您的 Git 与他们的 Git 是最新的,那么您的 origin/feature
和他们的 feature
都可以识别提交 J
,这是可以的。但是,如果就在您完成工作并即将推送之后,其他人在 origin
上的 Git 中向 feature
添加了一个新的提交 L
怎么办?你的 force-push 会告诉 Git 也放弃 that 提交。
force-with-lease 选项更好,因为您的 Git 会告诉另一个 Git,您相信他们的 feature
标识提交 J
,而不是提交 L
。他们会说:哎呀,不,我现在是L
而你的git push --force-with-lease
会失败。你现在可以git fetch
,看到有一个新的提交L
,并修复你的rebase以复制提交L
,然后再次尝试你的git push --force-with-lease
,因为你的origin/feature
说提交L
。
3这里的确切机制是为 Git 智能协议 v2 重写的,该协议最初是在 Git 2.26 中默认开启的。我不会详细介绍,但在早期的 v2 协议中有一个小但令人讨厌的小错误,您的 Git 有时会推送太多对象。此错误已在 Git 2.27 中修复。如果您有 2.26 并且推送时间过长,您可以使用 git -c protocol.version=0 push ...
解决它,或者只是升级。
【讨论】:
以上是关于推送到远程失败,因为“当前分支的尖端位于其远程对应部分之后”的主要内容,如果未能解决你的问题,请参考以下文章
推送到 GitHub - 推送到...(回购)过早的 EOF 失败