Skip to content

Instantly share code, notes, and snippets.

@tomholford
Created February 20, 2026 22:52
Show Gist options
  • Select an option

  • Save tomholford/0aa4cdb1340a9b5411ed6eaadabfcf37 to your computer and use it in GitHub Desktop.

Select an option

Save tomholford/0aa4cdb1340a9b5411ed6eaadabfcf37 to your computer and use it in GitHub Desktop.
Git branch cleanup aliases for zsh — worktree-safe, handles squash/rebase merges, auto-detects default branch

Git Branch Cleanup Aliases for Zsh

Improved one-liners for cleaning up stale local branches after merge. Inspired by the CIA's leaked dev docs and HN discussion.

Helpers

# 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/@@'
}

Aliases

# 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'

How it works

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.

Key improvements over the basic one-liner

  • Auto-detects default branch instead of hardcoding main/master
  • Checks against origin/<default> not just HEAD
  • xargs -n 1 so one failure doesn't abort the rest
  • Worktree-safe
  • Handles squash/rebase merges (not just regular merges)
  • Prunes stale remote refs automatically
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment