Git: How to clean up squash-merged branches

Git hosts offer a “squash merge” option that merges a reviewed branch by combining all its commits into one. This leads to a linear Git history on your main branch, which is easier to understand and refer back to code reviews.
If you use squash-merging, you may have found it tiresome to clean up your local branches after they are merged. You can do it manually, but that requires you to keep on top of which branches are merged. In this post, we’ll build a little Git alias that can delete these branches automatically.
A delicious example
Imagine you are creating mushroom risotto with Git, and this is the history on your main
branch:
$ git log --graph --oneline
* 1190bf0 (HEAD -> main, origin/main) Chop and fry onions (#2)
* ccee512 Create mushroom stock (#1)
* 5cb027b Set up kitchen
After the first commit “Set up kitchen”, two branches have been squash-merged to add the mushroom stock and onions.
Using --all
reveals your local feature branches stock
, onions
, and the work-in-progress mushrooms
:
$ git log --graph --oneline --all
* 5a34e13 (origin/mushrooms, mushrooms) Chop mushrooms
* 1190bf0 (HEAD -> main, origin/main) Chop and fry onions (#2)
| * 8b96195 (origin/onions, onions) Fry onions
| * b01a25f Chop onions
|/
* ccee512 Create mushroom stock (#1)
| * 06b7da0 (origin/stock, stock) Create mushroom stock
|/
* 5cb027b Set up kitchen
Because squash-merging recreates commits, the local stock
and onions
branches point at different versions of the commits to those on main
.
After a merge there are two versions of each branch to remove: the local one (e.g. stock
) and the remote one (e.g. origin/stock
).
You’d normally remove remote branches on your Git host, such as with GitHub’s “Automatically delete head branches” feature. Once the remote branch is removed, you need to fetch this fact locally with the --prune
option of git pull
(or git fetch
):
$ git pull --prune
From github.com:adamchainz/example
- [deleted] (none) -> origin/onions
- [deleted] (none) -> origin/stock
Already up to date.
Okay, great, those labels will no longer apper in git log
, or git branch -a
.
Removing the local versions takes a few more steps. After git pull --prune
, you can list your branches with their upstream status with --format
:
$ git branch --format '%(refname:short) %(upstream:track)'
main
mushrooms
onions [gone]
stock [gone]
The [gone]
indicates the status of the upstream branch. (Currently “gone” is not translated into your local language, but it may be in a future version of Git.)
You can use this output with good ol’ awk to select only the “gone” branches:
$ git branch --format '%(refname:short) %(upstream:track)' | awk '$2 == "[gone]" { print $1 }'
onions
stock
You can pipe this on to xargs
to run git branch -D
on each branch:
$ git branch --format '%(refname:short) %(upstream:track)' | awk '$2 == "[gone]" { print $1 }' | xargs -r git branch -D
Deleted branch onions (was 8b96195).
Deleted branch stock (was 06b7da0).
Huzzah, you cleaned up the branches!
Whack an alias on it
There’s no need to copy-paste that one-liner every time. You can use a Git alias.
To create an alias called rm-merged
, run this command:
$ git config --global alias.rm-merged '!git branch --format '\''%(refname:short) %(upstream:track)'\'' | awk '\''$2 == "[gone]" { print $1 }'\'' | xargs -r git branch -D'
That will add an alias to your ~/.gitconfig
:
[alias]
rm-merged = !git branch --format '%(refname:short) %(upstream:track)' | awk '$2 == \"[gone]\" { print $1 }' | xargs -r git branch -D
You can then run the rm-merged
alias as part of your workflow like so:
$ git switch main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
$ git pull --prune
From github.com:adamchainz/example
- [deleted] (none) -> origin/onions
- [deleted] (none) -> origin/stock
Already up to date.
$ git rm-merged
Deleted branch onions (was 8b96195).
Deleted branch stock (was 06b7da0).
Hmm, what about an alias to do this “main, pull, rm-merged” dance in one?
$ git config --global alias.sync '!git switch main && git pull --prune && git rm-merged'
In action:
$ git sync
Already on 'main'
Your branch is up to date with 'origin/main'.
From github.com:adamchainz/example
- [deleted] (none) -> origin/onions
- [deleted] (none) -> origin/stock
Already up to date.
Deleted branch onions (was 8b96195).
Deleted branch stock (was 06b7da0).
Sweet as.
Fin
Thanks to Erik Schierboom whose previous post I found during research.
I hope this helps you move a little faster with the “squash-merge” workflow,
—Adam
Learn how to make your tests run quickly in my book Speed Up Your Django Tests.
One summary email a week, no spam, I pinky promise.
Related posts:
Tags: git