CLI AI

Delete all merged local branches in git

2026-07-02

git branch --merged main | grep -vE '^\*|main|develop' | xargs -r git branch -d deletes every local branch already merged into main, except main and develop. It won't touch branches that were squash-merged: git never records their commits as merged, so --merged stays blind to them forever — check git branch -vv for the gone marker instead.

1. See what git considers merged first

clai
$ clai show branches merged into main→ git branch --merged main  feature-merged* main

This is a read-only list. The current branch is marked with an asterisk and can't be deleted — nor does it need to be.

2. Delete them, but protect your main branches

clai
$ clai delete local branches merged into main, except main and develop→ git branch --merged main | grep -vE '^\*|main|develop' | xargs -r git branch -dDeleted branch feature-merged (was fb52fec).

-d is safe: git refuses to delete a branch with unmerged commits. The grep -vE filter is required, or main itself ends up in the list. -r keeps xargs from running at all when the list is empty.

3. Confirm nothing important got deleted

clai
$ clai show remaining branches→ git branch  feature-open* main

feature-open still has unmerged commits and stayed put. That's the check that the filter worked correctly.

4. The trap: squash merge hides from --merged

clai
$ clai show branches whose upstream was deleted on the server→ git fetch --prune && git branch -vv | grep '\[.*: gone\]'

This is the part that matters. A squash merge never puts the branch's commits into main's history as-is, so --merged will never show it — it sits on your machine forever. But its upstream on the server is gone once the pull request merges, and git branch -vv flags that branch as gone. That's the reliable signal.

5. Delete branches with a vanished upstream

clai
$ clai delete local branches whose source branch was deleted on the server→ git fetch --prune && git branch --format '%(refname:short) %(upstream:track)' | awk '$2 == "[gone]" {print $1}' | xargs -r git branch -D⚠ CAUTION — this is -D, not -d: it also deletes unmerged branches

-D is required precisely because git doesn't consider squash-merged branches merged, and refuses on -d. The price: read the list from the previous command before you run this one.

6. Clean up stale remote-tracking refs

clai
$ clai remove references to branches that no longer exist on the server→ git remote prune origin

This only removes local origin/* references — it doesn't touch your branches. Handy when autocomplete keeps suggesting branches that were closed six months ago.

Gotchas

  • -d and -D carry different risk. Lowercase deletes only what's merged, uppercase deletes anything. Squash-merged branches need uppercase, so list first, delete second.
  • --merged without an argument counts from the current branch. Standing on a feature branch gives you a completely different list. Name the base explicitly: --merged main.
  • Deleting a local branch doesn't delete it on the server. That takes git push origin --delete <name>. And the reverse: deleting it on the server doesn't remove your local copy.

Related questions

How do I recover a branch I deleted by accident? git reflog shows its last commit; then git branch <name> <hash>. Works until garbage collection runs.

Why won't a branch delete? Either you're standing on it — switch to main — or it has unmerged commits and -d is protecting you from losing them.

How do I see when a branch was last updated? git branch --sort=-committerdate -v sorts by freshness — useful for spotting abandoned ones.

See also

CliAI turns the phrasing above into the exact command and shows it before running anything — the -D step gets a CAUTION label, not a silent run. Install it in one line.