Improved one-liners for cleaning up stale local branches after merge. Inspired by the CIA's leaked dev docs and HN discussion.
# Auto-detect default branch (main/master) from remote
function _git_default_branch() {
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main"
}
# List branches checked out in worktrees (so we never delete them)
function _git_worktree_branches() {
git worktree list --porcelain 2>/dev/null | grep '^branch ' | sed 's@^branch refs/heads/@@'
}# Delete branches that are merged into origin's default branch
alias cleanmerged='git branch --merged origin/$(_git_default_branch) | grep -Ev "^\s*(\*|master|develop|main|prod)" | grep -Fxv "$(_git_worktree_branches)" | xargs -n 1 git branch -d'
# Delete branches whose remote tracking branch is gone (handles squash/rebase merges)
alias cleangone='git fetch --prune && git branch -vv | grep "\[gone\]" | grep -Ev "^\s*\*" | awk "{print \$1}" | grep -Fxv "$(_git_worktree_branches)" | xargs -n 1 git branch -D'
# Delete branches that were squash-merged (tree-diff detection, safety net for cleangone)
alias cleansquashed='_default=$(_git_default_branch) && _wt=$(_git_worktree_branches) && git for-each-ref refs/heads/ "--format=%(refname:short)" | while read branch; do echo "$_wt" | grep -Fxq "$branch" && continue; mergeBase=$(git merge-base origin/$_default $branch) && [[ $(git cherry origin/$_default $(git commit-tree $(git rev-parse $branch^{tree}) -p $mergeBase -m _)) == "-"* ]] && git branch -D $branch; done'
# Run all three
alias cleanbranches='cleanmerged && cleangone && cleansquashed'
# Pull with prune + auto-cleanup
alias pull='git pull --prune && cleanbranches'cleanmerged — the classic approach. Checks git branch --merged against origin/main (or origin/master), filters out protected branches, and deletes with -d (safe delete, won't delete unmerged work).
cleangone — handles squash and rebase merges. After git fetch --prune, any local branch whose remote tracking branch no longer exists shows as [gone] in git branch -vv. These are deleted with -D since git can't verify they were "merged" in the traditional sense.
cleansquashed — safety net using the commit-tree/cherry trick. Reconstructs what the squash-merge would look like and checks if the tree already exists in the default branch. Catches squash-merged branches even if the remote wasn't pruned yet.
All three skip the current branch, protected branches (main, master, develop, prod), and any branch checked out in a git worktree.
pull — combines git pull --prune (fetches + removes stale remote-tracking refs) with the full cleanup chain.
- Auto-detects default branch instead of hardcoding
main/master - Checks against
origin/<default>not just HEAD xargs -n 1so one failure doesn't abort the rest- Worktree-safe
- Handles squash/rebase merges (not just regular merges)
- Prunes stale remote refs automatically