名称:git-cleanup 描述:“安全地分析和清理本地git分支和工作树,通过将它们分类为合并、squash合并、被取代或活跃工作。” 禁用模型调用:true 允许的工具:
- Bash
- Read
- Grep
- AskUserQuestion
Git清理
安全地清理积累的git工作树和本地分支,通过将它们分类为:安全可删除(合并)、潜在相关(相似主题)和活跃工作(保留)。
何时使用
- 当用户积累了许多本地分支和工作树时
- 当分支已合并但未在本地清理时
- 当远程分支已删除但本地跟踪分支仍存在时
何时不使用
- 不要用于远程分支管理(这只是本地清理)
- 不要用于仓库维护任务如gc或prune
- 不设计用于无头或非交互式自动化(需要在两个关卡进行用户确认)
核心原则:安全第一
未经明确用户确认,永不删除任何内容。 此技能使用门控工作流,用户在每次步骤前必须批准任何破坏性操作。
关键实现注意事项
Squash合并的分支需要强制删除
重要: git branch -d 总会失败于squash合并的分支,因为git无法检测到工作已被纳入。这是预期行为,不是错误。
当识别分支为squash合并时:
- 计划从一开始使用
git branch -D(强制删除) - 不要先尝试
git branch -d然后再问-D- 这浪费用户确认 - 在确认步骤中,为squash合并的分支显示
git branch -D
在分类前分组相关分支
强制: 在分类单个分支前,按名称前缀分组它们:
# 从分支名中提取公共前缀
# 例如,feature/auth-*, feature/api-*, fix/login-*
共享前缀的分支(例如,feature/api, feature/api-v2, feature/api-refactor)几乎肯定是相关迭代。将它们作为一个组分析:
- 按提交日期找到最旧和最新的
- 检查较新分支是否包含较旧分支的提交
- 检查哪些PR合并了每个分支的工作
- 确定较旧分支是否被取代
一起呈现相关分支,并给出清晰建议,不要分散在各个类别中。
彻底调查PR历史
不要依赖简单关键词匹配。对于 [gone] 分支:
# 1. 获取分支的提交,这些不在默认分支中
git log --oneline "$default_branch".."$branch"
# 2. 在默认分支中搜索包含此工作的PR
# 通过:分支名、提交消息关键词、PR号搜索
git log --oneline "$default_branch" | grep -iE "(branch-name|keyword|#[0-9]+)"
# 3. 对于相关分支组,追踪哪些PR合并了哪些工作
git log --oneline "$default_branch" | grep -iE "(#[0-9]+)" | head -20
工作流
阶段1:全面分析
在分类前收集所有信息:
# 获取默认分支名
default_branch=$(git symbolic-ref refs/remotes/origin/HEAD \
2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
# 受保护分支 - 从不分析或删除
protected='^(main|master|develop|release/.*)$'
# 列出所有本地分支及跟踪信息
git branch -vv
# 列出所有工作树
git worktree list
# 获取并prune以同步远程状态
git fetch --prune
# 获取合并到默认分支的分支
git branch --merged "$default_branch"
# 获取最近的PR合并历史(检测squash合并)
git log --oneline "$default_branch" | grep -iE "#[0-9]+" | head -30
# 对于每个非受保护分支,获取唯一提交和同步状态
for branch in $(git branch --format='%(refname:short)' \
| grep -vE "$protected"); do
echo "=== $branch ==="
echo "不在 $default_branch 中的提交:"
git log --oneline "$default_branch".."$branch" 2>/dev/null \
| head -5
echo "未推送到远程的提交:"
git log --oneline "origin/$branch".."$branch" 2>/dev/null \
| head -5 || echo "(无远程跟踪)"
done
注意分支名: Git分支名可以包含破坏shell扩展的字符。在命令中始终引用 "$branch"。
阶段2:分组相关分支
在个别分类前执行此操作。
通过共享前缀识别分支组:
# 列出分支并提取前缀
git branch --format='%(refname:short)' | sed 's/-[^-]*$//' | sort | uniq -c | sort -rn
对于每组有2+分支:
- 比较提交历史 - 哪些分支包含其他分支的提交?
- 查找合并证据 - 哪些PR合并了此组的工作?
- 识别“最终”分支 - 通常是最新或最完整的
- 标记被取代分支 - 旧迭代,其工作在main中或在较新分支中
被取代需要证据,不仅仅是共享前缀:
- 一个PR合并工作到main,OR
- 较新分支包含所有旧分支的提交
- 仅名称前缀不足 – 相似命名的分支可能包含独立工作
示例分析 feature/api-* 分支:
### 相关分支组:feature/api-*
| 分支 | 提交 | PR合并 | 状态 |
|--------|---------|-----------|--------|
| feature/api | 12 | #29(初始API) | 被取代 - 工作在main |
| feature/api-v2 | 8 | #45(API改进) | 被取代 - 工作在main |
| feature/api-refactor | 5 | #67(重构) | 被取代 - 工作在main |
| feature/api-final | 4 | 未找到 | 被上述PR取代 |
**建议:** 所有4个分支可删除 - 工作通过PR #29, #45, #67合并
阶段3:分类剩余分支
对于不在相关组的分支,个别分类:
分支是否合并到默认分支?
├─ 是 → SAFE_TO_DELETE(使用 -d)
└─ 否 → 是否跟踪远程?
├─ 是 → 远程删除?([gone])
│ ├─ 是 → 工作是否squash合并?(检查main中的PR)
│ │ ├─ 是 → SQUASH_MERGED(使用 -D)
│ │ └─ 否 → REMOTE_GONE(需要审查)
│ └─ 否 → 本地是否领先于远程?(检查:git log origin/<branch>..<branch>)
│ ├─ 是(有输出) → UNPUSHED_WORK(保留)
│ └─ 否(空输出) → SYNCED_WITH_REMOTE(保留)
└─ 否 → 有唯一提交?
├─ 是 → LOCAL_WORK(保留)
└─ 否 → SAFE_TO_DELETE(使用 -d)
类别定义:
| 类别 | 含义 | 删除命令 |
|---|---|---|
| SAFE_TO_DELETE | 合并到默认分支 | git branch -d |
| SQUASH_MERGED | 工作通过squash合并纳入 | git branch -D |
| SUPERSEDED | 属于组的一部分,工作通过PR或较新分支验证在main中 | git branch -D |
| REMOTE_GONE | 远程删除,工作未在main中找到 | 需要审查 |
| UNPUSHED_WORK | 有未推送到远程的提交 | 保留 |
| LOCAL_WORK | 未跟踪分支但有唯一提交 | 保留 |
| SYNCED_WITH_REMOTE | 与远程同步 | 保留 |
阶段4:脏状态检测
检查所有工作树和当前目录是否有未提交更改:
# 对于每个工作树路径
git -C <worktree-path> status --porcelain
# 对于当前目录
git status --porcelain
突出显示警告:
警告:../proj-auth 有未提交更改:
M src/auth.js
?? new-file.txt
如果移除此工作树,这些更改将丢失。
门1:呈现完整分析
在一个综合视图中呈现所有内容。将相关分支分组:
## Git清理分析
### 相关分支组
**组:feature/api-*(4个分支)**
| 分支 | 状态 | 证据 |
|--------|--------|----------|
| feature/api | 被取代 | 工作在PR #29合并 |
| feature/api-v2 | 被取代 | 工作在PR #45合并 |
| feature/api-refactor | 被取代 | 工作在PR #67合并 |
| feature/api-final | 被取代 | 旧迭代,分歧 |
建议:删除所有4个(工作在main中)
---
### 个别分支
**安全删除(合并,使用 -d)**
| 分支 | 合并到 |
|--------|-------------|
| fix/typo | main |
**安全删除(squash合并,需要 -D)**
| 分支 | 合并为 |
|--------|-----------|
| feature/login | PR #42 |
**需要审查(远程[gone],未找到PR)**
| 分支 | 最后提交 |
|--------|-------------|
| experiment/old | abc1234 "WIP something" |
**保留(活跃工作)**
| 分支 | 状态 |
|--------|--------|
| wip/new-feature | 5个未推送提交 |
### 工作树
| 路径 | 分支 | 状态 |
|------|--------|--------|
| ../proj-auth | feature/auth | STALE(合并) |
---
**总结:**
- 4个相关分支(feature/api-*)- 建议删除所有
- 1个合并分支 - 安全删除
- 1个squash合并分支 - 安全删除
- 1个需要审查
- 1个保留
您想清理哪些?
使用AskUserQuestion,带有清晰选项:
- 删除所有建议的(组 + 合并 + squash合并)
- 删除特定组/类别
- 让我选择个别分支
用户响应前不要继续。
门2:最终确认与确切命令
显示将运行的确切命令,带有正确标志:
我将执行:
# 合并分支(安全删除)
git branch -d fix/typo
# Squash合并分支(强制删除 - 工作在main中通过PR)
git branch -D feature/login
git branch -D feature/api
git branch -D feature/api-v2
git branch -D feature/api-refactor
git branch -D feature/api-final
# 工作树
git worktree remove ../proj-auth
确认?(是/否)
重要: 这是删除所需的唯一确认。如果 -D 是必需的,不要添加额外确认。
阶段5:执行
作为单独命令运行每个删除,以便部分失败不阻止剩余删除。报告每个结果:
git branch -d fix/typo
git branch -D feature/login
git branch -D feature/api
git branch -D feature/api-v2
git branch -D feature/api-refactor
git branch -D feature/api-final
git worktree remove ../proj-auth
如果删除失败,报告错误并继续剩余删除。
阶段6:报告
## 清理完成
### 删除的
- fix/typo
- feature/login
- feature/api
- feature/api-v2
- feature/api-refactor
- feature/api-final
- 工作树:../proj-auth
### 剩余的(4个分支)
| 分支 | 状态 |
|--------|--------|
| main | 当前 |
| wip/new-feature | 活跃工作 |
| experiment/old | 需要审查 |
安全规则
- 永不自动调用 - 仅当用户显式使用
/git-cleanup时运行 - 仅两个确认门 - 分析审查,然后删除确认
- 使用正确删除命令 -
-d用于合并,-D用于squash合并/被取代 - 永不触及受保护分支 - main, master, develop, release/*(编程过滤)
- 阻止脏工作树删除 - 未经明确数据丢失确认拒绝
- 分组相关分支 - 不要将它们分散在各个类别中
要拒绝的理性化
这些是导致数据丢失的常见快捷方式。拒绝它们:
| 理性化 | 为什么它是错误的 |
|---|---|
| “分支很旧,可能安全删除” | 年龄不指示合并状态。旧分支可能包含未合并工作。 |
| “如果需要,我可以从reflog恢复” | Reflog条目过期。用户通常不知道如何使用reflog。不要依赖它作为安全网。 |
| “只是一个本地分支,不重要” | 本地分支可能包含唯一的工作副本,未推送到任何地方。 |
| “PR已合并,所以分支安全” | Squash合并不保留分支历史。验证特定提交已纳入。 |
“我只删除所有 [gone] 分支” |
[gone] 仅意味着远程已删除。本地分支可能有未推送提交。 |
| “用户似乎想删除一切” | 总是首先呈现分析。让用户选择删除什么。 |
| “分支有不在main中的提交,所以有未推送工作” | “不在main中” ≠ “未推送”。分支可以与远程同步但未合并到main。始终检查 git log origin/<branch>..<branch>。 |