特性分支变基后 Git 推送被拒绝

1333

好的,我原以为这只是一个简单的git场景,我错了吗?

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

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

我可以毫无问题地使用git push origin master来保持远程master的更新,也可以在feature分支上使用git push origin feature来维护我的feature工作的远程备份。到目前为止,一切都很好。

但是现在,我想将feature重新基于master上的F--G提交,所以我执行了git checkout featuregit rebase master。还是很好的。现在我们有:

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

问题: 当我想备份已经用git rebase重置过的新的feature分支时,由于树形结构发生了变化,推送被拒绝。这只能通过使用git push --force origin feature来解决。

我不喜欢在不确定需要它的情况下使用--force。那么,我需要使用它吗?重置是否一定意味着下一个push必须使用--force选项?

这个特性分支并没有与其他开发人员共享,因此我没有使用--force选项的实际问题,我不会丢失任何数据,问题更多是概念上的。


4
“--force”不是一个怪物,而是一项功能。当需要时,您可以使用它。 - Handsome Nerd
15个回答

1006
问题在于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

在我看来,只要您是唯一在该分支上工作的人,就可以在特性分支上进行变基并强制推送回远程仓库。


105
老实说,将特性分支的原始版本拉取并合并到变基后的分支中,有点抵消了变基的整个概念。 - KL-7
35
也许我理解你的意思不太准确,但如果你拉取一个特性分支,将其变基到新的主分支上,就无法在不使用强制推送的情况下将其推回去,因为远程版本的特性分支无法快速合并到您的新(变基后的)特性分支版本。这正是OP在他的问题中描述的。如果在变基之后但在推送之前,您执行git pull feature-branch,这个pull将生成一个新的合并提交(通过合并特性分支的远程和本地版本)。因此,您要么在变基之后得到一个不必要的合并,要么使用--force选项进行推送。 - KL-7
6
啊,我明白了。你描述的方法和Mark Longair的答案是一样的。但它会生成一个合并提交。在某些情况下可能很有用,但我主要在自己的特性分支上使用变基(因此push --force不是问题),以保持提交历史线性且没有任何合并提交。 - KL-7
13
“force-push”的问题在于,你确实可以“丢失”(之前的提交)一些东西,这在任何版本控制系统中通常是绝对不应该发生的。因此,至少应该有一个“类似主分支”的分支设置为不接受强制推送,以限制潜在的损坏。可能导致损失的情况包括:情绪烦躁/被解雇的员工、自己的愚蠢、疲劳过度的决策等。 - Frank N
33
如@hardev所建议的--force-with-lease是一个很好的选择。 - augustorsouza
显示剩余6条评论

932

开发者应该使用 -n--no-clobber,而不是使用 -f--force

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

19
我认为已经被采纳的答案和这个答案都回答了问题。被采纳的答案解释了为什么需要强制,而这个答案则解释了为什么 --force-with-lease 可以解决使用 --force 时的问题。 - Jeff Appareti
1
这个答案的问题在于,--force-with-lease 有用的场景是在多人共享的分支上进行变基。而且,没有人应该首先在共享/协作分支上进行变基。变基的黄金法则:不要在公共/共享分支上使用变基。 - Brian Ogden
我认为这仍然是非常有用的。也许有人不知道别人在同一个分支中推送了代码。因此,使用--force-with-lease选项可以看到这一点,并有选择权来撤销他的变基操作,例如改为合并操作或应用其他解决方案。 - finrod
更好的方法是使用这两个标志的组合:--force-with-lease --force-if-includes。这比仅使用 --force-with-lease 更安全,例如当 IDE 进行后台获取时。 - Borek Bernard
1
值得一提的是:git fetch && git push --force-with-lease; == git push --force;!换句话说,在“lease”推送之前,本地状态不应该被更新! - Artfaith
当初回答这个问题时,我不认为存在这个选项,这就是为什么所选答案没有使用它的原因。 - Jyosua

72

我会使用"checkout -b"来代替,这样更容易理解。

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

当您删除时,会阻止将不同SHA ID的提交推送到已存在的分支中。在这种情况下,我只删除远程分支。


9
这个很有效,尤其是如果你的团队设置了一个 Git Hook,拒绝所有的 git push --force 命令。 - Ryan Thames
1
谢谢,它很有效。这是我阅读的更多详细信息,以便更好地理解。当您不想或无法进行强制推送时,这非常有用。删除远程分支变基 - RajKon
11
这与 push --force 的结果相同,因此只是绕过 Git 仓库阻止 --force 的一种方法。因此,我认为这从来不是一个好主意 - 要么仓库允许 push --force,要么由于某种正当原因禁用它。如果远程仓库禁用了 --force,那么 Nabi的回答更合适,因为它不会有可能丢失其他开发人员提交的代码或引起其他问题的风险。 - Logan Pickup
1
git push --force 不会关闭合并请求,而 git push origin --delete 会。 - Grzegorz Głowacki

22

解决这个问题的一种方法是采用像 msysGit 的 rebasing merge 脚本所做的方法 - 在变基之后,使用 -s oursfeature 的旧head合并。这样你就得到了这个提交历史:

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

...并且您对feature的推送将是快进式(fast-forward)。

换句话说,您可以执行:

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

(没有测试,但我认为是正确的...)


37
我认为使用 git rebase(而不是将 master 合并回您的功能分支)的最常见原因是制作干净的线性提交历史。使用您的方法,提交历史变得更糟。由于 rebase 创建新提交而没有任何参考其之前版本的信息,我甚至不确定此合并的结果是否合适。 - KL-7
6
@KL-7: merge -s ours的整个意图是人为地添加一个对先前版本的父级引用。当然,历史记录看起来不太干净,但是提问者似乎特别烦恼于必须强制推送“feature”分支,而这种方法可以规避这个问题。如果您想要变基,大体上只能选择其中之一。:) 总的来说,我认为msysgit项目这样做非常有趣... - Mark Longair
@KL-7:顺便说一下,我点赞了你的答案,显然是正确的 - 我只是觉得这也可能很有趣。 - Mark Longair
这绝对是有趣的,至少对我来说是这样。谢谢。我之前见过 ours 策略,但我认为它仅适用于冲突情况,通过在我们的分支中自动解决它们。结果它的工作方式不同。如果您需要重新基于版本(例如,供存储库维护者将其干净地应用到 master),但想避免强制推送(如果许多其他人因某种原因正在使用您的功能分支),那么以这种方式工作非常有用。 - KL-7
@KL-7 “我认为它仅适用于冲突情况,通过在我们的分支中进行更改来自动解决它们。” 我知道这是一个非常古老的帖子,但它需要一些澄清。您所描述的与“递归”(默认)“策略”的“ours”选项相匹配。 UI中“ours”一词的重载不幸。 (策略选项使用“-X”参数给出。) - boweeb

18

其他人已经回答过你的问题了。如果你执行rebase操作,你需要使用强制推送来推送该分支。

在共享存储库中,rebase和分支操作一般不兼容。这是因为它会重写历史记录。如果其他人正在使用该分支或者基于该分支创建分支,那么rebase操作将会非常麻烦。

通常来说,rebase操作适用于本地分支管理。对于远程分支管理最好使用显式合并(--no-ff)。

我们也避免将主分支(master)合并到功能分支(feature branch)中。相反,我们会对主分支进行rebase操作,但使用新的分支名称(例如添加版本后缀)。这样可以避免在共享存储库中执行rebase操作时出现的问题。


9
可以请您举个例子吗? - Thermech

17

我避免 force push 的方法是创建一个新分支,继续在新分支上工作,在保持一定稳定性后,删除那个被 rebase 过的旧分支:

  • 本地将当前分支进行 rebase
  • 从已经 rebased 过的分支创建一个新分支
  • 将该分支作为新分支推送到远程,并删除远程的旧分支

1
为什么这个选项没有受到关注?它绝对是最干净、最简单、最安全的选择。 - cdmo
2
因为我有大约200个系统跟踪分支名称,而且它必须是任务的特定名称,如果我每次推送都开始重命名分支,我会疯掉。 - Tamir Daniely
@TamirDaniely 我没有尝试过,但是在推送新分支之前删除旧分支(从远程)并使用相同的旧名称推送新分支是否解决了您的问题? - Nabi
3
@Nabi, “--force-with-lease” 的作用正是如此,但它也会验证是否有新的提交不属于您。 - Tamir Daniely

17

可能存在这个分支只有一个开发者的情况,现在(rebase后)与origin/feature不一致。

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

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

好的,新分支,这应该可以在不使用 --force 的情况下解决这个问题,我认为这通常是 Git 的一个重大缺点。


5
很抱歉地说:“不断生成分支”以避免强制覆盖现有分支并不能帮助“孤独的功能开发人员”(他们可以覆盖)或多人共同开发的功能分支(需要沟通该分支的“增量”并告知其他人移动),这更像是在版本控制系统中进行手动版本控制(如“thesis_00.doc,thesis_01.doc,...”)。 - Frank N
2
此外,当您在一个分支名称上打开了 GitHub PR 时,这并没有帮助到您,您必须为您推送的新分支名称创建一个新的 PR。 - gprasant
1
@frankee 从我的经验来看,这只是半真的。对于一个孤独的开发者来说,强制推送可能很容易,但这个习惯可能会在以后给你带来麻烦。如果一个新的开发人员加入了团队?或者一些没有使用--hard reset的CI系统?对于一个合作的团队来说,我认为沟通新分支名称已经足够简单了,这也可以很容易地脚本化。对于一个团队,我建议在本地进行变基,或者当分支准备好合并时进行变基,而不是在日常工作中进行变基,额外的提交比处理变基/合并冲突少得多。 - JAR.JAR.beans
@gprasant 关于 PR,我认为这样变基是不正确的,我实际上想看到单个提交与 PR 修复。变基(压缩)应该只在后面作为合并到主分支的一部分发生,并且当 PR 完成并准备就绪时(因此不需要打开新的 PR)。 - JAR.JAR.beans

10

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

A--B--C------F--G
       \         \
        D--E------H
编辑:啊,抱歉我没有读到你的问题陈述。你需要使用--force参数,因为你执行了一个rebase。所有修改历史记录的命令都需要这个参数,以防止你丢失工作(旧的DE将会丢失)。
所以,你执行了一个git rebase,使得树形结构看起来像这样(尽管部分隐藏了,因为DE不再是一个命名分支):
A--B--C------F--G
       \         \
        D--E      D'--E'

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


3
没问题,我知道那会起作用,但那不是我所需的。就像我说的,这个问题更多是概念性的而非实际应用性的。 - Yuval Adam

6

对我而言,遵循以下简单的步骤就可以:

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

6
以下是我使用的方法: git push -f origin branch_name 这不会删除我的任何代码。
但是,如果你想避免这种情况,可以采取以下措施:
git checkout master
git pull --rebase
git checkout -b new_branch_name

然后,您可以将所有提交内容挑选出来放到新分支中。

git cherry-pick COMMIT ID

然后推送您的新分支。


7
-f--force 的简称,这正是该问题尽可能要避免的。 - epochengine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接