git-cleanup by trailofbits/skills
npx skills add https://github.com/trailofbits/skills --skill git-cleanup通过将累积的 git 工作树和本地分支分类为:可安全删除(已合并)、可能相关(相似主题)和活跃工作(保留),来安全地清理它们。
未经用户明确确认,绝不删除任何内容。 此技能使用一个分阶段的工作流程,用户必须在任何破坏性操作之前批准每个步骤。
重要提示: 对于压缩合并的分支,git branch -d 将总是失败,因为 git 无法检测到工作已被合并。这是预期行为,不是错误。
当你识别出一个分支是压缩合并的时:
git branch -D(强制删除)git branch -d,然后再询问是否使用 -D —— 这会浪费用户的确认次数广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
git branch -D强制要求: 在对单个分支进行分类之前,按名称前缀对它们进行分组:
# 从分支名称中提取公共前缀
# 例如,feature/auth-*, feature/api-*, fix/login-*
共享前缀的分支(例如,feature/api、feature/api-v2、feature/api-refactor)几乎肯定是相关的迭代。将它们作为一个组进行分析:
将相关的分支放在一起,并给出明确的建议,而不是分散在各个类别中。
不要依赖简单的关键字匹配。对于 [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
在进行任何分类之前,预先收集所有信息:
# 获取默认分支名称
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
# 获取并清理以同步远程状态
git fetch --prune
# 获取已合并的分支(合并到默认分支)
git branch --merged "$default_branch"
# 获取最近的 PR 合并历史(检测压缩合并)
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"。
在对单个分支进行分类之前进行此操作。
通过共享前缀识别分支组:
# 列出分支并提取前缀
git branch --format='%(refname:short)' | sed 's/-[^-]*$//' | sort | uniq -c | sort -rn
对于每个包含 2 个或更多分支的组:
“已被取代”需要证据,不仅仅是共享前缀:
feature/api-* 分支的示例分析:
### 相关分支组:feature/api-*
| 分支 | 提交数 | 合并的 PR | 状态 |
|--------|---------|-----------|--------|
| feature/api | 12 | #29 (初始 API) | 已被取代 - 工作在主分支中 |
| feature/api-v2 | 8 | #45 (API 改进) | 已被取代 - 工作在主分支中 |
| feature/api-refactor | 5 | #67 (重构) | 已被取代 - 工作在主分支中 |
| feature/api-final | 4 | 未找到 | 被上述 PR 取代 |
**建议:** 所有 4 个分支都可以删除 - 工作已通过 PR #29、#45、#67 合并
对于不在相关组中的分支,进行单独分类:
分支是否已合并到默认分支?
├─ 是 → 可安全删除 (使用 -d)
└─ 否 → 是否跟踪远程?
├─ 是 → 远程是否已删除?([gone])
│ ├─ 是 → 工作是否被压缩合并?(在主分支中检查 PR)
│ │ ├─ 是 → 压缩合并 (使用 -D)
│ │ └─ 否 → 远程已删除 (需要审查)
│ └─ 否 → 本地是否领先于远程?(检查:git log origin/<branch>..<branch>)
│ ├─ 是 (有输出) → 未推送的工作 (保留)
│ └─ 否 (空输出) → 与远程同步 (保留)
└─ 否 → 是否有唯一提交?
├─ 是 → 本地工作 (保留)
└─ 否 → 可安全删除 (使用 -d)
类别定义:
| 类别 | 含义 | 删除命令 |
|---|---|---|
| 可安全删除 | 已合并到默认分支 | git branch -d |
| 压缩合并 | 工作通过压缩合并合并 | git branch -D |
| 已被取代 | 属于一个组,工作已通过 PR 或在较新分支中验证在主分支中 | git branch -D |
| 远程已删除 | 远程已删除,工作未在主分支中找到 | 需要审查 |
| 未推送的工作 | 有未推送到远程的提交 | 保留 |
| 本地工作 | 具有唯一提交的未跟踪分支 | 保留 |
| 与远程同步 | 与远程保持同步 | 保留 |
检查所有工作树和当前目录是否有未提交的更改:
# 对于每个工作树路径
git -C <worktree-path> status --porcelain
# 对于当前目录
git status --porcelain
醒目地显示警告:
警告:../proj-auth 有未提交的更改:
M src/auth.js
?? new-file.txt
如果移除此工作树,这些更改将丢失。
在一个全面的视图中呈现所有内容。将相关的分支分组在一起:
## Git 清理分析
### 相关分支组
**组:feature/api-* (4 个分支)**
| 分支 | 状态 | 证据 |
|--------|--------|----------|
| feature/api | 已被取代 | 工作在 PR #29 中合并 |
| feature/api-v2 | 已被取代 | 工作在 PR #45 中合并 |
| feature/api-refactor | 已被取代 | 工作在 PR #67 中合并 |
| feature/api-final | 已被取代 | 旧迭代,已偏离 |
建议:删除所有 4 个(工作已在主分支中)
---
### 单个分支
**可安全删除(已合并,使用 -d)**
| 分支 | 合并到 |
|--------|-------------|
| fix/typo | main |
**可安全删除(压缩合并,需要 -D)**
| 分支 | 合并为 |
|--------|-----------|
| feature/login | PR #42 |
**需要审查(远程 [gone],未找到 PR)**
| 分支 | 最后提交 |
|--------|-------------|
| experiment/old | abc1234 "WIP something" |
**保留(活跃工作)**
| 分支 | 状态 |
|--------|--------|
| wip/new-feature | 5 个未推送的提交 |
### 工作树
| 路径 | 分支 | 状态 |
|------|--------|--------|
| ../proj-auth | feature/auth | 陈旧 (已合并) |
---
**总结:**
- 4 个相关分支 (feature/api-*) - 建议全部删除
- 1 个已合并分支 - 可安全删除
- 1 个压缩合并分支 - 可安全删除
- 1 个需要审查
- 1 个保留
您想清理哪些?
使用 AskUserQuestion 并提供清晰的选项:
在用户响应之前不要继续。
显示将运行的确切命令,并带有正确的标志:
我将执行:
# 已合并的分支(安全删除)
git branch -d fix/typo
# 压缩合并的分支(强制删除 - 工作已通过 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,不要添加额外的确认。
将每个删除操作作为单独的命令运行,以便部分失败不会阻塞剩余的删除操作。报告每个操作的结果:
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
如果删除失败,报告错误并继续执行剩余的删除操作。
## 清理完成
### 已删除
- 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 用于压缩合并/已被取代这些是导致数据丢失的常见捷径。拒绝它们:
| 合理化理由 | 为何错误 |
|---|---|
| “分支很旧,删除可能很安全” | 年龄并不表示合并状态。旧分支可能包含未合并的工作。 |
| “如果需要,我可以从 reflog 恢复” | Reflog 条目会过期。用户通常不知道如何使用 reflog。不要依赖它作为安全网。 |
| “这只是一个本地分支,没什么重要的” | 本地分支可能包含未推送到任何地方的唯一工作副本。 |
| “PR 已合并,所以分支是安全的” | 压缩合并不保留分支历史。验证特定的提交已被合并。 |
“我就删除所有 [gone] 分支” | [gone] 仅表示远程已被删除。本地分支可能有未推送的提交。 |
| “用户似乎想删除所有内容” | 始终先呈现分析。让用户选择要删除的内容。 |
| “分支有不在主分支中的提交,所以它有未推送的工作” | “不在主分支中” ≠ “未推送”。一个分支可以与其远程同步但未合并到主分支。始终检查 git log origin/<branch>..<branch>。 |
每周安装次数
762
仓库
GitHub 星标数
3.9K
首次出现
2026年2月12日
安全审计
安装于
opencode683
codex682
claude-code677
gemini-cli672
github-copilot665
cursor663
Safely clean up accumulated git worktrees and local branches by categorizing them into: safely deletable (merged), potentially related (similar themes), and active work (keep).
Never delete anything without explicit user confirmation. This skill uses a gated workflow where users must approve each step before any destructive action.
IMPORTANT: git branch -d will ALWAYS fail for squash-merged branches because git cannot detect that the work was incorporated. This is expected behavior, not an error.
When you identify a branch as squash-merged:
git branch -D (force delete) from the startgit branch -d first and then ask again for -D - this wastes user confirmationsgit branch -D for squash-merged branchesMANDATORY: Before categorizing individual branches, group them by name prefix:
# Extract common prefixes from branch names
# e.g., feature/auth-*, feature/api-*, fix/login-*
Branches sharing a prefix (e.g., feature/api, feature/api-v2, feature/api-refactor) are almost certainly related iterations. Analyze them as a group:
Present related branches together with a clear recommendation, not scattered across categories.
Don't rely on simple keyword matching. For [gone] branches:
# 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
Gather ALL information upfront before any categorization:
# 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
Note on branch names: Git branch names can contain characters that break shell expansion. Always quote "$branch" in commands.
Do this BEFORE individual categorization.
Identify branch groups by shared prefixes:
# List branches and extract prefixes
git branch --format='%(refname:short)' | sed 's/-[^-]*$//' | sort | uniq -c | sort -rn
For each group with 2+ branches:
SUPERSEDED requires evidence, not just shared prefix:
Example analysis for feature/api-* branches:
### 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
For branches NOT in a related group, categorize individually:
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)
Category definitions:
| Category | Meaning | Delete Command |
|---|---|---|
| SAFE_TO_DELETE | Merged into default branch | git branch -d |
| SQUASH_MERGED | Work incorporated via squash merge | git branch -D |
| SUPERSEDED | Part of a group, work verified in main via PR or in newer branch | git branch -D |
| REMOTE_GONE | Remote deleted, work NOT found in main | Review needed |
| UNPUSHED_WORK | Has commits not pushed to remote | Keep |
| LOCAL_WORK | Untracked branch with unique commits | Keep |
| SYNCED_WITH_REMOTE |
Check ALL worktrees and current directory for uncommitted changes:
# For each worktree path
git -C <worktree-path> status --porcelain
# For current directory
git status --porcelain
Display warnings prominently:
WARNING: ../proj-auth has uncommitted changes:
M src/auth.js
?? new-file.txt
These changes will be LOST if you remove this worktree.
Present everything in ONE comprehensive view. Group related branches together:
## 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?
Use AskUserQuestion with clear options:
Do not proceed until user responds.
Show the EXACT commands that will run, with correct flags:
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)
IMPORTANT: This is the ONLY confirmation needed for deletion. Do not add extra confirmations if -D is required.
Run each deletion as a separate command so partial failures don't block remaining deletions. Report the result of each:
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
If a deletion fails, report the error and continue with remaining deletions.
## 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 |
/git-cleanup-d for merged, -D for squash-merged/supersededThese are common shortcuts that lead to data loss. Reject them:
| Rationalization | Why It's Wrong |
|---|---|
| "The branch is old, it's probably safe to delete" | Age doesn't indicate merge status. Old branches may contain unmerged work. |
| "I can recover from reflog if needed" | Reflog entries expire. Users often don't know how to use reflog. Don't rely on it as a safety net. |
| "It's just a local branch, nothing important" | Local branches may contain the only copy of work not pushed anywhere. |
| "The PR was merged, so the branch is safe" | Squash merges don't preserve branch history. Verify the specific commits were incorporated. |
"I'll just delete all the [gone] branches" | [gone] only means the remote was deleted. The local branch may have unpushed commits. |
| "The user seems to want everything deleted" | Always present analysis first. Let the user choose what to delete. |
| "The branch has commits not in main, so it has unpushed work" | "Not in main" ≠ "not pushed". A branch can be synced with its remote but not merged to main. Always check git log origin/<branch>..<branch>. |
Weekly Installs
762
Repository
GitHub Stars
3.9K
First Seen
Feb 12, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode683
codex682
claude-code677
gemini-cli672
github-copilot665
cursor663
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
136,300 周安装
Refactoring UI 设计系统:实用 UI 设计原则与前端代码优化指南
723 周安装
Refero-design:研究优先设计方法 - 从最佳实践学习,打造独特产品界面
724 周安装
日历自动化:Google Calendar与Outlook会议管理、时间块划分、每日摘要同步工作流
725 周安装
高级运维开发工程师工具包:自动化脚本、CI/CD流水线、Terraform脚手架、部署管理
726 周安装
Claude 代码插件技能开发指南 - 创建模块化AI技能扩展Claude能力
726 周安装
GitHub工作流自动化技能:AI辅助PR审查、问题分类与CI/CD集成
727 周安装
| Up to date with remote |
| Keep |