mcpField
G
git-cleanup

git-cleanup

github.com/trailofbits开发7.9k4.0k 次安装今天更新
代码仓库

关于 git-cleanup

Git Cleanup 是用于检查并清理本地 Git 分支和 worktree 的技能。它会先归组相关分支、核查合并与工作区状态,并在执行删除前要求用户经过两道确认。

主要功能

  1. 01审查前归组相关分支
  2. 02分类已合并、活动及待核查工作
  3. 03检查合并历史与跟踪状态
  4. 04检测 worktree 未提交更改
  5. 05删除前设置两道用户确认

适用场景

  1. 01检查积累的本地分支
  2. 02查找远程删除后遗留的跟踪分支
  3. 03在安全确认下删除已合并分支和 worktree

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/trailofbits/skills --skill git-cleanup

安装后在 Claude Code 中输入/git-cleanup确认已加载。

附带文件agents/openai.yamlassets/trail-of-bits-mark.svg

详细介绍

Git 清理

通过将累积的 git worktree 和本地分支分类为以下类别,安全地进行清理:可安全删除(已合并)、可能相关(主题相似)以及正在进行的工作(保留)。

适用场景

  • 用户积累了许多本地分支和 worktree 时
  • 分支已合并,但尚未在本地清理时
  • 远程分支已删除,但本地跟踪分支仍然存在时

不适用场景

  • 不要用于远程分支管理(这仅用于本地清理)
  • 不要用于 gc 或 prune 等仓库维护任务
  • 不适用于无头或非交互式自动化(两个关卡都需要用户确认)

核心原则:安全第一

未经用户明确确认,绝不删除任何内容。 此技能采用设有关卡的工作流,在执行任何破坏性操作前,用户必须逐步批准。

关键实现说明

经 Squash 合并的分支需要强制删除

重要: 对于经 squash 合并的分支,git branch -d 总会失败,因为 git 无法检测到相关工作已被纳入。这是预期行为,并非错误。

将分支识别为经 squash 合并后:

  • 从一开始就计划使用 git branch -D(强制删除)
  • 不要先尝试 git branch -d,再重新询问是否使用 -D —— 这会浪费用户的确认机会
  • 在确认步骤中,对经 squash 合并的分支显示 git branch -D
分类前先对相关分支分组

必须执行: 对单个分支进行分类前,先按名称前缀对它们分组:

# Extract common prefixes from branch names
# e.g., feature/auth-*, feature/api-*, fix/login-*

共享前缀的分支(例如 feature/api、feature/api-v2、feature/api-refactor)几乎肯定是相关的迭代。将它们作为一个组进行分析:

  1. 找出提交日期最早和最晚的分支
  2. 检查较新的分支是否包含较旧分支的提交
  3. 检查哪些 PR 合并了各分支的工作
  4. 判断较旧的分支是否已被取代

将相关分支放在一起,并给出明确建议,不要将它们分散到不同类别中。

彻底调查 PR 历史

不要依赖简单的关键词匹配。对于 [gone] 分支:

# 1. Get the branch's commits that aren't in default branch
git log --oneline "$default_branch".."$branch"

# 2. Search default branch for PRs that incorporated this work
# Search by: branch name, commit message keywords, PR numbers
git log --oneline "$default_branch" | grep -iE "(branch-name|keyword|#[0-9]+)"

# 3. For related branch groups, trace which PRs merged which work
git log --oneline "$default_branch" | grep -iE "(#[0-9]+)" | head -20

工作流

阶段 1:全面分析

在进行任何分类前,先收集所有信息:

# Get default branch name
default_branch=$(git symbolic-ref refs/remotes/origin/HEAD \
  2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")

# Protected branches - never analyze or delete
protected='^(main|master|develop|release/.*)$'

# List all local branches with tracking info
git branch -vv

# List all worktrees
git worktree list

# Fetch and prune to sync remote state
git fetch --prune

# Get merged branches (into default branch)
git branch --merged "$default_branch"

# Get recent PR merge history (squash-merge detection)
git log --oneline "$default_branch" | grep -iE "#[0-9]+" | head -30

# For EACH non-protected branch, get unique commits and sync status
for branch in $(git branch --format='%(refname:short)' \
  | grep -vE "$protected"); do
  echo "=== $branch ==="
  echo "Commits not in $default_branch:"
  git log --oneline "$default_branch".."$branch" 2>/dev/null \
    | head -5
  echo "Commits not pushed to remote:"
  git log --oneline "origin/$branch".."$branch" 2>/dev/null \
    | head -5 || echo "(no remote tracking)"
done

关于分支名称的说明: Git 分支名称可能包含会导致 shell 展开的字符。在命令中始终将 "$branch" 加上引号。

阶段 2:对相关分支分组

务必在单独分类前执行此步骤。

根据共享前缀识别分支组:

# List branches and extract prefixes
git branch --format='%(refname:short)' | sed 's/-[^-]*$//' | sort | uniq -c | sort -rn

对于包含 2 个或更多分支的每个组:

  1. 比较提交历史 —— 哪些分支包含其他分支的提交?
  2. 查找合并证据 —— 哪些 PR 纳入了该组的工作?
  3. 确定“最终”分支 —— 通常是最新或最完整的分支
  4. 标记已被取代的分支 —— 工作已进入 main 或较新分支的较早迭代

判定为已被取代必须有证据,不能仅凭前缀相同:

  • 某个 PR 将工作合并到了 main,或者
  • 较新的分支包含较旧分支的所有提交
  • 仅凭名称前缀并不足以判定 —— 名称相似的分支可能包含互不相关的工作

feature/api-* 分支的分析示例:

### Related Branch Group: feature/api-*

| Branch | Commits | PR Merged | Status |
|--------|---------|-----------|--------|
| feature/api | 12 | #29 (initial API) | Superseded - work in main |
| feature/api-v2 | 8 | #45 (API improvements) | Superseded - work in main |
| feature/api-refactor | 5 | #67 (refactor) | Superseded - work in main |
| feature/api-final | 4 | None found | Superseded by above PRs |

**Recommendation:** All 4 branches can be deleted - work incorporated via PRs #29, #45, #67
阶段 3:对其余分支分类

对于不属于相关组的分支,逐个分类:

Is branch merged into default branch?
├─ YES → SAFE_TO_DELETE (use -d)
└─ NO → Is tracking a remote?
        ├─ YES → Remote deleted? ([gone])
        │        ├─ YES → Was work squash-merged? (check main for PR)
        │        │        ├─ YES → SQUASH_MERGED (use -D)
        │        │        └─ NO → REMOTE_GONE (needs review)
        │        └─ NO → Local ahead of remote? (check: git log origin/<branch>..<branch>)
        │                ├─ YES (has output) → UNPUSHED_WORK (keep)
        │                └─ NO (empty output) → SYNCED_WITH_REMOTE (keep)
        └─ NO → Has unique commits?
                ├─ YES → LOCAL_WORK (keep)
                └─ NO → SAFE_TO_DELETE (use -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 阶段:检测脏状态

检查所有工作树和当前目录是否有未提交的更改:

# For each worktree path
git -C <worktree-path> status --porcelain

# For current directory
git status --porcelain

醒目地显示警告:

WARNING: ../proj-auth has uncommitted changes:
  M  src/auth.js
  ?? new-file.txt

These changes will be LOST if you remove this worktree.
门槛 1:呈现完整分析

在一个全面的视图中呈现所有内容。将相关分支归为一组:

## Git Cleanup Analysis

### Related Branch Groups

**Group: feature/api-* (4 branches)**
| Branch | Status | Evidence |
|--------|--------|----------|
| feature/api | Superseded | Work merged in PR #29 |
| feature/api-v2 | Superseded | Work merged in PR #45 |
| feature/api-refactor | Superseded | Work merged in PR #67 |
| feature/api-final | Superseded | Older iteration, diverged |

Recommendation: Delete all 4 (work is in main)

---

### Individual Branches

**Safe to Delete (merged with -d)**
| Branch | Merged Into |
|--------|-------------|
| fix/typo | main |

**Safe to Delete (squash-merged, requires -D)**
| Branch | Merged As |
|--------|-----------|
| feature/login | PR #42 |

**Needs Review ([gone] remotes, no PR found)**
| Branch | Last Commit |
|--------|-------------|
| experiment/old | abc1234 "WIP something" |

**Keep (active work)**
| Branch | Status |
|--------|--------|
| wip/new-feature | 5 unpushed commits |

### Worktrees
| Path | Branch | Status |
|------|--------|--------|
| ../proj-auth | feature/auth | STALE (merged) |

---

**Summary:**
- 4 related branches (feature/api-*) - recommend delete all
- 1 merged branch - safe to delete
- 1 squash-merged branch - safe to delete
- 1 needs review
- 1 to keep

Which would you like to clean up?

使用 AskUserQuestion,并提供清晰的选项:

  • 删除所有推荐的分支(分组项 + 已合并 + squash 合并)
  • 删除指定的分组/类别
  • 让我逐个选择分支

在用户回复前不要继续。

门槛 2:最终确认准确的命令

显示即将运行的确切命令,并使用正确的标志:

I will execute:

# Merged branches (safe delete)
git branch -d fix/typo

# Squash-merged branches (force delete - work is in main via PRs)
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

# Worktrees
git worktree remove ../proj-auth

Confirm? (yes/no)

重要: 这是删除前唯一需要的确认。如果需要使用 -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 阶段:报告
## Cleanup Complete

### Deleted
- fix/typo
- feature/login
- feature/api
- feature/api-v2
- feature/api-refactor
- feature/api-final
- Worktree: ../proj-auth

### Remaining (4 branches)
| Branch | Status |
|--------|--------|
| main | current |
| wip/new-feature | active work |
| experiment/old | needs review |

安全规则

  1. 绝不自动调用——仅当用户明确使用 /git-cleanup 时才运行
  2. 仅设置两个确认关卡——先审查分析,再确认删除
  3. 使用正确的删除命令——已合并的使用 -d,squash 合并/已被取代的使用 -D
  4. 绝不触碰受保护的分支——main、master、develop、release/*(以编程方式过滤)
  5. 阻止移除有未提交更改的工作树——除非用户明确确认接受数据丢失,否则拒绝移除
  6. 将相关分支归为一组——不要把它们分散到不同类别

应拒绝的合理化说法

以下是可能导致数据丢失的常见捷径。请拒绝:

合理化说法错误原因
“这个分支很旧,大概可以安全删除”年限并不能说明合并状态。旧分支可能包含尚未合并的工作。
“需要的话,我可以从 reflog 恢复”reflog 条目会过期。用户通常不知道如何使用 reflog。不要依赖它作为安全网。
“这只是一个本地分支,没什么重要的”本地分支可能包含唯一一份尚未推送到任何地方的工作。
“PR 已合并,所以这个分支可以安全删除”squash 合并不会保留分支历史。请确认具体提交已被纳入。
“我只需删除所有 [gone] 分支”[gone] 只表示远程分支已删除。本地分支可能包含尚未推送的提交。
“用户看起来想删除所有内容”始终先呈现分析。让用户选择要删除的内容。
“这个分支有 main 中没有的提交,所以有未推送的工作”“不在 main 中” ≠ “尚未推送”。分支可以已与其远程分支同步,但尚未合并到 main。始终检查 git log origin/<branch>..<branch>。

评论

支持 Markdown