推送到远程失败,因为“当前分支的尖端位于其远程对应部分之后”

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 可以这样使用。


提示: 如果您要切换到mastergit pulling、切换回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 mastergit switch master,然后运行git pull 或类似的,并获得一个新的master 提交,给我们这个:

...--G--H--K   <-- master (HEAD), origin/master
         \
          I--J   <-- feature, origin/feature

我们还添加或更新了这些远程跟踪名称origin/masterorigin/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。在这种情况下,这是提交 IJ 的哈希 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 resetgit 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> 提交IJ,并让他们的名字feature 记住提交J'。他们说不!他们说如果我这样做,我会丢失一些提交。

这就是我们希望他们做的事情:丢失提交 IJ,用新的和改进的提交替换它们。为了让他们这样做,我们必须向他们发送命令。

如果我们使用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 失败

git提交大文件无法推送到远程库

推送到 Heroku 失败 - 没有这样的应用程序致命

APNS SSlStream 身份验证失败,因为远程方已关闭传输流

完全撤消提交并推送到远程[重复]

每当我尝试通过 git 推送到远程仓库时都会发生错误。我已经尝试过使用我的 github 和 gitlab 帐户