Delete All Local Git Branches Except for master or main

In Brief
Treat this as a deliberate cleanup step, not a command to run casually. Confirm whether the branch you are keeping is main, master, or both, then delete only local branches you know are safe to remove. Use -d when Git should protect unmerged work, and reserve -D for branches you genuinely mean to force‑delete.
Have you ever found yourself drowning in a sea of old feature branches whilst working with Git? Whether it's local or remote branches that are cluttering up your workspace, sometimes a little cleanup is necessary. If you're looking to tidy up by deleting all branches except the master (or main), here's a couple of one‑liners that I use...
Why Delete Branches?
First off, why even bother deleting branches? I tend to find that after a while, my local repository becomes a huge list of feature branches, bugfixes, tweaks, and changes. Particularly as you draw towards the close of a project and really you are only dealing with very minor changes on each branch (but on a lot of them), it can get really unwieldy. They have all long‑since been merged into production and forgotten about anyway.
So, having a good clean‑out every now and again keeps things manageable. It helps in avoiding confusion over which branches are active or outdated, and makes it easier to find what you need, when you need it.
Deleting Local Branches
So, starting with local branches. Before deleting anything, ensure you've merged anything that you want to keep into the master or main branch. Then run this preview: it prints the local branches that would be selected without deleting them.
current_branch=$(git branch --show-current)
git for-each-ref --format='%(refname:short)' refs/heads/ |
while IFS= read -r branch; do
case "$branch" in
"$current_branch"|master|main) ;;
*) printf '%s\n' "$branch" ;;
esac
doneNote: The preview uses Git's structured ref output and skips the currently checked‑out branch as well as branches named master or main. Review every name before continuing.
Nothing has been deleted at this point. The output is the exact list that the deletion command below will process.
This avoids parsing the decorated output from git branch or using a substring match that could select the wrong names. The -- before the branch name also prevents a name beginning with a hyphen from being treated as an option.
Once the preview is correct, use the same explicitly parsed list with Git's safe deletion option:
current_branch=$(git branch --show-current)
git for-each-ref --format='%(refname:short)' refs/heads/ |
while IFS= read -r branch; do
case "$branch" in
"$current_branch"|master|main) ;;
*) git branch -d -- "$branch" ;;
esac
doneThe selection logic remains identical to the preview: it skips the current branch, master, and main, then passes each remaining name to git branch -d as a separate argument. If the preview changes before deletion, stop and investigate.
The difference between -d and -D
For the sake of a little extra security, in the commands above, I've included the use of -d (in lowercase) for deletion. If you use those commands, you may find that some branches aren't deleted, and you instead get presented with messages that look like this:
error: The branch 'feat/interstitial-messaging' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feat/interstitial-messaging'.Allow me to explain. Git's -d option checks whether the branch has been fully merged into its configured upstream branch, or into HEAD if no upstream is configured. That is not necessarily main or master, so still check that the work is where you intend to keep it. A squash merge can also leave Git unable to recognise the original commits as merged, even when their changes have been carried across.
If you're absolutely sure that you want to delete it, you can use -D instead. This option forcefully deletes a branch regardless of whether it has been merged or not. As you might appreciate, a mistake here could lead to you permanently losing your code if it hasn't been integrated somewhere else.
If you are absolutely and positively sure that you want to force‑delete the reviewed list, run the preview again immediately beforehand, check that the current branch, master, and main are absent, then use the same guarded loop with -D:
current_branch=$(git branch --show-current)
git for-each-ref --format='%(refname:short)' refs/heads/ |
while IFS= read -r branch; do
case "$branch" in
"$current_branch"|master|main) ;;
*) git branch -D -- "$branch" ;;
esac
doneWhat About Deleting Remote Branches?
Generally, I would advocate against using copy‑and‑paste commands that you've found on the internet to delete in your remote repository source. In my mind, it makes much more sense to spend time in your Git UI on GitHub (or wherever) manually going through each branch.
Nevertheless, it would be a half‑complete article if I didn't include the how‑to of deleting feature branches from remote.
So firstly, we need to fetch the latest updates from your remote:
git fetch -pWe use the -p or --prune flag here to get rid of any remote‑tracking references that no longer exist on the remote.
Now, for the actual deletin':
git for-each-ref --format='%(refname:strip=3)' refs/remotes/origin/ | grep -Ev '^(HEAD|main|master)$'This lists the branches on origin and excludes exact master and main. Review that list against the remote's protected branches before using git push origin --delete for any one branch.
A Word of Caution
I already mentioned above how dangerous it can be to simply copy and paste code that you've found on a random website into your terminal. Hopefully, there's enough information about me floating around for you to feel relatively safe in what I'm sharing with you, but please ‑ always do your own research.
Recovery after deleting a branch is not guaranteed. You may be able to recreate it if you can still find its tip commit, perhaps through a reflog or another clone, but don't rely on that as your safety net. I have been known to turn to Time Machine backups in a semi‑futile attempt to recover lost work. Double‑check the branches you're about to delete and make sure there's nothing you still need before hitting enter.