清理已删除的Git分支
很久以前,我编写了一套有用的Git别名来支持GitHub工作流。我最喜欢的别名是bdone
,它会:
- 切换到默认分支
- 运行
git up
确保代码是最新的
- 运行
git bclean
删除所有已合并到默认分支的分支
这个方法在很长一段时间内都工作得很好。它的工作原理是列出所有已合并到默认分支的分支,然后删除它们。当时我并没有使用git branch --merged
来列出已合并的分支,因为那时我还不知道这个命令。
然而,在我加入PostHog后,我的别名最近停止工作了。主要原因是他们几乎在所有仓库中都使用Squash和Merge来合并PR。
当你使用git merge --squash
或GitHub的"Squash and merge"功能时,Git会在目标分支上创建一个新的提交,将源分支的所有更改合并为单个提交。这个新提交不会保留来自源分支的原始提交的任何引用。因此,Git不会认为源分支已被合并,像git branch --merged
这样的命令也不会显示它。
但关于Git的一点是:几乎总有解决办法。
解决方案
当你在GitHub上合并PR时,它会显示一个"Delete branch"按钮:
这是一个很棒的功能。它是清理已合并到默认分支的分支的好方法。事实上,你可以配置GitHub在合并时"自动删除头部分支":
我强烈建议你也这样做。当远程分支被删除时,Git会将其标记为"gone"。例如,如果你运行git branch -vv
,你会看到类似这样的输出:
1
2
3
4
5
6
|
> git branch -vv
haacked/decide-v4 ba39408 [origin/haacked/decide-v4: gone] Fix demo to handle variants
haacked/fix-sample-app ec15751 [origin/haacked/fix-sample-app] Handle variants
* haacked/local-only e78d2f6 Do important stuff
main ab885d0 [origin/main] chore: Bump to v1.0.2
|
注意haacked/decide-v4
被标记为gone。
我们可以使用git的porcelain命令以更易解析的格式列出分支及其跟踪信息:
1
2
3
4
5
6
|
> git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads/
haacked/decide-v4 [gone]
haacked/fix-sample-app
haacked/local-only
master
|
让我们创建一个别名来列出这些gone分支:
1
|
> git config --global alias.gone "!git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads/ | awk '\$2 == \"[gone]\" { print \$1 }'"
|
现在我们可以使用这个别名来列出gone分支:
1
2
3
|
> git gone
haacked/decide-v4
|
下一步是更新我旧的bclean
别名以使用新的gone
别名:
1
|
> git config --global alias.bclean "!git gone | xargs -r git branch -D"
|
不幸的是,由于git不知道它已被合并,我们必须进行强制删除。这有点吓人,但这不会触及本地分支或任何仍在跟踪远程分支的分支。使用这个别名,你可以运行git bclean
来删除所有已合并到默认分支的分支。最后,我们有旧的bdone
别名来切换到默认分支,运行git up
,然后运行bclean
。
以下是本文中提到的完整别名集合,你可以直接复制粘贴到你的.gitconfig
中:
1
2
3
4
5
6
|
[alias]
default = "!git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'"
bclean = "!git gone | xargs -r git branch -D"
# 切换到指定分支(如果未指定分支则使用默认分支),运行git up,然后运行bclean
bdone = "!f() { DEFAULT=$(git default); git checkout ${1-$DEFAULT} && git up && git bclean; }; f"
gone = "!git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads/ | awk '$2 == \"[gone]\" { print $1 }'"
|
或者,如果你想使用git命令行,可以使用以下命令:
1
2
3
4
|
git config --global alias.default "!git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'"
git config --global alias.gone "!git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads/ | awk '\$2 == \"[gone]\" { print \$1 }'"
git config --global alias.bclean "!git gone | xargs -r git branch -D"
git config --global alias.bdone "!f() { DEFAULT=\$(git default); git checkout \${1:-\$DEFAULT} && git up && git bclean; }; f"
|
注意:使用git default
别名时,你可能会遇到以下错误:
1
|
fatal: ref refs/remotes/origin/HEAD is not a symbolic ref
|
这个别名依赖于origin/HEAD
符号引用的存在。在某些情况下,特别是新克隆的仓库或某些配置中,这个引用可能没有被设置。要修复它:
1
|
git remote set-head origin --auto
|
现在,我的旧工作流程又回来了。当我合并一个PR时,我可以从合并的分支运行git bdone
来清理分支。世界又恢复正常了。
和往常一样,你可以在我的dotfiles仓库中找到这些别名和更多内容。