git:为啥我不能在壁球合并后删除我的分支?

Posted

技术标签:

【中文标题】git:为啥我不能在壁球合并后删除我的分支?【英文标题】:git: why can't I delete my branch after a squash merge?git:为什么我不能在壁球合并后删除我的分支? 【发布时间】:2017-06-16 05:37:10 【问题描述】:

我有一个带有mainline(相当于master)和一些本地特性分支的git repo。例如:

$ git branch
* mainline
  feature1
  feature2
  feature3

当我执行以下操作时,我可以将我在一个功能分支中的所有编辑合并到一个对mainline 的提交中:

$ git checkout mainline
$ git pull
$ git checkout feature1
$ git rebase mainline
$ git checkout mainline
$ git merge --squash feature1
$ git commit
$ git push

我的问题是,此时,当我尝试删除 feature1 分支时,它告诉我它没有完全合并:

$ git branch -d feature1
error: The branch 'feature1' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature1'.

是什么导致了这个错误?我以为git merge --squash feature1 合并了feature1mainline

【问题讨论】:

【参考方案1】:

发生这种情况是因为 Git 不知道 squash 合并“等同于”各种特定于分支的提交。您必须强制删除分支,使用git branch -D 而不是git branch -d

(剩下的只是关于为什么会这样。)

绘制提交图

让我们绘制(部分)提交图(此步骤适用于 Git 中的许多事情......)。实际上,让我们再退一步,以便我们在您的git rebase 之前开始,如下所示:

...--o--o--o     <-- mainline
      \
       A--B--C   <-- feature1

分支名称,如mainlinefeature1,只指向一个特定的提交。该提交向后(向左)指向先前的提交,依此类推,正是这些向后的指针形成了实际的分支。

最上面一行的提交,在这里都称为o,有点无聊,所以我们没有给它们命名。提交的最后一行 A-B-C在分支 feature1 上。 C 是最新的此类提交;它返回到B,然后返回到A,然后返回到无聊的o 提交之一。 (顺便说一句:最左边的 o 提交以及 ... 部分中的所有早期提交都在 both 分支上。)

当您运行git rebase 时,三个A-B-C 提交被复制 提交附加到mainline 的提示,给我们:

...--o--o--o            <-- mainline
      \     \
       \     A'-B'-C'   <-- feature1
        \
         A--B--C       [old feature1, now abandoned]

新的A'-B'-C' 提交与原来的三个基本相同,但它们在图中移动。 (请注意,所有三个无聊的o 提交现在都在两个分支上。)放弃原来的三个意味着Git 通常没有 将副本与原件进行比较。 (如果原件可以通过其他名称访问——例如,附加到旧 feature1 的分支——Git 可以弄清楚这一点,至少在大多数情况下。关于如何Git 认为这一点在这里并不是特别重要。)

不管怎样,现在你继续运行git checkout mainline; git merge --squash feature1。这会生成一个新的提交,它是feature1 上的三个或多个提交的“压缩副本”。我将停止绘制旧的废弃的,并为 Squash 调用新的 squash-commit S

...--o--o--o--S         <-- mainline
            \
             A'-B'-C'   <-- feature1

“删除安全”完全由提交历史决定

当你要求 Git 删除 feature1 时,它会执行安全检查:“feature1 是否合并到 mainline 中?”这种“合并到”测试完全基于图连接。名称mainline 指向提交S;提交S 指向第一个无聊的o 提交,这导致更无聊的o 提交。提交C'feature1 的提示,无法S 到达:我们不能向右移动,只能向左移动。

将此与“正常”合并 M 进行对比:

...--o--o--o---------M   <-- mainline
            \       /
             A'-B'-C'    <-- feature1

使用相同的测试——“feature1 的提示提交是否可以从mainline 的提示提交中到达?”——答案现在是,因为提交 M 有一个下降-and-left 提交C' 的链接。 (提交 C' 是,在 Git 内部用语中,合并提交 M第二个父节点。)

由于 squash 合并实际上并不是 merges,因此没有从 S 回到 C' 的连接。

同样,Git 甚至没有尝试查看S 是否与A'B'C'“相同”。但是,如果确实如此,它会说“不一样”,因为S 仅与所有三个提交的 sum 相同。让S 匹配压缩提交的唯一方法是只有一个这样的提交(在这种情况下,首先不需要压缩)。

【讨论】:

那么没有比手动跟踪合并的分支更好的解决方案了吗? @Alec:使用 squash 合并,通常正确的做法是删除您刚刚合并的分支,丢失您刚刚换取的所有提交壁球提交。换句话说,如果我们已经放弃了A-B-C 以支持A'-B'-C',并且我们现在使用S 支持A'-B'-C',我们也应该放弃A'-B'-C'——并且名称feature1 可以和他们一起进垃圾箱,实际上,折腾功能分支名称是如何他们都消失了。如果我们在一个大的一次性流程中完成这一切,我们就可以了。如果我们是零敲碎打,那么是的,您必须仔细跟踪所有内容。 @torek 这感觉像是一个繁琐的工作流程,尤其是在使用 GitHub 时。您将 PR 与 squash 提交合并,但是您需要记住删除分支。如果你忘记了,git 无法告诉你哪些分支可以安全删除。 @Alec:我同意;但我认为壁球合并应该永远被使用,除非是首先编写代码的人,因此可以控制分支。 @thanos.a:这个流程很有意义。但总的来说,我不直接使用 squash:如果我有一些我认为有良好结果的提交,但个别提交需要清理,我使用交互式 rebase(或手动等效)来进行清理。这给我留下了 N 次(通常是 N > 1)次提交,每一次都是很好的独立提交,其中所有 N 次都实现了该功能,现在可以合并。【参考方案2】:

您实际上可以确定和删除 squash-merged 分支,尽管这并不简单。

git for-each-ref refs/heads/ "--format=%(refname:short)" | while read branch; do mergeBase=$(git merge-base master $branch) && [[ $(git cherry master $(git commit-tree $(git rev-parse $branch\^tree) -p $mergeBase -m _)) == "-"* ]] && git branch -D $branch; done

为了确定一个分支是否被压缩合并,git-delete-squashed 创建一个带有git commit-tree 的临时悬空压缩提交。然后它使用git cherry 来检查压缩的提交是否已经应用于master。如果是,则删除分支。

所有功劳归于 Teddy Katz,https://github.com/not-an-aardvark/git-delete-squashed

【讨论】:

非常有趣,但我有点害怕使用这样的自动程序;另外,我压缩提交的主要原因只是因为我想摆脱主题分支上的中间提交,那么为什么我不应该在合并后简单地删除分支呢?

以上是关于git:为啥我不能在壁球合并后删除我的分支?的主要内容,如果未能解决你的问题,请参考以下文章

git合并壁球和反复发生的冲突

Git Rebase 从带有 Squashed 合并的分支

为啥 git 会将分支合并到自身中?

为啥 Git 说我的主分支“已经是最新的”,即使它不是?

删除合并的分支给出“错误:分支 X 未完全合并......”

为啥合并后GIT会说“已经是最新的”,但分支之间的差异仍然存在?