功能分支变基后 Git 推送被拒绝

Posted

技术标签:

【中文标题】功能分支变基后 Git 推送被拒绝【英文标题】:Git push rejected after feature branch rebase 【发布时间】:2012-02-14 22:24:51 【问题描述】:

好的,我以为这是一个简单的 git 场景,我错过了什么?

我有一个master 分支和一个feature 分支。我在master 上做了一些工作,在feature 上做了一些工作,然后在master 上做了更多工作。我最终得到了这样的结果(字典顺序意味着提交的顺序):

A--B--C------F--G  (master)
       \    
        D--E  (feature)

我对@9​​87654327@ 保持远程master 更新没有问题,也没有问题git push origin feature(在feature 上时)为我的feature 工作维护远程备份。到目前为止,我们都很好。

但现在我想在 master 上的 F--G 提交之上重新设置 feature,所以我要 git checkout featuregit rebase master。还好。现在我们有:

A--B--C------F--G  (master)
                 \
                  D'--E'  (feature)

问题:当我要备份与git push origin feature 分支的新重定位feature 时,推送被拒绝,因为树已因重定位而改变.这只能通过git push --force origin feature 解决。

我讨厌在不确定自己是否需要的情况下使用--force。那么,我需要它吗?变基必然是否暗示下一个push应该是--forceful?

此功能分支不与任何其他开发人员共享,所以我对强制推送没有问题事实上,我不会丢失任何数据,这个问题更具概念性。

【问题讨论】:

--force 不是怪物,而是功能。您可以在需要时使用它。 【参考方案1】:

问题在于git push 假设远程分支可以快速转发到您的本地分支,也就是说,本地分支和远程分支之间的所有区别都在于本地在末尾有一些新的提交,如下所示:

Z--X--R         <- origin/some-branch (can be fast-forwarded to Y commit)
       \        
        T--Y    <- some-branch

当您执行git rebase 提交时,D 和 E 将应用于新的基础并创建新的提交。这意味着在 rebase 之后你有类似的东西:

A--B--C------F--G--D'--E'   <- feature-branch
       \  
        D--E                <- origin/feature-branch

在这种情况下,远程分支无法快速转发到本地。虽然理论上本地分支可以合并到远程(显然在这种情况下您不需要它),但由于 git push 只执行快进合并,它会抛出错误。

--force 选项所做的只是忽略远程分支的状态并将其设置为您正在推送的提交。所以git push --force origin feature-branch 只是用本地feature-branch 覆盖origin/feature-branch

在我看来,在 master 上重新设置功能分支并将它们强制推送回远程存储库是可以的,只要您是唯一在该分支上工作的人。

【讨论】:

老实说,将特性分支的原始版本拉入并合并到 rebase 中有点消除了 rebase 的整个想法。 也许我没有正确理解你,但是如果你拉特性分支,将它重新定位到新的主分支上,你不能不强制将它推回去,因为特性分支的远程版本可以'不要快速转发到新的(重新定位的)功能分支版本。这正是 OP 在他的问题中所描述的。如果在 rebase 之后,但在 push 之前,你做了git pull feature-branch,这个 pull 将生成一个新的合并提交(通过合并功能分支的远程和本地版本)。所以要么你在变基后得到一个不必要的合并,要么你用--force推送。 啊,我想我明白了。您描述的方法与 Mark Longair 的回答相同。但它确实会生成一个合并提交。在某些情况下它可能有用,但我主要在我自己的功能分支中使用 rebase(因此push --force 不是问题)来保持提交历史线性,根本不需要任何合并提交。 “force-push”的问题在于,你确实可以“丢失东西”(之前的提交),这在任何版本控制系统中通常都不应该发生 ➪ 出于这个原因,至少有一个“master-ish”分支应该设置为not accept force-pushes,以限制潜在的损坏。 (说出以下任何一种:脾气暴躁/被解雇的员工、自己的白痴、疲惫和过度劳累的“决定”......)。 --force-with-lease as @hardev 建议是一个不错的选择【参考方案2】:

开发人员应该使用 -f--force 而不是使用

--force-with-lease

为什么?因为它检查远程分支是否有更改,这绝对是个好主意。让我们假设 James 和 Lisa 正在开发同一个功能分支,并且 Lisa 已经推送了一个提交。 James 现在重新设置了他的本地分支并在尝试推送时被拒绝。当然,James 认为这是由于 rebase 并使用 --force 并将重写所有 Lisa 的更改。如果 James 使用了--force-with-lease,他会收到警告说有其他人完成了提交。我不明白为什么有人会在 rebase 后推送时使用 --force 而不是 --force-with-lease

【讨论】:

很好的解释。 git push --force-with-lease 救了我一大堆。 这是一个有用的评论,但并不是问题的真正答案。 这就是答案,变基到 master/develop 会产生问题,这正是 --force-with-lease 存在的原因。 这应该是公认的答案。完全解决了所描述的问题 - 如果其他人同时提交,则强制推送而不强制。 我认为接受的答案和这个答案都解决了这个问题。接受的答案解释了为什么你需要强制。这解释了为什么--force-with-lease 解决了使用--force 的问题【参考方案3】:

我会改用“checkout -b”,这样更容易理解。

git checkout myFeature
git rebase master
git push origin --delete myFeature
git push origin myFeature

当您删除时,您会阻止推送包含不同 SHA ID 的现有分支。 在这种情况下,我只删除远程分支。

【讨论】:

这很好用,特别是如果您的团队有一个拒绝所有 git push --force 命令的 git 钩子。 感谢它运行良好。这是我阅读以更好地理解的更多详细信息。当您不想或不能进行强制推动时,这非常有用。 Deleting Remote Branches 和 Rebasing 这与push --force 具有相同的结果,因此只是绕过git repo 防止--force 的一种方法。因此,我认为这不是一个好主意——回购允许push --force,或者有充分的理由禁用它。如果在远程仓库中禁用了--force,Nabi 的答案更合适,因为它没有丢失其他开发人员提交或以其他方式导致问题的风险。【参考方案4】:

对此的一个解决方案是执行 msysGit 的 rebasing merge 脚本所做的事情 - 在 rebase 之后,将 feature 的旧头与 -s ours 合并。你最终得到了提交图:

A--B--C------F--G (master)
       \         \
        \         D'--E' (feature)
         \           /
          \       --
           \    /
            D--E (old-feature)

...您对feature 的推送将是一个快进。

换句话说,你可以这样做:

git checkout feature
git branch old-feature
git rebase master
git merge -s ours old-feature
git push origin feature

(未测试,但我认为是对的......)

【讨论】:

我相信使用git rebase(而不是将master 合并回您的功能分支)的最常见原因是制作干净的线性提交历史。随着你的方法提交历史变得更糟。由于变基会创建新的提交而没有任何参考他们以前的版本,我什至不确定这次合并的结果是否足够。 @KL-7:merge -s ours 的全部意义在于它人为地添加了对先前版本的父引用。当然,历史看起来并不干净,但发问者似乎特别为不得不强制推送feature 分支而烦恼,这解决了这个问题。如果你想变基,它或多或少是一个或另一个。 :) 更一般地说,我认为 msysgit 项目这样做很有趣.... @KL-7:顺便说一句,我 +1 了你的答案,这显然是正确的 - 我只是觉得这可能也很有趣。 这绝对是有趣的,至少对我来说。谢谢你。我以前见过ours 策略,但我认为它仅适用于冲突情况,通过使用我们分支中的更改自动解决它们。事实证明它的工作方式不同。如果你需要 rebase 版本(例如,repo 维护者将它干净地应用到master)但想要避免强制推送(如果许多其他 ppl 出于某种原因正在使用你的功能分支),那么以这种方式工作非常有用。 @KL-7 “我认为它仅适用于冲突情况,通过使用我们分支中的更改自动解决它们。”我知道这是一篇 非常 旧的帖子,但需要澄清一些。您所描述的将“我们的”选项与“递归”(默认)策略相匹配。 UI 中“我们的”这个词的重载是不幸的。 (策略选项使用 -X 参数给出。)【参考方案5】:

其他人已经回答了您的问题。如果你重新设置一个分支,你将需要强制推送该分支。

Rebase 和共享存储库通常无法相处。这是改写历史。如果其他人正在使用该分支或已从该分支分支,那么 rebase 将非常不愉快。

一般来说,rebase 适用于本地分支管理。远程分支管理最适合显式合并 (--no-ff)。

我们还避免将 master 合并到功能分支中。相反,我们变基为 master 但使用新的分支名称(例如添加版本后缀)。这避免了在共享存储库中变基的问题。

【讨论】:

你能添加一个例子吗?【参考方案6】:

这个分支上可能只有一个开发人员,也可能没有,即现在(在 rebase 之后)不与源/功能内联。

因此我建议使用以下顺序:

git rebase master
git checkout -b feature_branch_2
git push origin feature_branch_2

是的,新分支,这应该可以在没有 --force 的情况下解决这个问题,我认为这通常是 git 的主要缺点。

【讨论】:

很抱歉,但是:“继续生成分支”以避免强制覆盖现有分支并不能帮助“孤独的功能开发人员”(可以覆盖),也不能帮助多个人在功能分支上工作(需要传达那个分支“增量”并告诉移动,伙计们)。 — 它更像是版本控制系统中的手动版本控制(“thesis_00.doc, thesis_01.doc, ...”)... 另外,当您在一个分支名称上打开 github PR 时,这无济于事,您必须为您推送的新分支名称创建一个新 PR。 @frankee 根据我的经验,一半是真的。对于一个孤独的开发者来说,是的,强制推送很容易,但它是以后可能会咬你的习惯。 + 新开发者刚刚加入?或者可能是一些不使用 --hard reset 的 CI 系统?对于协作的团队,我认为传达新的分支名称很容易,这也可以很容易地编写脚本 + 对于团队,我建议在本地或分支准备合并时进行 rebase,而不是在日常工作中,额外的提交比处理变基/合并冲突更容易。 @gprasant 再次代表 PR,我认为重新设置基准是错误的,我实际上希望看到带有 PR 修复的单个提交。变基(壁球)应该只在以后作为合并到 master 的一部分发生,并且当 PR 全部完成并准备好(因此不需要打开新的 PR)。【参考方案7】:

我避免强制推送的方法是创建一个新分支并继续在该新分支上工作,并在稳定后删除重新定位的旧分支:

在本地重新设置签出分支 从重新设置的分支分支到新分支 将该分支作为新分支推送到远程。并删除远程上的旧分支

【讨论】:

为什么不喜欢这个选项?它绝对是最干净、最简单、最安全的。 因为我有大约 200 个系统跟踪分支名称,并且它必须是任务的特定名称,如果我开始每次推送都重命名分支,我会失去理智。 @TamirDaniely 我没有尝试过,但是在推送和推送具有相同旧名称的新分支之前删除旧分支(从远程)可以解决您的问题吗? @Nabi 这正是 --force-with-lease 所做的,除了它还验证没有不属于您的新提交。【参考方案8】:

feature 分支上的 git merge master 有什么问题?这将保留您的工作,同时将其与主线分支分开。

A--B--C------F--G
       \         \
        D--E------H

编辑:抱歉,没有阅读您的问题说明。当您执行rebase 时,您将需要力量。所有修改历史的命令都需要--force 参数。这是防止您丢失工作的故障保险(旧的DE 会丢失)。

所以你执行了一个git rebase,它使树看起来像(虽然部分隐藏,因为DE 不再在命名分支中):

A--B--C------F--G
       \         \
        D--E      D'--E'

因此,当您尝试推送新的feature 分支(其中包含D'E')时,您将丢失DE

【讨论】:

这没有什么问题,我知道它会起作用。这不是我需要的。就像我说的,这个问题是概念性的而不是实际的。【参考方案9】:

对我来说,遵循简单的步骤即可:

1. git checkout myFeature
2. git rebase master
3. git push --force-with-lease
4. git branch -f master HEAD
5. git checkout master
6. git pull

完成上述操作后,我们也可以通过以下命令删除 myFeature 分支:

git push origin --delete myFeature

【讨论】:

【参考方案10】:

以下对我有用:

git push -f origin branch_name

它不会删除我的任何代码。

但是,如果您想避免这种情况,您可以执行以下操作:

git checkout master
git pull --rebase
git checkout -b new_branch_name

然后您可以挑选所有提交到新分支。 git cherry-pick COMMIT ID 然后推送你的新分支。

【讨论】:

-f--force 的别名,如果可能的话,这是问题试图避免的。【参考方案11】:

由于 OP 确实了解问题,只是寻找更好的解决方案...

作为一种练习怎么样?

拥有实际的特性开发分支(你永远不会变基和强制推送,所以你的特性开发人员不会讨厌你)。在这里,通过合并定期从 main 中获取这些更改。 梅西耶的历史,是的,但生活很轻松,没有人会打扰他的工作。

有第二个功能开发分支,一个功能团队成员定期将所有功能提交推送到,实际上是重新定位,实际上是强制的。所以几乎完全基于最近的一次主提交。功能完成后,将该分支推送到 master 之上。

可能已经有此方法的模式名称。

【讨论】:

【参考方案12】:

在最新的 master 之上获取 master 和 rebase 功能分支的新更改

git checkout master
git pull
git checkout feature
git pull --rebase origin master
git push origin feature

【讨论】:

【参考方案13】:

我会这样做

rebase feature
git checkout -b feature2 origin/feature
git push -u origin feature2:feature2
Delete the old remote branch feature
git push -u origin feature:feature

现在遥控器将具有功能(基于最新主控)和功能2(旧主控头)。如果您在解决冲突时犯了错误,这将允许您稍后进行比较。

【讨论】:

以上是关于功能分支变基后 Git 推送被拒绝的主要内容,如果未能解决你的问题,请参考以下文章

变基后无法推送到分支

删除坏变基后引入的重复提交

Git从青铜到王者第四篇:Git的分支与合并

Git推送拒绝“非快进”

关于 git rebase 到选项的一些问题

git rebase——分支变基及变基的风险