I have a cleanup command that integrates with fzf. It pre selects every merged branch, so I can just hit return to delete them all. But it gives me the opportunity to deselect to preserve any branches if I want. It also prunes any remote branches
I've got a few aliases that integrate with fzf like an interactive cherry pick (choose branch, choose 1 or more commits), or a branch selector with a preview panel showing commits to the side. Super useful
The article also mentions that master has changed to main mostly, but some places use develop and other names as their primary branch. For that reason I always use a git config variable to reference such branches. In my global git config it's main. Then I override where necessary in any repo's local config eg here's an update command that updates primary and rebases the current branch on top:
# switch to primary branch, pull, switch back, rebase
update = !"git switch ${1:-$(git config user.primaryBranch)}; git pull; git switch -; git rebase -;"
I've had essentially that - if a bit fancier to accept an optional argument as well as handle common "mainline" branch names - aliased as `git lint` for a while:
The main issue with `git branch --merged` is that if the repo enforces squash merges, it obviously won't work, because SHA of squash-merged commit in main != SHA of the original branch HEAD.
What tools are the best to do the equivalent but for squash-merged branches detections?
Note: this problem is harder than it seems to do safely, because e.g. I can have a branch `foo` locally that was squash-merged on remote, but before it happened, I might have added a few more commits locally and forgot to push. So naively deleting `foo` locally may make me lose data.
show comments
gritzko
If something this natural requires several lines of bash, something is just not right. Maybe branches should go sorted by default, either chronologically or topologically? git's LoC budget is 20x LevelDBs or 30% of PostgreSQL or 3 SQLites. It must be able to do these things out of the box, isn't it?
I currently have a TUI addiction. Each time I want something to be easier, I open claude-code and ask for a TUI. Now I have a git worktree manager where I can add/rebase/delete. As TUI library I use Textual which claude handles quite well, especially as it can test-run quite some Python code.
show comments
parliament32
So effectively "I just discovered xargs"? Not to disparage OP but there isn't anything particularly novel here.
show comments
jo-m
I have something similar, but open fzf to select the branches to delete [1].
function fcleanb -d "fzf git select branches to delete where the upstream has disappeared"
set -l branches_to_delete (
git for-each-ref --sort=committerdate --format='%(refname:lstrip=2) %(upstream:track)' refs/heads/ | \
egrep '\[gone\]$' | grep -v "master" | \
awk '{print $1}' | $_FZF_BINARY --multi --exit-0 \
)
for branch in $branches_to_delete
git branch -D "$branch"
end
end
prune-local = "!git fetch -p && for branch in $(git branch -vv | awk '/: gone]/{if ($1!=\"\*\") print $1}'); do git branch -d $branch; done"
1. Fetch the latest from my remote, removing any remote tracking branches that no longer exist
2. Enumerate local branches, selecting each that has been marked as no longer having a remote version (ignoring the current branch)
3. Delete the local branch safely
rudnevr
What's wrong with just deleting the whole folder and clone repo and whatever branch you're interested in? In any case it's not an urgent thing. You don't have to do this mid-work, you can wait until you push most stuff and then rm && git clone.
The only case in which this wouldn't work is when you have a ton of necessary local branches you can't even push to remote, which is a risk and anti-pattern per se.
show comments
EricRiese
Much more complicated than necessary. I just use
git branch | xargs git branch -d
Don't quote me, that's off the top of my head.
It won't delete unmerged branches by default. The line with the marker for the current branch throws an error but it does no harm. And I just run it with `develop` checked out. If I delete develop by accident I can recreate it from origin/develop.
Sometimes I intentionally delete develop if my develop branch is far behind the feature branch I'm on. If I don't and I have to switch to a really old develop and pull before merging in my feature branch, it creates unnecessary churn on my files and makes my IDE waste time trying to build the obsolete stuff. And depending how obsolete it is and what files have changed, it can be disruptive to the IDE.
I also set mine up to run on `git checkout master` so that I don't really have to think about it too hard -- it just runs automagically. `gcm` has now become muscle memory for me.
This is the same thing as `git rebase-update`, available in Chrome's `depot_tools`, which deletes merged branches.
Beyond that, this is just OP learning how `xargs` works.
andix
I sometimes convert old branches to tags. So they don't show up in the list of branches, but I never lose any branches by accident.
All those "merged" workflows only work, if you actually merge the branches. It doesn't work with a squash merge workflow.
edit: I delegate this task to a coding agent. I'm really bad at bash commands. yolo!
d0liver
IIRC, you can do git branch -D $(git branch) and git will refuse to delete your current branch. Kind of the lazy way. I never work off of master/main, and usually when I need to look at them I checkout the remote branches instead.
This is the version I'd want in my $EMPLOYER's codebase that has a mix of default branches
gritzko
Speaking of user friendliness of git UI. I am working on a revision control system that (ideally) should be as user friendly as Ctrl+S Ctrl+Z in most common cases. Spent almost a week on design docs, looking for feedback (so far it was very valuable, btw)
I keep a command `git-remove-merged`, which uses `git ls-remote` to see if the branch is set up to track a remote branch, and if it is then whether the remote branch still exists. On the assumption that branches which have had remote tracking but no longer do are either merged or defunct.
Save that next to your git binary, call it whatever you want. It's destructive on purpose.
jimnotgym
I have an image of running his command, 'ciaclean', and a black van turnes up with a bunch of agents in coveralls, brandishing rolls of polyethylene sheeting and drums of acid.
`Out-GridView` gives you a quick popup dialog with all the branch names that supports easy multi-select. That way you get a quick preview of what you are cleaning up and can skip work in progress branch names that you haven't committed anything to yet.
tonmoy
I’m surprised to see not enough people using xargs, or maybe I overuse it
cowlby
Anyone else "vibe git-ing” lately? I just ask Claude Opus to clean it up and it does really well. Same for build commands and test harnesses.
I needed that exact functionality and Claude code and ChatGPT consistently showing this same exact combo CLI receipt with the simple prompt "how to do use CLI to remove merged branch locally."
show comments
microflash
I’ve been using something similar for years with Nushell.
git branch
| lines
| where ($it !~ '^*')
| each {|br| git branch -D ($br | str trim)}
| str trim
bobabob
Using grep and xargs is worth a whole blog post now? hmmmmm
show comments
password4321
I don't delete branches, I just work with the top several most recently modified.
show comments
trashymctrash
If you squash your PR before merging, then this alternative worked really well for me:
Dont most git instances, like github, delete branch after a PR was merged, by default?
I am not sure under what usecases, you will end up with a lot of stale branches. And git fetch -pa should fix it locally
show comments
mrbonner
> Since most projects now use main instead of master…
I see that even the CIA, a federal government office, has not fully used DEI approved, inclusive language yet :-)
show comments
samtrack2019
gh-poi plugin is a must if you manage a lot of github pr and want to easily clean the branch attached to it when pr is merged https://github.com/seachicken/gh-poi
micw
I recently let copilot create a document with a few helpful git commands and that particular one was the one it came with as solution for exactly this case.
block_dagger
I cleanup branches interactively with a few lines of bash, which takes a bit more time but is less likely to destroy active work.
spectaclepiece
how is this a news item. LLMs figured this out for me two years ago
dietr1ch
Wait, why would the update for the silly master->main change be swapping the excluded regex instead of just excluding both?
show comments
markus_zhang
Oh this is what ChatGPT told me when I asked "How to remove all local branch except main"...
tpoacher
grep + xargs ... in other words, we're finally back at good old standard svn workflows
galbar
The git plugin in oh-my-zsh has an alias for this: gbda
It also has one for squash-merged branches: gbds
Very useful I've been using them for years
show comments
fragmede
I want to point out explicitly that .git config supports aliases, so in .gitconfig put an [alias] section, and in that you can put
ciaclean = "!alias ciaclean='git branch --merged origin/main | grep -vE "^\s(*|main|develop)" | xargs -n 1 git branch -d'"
so then it's `git ciaclean` and not bare `ciaclean` which imo is cleaner.
Arch-TK
Unfortunately doesn't work if the project you work on squashes everything :(
schiffern
"ciaclean" is a nice touch.
I assume CIA stands for Clean It All.
show comments
arduanika
Trust Langley to neatly tie up all the loose ends.
locallost
I've used this for about 10 years now. Pretty sure it was a widespread way of doing it before any CIA leak.
rickknowlton
honestly my go to is kind of similar, but I prefer using --format vs. straight grep. just feels like the plumbing is cleaner out of the box:
I use `master` in all my repos because I've been using it since forever and it never has once occurred to me "oh shit I better change it to `main` this time in case `master` may offend somebody some day. Unfortunately, that's the last thing on my mind when I'm in programming mode. Now that everything is `master`, maybe it is just a simple git command to change it to `main`. But, my fear is it'll subtly break something and I just don't have enough hours left in my life to accept yet unknown risk that it'll cost me even more hours, just to make some random sensitive developer not get offended one day.
Here's my take on the one-liner that I use via a `git tidy` alias[1]. A few points:
* It ensures the default branch is not deleted (main, master)
* It does not touch the current branch
* It does not touch the branch in a different worktree[2]
* It also works with non-merge repos by deleting the local branches that are gone on the remote
[1]: https://github.com/fphilipe/dotfiles/blob/ba9187d7c895e44c35...[2]: https://git-scm.com/docs/git-worktree
I have a cleanup command that integrates with fzf. It pre selects every merged branch, so I can just hit return to delete them all. But it gives me the opportunity to deselect to preserve any branches if I want. It also prunes any remote branches
https://github.com/WickyNilliams/dotfiles/blob/c4154dd9b6980...I've got a few aliases that integrate with fzf like an interactive cherry pick (choose branch, choose 1 or more commits), or a branch selector with a preview panel showing commits to the side. Super useful
The article also mentions that master has changed to main mostly, but some places use develop and other names as their primary branch. For that reason I always use a git config variable to reference such branches. In my global git config it's main. Then I override where necessary in any repo's local config eg here's an update command that updates primary and rebases the current branch on top:
https://github.com/WickyNilliams/dotfiles/blob/c4154dd9b6980...I've had essentially that - if a bit fancier to accept an optional argument as well as handle common "mainline" branch names - aliased as `git lint` for a while:
so: sits very high in my history statsThe main issue with `git branch --merged` is that if the repo enforces squash merges, it obviously won't work, because SHA of squash-merged commit in main != SHA of the original branch HEAD.
What tools are the best to do the equivalent but for squash-merged branches detections?
Note: this problem is harder than it seems to do safely, because e.g. I can have a branch `foo` locally that was squash-merged on remote, but before it happened, I might have added a few more commits locally and forgot to push. So naively deleting `foo` locally may make me lose data.
If something this natural requires several lines of bash, something is just not right. Maybe branches should go sorted by default, either chronologically or topologically? git's LoC budget is 20x LevelDBs or 30% of PostgreSQL or 3 SQLites. It must be able to do these things out of the box, isn't it?
https://replicated.wiki/blog/partII.html
I currently have a TUI addiction. Each time I want something to be easier, I open claude-code and ask for a TUI. Now I have a git worktree manager where I can add/rebase/delete. As TUI library I use Textual which claude handles quite well, especially as it can test-run quite some Python code.
So effectively "I just discovered xargs"? Not to disparage OP but there isn't anything particularly novel here.
I have something similar, but open fzf to select the branches to delete [1].
[1]: https://github.com/jo-m/dotfiles/blob/29d4cab4ba6a18dc44dcf9...I use this alias:
1. Fetch the latest from my remote, removing any remote tracking branches that no longer exist2. Enumerate local branches, selecting each that has been marked as no longer having a remote version (ignoring the current branch)
3. Delete the local branch safely
What's wrong with just deleting the whole folder and clone repo and whatever branch you're interested in? In any case it's not an urgent thing. You don't have to do this mid-work, you can wait until you push most stuff and then rm && git clone.
The only case in which this wouldn't work is when you have a ton of necessary local branches you can't even push to remote, which is a risk and anti-pattern per se.
Much more complicated than necessary. I just use
git branch | xargs git branch -d
Don't quote me, that's off the top of my head.
It won't delete unmerged branches by default. The line with the marker for the current branch throws an error but it does no harm. And I just run it with `develop` checked out. If I delete develop by accident I can recreate it from origin/develop.
Sometimes I intentionally delete develop if my develop branch is far behind the feature branch I'm on. If I don't and I have to switch to a really old develop and pull before merging in my feature branch, it creates unnecessary churn on my files and makes my IDE waste time trying to build the obsolete stuff. And depending how obsolete it is and what files have changed, it can be disruptive to the IDE.
Why not just git dmb? https://manpages.debian.org/testing/git-delete-merged-branch...
We all have something similar, it seems! I stole mine from https://stackoverflow.com/questions/7726949/remove-tracking-....
I also set mine up to run on `git checkout master` so that I don't really have to think about it too hard -- it just runs automagically. `gcm` has now become muscle memory for me.
This is the same thing as `git rebase-update`, available in Chrome's `depot_tools`, which deletes merged branches.
Beyond that, this is just OP learning how `xargs` works.
I sometimes convert old branches to tags. So they don't show up in the list of branches, but I never lose any branches by accident.
All those "merged" workflows only work, if you actually merge the branches. It doesn't work with a squash merge workflow.
edit: I delegate this task to a coding agent. I'm really bad at bash commands. yolo!
IIRC, you can do git branch -D $(git branch) and git will refuse to delete your current branch. Kind of the lazy way. I never work off of master/main, and usually when I need to look at them I checkout the remote branches instead.
I use this tool, which allows one to select the branches to delete instead of just deleting everything: https://github.com/stefanwille/git-branch-delete
Unfortunately its name makes it hard to search for and find.
Speaking of user friendliness of git UI. I am working on a revision control system that (ideally) should be as user friendly as Ctrl+S Ctrl+Z in most common cases. Spent almost a week on design docs, looking for feedback (so far it was very valuable, btw)
https://replicated.wiki/blog/partII.html#navigating-the-hist...
I keep a command `git-remove-merged`, which uses `git ls-remote` to see if the branch is set up to track a remote branch, and if it is then whether the remote branch still exists. On the assumption that branches which have had remote tracking but no longer do are either merged or defunct.
https://gist.github.com/andrewaylett/27c6a33bd2fc8c99eada605...
But actually nowadays I use JJ and don't worry about named branches :).
`git trash`
https://github.com/henrikpersson/git-trash
I use this script with a quick overview to prevent accidentally deleting something important
I use git-trim for that:
https://github.com/foriequal0/git-trim
Readme also explains why it's better than a bash-oneliner in some cases.
I've had this command as 'git drop-merged' for a few years now (put as a script in your path named git-drop-merged:
I use
Save that next to your git binary, call it whatever you want. It's destructive on purpose.I have an image of running his command, 'ciaclean', and a black van turnes up with a bunch of agents in coveralls, brandishing rolls of polyethylene sheeting and drums of acid.
I use this PowerShell variant:
`Out-GridView` gives you a quick popup dialog with all the branch names that supports easy multi-select. That way you get a quick preview of what you are cleaning up and can skip work in progress branch names that you haven't committed anything to yet.I’m surprised to see not enough people using xargs, or maybe I overuse it
Anyone else "vibe git-ing” lately? I just ask Claude Opus to clean it up and it does really well. Same for build commands and test harnesses.
I've had this in my ~/.bash_aliases for awhile:
Trying to remember where I got that one, as I had commented the following version out:Or don't go crazy making branches in the first place.
Have a merge workflow which deletes the branch right there.
You probably want git-dmb (dmb = delete merged branches) for a safe and more comprehensive way of dealing with this.
This looks loosely like something already present in git-extras[1].
If you are using Fork.app on Mac as your git client, this now exists (For one month now) there too: https://github.com/fork-dev/Tracker/issues/2200#issuecomment...
Missed opportunity to call it `git ciao`
I needed that exact functionality and Claude code and ChatGPT consistently showing this same exact combo CLI receipt with the simple prompt "how to do use CLI to remove merged branch locally."
I’ve been using something similar for years with Nushell.
git branch | lines | where ($it !~ '^*') | each {|br| git branch -D ($br | str trim)} | str trim
Using grep and xargs is worth a whole blog post now? hmmmmm
I don't delete branches, I just work with the top several most recently modified.
If you squash your PR before merging, then this alternative worked really well for me:
Dont most git instances, like github, delete branch after a PR was merged, by default?
I am not sure under what usecases, you will end up with a lot of stale branches. And git fetch -pa should fix it locally
> Since most projects now use main instead of master…
I see that even the CIA, a federal government office, has not fully used DEI approved, inclusive language yet :-)
gh-poi plugin is a must if you manage a lot of github pr and want to easily clean the branch attached to it when pr is merged https://github.com/seachicken/gh-poi
I recently let copilot create a document with a few helpful git commands and that particular one was the one it came with as solution for exactly this case.
I cleanup branches interactively with a few lines of bash, which takes a bit more time but is less likely to destroy active work.
how is this a news item. LLMs figured this out for me two years ago
Wait, why would the update for the silly master->main change be swapping the excluded regex instead of just excluding both?
Oh this is what ChatGPT told me when I asked "How to remove all local branch except main"...
grep + xargs ... in other words, we're finally back at good old standard svn workflows
The git plugin in oh-my-zsh has an alias for this: gbda
It also has one for squash-merged branches: gbds
Very useful I've been using them for years
I want to point out explicitly that .git config supports aliases, so in .gitconfig put an [alias] section, and in that you can put ciaclean = "!alias ciaclean='git branch --merged origin/main | grep -vE "^\s(*|main|develop)" | xargs -n 1 git branch -d'"
so then it's `git ciaclean` and not bare `ciaclean` which imo is cleaner.
Unfortunately doesn't work if the project you work on squashes everything :(
"ciaclean" is a nice touch.
I assume CIA stands for Clean It All.
Trust Langley to neatly tie up all the loose ends.
I've used this for about 10 years now. Pretty sure it was a widespread way of doing it before any CIA leak.
honestly my go to is kind of similar, but I prefer using --format vs. straight grep. just feels like the plumbing is cleaner out of the box:
that said... pretty hilarious a dev was just like "uhh yeah ciaclean..." curious what... other aliases they might have??I work with GitHub, so this oneliner relly helps me out. It also doesn't rely on grep, since --format have all you need:
It deletes all the branches for which remotes were deleted. GitHub deletes branches after PR was merged. I alias it to delete-mergedi once used ai to generate a command doing the exact same thing.
git branch -vv | grep ': gone\]' | awk '{print $1}' | xargs -n 1 git branch -D
don't forget to fetch first
I use `master` in all my repos because I've been using it since forever and it never has once occurred to me "oh shit I better change it to `main` this time in case `master` may offend somebody some day. Unfortunately, that's the last thing on my mind when I'm in programming mode. Now that everything is `master`, maybe it is just a simple git command to change it to `main`. But, my fear is it'll subtly break something and I just don't have enough hours left in my life to accept yet unknown risk that it'll cost me even more hours, just to make some random sensitive developer not get offended one day.