cleanup-git · diff
git:20260716.29b4d22 to git:20260928.b87f747
20 added, 64 removed. Audit A to A.
---
description: Remove merged local branches and stale git worktrees. Use when the user
says "cleanup branches", "prune worktrees", "tidy git", "remove merged branches",
"delete merged branches", "gone branches", or wants to clean local git state. NOT
for creating commits, creating worktrees, or configuring git hooks.
name: cleanup-git
---
# Cleanup Git
- Clean local git branches and worktrees after work has merged. Dry-run first. Destructive commands require user approval.
-
- Prefer a repo-local `scripts/cleanup-git.sh` when the target repo ships one. If it does not, use the bundled skill script from this skill's `scripts/` directory. Do not improvise destructive cleanup commands outside the script workflow.
-
- ## Command
-
- Run from anywhere inside a repository:
+ All deletion goes through the cleanup script: the target repo's own `scripts/cleanup-git.sh` if it ships one, otherwise this skill's `scripts/cleanup-git.sh`. Do not hand-write branch or worktree deletion commands.
```bash
- scripts/cleanup-git.sh
- scripts/cleanup-git.sh --apply
- scripts/cleanup-git.sh --apply --force
- scripts/cleanup-git.sh --base <ref>
+ scripts/cleanup-git.sh # preview (default)
+ scripts/cleanup-git.sh --apply # delete after user approval
+ scripts/cleanup-git.sh --apply --force # also delete branches with ahead commits
+ scripts/cleanup-git.sh --base <ref> # override base detection
```
- ## Branch Detection
-
- The script fetches/prunes remotes, then chooses the comparison branch:
-
- 1. Remote default branch from `refs/remotes/<remote>/HEAD`.
- 2. Local `main`, `master`, `trunk`, `develop`, or `dev`.
- 3. Remote `<remote>/{main,master,trunk,develop,dev}`.
-
- Use `--base <ref>` only for unusual repositories.
-
- ## What It Removes
-
- - Worktrees whose branch has a GitHub PR in `MERGED` state, is merged into the detected base branch, or whose upstream is gone.
- - Local branches with the same criteria.
-
- When `gh` is available, PR `MERGED` state is the source of truth for squash/rebase merges. Do not miss those just because `git merge-base --is-ancestor` fails after history rewrite. If no PR is found, or `gh` is unavailable, fall back to the git-based checks.
-
- ## Guards
+ The script fetches and prunes remotes, then picks the base from the remote default branch, then local or remote `main`, `master`, `trunk`, `develop`, or `dev`.
- Hard guards:
+ ## What the script decides
- - Skip the current worktree.
- - Skip the current branch.
- - Skip the detected base branch and common long-lived branches: `main`, `master`, `trunk`, `develop`, `dev`.
- - Keep worktrees with uncommitted changes.
+ A branch or its worktree is a candidate when one of these holds:
- Soft guard:
+ - `PR merged`: `gh pr view <branch>` reports `MERGED`. This catches squash and rebase merges; ahead commits count only past the PR head commit.
+ - `upstream gone`: the tracking branch was deleted.
+ - `merged`: the branch is an ancestor of the base.
- - Keep items with commits ahead of the base branch. `--force` overrides only this guard.
- - For merged PRs, treat only commits added after the PR head as "ahead". A squash/rebase-merged branch with no new local commits is still removable.
+ It always skips the current worktree, the current branch, the base, and `main`/`master`/`trunk`/`develop`/`dev`. It keeps dirty worktrees. It keeps candidates with ahead commits, or an unknown ahead count, unless `--force` is passed. A branch with none of these signals prints `skip ... (active)`; without `gh`, squash-merged branches land there.
## Workflow
- 1. Run `scripts/cleanup-git.sh` and show the preview.
- 2. Read reasons literally: `remove ... (PR merged)`, `remove ... (upstream gone)`, `KEEP ... (dirty)`, `KEEP ... (PR merged, N ahead — use --force)`.
- 3. Surface every `KEEP` line as a human decision.
- 4. Ask before running `scripts/cleanup-git.sh --apply`.
- 5. Use `--force` only when the user confirms ahead commits are throwaway.
+ 1. Run the preview and show it. Read each line literally: `remove <worktree>` and `delete <branch>` are candidates; `KEEP ... (dirty)`, `KEEP ... (N ahead — use --force)`, and `KEEP ... (ahead unknown — run git fetch ...)` need the user; `skip` lines for the current, base, or protected branch are guarded; mention `skip ... (active)` lines when `gh` is unavailable.
+ 2. Present every `KEEP` line as a decision for the user.
+ 3. Ask before `--apply`. Add `--force` only when the user confirms the ahead commits are throwaway.
+ Stop if the directory is not a git repo. If no base is found, ask for `--base <ref>`. A fetch failure makes the preview use stale refs (say so) and makes `--apply` refuse.
+
## Output
```text
GIT CLEANUP
- ===========
Status: PREVIEW | APPLIED | BLOCKED
Base: <ref>
-
- Remove:
- - <branch/worktree> — <reason>
-
- Keep:
- - <branch/worktree> — <reason and user decision needed>
-
- Verification:
- - <command> — pass/fail/not run
+ Remove: <branch/worktree> — <reason>
+ Keep: <branch/worktree> — <reason and decision needed>
+ Verification: <command> — pass/fail/not run
```
- ## Conditional References
-
- - [gh-cli.md](references/gh-cli.md) — read when `gh` is available and PR-state detection is needed: squash/rebase merge detection, rate-limit batching, offline fallback.
-
- ## Failure Handling
-
- - Not a git repo: say so and stop.
- - Base branch not found: ask for `--base <ref>`.
- - Fetch fails during preview: report that refs may be stale.
- - Fetch fails during apply: stop; do not delete with stale refs.
- - Dirty worktree: keep it and ask the user what to do.
- - Ahead commits: keep unless the user explicitly approves `--force`.