mcpField
G
git-branch-cleanup

git-branch-cleanup

github.com/dzhuneyt开发062 次安装3 周前更新
代码仓库

关于 git-branch-cleanup

git-branch-cleanup 是一项用于查找并清理过期 Git 分支的安全技能,只删除已确认合并的分支。它会处理 squash merge,保护含未合并工作的分支,并在请求删除确认前列出候选项。

主要功能

  1. 01发现本地和远程分支
  2. 02利用 GitHub PR 数据识别 squash merge
  3. 03比较分支内容及合并后的新增工作
  4. 04保护开放、活跃和状态不明的分支
  5. 05请求确认并提供恢复命令

适用场景

  1. 01清理已合并 PR 留下的分支
  2. 02检查被标记为 gone 的分支
  3. 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:查找每个分支的合并状态

按以下顺序执行测试。遇到能给出结果的第一项时即停止。

  1. git merge-base --is-ancestor <branch> origin/<default> — 退出码为 0 表示已合并或已快进。不需要联网。

  2. gh pr list --head <branch> --state all --limit 5 --json number,state,url,mergedAt,headRefOid

    • 状态为 MERGED — 分支已合并。这是 squash 合并的情况。
    • 状态为 OPEN — 保护该分支。绝不要删除。
    • 状态为 CLOSED 且没有 mergedAt — 工作已放弃。请参见 abandoned 类别。
    • 没有拉取请求 — 继续测试 3。
  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-mergedorigin/<x> 已合并,且没有本地分支在其基础上包含工作批量确认一次,然后删除
orphan-work已合并,但 headRefOid 之后还有提交阻止删除
open-pr拉取请求仍处于开启状态阻止删除
worktree已在其他工作树中检出阻止删除
system是默认分支或当前分支阻止删除
abandoned拉取请求已关闭但未合并单独询问
stranded从未推送、没有上游、没有拉取请求仅报告,绝不删除
unknown有上游、未合并、没有拉取请求仅报告

阻止删除的分支不会收到删除提示。报告这些分支,然后继续。

第 6 步:先报告,再删除

在显示任何提示之前,先显示完整报告。为每个 orphan-work 条目补充提交 sha、主题、时间和 diffstat。其他条目各占一行。

然后按以下顺序提示。

  1. 对所有 local-merged 分支进行一次批量确认。
  2. 对所有 remote-merged 分支进行一次批量确认。
  3. 单独询问是否处理 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 合并到已合并批次中已关闭的工作和已合并的工作需要分别处理。
只在最后一批之后打印回执每批之后都要打印。之后发生的故障不应导致回执丢失。

评论

支持 Markdown