git-cleanup
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGit Cleanup
Git 清理
Safely clean up accumulated git worktrees and local branches by categorizing them into: safely deletable (merged), potentially related (similar themes), and active work (keep).
通过将累积的git工作树和本地分支归类为可安全删除(已合并)、潜在相关(主题相似)和活跃工作(保留),来安全地清理它们。
When to Use
适用场景
- When the user has accumulated many local branches and worktrees
- When branches have been merged but not cleaned up locally
- When remote branches have been deleted but local tracking branches remain
- 当用户累积了大量本地分支和工作树时
- 当分支已合并但未在本地清理时
- 当远程分支已被删除但本地跟踪分支仍存在时
When NOT to Use
不适用场景
- Do not use for remote branch management (this is local cleanup only)
- Do not use for repository maintenance tasks like gc or prune
- Not designed for headless or non-interactive automation (requires user confirmations at two gates)
- 不要用于远程分支管理(此工具仅用于本地清理)
- 不要用于仓库维护任务如gc或prune
- 不适合无头或非交互式自动化(需要在两个关卡获得用户确认)
Core Principle: SAFETY FIRST
核心原则:安全第一
Never delete anything without explicit user confirmation. This skill uses a gated workflow where users must approve each step before any destructive action.
绝不未经用户明确确认就删除任何内容。 此技能采用 gated 工作流,用户必须在每一步操作前批准,才能执行任何破坏性动作。
Critical Implementation Notes
关键实现说明
Squash-Merged Branches Require Force Delete
Squash合并分支需要强制删除
IMPORTANT: will ALWAYS fail for squash-merged branches because git cannot detect that the work was incorporated. This is expected behavior, not an error.
git branch -dWhen you identify a branch as squash-merged:
- Plan to use (force delete) from the start
git branch -D - Do NOT try first and then ask again for
git branch -d- this wastes user confirmations-D - In the confirmation step, show for squash-merged branches
git branch -D
重要提示: 对squash合并的分支总会执行失败,因为git无法检测到工作内容已被合并。这是预期行为,并非错误。
git branch -d当你识别出某个分支是squash合并时:
- 从一开始就计划使用 (强制删除)
git branch -D - 不要先尝试 然后再要求使用
git branch -d——这会浪费用户的确认步骤-D - 在确认步骤中,为squash合并的分支显示 命令
git branch -D
Group Related Branches BEFORE Categorization
在分类前先对相关分支分组
MANDATORY: Before categorizing individual branches, group them by name prefix:
bash
undefined强制性要求: 在对单个分支进行分类之前,先按名称前缀对它们进行分组:
bash
undefinedExtract common prefixes from branch names
Extract common prefixes from branch names
e.g., feature/auth-, feature/api-, fix/login-*
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:
1. Find the oldest and newest by commit date
2. Check if newer branches contain commits from older ones
3. Check which PRs merged work from each
4. Determine if older branches are superseded
Present related branches together with a clear recommendation, not scattered across categories.
共享前缀的分支(例如 `feature/api`、`feature/api-v2`、`feature/api-refactor`)几乎可以肯定是相关的迭代版本。将它们作为一个组进行分析:
1. 根据提交日期找出最早和最新的分支
2. 检查较新的分支是否包含旧分支的提交
3. 检查哪些PR合并了每个分支的工作内容
4. 判断旧分支是否已被取代
将相关分支放在一起展示并给出明确建议,不要分散到不同分类中。Thorough PR History Investigation
全面调查PR历史
Don't rely on simple keyword matching. For branches:
[gone]bash
undefined不要依赖简单的关键词匹配。对于标记为的分支:
[gone]bash
undefined1. Get the branch's commits that aren't in default branch
1. Get the branch's commits that aren't in default branch
git log --oneline "$default_branch".."$branch"
git log --oneline "$default_branch".."$branch"
2. Search default branch for PRs that incorporated this work
2. Search default branch for PRs that incorporated this work
Search by: branch name, commit message keywords, PR numbers
Search by: branch name, commit message keywords, PR numbers
git log --oneline "$default_branch" | grep -iE "(branch-name|keyword|#[0-9]+)"
git log --oneline "$default_branch" | grep -iE "(branch-name|keyword|#[0-9]+)"
3. For related branch groups, trace which PRs merged which work
3. For related branch groups, trace which PRs merged which work
git log --oneline "$default_branch" | grep -iE "(#[0-9]+)" | head -20
undefinedgit log --oneline "$default_branch" | grep -iE "(#[0-9]+)" | head -20
undefinedWorkflow
工作流
Phase 1: Comprehensive Analysis
阶段1:全面分析
Gather ALL information upfront before any categorization:
bash
undefined在进行任何分类之前,先收集所有信息:
bash
undefinedGet default branch name
Get default branch name
default_branch=$(git symbolic-ref refs/remotes/origin/HEAD
2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
default_branch=$(git symbolic-ref refs/remotes/origin/HEAD
2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
Protected branches - never analyze or delete
Protected branches - never analyze or delete
protected='^(main|master|develop|release/.*)$'
protected='^(main|master|develop|release/.*)$'
List all local branches with tracking info
List all local branches with tracking info
git branch -vv
git branch -vv
List all worktrees
List all worktrees
git worktree list
git worktree list
Fetch and prune to sync remote state
Fetch and prune to sync remote state
git fetch --prune
git fetch --prune
Get merged branches (into default branch)
Get merged branches (into default branch)
git branch --merged "$default_branch"
git branch --merged "$default_branch"
Get recent PR merge history (squash-merge detection)
Get recent PR merge history (squash-merge detection)
git log --oneline "$default_branch" | grep -iE "#[0-9]+" | head -30
git log --oneline "$default_branch" | grep -iE "#[0-9]+" | head -30
For EACH non-protected branch, get unique commits and sync status
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
| 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.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
| 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"`。Phase 2: Group Related Branches
阶段2:对相关分支分组
Do this BEFORE individual categorization.
Identify branch groups by shared prefixes:
bash
undefined在对单个分支分类之前完成此步骤。
通过共享前缀识别分支组:
bash
undefinedList branches and extract prefixes
List branches and extract prefixes
git branch --format='%(refname:short)' | sed 's/-[^-]*$//' | sort | uniq -c | sort -rn
For each group with 2+ branches:
1. **Compare commit histories** - Which branches contain commits from others?
2. **Find merge evidence** - Which PRs incorporated work from this group?
3. **Identify the "final" branch** - Usually the most recent or most complete
4. **Mark superseded branches** - Older iterations whose work is in main or in a newer branch
**SUPERSEDED requires evidence, not just shared prefix:**
- A PR merged the work into main, OR
- A newer branch contains all commits from the older branch
- Name prefix alone is NOT sufficient — similarly named branches may contain independent work
Example analysis for `feature/api-*` branches:
```markdowngit branch --format='%(refname:short)' | sed 's/-[^-]*$//' | sort | uniq -c | sort -rn
对于每个包含2个及以上分支的组:
1. **比较提交历史** - 哪些分支包含其他分支的提交?
2. **查找合并证据** - 哪些PR合并了该组的工作内容?
3. **确定"最终"分支** - 通常是最新或最完整的分支
4. **标记已被取代的分支** - 工作内容已通过PR合并到主分支或存在于较新分支中的旧迭代版本
**已被取代的判断需要证据,不能仅靠共享前缀:**
- PR已将工作内容合并到主分支,或者
- 较新的分支包含旧分支的所有提交
- 仅名称前缀不足以判断——名称相似的分支可能包含独立的工作内容
`feature/api-*`分支组的分析示例:
```markdownRelated Branch Group: feature/api-*
相关分支组: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
undefined| 分支 | 提交数 | 合并的PR | 状态 |
|---|---|---|---|
| feature/api | 12 | #29 (初始API) | 已被取代 - 工作内容已合并到主分支 |
| feature/api-v2 | 8 | #45 (API优化) | 已被取代 - 工作内容已合并到主分支 |
| feature/api-refactor | 5 | #67 (重构) | 已被取代 - 工作内容已合并到主分支 |
| feature/api-final | 4 | 未找到 | 被上述PR的工作内容取代 |
建议: 可删除全部4个分支 - 工作内容已合并到主分支
undefinedPhase 3: Categorize Remaining Branches
阶段3:对剩余分支进行分类
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 | |
| SQUASH_MERGED | Work incorporated via squash merge | |
| SUPERSEDED | Part of a group, work verified in main via PR or in newer branch | |
| 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 | Up to date with remote | Keep |
对于不属于任何相关组的分支,单独进行分类:
分支是否已合并到默认分支?
├─ 是 → 可安全删除(使用-d)
└─ 否 → 是否跟踪远程分支?
├─ 是 → 远程分支已删除?([gone])
│ ├─ 是 → 工作内容是否通过squash合并?(检查主分支的PR)
│ │ ├─ 是 → Squash合并(使用-D)
│ │ └─ 否 → 远程已删除(需要审核)
│ └─ 否 → 本地分支是否领先于远程?(检查: git log origin/<branch>..<branch>)
│ ├─ 是(有输出)→ 未推送的工作内容(保留)
│ └─ 否(无输出)→ 与远程同步(保留)
└─ 否 → 是否有唯一提交?
├─ 是 → 本地工作内容(保留)
└─ 否 → 可安全删除(使用-d)分类定义:
| 分类 | 含义 | 删除命令 |
|---|---|---|
| SAFE_TO_DELETE | 已合并到默认分支 | |
| SQUASH_MERGED | 工作内容通过squash合并被纳入 | |
| SUPERSEDED | 属于某个组,工作内容已通过PR或较新分支验证存在于主分支 | |
| REMOTE_GONE | 远程分支已删除,工作内容未在主分支找到 | 需要审核 |
| UNPUSHED_WORK | 包含未推送到远程的提交 | 保留 |
| LOCAL_WORK | 未被跟踪且包含唯一提交的分支 | 保留 |
| SYNCED_WITH_REMOTE | 与远程分支同步 | 保留 |
Phase 4: Dirty State Detection
阶段4:脏状态检测
Check ALL worktrees and current directory for uncommitted changes:
bash
undefined检查所有工作树和当前目录的未提交更改:
bash
undefinedFor each worktree path
For each worktree path
git -C <worktree-path> status --porcelain
git -C <worktree-path> status --porcelain
For current directory
For current directory
git status --porcelain
**Display warnings prominently:**
```markdown
WARNING: ../proj-auth has uncommitted changes:
M src/auth.js
?? new-file.txt
These changes will be LOST if you remove this worktree.git status --porcelain
**突出显示警告:**
```markdown
警告:../proj-auth 存在未提交的更改:
M src/auth.js
?? new-file.txt
如果删除此工作树,这些更改将丢失。GATE 1: Present Complete Analysis
关卡1:展示完整分析结果
Present everything in ONE comprehensive view. Group related branches together:
markdown
undefined在一个完整的视图中展示所有信息。将相关分支分组展示:
markdown
undefinedGit Cleanup Analysis
Git清理分析结果
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)
组:feature/api-*(4个分支)
| 分支 | 状态 | 证据 |
|---|---|---|
| feature/api | 已被取代 | 工作内容通过PR #29合并 |
| feature/api-v2 | 已被取代 | 工作内容通过PR #45合并 |
| feature/api-refactor | 已被取代 | 工作内容通过PR #67合并 |
| feature/api-final | 已被取代 | 旧迭代版本,已分叉 |
建议:删除全部4个分支(工作内容已在主分支)
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 |
可安全删除(已合并,使用-d)
| 分支 | 合并到 |
|---|---|
| fix/typo | main |
可安全删除(squash合并,需要使用-D)
| 分支 | 合并方式 |
|---|---|
| feature/login | PR #42 |
需要审核([gone]远程分支,未找到PR)
| 分支 | 最后一次提交 |
|---|---|
| experiment/old | abc1234 "WIP something" |
保留(活跃工作)
| 分支 | 状态 |
|---|---|
| wip/new-feature | 5个未推送的提交 |
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:
- Delete all recommended (groups + merged + squash-merged)
- Delete specific groups/categories
- Let me pick individual branches
**Do not proceed until user responds.**| 路径 | 分支 | 状态 |
|---|---|---|
| ../proj-auth | feature/auth | 已过期(已合并) |
摘要:
- 4个相关分支(feature/api-*)- 建议全部删除
- 1个已合并分支 - 可安全删除
- 1个squash合并分支 - 可安全删除
- 1个需要审核
- 1个需要保留
你想要清理哪些内容?
使用AskUserQuestion提供清晰选项:
- 删除所有推荐的内容(分支组 + 已合并 + squash合并)
- 删除特定分支组/分类
- 让我选择单个分支
**在用户回复前不要继续执行。**GATE 2: Final Confirmation with Exact Commands
关卡2:使用精确命令进行最终确认
Show the EXACT commands that will run, with correct flags:
markdown
I will execute:展示将执行的精确命令,包含正确的参数:
markdown
我将执行以下命令:Merged branches (safe delete)
已合并分支(安全删除)
git branch -d fix/typo
git branch -d fix/typo
Squash-merged branches (force delete - work is in main via PRs)
Squash合并分支(强制删除 - 工作内容已通过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 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.git worktree remove ../proj-auth
确认执行?(是/否)
**重要提示:** 这是删除操作所需的唯一确认步骤。如果需要使用`-D`,不要额外添加确认步骤。Phase 5: Execute
阶段5:执行操作
Run each deletion as a separate command so partial failures don't block remaining deletions. Report the result of each:
bash
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-authIf a deletion fails, report the error and continue with remaining deletions.
将每个删除操作作为单独的命令运行,这样部分失败不会阻止剩余操作的执行。报告每个操作的结果:
bash
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如果某个删除操作失败,报告错误并继续执行剩余操作。
Phase 6: Report
阶段6:生成报告
markdown
undefinedmarkdown
undefinedCleanup Complete
清理完成
Deleted
已删除
- fix/typo
- feature/login
- feature/api
- feature/api-v2
- feature/api-refactor
- feature/api-final
- Worktree: ../proj-auth
- fix/typo
- feature/login
- feature/api
- feature/api-v2
- feature/api-refactor
- feature/api-final
- 工作树:../proj-auth
Remaining (4 branches)
剩余分支(4个)
| Branch | Status |
|---|---|
| main | current |
| wip/new-feature | active work |
| experiment/old | needs review |
undefined| 分支 | 状态 |
|---|---|
| main | 当前分支 |
| wip/new-feature | 活跃工作 |
| experiment/old | 需要审核 |
undefinedSafety Rules
安全规则
- Never invoke automatically - Only run when user explicitly uses
/git-cleanup - Two confirmation gates only - Analysis review, then deletion confirmation
- Use correct delete command - for merged,
-dfor squash-merged/superseded-D - Never touch protected branches - main, master, develop, release/* (filtered programmatically)
- Block dirty worktree removal - Refuse without explicit data loss acknowledgment
- Group related branches - Don't scatter them across categories
- 绝不自动执行 - 仅当用户明确使用时才运行
/git-cleanup - 仅设置两个确认关卡 - 分析结果审核,然后是删除确认
- 使用正确的删除命令 - 已合并分支使用,squash合并/已被取代分支使用
-d-D - 绝不触碰受保护分支 - main、master、develop、release/*(通过程序过滤)
- 阻止删除脏工作树 - 未经用户明确确认数据丢失风险,拒绝执行
- 对相关分支分组 - 不要将它们分散到不同分类中
Rationalizations to Reject
需拒绝的不合理操作理由
These 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 | |
| "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 |
这些常见的捷径会导致数据丢失,应拒绝:
| 不合理理由 | 错误原因 |
|---|---|
| "这个分支很旧了,删除应该没问题" | 分支的存在时间无法说明其合并状态。旧分支可能包含未合并的工作内容。 |
| "如果需要,我可以从reflog恢复" | Reflog条目会过期。用户通常不知道如何使用reflog。不要将其作为安全保障依赖。 |
| "这只是本地分支,没什么重要的" | 本地分支可能包含唯一未推送到任何地方的工作内容副本。 |
| "PR已合并,所以这个分支可以安全删除" | Squash合并不会保留分支历史。需要验证特定提交是否已被纳入。 |
"我要删除所有 | |
| "用户似乎想要删除所有内容" | 始终先展示分析结果。让用户选择要删除的内容。 |
| "这个分支有主分支没有的提交,所以有未推送的工作内容" | "不在主分支中"不等于"未推送"。分支可能已与远程同步但未合并到主分支。始终检查 |