删除合并的分支时出现“错误:分支X没有完全合并...”

17

我遇到了麻烦,无法从其他SO问题或阅读git文档中找到答案。非常感谢您在这里提供帮助。

在Github上合并我的分支后,我从UI(远程)中删除了我的分支。我定期修剪我的本地分支:

git checkout master
git pull origin master
git fetch -p
git branch -d X
Error: error: The branch X is not fully merged. If you are sure you want to delete it, run 'git branch -D X'

分支X已合并。 当我在master上执行git log时,我可以看到来自分支X的提交记录。这是为什么? 我想使用-d而不是-D,因为这可能会导致删除真正未合并的分支。


你可以使用 -f 强制本地删除。 - panza
2个回答

14

这里有几个棘手的问题。

git branch -d(不强制删除)的关键问题不是“分支是否已合并?”,因为这个问题根本无法回答。而是“该分支是否已经合并到了 <填写内容> 中?”

这个测试在 Git 1.7 版本中发生了改变(这意味着每个人今天都应该拥有它,但请检查您的 Git 版本),由 Junio C Hamano 在 提交记录 99c419c91554e9f60940228006b7d39d42704da7 中进行了更改:

"branch -d: 基于合并的分支来保证“已经合并”的安全性。当一个分支被标记为与另一个引用进行合并(例如,本地的'next'与远程'next'合并,并将其推回到远程,'branch.next.merge'设置为'refs/heads/next'),则基于当前分支来确保“branch -d”安全性没有太多意义。它的目的是不要丢失未合并到其他分支的提交,因此检查它是否与它合并的另一个分支合并更加明智。因此,有两个或三个问题(如果查看上面的提交,则有三个)需要您(或Git)回答,以确定是否允许使用-d:

  1. 我们打算删除的分支的上游名称是什么?(如果没有,请参见下文。)
  2. 分支名称指向一个特定的提交(我们称之为T)。上游分支名称也指向一个特定的提交(将此提交称为U)。
    T是U的祖先吗?
"
如果问题2的答案是肯定的(即T ≤ U),则允许删除。如果没有上游呢?这就是“在1.7中更改”的意义所在:最初的测试不是T ≤ U,而是T ≤ HEAD。该测试仍然存在。现在,如果没有上游,则使用HEAD测试而不是上游测试。同时,在“过渡期”内,当每个人都适应新的Git 1.7行为时,您还可以获得警告或额外的解释。即使在Git 2.10中(现在已经过去了近7年:这是一个非常长的过渡期!),此警告仍然存在。
if ((head_rev != reference_rev) &&
    in_merge_bases(rev, head_rev) != merged) {
        if (merged)
            warning("deleting branch ... not yet merged to HEAD.");
        else
            warning("not deleting branch ... even though it is merged to HEAD.");
}

(I trimmed some of the code for display purposes here): 基本上,如果HEAD解析到的提交与我们在T ≤测试中使用的提交不同,并且我们对T ≤ HEADT ≤ U得到了不同的结果,那么我们会添加额外的警告信息。(请注意,测试的第一部分是多余的:如果由于1.7之前的兼容性和缺少上游而将其与HEAD进行比较,则如果我们再次将其与HEAD进行比较,则会得到相同的结果。我们真正需要的只是in_merge_bases测试。)
如何知道上游是什么?实际上,有一种简单的命令行方式可以找出:
$ git rev-parse --abbrev-ref master@{u}
origin/master
$ git rev-parse --symbolic-full-name master@{u}
refs/remotes/origin/master

--abbrev-ref是Git的典型缩写版本,--symbolic-full-name提供完整引用名称。如果在没有上游设置的分支上运行,两者都会失败。当然,您也可以使用git branch -vv来查看所有分支的简写上游。

如何测试整个T ≤ U?对于shell脚本,git merge-base --is-ancestor命令可以实现此功能:

$ git merge-base --is-ancestor master origin/master && echo yes || echo no

如果masterorigin/master的祖先,它会告诉你(对于此目的,“指向相同提交”视为“是祖先”,即这是那个≤测试)。

无论何时这个过渡期最终结束,“使用HEAD”测试可能会完全消失,或者可能继续用于没有设置上游的分支。无论如何,问题始终是“要删除的分支是否已合并到____中?(填空)”,您必须查看填充的内容。

与您建议删除的分支上的提交对应的上游提交必须(或至少在其历史记录中)通过提交哈希ID相同的提交来进行“合并到”测试才能成功。如果feature/X通过所谓的“压缩合并”(虽然是通过合并完成的2)合并到origin/develop,则该提交ID将不匹配,并且“合并到”测试将始终失败。


1顺便提一下,从Git 2.3开始,您现在可以在git branch -d后添加--force,而不是必须使用git branch -D,尽管当然,-D仍然有效。

2这里的区别在于“合并”——作为名词的合并,意思是“一个提交是一个合并提交”(使用合并作为形容词),以及“合并”——作为动词的合并,表示组合某些更改集的操作。 git merge命令可以:

  • 进行快进,既不合并也不产生合并,因此根本没有合并;或者
  • 进行实际合并,将一些更改合并(动词)以生成一个合并(名词);或者
  • 进行“压缩合并”,即进行合并(动词)但生成非合并(由于某种无法解释的原因,您必须手动提交)。
“挤压合并”仅在您请求时发生,而“快进非合并”仅在它可以且您不禁止时发生。

1
这些都是有用的信息,但我希望它提供了一个指南,告诉我们如何解决(或至少改善)问题描述中的问题。 - Alex Johnson
1
@AlexJohnson:问题在于没有解决方案 - torek
好的。令人惊讶的是,在将功能分支合并到上游后,没有更好的工作流程来删除本地和远程的分支,这种情况非常普遍。 - Alex Johnson
3
@AlexJohnson:如果您使用真正的合并(包括GitHub的“合并”按钮在真正的合并模式下),本地的git branch -d将会起作用(在git fetch和更新其他本地分支之后)。如果您使用GitHub的“变基并合并”或“压缩并合并”,则只能强制删除。这不是很美观。 - torek
2
我在合并之前压缩了,所以这很有道理。我想我必须选择在合并时(压缩)还是在删除时(非强制删除)使其更美观。感谢您的解释。 - Alex Johnson

7
当提交的内容与您要删除的分支有足够大的差异时,就会发生这种情况。这可能是由于压缩提交或附加到以前基础的分支而导致的,然后由远程其他人合并。
如果您确信更改已被合并(正如您所说),那么branch -D是安全的。
最后,

灾难性的分支删除

不是真的。删除分支不是不可撤消的问题。所有操作都存储在git reflog中,并且分支指向的提交至少保留30天。如果真的很担心,可以将分支重命名。但实际上,只需删除分支即可更容易地完成。
换句话说,分支只是一个指针。删除指针不会删除内容。因此,如果需要,您可以轻松地恢复分支。

2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - torek

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