git: 为什么我不能在 squash 合并后删除我的分支?

git: why can't I delete my branch after a squash merge?

我有一个 git 存储库,其中包含 mainline(相当于 master)和一些本地功能分支。例如:

$ 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.

发生这种情况是因为 Git 不知道压缩合并是 "equivalent to" 各种 branch-specific 提交。你必须强行删除b运行ch,用git branch -D代替git branch -d.

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

绘制提交图

让我们绘制(部分)提交图(此步骤适用于 Git... 中的许多内容)。事实上,让我们再退一步,这样我们就可以在您的 git rebase 之前开始,像这样:

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

A b运行ch name,与 mainlinefeature1 一样,仅指向一个特定的提交。该提交向后(向左)指向前一个提交,依此类推,正是这些向后的指针构成了实际的 b运行ches.

顶行的提交,在这里都称为 o,有点无聊,所以我们没有给它们 letter-names。提交的底行 A-B-C 仅在 b运行ch feature1C 是最新的此类提交;它返回到 B,返回到 A,这又返回到一个无聊的 o 提交。 (顺便说一句:最左边的 o 提交以及 ... 部分中的所有早期提交都在 both b运行ches 上。)

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

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

新的 A'-B'-C' 提交与原来的三个提交大部分相同,但它们在图中 移动了 。 (请注意,所有三个无聊的 o 提交现在都在两个 b运行 上。)放弃原来的三个意味着 Git 通常 没有 将副本与原件进行比较。 (如果原件可以通过其他名称访问——例如,附加到旧 feature1 的 b运行ch——Git can 图至少在大多数情况下是这样。Git 如何计算的精确细节在这里并不特别重要。)

无论如何,现在你继续 运行 git checkout mainline; git merge --squash feature1。这使得一个新提交成为 feature1 上的三个(或无论如何)提交中的 "squash copy" 个。我将停止绘制旧的废弃的,并调用新的 squash-commit S for Squash:

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

"Delete safety" 完全由提交历史决定

当您要求 Git 删除 feature1 时,它会执行安全检查:"is feature1 merged into mainline?" 此 "is merged into" 测试完全基于 图连通性。名称 mainline 指向提交 S; commit S 指向第一个无聊的 o 提交,这会导致更无聊的 o 提交。提交 C'feature1 的顶端, 无法从 S 到达:我们不允许向右移动,只能向左移动。

将此与进行 "normal" 合并对比 M:

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

使用相同的测试——"is the tip commit of feature1 reachable from the tip commit of mainline?"——答案现在是,因为提交M有一个down-and-leftlink 提交 C'。 (提交 C' 是,在 Git 内部说法中,合并提交 M 第二父级 。)

由于壁球合并实际上并不是 合并,但是,从 S 回到 C' 没有任何联系。

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

您实际上可以确定和删除压缩合并的分支,尽管这很重要。

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. Then it uses git cherry 创建一个临时的悬挂压缩提交来检查压缩提交是否已经应用到主分支。如果是这样,它会删除该分支。

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