I found a useful Git one liner buried in leaked CIA developer docs
572 points - today at 2:03 PM
SourceComments
* 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
git branch --merged "$(git config init.defaultBranch)" \
| grep -Fv "$(git config init.defaultBranch)" \
| grep -vF '*' \
| grep -vF '+' \
| xargs git branch -d \
&& git fetch \
&& git remote prune origin \
&& git branch -v \
| grep -F '[gone]' \
| grep -vF '*' \
| grep -vF '+' \
| awk '{print $1}' \
| xargs git branch -D
[1]: https://github.com/fphilipe/dotfiles/blob/ba9187d7c895e44c35... # remove merged branches (local and remote)
cleanup = "!git branch -vv | grep ': gone]' | awk '{print $1}' | fzf --multi --sync --bind start:select-all | xargs git branch -D; git remote prune origin;"
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:
# switch to primary branch, pull, switch back, rebase
update = !"git switch ${1:-$(git config user.primaryBranch)}; git pull; git switch -; git rebase -;"
https://github.com/WickyNilliams/dotfiles/blob/c4154dd9b6980...https://gist.github.com/tomholford/0aa4cdb1340a9b5411ed6eaad...
[alias]
lint = !git branch --merged ${1-} | grep -v -E -e '^[*]?[ ]*(main|master|[0-9]+[.]([0-9]+|x)-stable)$' -e '^[*][ ]+' | xargs -r -n 1 git branch --delete
so: git pull --prune && git lint
sits very high in my history statsWhat 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.
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
[1]: https://github.com/jo-m/dotfiles/blob/29d4cab4ba6a18dc44dcf9... 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 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
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.
alias gcm=$'git checkout master || git checkout main && git pull && git remote prune origin && git branch -vv | grep \': gone]\'| grep -v "\*" | awk \'{ print $1; }\' | xargs -r git branch -D'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.
#!/bin/sh
git branch --merged | egrep -v "(^\*|master|main|dev)" | xargs --no-run-if-empty
git branch -dAll 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!
Unfortunately its name makes it hard to search for and find.
Beyond that, this is just OP learning how `xargs` works.
DEFAULT_BRANCH=$(git remote show origin | sed -n '/HEAD branch/s/.*: //p')
git branch --merged "origin/$DEFAULT_BRANCH" \
| grep -vE "^\s*(\*|$DEFAULT_BRANCH)" \
| xargs -r -n 1 git branch -d
This is the version I'd want in my $EMPLOYER's codebase that has a mix of default brancheshttps://replicated.wiki/blog/partII.html#navigating-the-hist...
https://gist.github.com/andrewaylett/27c6a33bd2fc8c99eada605...
But actually nowadays I use JJ and don't worry about named branches :).
Have a merge workflow which deletes the branch right there.
https://github.com/henrikpersson/git-trash
I use this script with a quick overview to prevent accidentally deleting something important
https://github.com/foriequal0/git-trim
Readme also explains why it's better than a bash-oneliner in some cases.
#!/bin/sh
git checkout main
git fetch --prune
git branch | grep -v main | xargs --no-run-if-empty git branch -D
git pull
Save that next to your git binary, call it whatever you want. It's destructive on purpose. function Remove-GitBranches {
git branch --merged | Out-GridView -Title "Branches to Remove?" -OutputMode Multiple | % { git branch -d $_.Trim() }
}
`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. alias git-wipe-merged-branches='git branch --merged | grep -v \* | xargs git branch -D'
Trying to remember where I got that one, as I had commented the following version out: alias git-wipe-all-branches='git for-each-ref --format '%(refname:short)' refs/heads | grep -v master | xargs git branch -D' [1]: https://github.com/tj/git-extras/blob/main/Commands.md#git-delete-merged-branchesgit branch | lines | where ($it !~ '^*') | each {|br| git branch -D ($br | str trim)} | str trim
git fetch --prune && git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -DI am not sure under what usecases, you will end up with a lot of stale branches. And git fetch -pa should fix it locally
I see that even the CIA, a federal government office, has not fully used DEI approved, inclusive language yet :-)
It also has one for squash-merged branches: gbds
Very useful I've been using them for years
I assume CIA stands for Clean It All.
so then it's `git ciaclean` and not bare `ciaclean` which imo is cleaner.
git branch --merged origin/main --format="%(refname:short)" \ | grep -vE "^(main|develop)$" \ | xargs -r git branch -d
that said... pretty hilarious a dev was just like "uhh yeah ciaclean..." curious what... other aliases they might have?? git branch --format '%(if:equals=gone)%(upstream:track,nobracket)%(then)%(refname:short)%(end)' --omit-empty | xargs --verbose -r git branch -D
It deletes all the branches for which remotes were deleted. GitHub deletes branches after PR was merged. I alias it to delete-mergedgit branch -vv | grep ': gone\]' | awk '{print $1}' | xargs -n 1 git branch -D