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
合并了feature1
到mainline
。
【问题讨论】:
【参考方案1】:发生这种情况是因为 Git 不知道 squash 合并“等同于”各种特定于分支的提交。您必须强制删除分支,使用git branch -D
而不是git branch -d
。
(剩下的只是关于为什么会这样。)
绘制提交图
让我们绘制(部分)提交图(此步骤适用于 Git 中的许多事情......)。实际上,让我们再退一步,以便我们在您的git rebase
之前开始,如下所示:
...--o--o--o <-- mainline
\
A--B--C <-- feature1
分支名称,如mainline
或feature1
,只指向一个特定的提交。该提交向后(向左)指向先前的提交,依此类推,正是这些向后的指针形成了实际的分支。
最上面一行的提交,在这里都称为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:为啥我不能在壁球合并后删除我的分支?的主要内容,如果未能解决你的问题,请参考以下文章