Once a PR is merged, the local branch behind it is dead weight. After a few months git branch scrolls past a screenful and you can’t find the branch you’re working on. The commands to clean this up are simple. The point is knowing what’s safe to delete.
-d is safe, -D is dangerous
There are two ways to delete a local branch. git branch -d deletes only branches that are already merged. If unmerged commits remain, it refuses.
$ git branch -d feature/login
Deleted branch feature/login (was 13d794b).
$ git branch -d feature/experimental
error: the branch 'feature/experimental' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D feature/experimental'
feature/login was merged into main, so it’s gone instantly. feature/experimental still has unique commits, so the delete is refused. That refusal stops you from throwing away work by accident.
git branch -D ignores merge status and force-deletes. Use it when you ignore the error above and really do want the branch gone.
git branch -D feature/experimental
Use -d by default. When Git refuses, it’s a signal that there’s unfinished work. Reach for -D deliberately, only to discard experimental branches.
To see what’s safe before deleting anything, these two commands tell you.
$ git branch --merged # merged into current branch -> safe to delete
feature/login
* main
$ git branch --no-merged # has unique commits -> delete with care
feature/experimental
Deleting remote branches
Delete a remote branch with push --delete.
git push origin --delete feature/user-authentication
Deleting it on the remote leaves a stale tracking reference (origin/feature/...) in everyone else’s local clone, and in yours. That leads to the next problem.
‘gone’ branches
A branch that was merged and deleted on the remote leaves a trace locally. git branch -vv shows each branch’s tracking status and exposes it.
$ git branch -vv
bugfix/typo 884c27d [origin/bugfix/typo: gone] Fix typo
* main 24e0729 [origin/main]
: gone] means the upstream this branch tracked no longer exists, which is the usual aftermath of a merged PR whose remote branch got auto-deleted. Most local cruft is this kind of branch.
First clean up the vanished references with fetch -p, then pick out the locals marked gone and delete them.
git fetch -p && git branch -vv | grep ': gone]' | awk '{print $1}' | xargs git branch -D
Deleted branch bugfix/typo (was 884c27d).
If passing -p every time is annoying, configure fetch to prune automatically.
git config --global fetch.prune true
The -D in that command is intentional. A gone branch was merged on the remote, but depending on how it was merged (squash merges especially), it can still look unmerged locally, which would make -d refuse.
Bulk-deleting merged local branches
For branches that don’t end up gone (merged locally, or with auto-delete off), feed the --merged list straight in. Exclude the current branch (*) and any you want to protect.
git branch --merged | grep -Ev '^\*|main|master|develop' | xargs -n 1 git branch -d
Because this uses -d, Git skips anything unmerged that slips through. To preview what would be deleted, drop the xargs part and run just the list.
git branch --merged | grep -Ev '^\*|main|master|develop'
Recovering with reflog
If you nuked an unmerged branch with -D, its last commit is still in the reflog, which records every position HEAD has moved through for 90 days.
$ git reflog
4fc8d7b HEAD@{0}: checkout: moving from feature/user-auth to main
e723b01 HEAD@{1}: commit: Add user authentication feature
559cee6 HEAD@{2}: commit: Create login form
4fc8d7b HEAD@{3}: checkout: moving from main to feature/user-auth
Find the deleted branch’s last commit hash (e723b01) and create a new branch from that point.
git checkout -b feature/user-auth e723b01
$ git log --oneline
e723b01 Add user authentication feature
559cee6 Create login form
4fc8d7b Initial commit
The commits are back. The reflog is local-only and gets cleaned after 90 days, so tag anything you need to keep long-term before deleting it.
git tag archive/feature-user-auth feature/user-authentication