# git cleanup Preview and remove redundant Git branches and their clean linked worktrees. Works in any repository without a hosting service. Requires Git 2.40+ and Python 3.11+. GitHub PR evidence optionally uses `gh`. ```bash git cleanup # explain what can go and what stays git cleanup --apply # refresh remote refs, back up, then delete git cleanup --local # local branches only; no network git cleanup --fetch # refresh remote refs before previewing git cleanup --into feature/combined # also recognize squashed work in this branch git cleanup --github # consult merged and open GitHub PRs git cleanup --scan-history 200 # also search historical integration results git cleanup --obsolete old-spike # explicitly abandon a local branch git cleanup --obsolete origin/spike # explicitly abandon a remote branch git cleanup --protect 'release/*' # retain additional branches git cleanup --json # machine-readable plan git cleanup --no-color # plain-text output git cleanup --color=always # force color, including in a pipe ``` Add this directory to `PATH`, or symlink its executable into a directory already on `PATH`. The executable uses `python3` from `PATH`, which must be version 3.11 or newer. From this checkout you can also run `./git-cleanup --help`. Human-readable plans use color automatically on terminals: red for deletion, green for retention, and highlighted headings and recovery information. Use `--color=auto|always|never` to choose a mode; bare `--color` means `always`, and `--no-color` means `never`. In automatic mode, piped or redirected output, `TERM=dumb`, and a nonempty `NO_COLOR` environment variable disable color. Explicit color options take precedence over the environment. JSON output always remains free of color codes. ## What counts as redundant A branch is eligible when its tip is an ancestor of the integration baseline, or its complete net changes have the exact same file contents and modes at the baseline tip. The latter recognizes many squash merges and cherry-picks, including binary files, renames, and deletions. It deliberately declines source histories containing merges, repeated trees (including empty commits), wholly reverted paths, or more than 1,000 exclusive commits. Partial incorporation and later edits remain protected. Content matching proves the resulting files; it does not prove that commit messages or authorship were preserved. Local branches incorporated by ancestry into another retained local branch can also go. The receiving branch is kept for this run. `--into REF` additionally permits content matching against an explicitly retained recipient. Merely publishing a branch to its remote counterpart is not cleanup evidence. By default content matching examines recipient tips only. `--scan-history N` opts into checking the latest N commits reachable from each recipient, allowing cleanup after landed files have changed again. This is a broader inference; inspect the reported historical commit before applying it. `--github` queries open PRs and up to 1,000 merged PRs in the selected GitHub remote using `gh`. It aborts if the open-PR query reaches its 1,000-record limit. A merged PR proves only its exact recorded head, and its merge commit must be reachable from a retained recipient. Open PR branches are kept. Cross-repository PRs are excluded. Query failures abort cleanup. Closed but unmerged PRs, deleted upstreams, old timestamps, and branch names do not by themselves prove anything. Use `--obsolete` to explicitly select abandoned branches; this bypasses proof, but still preserves protected branches and unsafe worktrees. ## Baseline and remote `--base REF` overrides `cleanup.base`. Otherwise `cleanup.mainBranch`, the selected remote's cached default branch, then `main` or `master` identify the integration branch. Its selected-remote tracking ref is preferred over its local ref. Without a recognizable integration branch, use `--base` or configure `cleanup.mainBranch`. Empty option values, `cleanup.base`, and `cleanup.mainBranch` settings are errors. The selected remote is `--remote NAME`, otherwise `origin`, otherwise the only configured remote. Multiple remotes without `origin` require a selection. Only this remote's branches are considered. `--local` disables remote branch cleanup and all network calls, using the local integration branch by default. The default preview does not write files, fetch, or delete anything. Cached remote refs may be stale. `--fetch` explicitly refreshes and prunes the selected remote's tracking refs even during a preview; `--apply` does so automatically. Remote deletion requires incorporation into a retained branch on that remote (or explicit `--obsolete`), and a single matching fetch/push URL. Remote tips and recipient tips are checked live before deletion. The resolved endpoint is pinned for each operation, including when Git URL rewrites are configured. Each push uses an explicit expected-object lease, so a branch updated since inspection is not deleted. Fetches and pushes explicitly exclude tags and recursive submodule operations, even when enabled in your ordinary Git configuration. Command-scoped settings such as `git -c cleanup.protect=topic cleanup --apply` are honored. ## Safeguards and recovery The current and primary worktrees, `main`, `master`, the integration/default branch, `--base`, and `--into` refs are protected. Repeat `--protect GLOB`, or configure repeated `cleanup.protect` values, for other long-lived branches. Detached, locked, missing, dirty, untracked, ignored, or operation-in-progress worktrees are retained. Ignored build output is retained too: move or remove it yourself before cleanup. Stale worktree registrations are left for manual inspection. Worktrees with `assume-unchanged` or `skip-worktree` index entries (including sparse checkouts) are retained because these can hide edits from Git. No worktree removal uses force. Before deletion, every selected tip and its proof recipient get recovery refs under `refs/cleanup//`. An execution journal is written under the common Git directory at `git-cleanup/.json`. These keep commits reachable through normal garbage collection. Recover a branch with: ```bash git for-each-ref refs/cleanup/ git branch recovered refs/cleanup//heads/old-branch ``` Remote tips are under `refs/cleanup//remotes//`. A recovered remote branch can be pushed normally. Worktree directories can be recreated with `git worktree add`; their ignored/untracked data is never discarded. Recovery refs are retained until you deliberately delete them. Branch-specific Git configuration is left intact, avoiding a race with branch recreation. Run cleanup while other Git operations and editors are idle. Tips, recipients, and worktree state are rechecked during apply; local ref deletion uses an expected-object transaction. Recipient proofs use one captured object, and transaction locks protect the final check for symbolic-ref changes. Worktree removal and remote operations cannot form one atomic transaction. An error stops execution, leaves recovery refs and the journal, and may leave a partially completed cleanup. Shallow repositories, partial clones (including promisor repositories), and legacy graft files are refused; replacement objects are ignored for proof. Partial clones must be replaced with complete clones so inspection never fetches missing objects. ## Development ```bash python3 -m unittest discover -s tests -v ``` Tests use disposable repositories and local bare remotes; they do not delete real repository branches or contact a hosted service.