Branch sprawl is real
After six months on an active project, git branch can list dozens of dead branches - merged features, abandoned experiments, hotfixes for releases nobody runs. Cleanup is a five-minute monthly habit that pays back compounded interest in clarity.
Listing merged branches
git checkout main
git pull
git branch --merged # local branches already in main
git branch --no-merged # the still-active ones
Bulk-deleting merged branches
The classic one-liner:
git branch --merged main | grep -v -E '^\\*|^\\s*(main|master|develop)$' | xargs -n 1 git branch -d
This filters the asterisked current branch and protected branches, then deletes the rest. -d (lowercase) refuses to delete unmerged branches; -D forces deletion. Always start with -d.
Pruning stale remote-tracking branches
When a teammate deletes a remote branch, your origin/feature-x reference lingers. Prune it:
git fetch --prune
# or set it permanently
git config --global fetch.prune true
Finding branches without an upstream
git branch -vv | grep -v origin/
# or
git for-each-ref --format='%(refname:short) %(upstream)' refs/heads
Branches with no upstream are usually local-only experiments. Inspect them, then delete or push.
Detecting branches whose remotes are gone
git fetch --prune
git branch -vv | awk '/: gone]/{print $1}'
Pipe that into xargs git branch -D when you are confident.
A cleanup alias
git config --global alias.cleanup '!git fetch --prune && git branch --merged main | grep -v -E "^\\*|^\\s*(main|master|develop)$" | xargs -n 1 git branch -d'
Reflog is your safety net
Even after deletion, branches survive in the reflog for the configured expiry (90 days by default). To recover:
git reflog | grep accidentally-deleted
git checkout -b accidentally-deleted <sha>
A monthly ritual
Block five minutes on the first Monday of each month: pull main, prune, list merged branches, delete the obvious ones, and skim the rest. Your git branch output stays meaningful, your tab completion stays useful, and your mental model of the repo stays accurate.