Reputation: 34978
With git remote prune origin
I can remove the local branches that are not on the remote any more.
But I also want to remove local branches that were created from those remote branches (a check if they are unmerged would be nice).
How can I do this?
Upvotes: 1419
Views: 811470
Reputation: 7124
Try this Powershell expression
git.exe branch -av `
| Where { $_ | Select-String -Pattern '\[gone\]' } `
| Select-String -Pattern '[\w/\.-]+' `
| ForEach-Object { git.exe branch -d -v -- $_.Matches }
This matches on the [gone]
substring found in a git -av
listing of branches, like
PS> git.exe branch -av
branch1 a9f90391 [gone] do stuff 1
branch2 b4f50697 [gone] do stuff 2
mainline d8358be8 stuff added
The powershell expression will delete branch1
and branch2
.
Upvotes: 0
Reputation: 43700
After pruning, you can get the list of remote branches with git branch -r
. The list of branches with their remote tracking branch can be retrieved with git branch -vv
. So using these two lists you can find the remote tracking branches that are not in the list of remotes.
This line should do the trick (requires bash
or zsh
, won't work with standard Bourne shell):
git fetch -p ; git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs git branch -d
This string gets the list of remote branches and passes it into egrep
through the standard input. And filters the branches that have a remote tracking branch (using git branch -vv
and filtering for those that have origin
) then getting the first column of that output which will be the branch name. Finally passing all the branch names into the delete branch command.
Since it is using the -d
option, it will not delete branches that have not been merged into the branch that you are on when you run this command.
If you would like to have this as an alias, add these lines to your .bashrc
file:
alias git-list-untracked='git fetch --prune && git branch -r | awk "{print \$1}" | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk "{print \$1}"'
alias git-remove-untracked='git fetch --prune && git branch -r | awk "{print \$1}" | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk "{print \$1}" | xargs git branch -d'
Then, the procedure for pruning becomes:
git remote prune --dry-run origin
git remote prune origin # Removes origin/asdf that's no longer on origin
git-list-untracked
git-remove-untracked # removes asdf [origin/asdf] where [origin/asdf] no longer exists (after previous command)
If you use squash merges, replace -d
with -D
. But note that this means that git is not checking whether the branch has been merged before deleting it.
Upvotes: 1537
Reputation: 63
Tacking onto the comment suggesting using npx git-removed-branches --prune, I actually created a function that I put in my .bashrc which will do a git fetch and git remote prune origin, then check and trim local branches for me using npx git-removed-branches. Also supports the --force flag.
DEFAULT='\e[1;39m'
RED='\e[1;31m'
GREEN='\e[1;32m'
YELLOW='\e[1;33m'
NC='\e[0m'
function trimbranch() {
echo -e "${YELLOW}Fetching branches${NC}" && git fetch -p
export branches="$(npx git-removed-branches | grep -e "\- " -e "No removed branches found")"
echo -e "${YELLOW}Trimming Stale Origin Branches${NC}" && git remote prune origin
echo -e "${YELLOW}The following local branches are tracking ${RED}nonexistent${YELLOW} remotes:${NC}"
if [[ $branches == "No removed branches found" ]]; then
echo -e "${CYAN}${branches}, exiting.${NC}"
else
echo -e "${branches}${RED}"
read -p "Would you like to prune these branches? (They will be unrecoverable) [y/n]: " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo -e "\n"
echo -e "${YELLOW}Pruning Branches, just a moment...${NC}"
if [[ $1 == "--force" ]]; then
echo -e "${branches}${RED}"
read -p "YOU HAVE CHOSEN TO USE FORCE! Branches will be pruned even if they are not fully merged! Are you sure? [y/n]: " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo -e "\n"
echo -e "${YELLOW}Pruning Branches ${RED}USING FORCE${YELLOW}, just a moment...${NC}"
npx git-removed-branches --prune --force
else
echo -e "\n"
echo -e "${YELLOW}Pruning Branches ${GREEN}WITHOUT FORCE${YELLOW}, just a moment...${NC}"
npx git-removed-branches --prune
fi
elif [ -z "$1" ]; then
npx git-removed-branches --prune
else
echo -e "${YELLOW}You have used an ${RED}unsupported argument${YELLOW}. Ignoring local branches.${NC}"
echo -e "${GREEN}Pruning Complete!${NC}"
fi
else
echo -e "\n"
echo -e "${YELLOW}Local branches will not be harmed. Run trimbranch again if you wish to prune them.${NC}"
fi
fi
}
Upvotes: 1
Reputation: 273
You can use the --no-contains
option to avoid removing your master
branch when using branch --merged
.
Running alone this looks like
$ git branch --no-contains master --merged master
old-branch1
old-branch2
old-branch3
To delete the branches in Unix shell using xargs
git branch --no-contains master --merged master | xargs git branch -d
To delete the branches in Powershell using Pipes
git.exe branch --no-contains master --merged master `
| foreach { git.exe branch -v -d $_.Trim() }
Upvotes: 8
Reputation: 17416
If you want to delete all local branches that are already merged into master, you can use the following command:
git branch --merged master | grep -v '^[ *]*master$' | xargs -d'\n' git branch -d
If you are using main
as your master branch, you should modify the command accordingly:
git branch --merged main | grep -v '^[ *]*main$' | xargs -d'\n' git branch -d
NOTE: xargs
-d'\n'
parameter is used to allow proper deletion of branches with an apostrophe in the name, see https://unix.stackexchange.com/questions/38148/why-does-xargs-strip-quotes-from-input.
Upvotes: 656
Reputation: 10490
I love the approach below. I have it im my history and just type gfa + UP
to get the latest shell-history item starting with gfa
and I'm done. It works for me for 6 years already.
gfa && gco develop && gl && for branch in $(git for-each-ref --format '%(refname) %(upstream:track)' refs/heads | awk '$2 == "[gone]" {sub("refs/heads/", "", $1); print $1}' | egrep -v '\*|master|main|develop'); do git branch -d $branch; done
The aliases used here resolve to (.zshrc compatible format):
alias gfa='git fetch --all --prune --jobs=10'
alias gco='git checkout'
alias gl='git pull'
alias gb='git --no-pager branch'
Upvotes: 0
Reputation: 9356
For Microsoft Windows Powershell:
git checkout master; git remote update origin --prune; git branch -vv | Select-String -Pattern ": gone]" | % { $_.toString().Trim().Split(" ")[0]} | % {git branch -d $_}
git checkout master
switches to the master branch
git remote update origin --prune
prunes remote branches
git branch -vv
gets a verbose output of all branches (git reference)
Select-String -Pattern ": gone]"
gets only the records where they have been removed from remote.
% { $_.toString().Split(" ")[0]}
get the branch name
% {git branch -d $_}
deletes the branch
Upvotes: 116
Reputation: 5381
On Windows with Powershell, to delete all local branches that have been deleted on the remote, regardless of merge status, this will do the trick:
git fetch -p
git branch --v | ? {$_.contains('[gone]')} | % {$_.trim().split()[0].trim()} | % {git branch -D $_}
git branch --v
Gives verbose list of your branches, critically including the [gone]
status? {$_.contains('[gone]')}
True if [gone]
is present% {$_.trim().split()[0].trim()}
Gives us just the branch name% {git branch -D $_}
Forced delete of local branch, even if unmergedIf you want to avoid unmerged branches change -D
to -d
and handle those manually.
Upvotes: 3
Reputation: 5554
On OS X, which supports cut -w
git branch -d $(git branch -vv | grep ': gone]' | cut -w -f 2 )
git branch -d ...
$ git branch -vv | grep ': gone]'
chore/TECH-456 a4bdac8ac8 [origin/TECH-456: gone] chore: syntax error
feature/TECH-678 0342f8e277 [origin/feature/TECH-678: gone] Added IsGross in new Income Details
$ git branch -vv | grep ': gone]' | cut -w -f 2
chore/TECH-456
feature/TECH-678
Upvotes: 5
Reputation: 6020
You can do this by a few simple actions:
git branch > branches.tmp
develop
/master
/main
/...
)xargs
by cat
command and remove branches:cat branches.tmp | xargs git branch -D
Upvotes: 4
Reputation: 172530
I have turned the accepted answer into a robust script. You'll find it in my git-extensions repository.
$ git-rprune --help
Remove old local branches that do not exist in REMOTE any more.
With --test, only print which local branches would be deleted.
Note: To do this automatically on each fetch / pull:
git config --global fetch.prune true
Usage: git-rprune REMOTE [-t|--test|-f|--force] [-?|-h|--help]
Upvotes: -1
Reputation: 198
This command and the script below work in Linux and Windows with Git Bash (MinGW).
It is best to use git's internal commands so that comments or names don't accidentally match and delete a branch that you don't want to delete. There are many internal "atoms" that can be used with git for-each-ref
's format option to output the desired information. This way we don't have to rely on piping to awk
or grep
to check a regular expression on output that may contain unnecessary information.
The command below uses only git for-each-ref
's internal low level commands to list only orphaned local branches. Once you have these you can pipe to git branch -D
. Also, don't forget to prune and fetch your remote references first, or it won't find any matches:
git fetch -p
git for-each-ref --format '%(if:equals=[gone])%(upstream:track)%(then)%(refname:short)%(end)' 'refs/heads/**' | xargs -r git branch -D
Here is a breakdown:
git fetch -p
- prune removed references and fetch new ones from the remote
git for-each-ref --format
- lists all references using a specific output format.
%(if:equals=[gone])%(upstream:track)
- only output if the upstream tracking branch is "[gone]".
%(then)%(refname:short)%(end)
- output the branch name (when tracking is gone).
refs/heads/**
- limit to head references (for efficiency).
| xargs -r git branch -D
- pipe output as parameters for deletion. The -r
indicates to ignore blank input.
By itself, this solution is long, ridiculous to type, and hard to remember. Luckily, adding custom commands to git is easy. Below is a script that uses the same command above, but it allows the user to see which branches will be selected with a --dry-run
option.
I named my file git-prune-local
and dropped it in a folder that was included on my PATH
. It also needs execution permissions (chmod 755 git-prune-local
).
Git automatically looks for executable files like git-[command]
. With this, you only need to type git prune-local
for the correct branches to be deleted.
git-prune-local
#!/bin/sh
if [ $# -gt 1 ] || ([ ! -z $1 ] && [ $1 != "--dry-run" ])
then
echo "Usage: git prune-local [--dry-run]"
exit
fi
git fetch -p --quiet
branchesToDelete=$(git for-each-ref --format '%(if:equals=[gone])%(upstream:track)%(then)%(refname:short)%(end)' 'refs/heads/**')
while read -r branch
do
if [ ! -z $branch ]
then
if [ ! -z $1 ]
then
echo $branch
else
git branch -D $branch
fi
fi
done <<< "$branchesToDelete"
Upvotes: 9
Reputation: 1
In addition to Schleis answer (which works perfectly), this can be integrated into Visual Studio, so pruning local branches when a git repo is open in VS is really simple.
You can use the External Tools functionality to call sh.exe (git bash) with a specific directory and command. This is located in the Tools > External Tools menu option (in VS 2022 17.1.0). The parameters I use are as follows:
Command: {Path-To-Git-Installation-Location}\bin\sh.exe
Arguments: --cd=$(SolutionDir) -c "git fetch -p ; git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs git branch -d"
Initial Directory: $(SolutionDir)
Screenshot of Git Prune Local Branches
It's worth noting that this will work only when the solution you have open in VS is within a git repo directory.
Final note - this can be keybound via the Visual Studio key binding user interface in Settings > General > Keyboard and searching for Tools.ExternalCommand[n] where n is the position in the External Command table that you have this external tool positioned at (they can be reordered in the Tools > External Tools dialog). See screenshot below.
Keybinding an External Tools Command
Upvotes: 0
Reputation: 5019
There's a neat npm package that does it for you (and it should work cross platform).
Install it with: npm install -g git-removed-branches
And then git removed-branches
will show you all the stale local branches, and git removed-branches --prune
to actually delete them.
Upvotes: 252
Reputation: 704
check for targets
for target in $(git branch | grep -Eiv "master|develop|branchYouWantToLive"); do echo $target; done
run with for & subcommands
for target in $(git branch | grep -Eiv "master|develop|branchYouWantToLive"); do git branch -D $target; done
you can extend other something works about branches.
Upvotes: 0
Reputation: 3049
I did not find the answers here usefull when the remote itself does not exist anymore. I kept seing branches of remotes that did not exist anymore, but did not find a git command to delete them.
The solution for me was to go to the .git\refs\remotes
directory and directly delete the files that are not relevant any more. The file structure is very easy to understand. It is the same structure as what you see with git branch -r
.
Upvotes: 0
Reputation: 2004
This works for me using git 2.21.0 - it deletes local tracking branches which are merged into HEAD
where I have previously --set-upstream
on push (I use push.default=upstream
because it works best with multiple remotes) and that upstream branch has since been deleted by a fetch --prune
(or implicitly if fetch.prune=true
in git config):
git branch -vv --merged | grep ': gone]' | awk '{print $1}' | xargs git branch -d
The use of --merged
and -d
make this a very 'safe' delete. A more aggressive version could drop the --merged
and use -D
Upvotes: 3
Reputation: 1366
Here is my solution :
git fetch -p
git branch -vv | grep ": gone" | awk '{print $1}' | xargs git branch -d
-p is to remove any remote-tracking references that no longer exist on the remote. So first step will remove references to remote branches.
-vv is for showing sha1 and commit subject line for each head, along with relationship to upstream branch (if any). 2nd step will get all the local branches and grep command will filter out branches that have been deleted.
Upvotes: 16
Reputation: 259
To remove remote branches:
$git remote prune origin
To remove local branches that are already merged:
$git branch -D $(git branch --merged)
Upvotes: 9
Reputation: 3650
In Powershell:
git branch -D (git branch --merged |% { $_.trim() } )
Upvotes: 6
Reputation: 685
As @tzacks notes... there is an npm package that is handy for this. Just do:
npx git-removed-branches --prune
(I would have commented but not enough reputation)
Upvotes: 65
Reputation: 3087
Even shorter and safer one-liner:
git branch -d $(git branch --merged | cut -c 3- | grep -v master)
Be sure to checkout to branch that is not merged yet, before run it. Because you can not delete branch that you are currently checked in.
Upvotes: 20
Reputation: 706
Based on the answers above I'm using this shorter one liner:
git remote prune origin | awk 'BEGIN{FS="origin/"};/pruned/{print $2}' | xargs -r git branch -d
Also, if you already pruned and have local dangling branches, then this will clean them up:
git branch -vv | awk '/^ .*gone/{print $1}' | xargs -r git branch -d
Upvotes: 5
Reputation: 4454
I reached this page seeking the answer for "how do I delete locally checked out branches that no longer have an upstream branch"
I also did not care whether or not the local branch had been merged in yet, since piping into git branch -d
will simply warn instead of deleting unmerged local branches.
git branch -a | grep origin | tr -s ' ' | cut -d '/' -f3 | egrep -v -f /dev/fd/0 <(git branch -a | grep -v origin) | grep branch_prefix_that_I_care_about | xargs git branch -d
# translation
# git branch -a | grep origin | tr -s ' ' | cut -d '/' -f3
## this finds all remote branch names minus the "remote/origin/" part
#
# <(git branch -a | grep -v origin)
## this finds all local branch names and redirects it into the previous command
#
# egrep -v -f /dev/fd/0 STUFF
## this is doing some weird magic that I'm grokking as "take the set difference from whatever was piped in"
#
#
# overall translation: (STUFF TO CONSIDER) | egrep magic <(STUFF TO REMOVE FROM CONSIDERATION) | do cool things with resulting stuff
Upvotes: 0
Reputation: 29483
There doesn't seem to be a safe one-liner, too many edge cases (like a branch having "master" as part of its name, etc). Safest is these steps:
git branch -vv | grep 'gone]' > stale_branches.txt
awk '{print $1}' stale_branches.txt | xargs git branch -d
Upvotes: 12
Reputation: 136
The Powershell Version of git branch --merged master | grep -v '^[ *]*master$' | xargs git branch -d
git branch --merged master | %{ if($_ -notmatch '\*.*master'){ git branch -d "$($_.Trim())" }}
This will remove any local branches that have been merged into master, while you are on the master branch.
git checkout master
to switch.
Upvotes: 2
Reputation: 11234
You can use this command:
git branch --merged master | grep -v "\* master" | xargs -n 1 git branch -d
Git Clean: Delete Already-Merged Branches including break down of command
Upvotes: 3
Reputation: 6187
I wanted something that would purge all local branches that were tracking a remote branch, on origin
, where the remote branch has been deleted (gone
). I did not want to delete local branches that were never set up to track a remote branch (i.e.: my local dev branches). Also, I wanted a simple one-liner that just uses git
, or other simple CLI tools, rather than writing custom scripts. I ended up using a bit of grep
and awk
to make this simple command, then added it as an alias in my ~/.gitconfig
.
[alias]
prune-branches = !git remote prune origin && git branch -vv | grep ': gone]' | awk '{print $1}' | xargs -r git branch -D
Here is a git config --global ...
command for easily adding this as git prune-branches
:
git config --global alias.prune-branches '!git remote prune origin && git branch -vv | grep '"'"': gone]'"'"' | awk '"'"'{print $1}'"'"' | xargs -r git branch -d'
NOTE: Use of the -D
flag to git branch
can be very dangerous. So, in the config command above I use the -d
option to git branch
rather than -D
; I use -D
in my actual config. I use -D
because I don't want to hear Git complain about unmerged branches, I just want them to go away. You may want this functionality as well. If so, simply use -D
instead of -d
at the end of that config command.
Upvotes: 20
Reputation: 62369
Amidst the information presented by git help fetch
, there is this little item:
-p, --prune
After fetching, remove any remote-tracking branches which no longer exist on the remote.
So, perhaps, git fetch -p
is what you are looking for?
EDIT: Ok, for those still debating this answer 3 years after the fact, here's a little more information on why I presented this answer...
First, the OP says they want to "remove also those local branches that were created from those remote branches [that are not any more on the remote]". This is not unambiguously possible in git
. Here's an example.
Let's say I have a repo on a central server, and it has two branches, called A
and B
. If I clone that repo to my local system, my clone will have local refs (not actual branches yet) called origin/A
and origin/B
. Now let's say I do the following:
git checkout -b A origin/A
git checkout -b Z origin/B
git checkout -b C <some hash>
The pertinent facts here are that I for some reason chose to create a branch on my local repo that has a different name than its origin, and I also have a local branch that does not (yet) exist on the origin repo.
Now let's say I remove both the A
and B
branches on the remote repo and update my local repo (git fetch
of some form), which causes my local refs origin/A
and origin/B
to disappear. Now, my local repo has three branches still, A
, Z
, and C
. None of these have a corresponding branch on the remote repo. Two of them were "created from ... remote branches", but even if I know that there used to be a branch called B
on the origin, I have no way to know that Z
was created from B
, because it was renamed in the process, probably for a good reason. So, really, without some external process recording branch origin metadata, or a human who knows the history, it is impossible to tell which of the three branches, if any, the OP is targeting for removal. Without some external information that git
does not automatically maintain for you, git fetch -p
is about as close as you can get, and any automatic method for literally attempting what the OP asked runs the risk of either deleting too many branches, or missing some that the OP would otherwise want deleted.
There are other scenarios, as well, such as if I create three separate branches off origin/A
to test three different approaches to something, and then origin/A
goes away. Now I have three branches, which obviously can't all match name-wise, but they were created from origin/A
, and so a literal interpretation of the OPs question would require removing all three. However, that may not be desirable, if you could even find a reliable way to match them...
Upvotes: 287
Reputation: 37827
This will delete the local branches for which the remote tracking branches have been pruned. (Make sure you are on master
branch!)
git checkout master
git branch -vv | grep ': gone]' | awk '{print $1}' | xargs git branch -d
Details:
git branch -vv
displays "gone" for local branches that the remote has been pruned.
mybranch abc1234 [origin/mybranch: gone] commit comments
-d
will check if it has been merged (-D
will delete it regardless)
error: The branch 'mybranch' is not fully merged.
Upvotes: 190