关于 git-branch-cleanup
git-branch-cleanup 是一项用于查找并清理过期 Git 分支的安全技能,只删除已确认合并的分支。它会处理 squash merge,保护含未合并工作的分支,并在请求删除确认前列出候选项。
主要功能
- 01发现本地和远程分支
- 02利用 GitHub PR 数据识别 squash merge
- 03比较分支内容及合并后的新增工作
- 04保护开放、活跃和状态不明的分支
- 05请求确认并提供恢复命令
适用场景
- 01清理已合并 PR 留下的分支
- 02检查被标记为 gone 的分支
- 03安全评估指定分支是否可删除
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dzhuneyt/ skills --skill git-branch-cleanup安装后在 Claude Code 中输入/git-branch-cleanup确认已加载。
详细介绍
清理陈旧的 Git 分支
删除已合并的分支。保护仍包含工作的所有分支。
Squash 合并会改写提交。Squash 合并后,git branch --merged 不会列出该分支,而 git branch -d 也会拒绝删除它。因此,这两个工具在此处都不能作为安全判断依据。本技能会向 GitHub 查询合并了哪个拉取请求,然后将该提交与本地分支的最新提交进行比较。
适用场景
符合以下任一情况时,使用本技能:
- 已合并的拉取请求留下了本地分支。
git branch -vv将分支标记为[gone]。- 仓库未启用自动删除设置,且已合并的远程分支仍然存在。
- 用户指定了一个或多个要清理的分支。
不要用本技能删除包含未合并工作的分支。不要用它放弃尚未关闭的拉取请求。
参数
$ARGUMENTS 存放可选的分支名称列表。
- 如果列表为空,则扫描整个仓库并提出候选分支。
- 如果列表包含分支名称,则只评估这些名称。逐项运行所有安全检查,不做任何更改。对指定分支的处理标准不能比发现的分支宽松。
步骤 1:预检
先运行以下命令。如果第一个命令失败,则停止。
git rev-parse --git-dir # not a repo -> stop
git symbolic-ref --quiet --short refs/remotes/origin/HEAD # -> origin/main
git fetch --prune # every later check needs this
git worktree list --porcelain # branches checked out elsewhere
git remote get-url origin
gh auth status
查找默认分支。绝不要假定它叫 main 或 master。如果 git symbolic-ref 没有返回结果,则运行 gh repo view --json defaultBranchRef -q .defaultBranchRef.name。如果两种方式都失败,则询问用户。
gh 的结果和 origin 主机共同决定运行模式。GitHub origin 加上有效的 gh 身份验证会启用完整模式。其他情况均为降级模式(见“降级模式”)。
步骤 2:列出分支
git for-each-ref refs/heads \
--format='%(refname:short)%09%(upstream:short)%09%(upstream:track)%09%(objectname:short)%09%(committerdate:relative)'
git for-each-ref refs/remotes/origin \
--format='%(refname:short)%09%(objectname:short)%09%(committerdate:relative)'
当远程分支已不存在时,%(upstream:track) 会输出 [gone]。这是判断已合并并删除的拉取请求最省事的信号,不会消耗 API 调用。
步骤 3:查找每个分支的合并状态
按以下顺序执行测试。遇到能给出结果的第一项时即停止。
-
git merge-base --is-ancestor <branch> origin/<default>— 退出码为 0 表示已合并或已快进。不需要联网。 -
gh pr list --head <branch> --state all --limit 5 --json number,state,url,mergedAt,headRefOid- 状态为
MERGED— 分支已合并。这是 squash 合并的情况。 - 状态为
OPEN— 保护该分支。绝不要删除。 - 状态为
CLOSED且没有mergedAt— 工作已放弃。请参见abandoned类别。 - 没有拉取请求 — 继续测试 3。
- 状态为
-
将合并结果与默认分支进行比较。在降级模式下运行此测试;在完整模式下,如果测试 2 未发现拉取请求,也运行此测试。
git merge-tree --write-tree origin/<default> <branch> # prints a tree sha git rev-parse origin/<default>^{tree}
树相同表示该分支没有新增内容。该分支已合并。退出码非零表示存在冲突,因此该分支未合并。
此测试读取的是内容,而不是历史记录。通过其他途径进入默认分支的分支更改,也会得到相同的树。结果仍然安全:树相同意味着该分支没有默认分支所缺少的内容。
此测试绝不要使用 git cherry 或 git branch --merged。两者都比较提交,而 squash 合并会改写提交。它们都会将完全合并的分支报告为未合并。
git merge-tree 比较的是内容,因此 squash 不会掩盖合并状态。
git merge-tree --write-tree 需要 git 2.38 或更高版本。如果 git 版本较旧,在删除任何内容之前询问用户。
第 4 步:找出合并后新增的提交
Squash 合并会让分支上的每个提交看起来都不在默认分支中。因此,
git log origin/<default>..<branch> 无法区分已合并的工作和新增工作。向 GitHub
查询拉取请求实际合并的提交,然后进行比较:
git cat-file -e <headRefOid>^{commit} # the sha must exist locally
git log --oneline <headRefOid>..<branch>
git diff --stat <headRefOid>..<branch>
没有输出表示该分支没有新增内容,可以安全删除。 有输出表示该分支包含拉取请求未合并的工作。阻止删除该分支。
如果 git cat-file 失败,请运行 git fetch origin <headRefOid>。如果这也失败,请使用
下面的降级检查。
在降级模式下,没有 headRefOid。请改用合并结果:
git diff --stat origin/<default> $(git merge-tree --write-tree origin/<default> <branch>)
这会打印该分支新增、而默认分支中不存在的内容。输出为空表示可以安全删除该分支。任何输出都表示有未合并的工作,因此应阻止删除。 此测试报告的是文件而非提交,因此报告中应列出文件,而不是 sha。
第 5 步:将每个分支归入一个类别
| 类别 | 条件 | 操作 |
|---|---|---|
local-merged | 已合并,tip 与 headRefOid 相同,不是当前分支或默认分支,也不在任何工作树中 | 批量确认一次,然后删除 |
remote-merged | origin/<x> 已合并,且没有本地分支在其基础上包含工作 | 批量确认一次,然后删除 |
orphan-work | 已合并,但 headRefOid 之后还有提交 | 阻止删除 |
open-pr | 拉取请求仍处于开启状态 | 阻止删除 |
worktree | 已在其他工作树中检出 | 阻止删除 |
system | 是默认分支或当前分支 | 阻止删除 |
abandoned | 拉取请求已关闭但未合并 | 单独询问 |
stranded | 从未推送、没有上游、没有拉取请求 | 仅报告,绝不删除 |
unknown | 有上游、未合并、没有拉取请求 | 仅报告 |
阻止删除的分支不会收到删除提示。报告这些分支,然后继续。
第 6 步:先报告,再删除
在显示任何提示之前,先显示完整报告。为每个 orphan-work 条目补充提交
sha、主题、时间和 diffstat。其他条目各占一行。
然后按以下顺序提示。
- 对所有
local-merged分支进行一次批量确认。 - 对所有
remote-merged分支进行一次批量确认。 - 单独询问是否处理
abandoned类别。显示拉取请求 URL 和关闭 日期。绝不要将此问题并入第 1 步或第 2 步。
删除分支前,先读取该分支的 sha。然后删除:
git branch -D <branch> # -d refuses squash-merged branches
git push origin --delete <branch>
git branch -D 会跳过 git 的合并检查。安全保障来自第 3 步和第 4 步。
绝不要对这两步未通过检查的分支调用 -D。
第 7 步:打印撤销凭据
每批操作后、下一个提示前,打印恢复命令:
git branch feat/login-fix a3f9c21
git push origin 8b1e044:refs/heads/feat/export
reflog 会过期,打印出的 sha 不会过期。远程恢复需要本地仓库中存在该对象。 如果对象不存在,请提醒用户。
降级模式
当 gh 不存在、网络断开或 origin 不是 GitHub 时,进入降级模式。
在运行开始时报告一次。然后:
- Squash 检测改用
git merge-tree。就内容而言,结果是精确的。 orphan-work报告列出文件,而不是提交。abandoned类别不可用。关闭的拉取请求看起来与没有拉取请求相同,因此这些分支会归入unknown并保留。- 每个候选分支都需要单独确认。不进行批量确认。
降级模式下有一种情况无法检测到:如果已合并的工作之后在默认分支上被还原,git merge-tree 会将该分支报告为未合并。报告该分支。绝不要删除它。
失败处理
某个分支失败不会中止整个运行。记录失败并继续。摘要会列出成功、失败和跳过的分支。
危险信号——停止
- 使用
git branch --merged作为合并信号。它会漏掉所有压缩合并。 - 使用
git cherry作为合并信号。原因相同,它会漏掉所有压缩合并。 - 使用
git log <default>..<branch>作为孤立工作信号。它会把已合并的工作报告为新工作。 - 对 Step 3 未清除的分支执行
git branch -D。 - 在报告之前删除分支。
- 提议删除
stranded分支。 - 假定默认分支名称,而不是检测它。
常见错误
| 错误 | 修正 |
|---|---|
跳过 git fetch --prune | 每次合并检查都会读取过时的引用。先运行它。 |
| 将具名分支视为已预先批准 | 名称只会缩小扫描范围,不会跳过检查。 |
| 在本地对应分支仍领先时删除远程分支 | 本地分支保存着这部分工作的唯一副本。 |
将 abandoned 合并到已合并批次中 | 已关闭的工作和已合并的工作需要分别处理。 |
| 只在最后一批之后打印回执 | 每批之后都要打印。之后发生的故障不应导致回执丢失。 |