Is there a simple way to delete all tracking branches whose remote equivalent no longer exists?
Example:
Branches (local and remote)
- master
- origin/master
- origin/bug-fix-a
- origin/bug-fix-b
- origin/bug-fix-c
Locally, I only have a master branch. Now I need to work on bug-fix-a, so I check it out, work on it, and push changes to the remote. Next I do the same with bug-fix-b.
Branches (local and remote)
- master
- bug-fix-a
- bug-fix-b
- origin/master
- origin/bug-fix-a
- origin/bug-fix-b
- origin/bug-fix-c
Now I have local branches master, bug-fix-a, bug-fix-b. The Master branch maintainer will merge my changes into master and delete all branches he has already merged.
So the current state is now:
Branches (local and remote)
- master
- bug-fix-a
- bug-fix-b
- origin/master
- origin/bug-fix-c
Now I would like to call some command to delete branches (in this case bug-fix-a, bug-fix-b), which are no longer represented in the remote repository.
It would be something like the existing command git remote prune origin, but more like git local prune origin.
git remote prune origin prunes tracking branches not on the remote.
git branch --merged lists branches that have been merged into the current branch.
xargs git branch -d deletes branches listed on standard input.
Be careful deleting branches listed by git branch --merged. The list could include master or other branches you'd prefer not to delete.
To give yourself the opportunity to edit the list before deleting branches, you could do the following in one line:
git branch --merged >/tmp/merged-branches && vi /tmp/merged-branches && xargs git branch -d </tmp/merged-branches
After the command
git fetch -p
removes the remote references, when you run
git branch -vv
it will show 'gone' as the remote status. For example,
$ git branch -vv
master b900de9 [origin/master: behind 4] Fixed bug
release/v3.8 fdd2f4e [origin/release/v3.8: behind 2] Fixed bug
release/v3.9 0d680d0 [origin/release/v3.9: behind 2] Updated comments
bug/1234 57379e4 [origin/bug/1234: gone] Fixed bug
So you can write a simple script to remove local branches that have gone remotes:
git fetch -p && for branch in `git branch -vv | grep ': gone]' | awk '{print $1}'`; do git branch -D $branch; done
Most of these answers do not actually answer the original question. I did a bunch of digging and this was the cleanest solution I found. Here is a slightly more thorough version of that answer:
- Check out your default branch. Usually
git checkout master - Run
git fetch -p && git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -d
Explanation:
Works by pruning your tracking branches then deleting the local ones that show they are "gone" in git branch -vv.
Notes:
If your language is set to something other than English you will need to change gone to the appropriate word. Branches that are local only will not be touched. Branches that have been deleted on remote but were not merged will show a notification but not be deleted on local. If you want to delete those as well change -d to -D.
I wouldn't normally answer a question that already has 16 answers, but all the other answers are wrong, and the right answer is so simple. The question says, "Is there a simple way to delete all tracking branches whose remote equivalent no longer exists?"
If "simple" means deleting them all in one go, not fragile, not dangerous, and without reliance on tools that not all readers will have, then the right answer is: no.
Some answers are simple, but they don't do what was asked. Others do what was asked, but are not simple: all rely on parsing Git output through text-manipulation commands or scripting languages, which may not be present on every system. On top of that, most of the suggestions use porcelain commands, whose output is not designed to be parsed by script ("porcelain" refers to the commands intended for human operation; scripts should use the lower-level "plumbing" commands).
Further reading:
- why you shouldn't parse
git branchoutput in a script. - tracking branches and the differences between
git remote prune,git prune,git fetch --prune
If you want to do this safely, for the use case in the question (garbage-collect tracking branches which have been deleted on the server but still exist as local branches) and with high-level Git commands only, you have to
git fetch --prune(orgit fetch -p, which is an alias, orgit prune remote originwhich does the same thing without fetching, and is probably not what you want most of the time).- Note any remote branches that are reported as deleted. Or, to find them later on,
git branch -v(any orphaned tracking branch will be marked "[gone]"). git branch -d [branch_name]on each orphaned tracking branch
(which is what some of the other answers propose).
If you want to script a solution, then for-each-ref is your starting point, as in Mark Longair's answer here and this answer to another question, but I can't see a way to exploit it without writing a shell script loop, or using xargs or something.
Background explanation
To understand what's happening, you need to appreciate that, in the situation of tracking branches, you have not one branch, but three. (And recall that "branch" means simply a pointer to a commit.)
Given a tracking branch feature/X, the remote repository (server) will have this branch and call it feature/X. Your local repository has a branch remotes/origin/feature/X which means, "This is what the remote told me its feature/X branch was, last time we talked," and finally, the local repository has a branch feature/X which points to your latest commit, and is configured to "track" remotes/origin/feature/X, meaning that you can pull and push to keep them aligned.
At some point, someone has deleted the feature/X on the remote. From that moment, you are left with your local feature/X (which you probably don't want any more, since work on feature X is presumably finished), and your remotes/origin/feature/X which is certainly useless because its only purpose was to remember the state of the server's branch.
And Git will let you automatically clean up the redundant remotes/origin/feature/X -- that's what git fetch --prune does -- but for some reason, it doesn't let you automatically delete your own feature/X... even though your feature/X still contains the orphaned tracking information, so it has the information to identify former tracking branches that have been fully merged. (After all, it can give you the information that lets you do the operation by hand yourself.)
Seems solution is here – https://stackoverflow.com/a/1072178/133986
In short, git remote prune does the magic
I found the answer here: How can I delete all git branches which have been merged?
git branch --merged | grep -v "\*" | xargs -n 1 git branch -d
Make sure we keep master
You can ensure that master, or any other branch for that matter, doesn't get removed by adding another grep after the first one. In that case you would go:
git branch --merged | grep -v "\*" | grep -v "YOUR_BRANCH_TO_KEEP" | xargs -n 1 git branch -d
So if we wanted to keep master, develop and staging for instance, we would go:
git branch --merged | grep -v "\*" | grep -v "master" | grep -v "develop" | grep -v "staging" | xargs -n 1 git branch -d
Make this an alias
Since it's a bit long, you might want to add an alias to your .zshrc or .bashrc. Mine is called gbpurge (for git branches purge):
alias gbpurge='git branch --merged | grep -v "\*" | grep -v "master" | grep -v "develop" | grep -v "staging" | xargs -n 1 git branch -d'
Then reload your .bashrc or .zshrc:
. ~/.bashrc
or
. ~/.zshrc
Remove all branches that have been merged into master, but don't try to remove master itself:
git checkout master && git pull origin master && git fetch -p && git branch -d $(git branch --merged | grep master -v)
or add an alias:
alias gitcleanlocal="git checkout master && git pull origin master && git fetch -p && git branch -d $(git branch --merged | grep master -v)"
Explanation:
git checkout master checkout master branch
git pull origin master ensure local branch has all remote changes merged
git fetch -p remove references to remote branches that have been deleted
git branch -d $(git branch master --merged | grep master -v) delete all branches that have been merged into master, but don't try to remove master itself
Windows Solution
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 $_}
Explaination
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().Trim().Split(" ")[0]} get the branch name
% {git branch -d $_} deletes the branch
The pattern matching for "gone" in most of the other solutions was a little scary for me. To be safer, this uses the --format flag to pull out each branch's upstream tracking status.
I needed a Windows-friendly version, so this deletes all branches that are listed as "gone" using Powershell:
git branch --list --format "%(if:equals=[gone])%(upstream:track)%(then)%(refname:short)%(end)" |
? { $_ -ne "" } |
% { git branch -D $_ }
The first line lists the name of local branches whose upstream branch is "gone". The next line removes blank lines (which are output for branches that aren't "gone"), then the branch name is passed to the command to delete the branch.
git fetch -p
This will delete all branches that are not tracked remotely.
Might be useful to some, simple one line to clear all local branches except master and develop
git branch | grep -v "master" | grep -v "develop" | xargs git branch -D
This will delete all the merged local branched except local master reference and the one currently being used:
git branch --merged | grep -v "*" | grep -v "master" | xargs git branch -d
And this will delete all the branches having already been removed from the remote repository referenced by "origin", but are still locally available in "remotes/origin".
git remote prune origin
I don't think there is a built-in command to do this, but it is safe to do the following:
git checkout master
git branch -d bug-fix-a
When you use -d, git will refuse to delete the branch unless it is completely merged into HEAD or its upstream remote-tracking branch. So, you could always loop over the output of git for-each-ref and try to delete each branch. The problem with that approach is that I suspect that you probably don't want bug-fix-d to be deleted just because origin/bug-fix-d contains its history. Instead, you could create a script something like the following:
#!/bin/sh
git checkout master &&
for r in $(git for-each-ref refs/heads --format='%(refname:short)')
do
if [ x$(git merge-base master "$r") = x$(git rev-parse --verify "$r") ]
then
if [ "$r" != "master" ]
then
git branch -d "$r"
fi
fi
done
Warning: I haven't tested this script - use only with care...
Yet-another-answer for the pile, drawing heavily from Patrick's answer (which I like because it seems to do away with any ambiguity about where gone] will match in the git branch output) but adding a *nix bent:
git branch --list --format "%(if:equals=[gone])%(upstream:track)%(then)%(refname)%(end)" \
| sed 's,^refs/heads/,,;/^$/d' \
| xargs git branch -D
I have this wrapped up in a git-gone script on my path:
#!/usr/bin/env bash
action() {
${DELETE} && xargs git branch -D || cat
}
get_gone() {
git branch --list --format "%(if:equals=[gone])%(upstream:track)%(then)%(refname)%(end)" \
| sed 's,^refs/heads/,,;/^$/d'
}
main() {
DELETE=false
while [ $# -gt 0 ] ; do
case "${1}" in
(-[dD] | --delete) DELETE=true ;;
esac
shift
done
get_gone | action
}
main "${@}"
NB - The --format option seems to be fairly new; I needed to upgrade git from 2.10.something to 2.16.3 to get it.
Based on info above, this worked for me:
git br -d `git br -vv | grep ': gone] ' | awk '{print $1}' | xargs`
It removes all local branches with are ': gone] ' on remote.
grep gone <(git branch -v) | cut -d ' ' -f 3 | xargs git branch -d
The above command can be used to fetch branches which are merged and deleted in remote and it deletes the local branch which no longer available in remote
None of this was really right for me. 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.
This is ultimately what ended up 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: In the config command, I use the -d option to git branch rather than -D, as I do in my actual config. I use -D because I don't want to hear Git complain about unmerged branches. You may want this functionality as well. If so, simply use -D instead of -d at the end of that config command.
Based on Git Tip: Deleting Old Local Branches, which looks similar to jason.rickman's solution I implemented a custom command for this purpose called git gone using Bash:
$ git gone
usage: git gone [-pndD] [<branch>=origin]
OPTIONS
-p prune remote branch
-n dry run: list the gone branches
-d delete the gone branches
-D delete the gone branches forcefully
EXAMPLES
git gone -pn prune and dry run
git gone -d delete the gone branches
git gone -pn combines the pruning and listing the "gone" branches:
$ git gone -pn
bport/fix-server-broadcast b472d5d2b [origin/bport/fix-server-broadcast: gone] Bump modules
fport/rangepos 45c857d15 [origin/fport/rangepos: gone] Bump modules
Then you can pull the trigger using git gone -d or git gone -D.
Notes
- The regular expression I used is
"$BRANCH/.*: gone]"where$BRANCHwould normally beorigin. This probably won't work if your Git output is localized to French etc. - Sebastian Wiesner also ported it to Rust for Windows users. That one is also called git gone.
Drawing heavily from a number of other answers here, I've ended up with the following (git 2.13 and above only, I believe), which should work on any UNIX-like shell:
git for-each-ref --shell --format='ref=%(if:equals=[gone])%(upstream:track)%(then)%(refname)%(end)' refs/heads | while read entry; do eval "$entry"; [ ! -z "$ref" ] && git update-ref -d "$ref" && echo "deleted $ref"; done
This notably uses for-each-ref instead of branch (as branch is a "porcelain" command designed for human-readable output, not machine-processing) and uses its --shell argument to get properly escaped output (this allows us to not worry about any character in the ref name).
I came up with this bash script. It always keep the branches develop, qa, master.
git-clear() {
git pull -a > /dev/null
local branches=$(git branch --merged | grep -v 'develop' | grep -v 'master' | grep -v 'qa' | sed 's/^\s*//')
branches=(${branches//;/ })
if [ -z $branches ]; then
echo 'No branches to delete...'
return;
fi
echo $branches
echo 'Do you want to delete these merged branches? (y/n)'
read yn
case $yn in
[^Yy]* ) return;;
esac
echo 'Deleting...'
git remote prune origin
echo $branches | xargs git branch -d
git branch -vv
}
This worked for me:
git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs git branch -d
I am not sure for how long, but I do use git-up now, which takes care of that.
I do git up and it starts to track new branches and deletes the old ones.
Just to make it clear, it is not out-of-box git command — https://github.com/aanand/git-up
BTW it also stashes dirty tree and makes rebases still with just git up.
Hope it'll be useful for someone
Here's a solution that I use for the fish shell. Tested on Mac OS X 10.11.5, fish 2.3.0 and git 2.8.3.
function git_clean_branches
set base_branch develop
# work from our base branch
git checkout $base_branch
# remove local tracking branches where the remote branch is gone
git fetch -p
# find all local branches that have been merged into the base branch
# and delete any without a corresponding remote branch
set local
for f in (git branch --merged $base_branch | grep -v "\(master\|$base_branch\|\*\)" | awk '/\s*\w*\s*/ {print $1}')
set local $local $f
end
set remote
for f in (git branch -r | xargs basename)
set remote $remote $f
end
for f in $local
echo $remote | grep --quiet "\s$f\s"
if [ $status -gt 0 ]
git branch -d $f
end
end
end
A few notes:
Make sure to set the correct base_branch. In this case I use develop as the base branch, but it could be anything.
This part is very important: grep -v "\(master\|$base_branch\|\*\)". It ensures that you don't delete master or your base branch.
I use git branch -d <branch> as an extra precaution, so as to not delete any branch that has not been fully merged with upstream or current HEAD.
An easy way to test is to replace git branch -d $f with echo "will delete $f".
I suppose I should also add: USE AT YOUR OWN RISK!
I use a short method to do the trick, I recommend you to do the same as it could save some hours & give you more visibility
Just add the following snippet into your .bashrc (.bashprofile on macos).
git-cleaner() { git fetch --all --prune && git branch --merged | grep -v -E "\bmaster|preprod|dmz\b" | xargs -n 1 git branch -d ;};
- Fetch all remotes
- Get only merged branches from git
- Remove from this list the "protected / important" branches
- Remove the rest (e.g, clean and merged branches)
You'll have to edit the grep regex in order to fit to your needs (here, it prevent master, preprod and dmz from deletion)
git pull -p
If we use -p with pull, it will remove all deleted branches from local.
This is gonna delete all the remote branches that are not present locally (in ruby):
bs = `git branch`.split; bs2 = `git branch -r | grep origin`.split.reject { |b| bs.include?(b.split('/')[1..-1].join('/')) }; bs2.each { |b| puts `git push origin --delete #{b.split('/')[1..-1].join('/')}` }
Explained:
# local branches
bs = `git branch`.split
# remote branches
bs2 = `git branch -r | grep origin`.split
# reject the branches that are present locally (removes origin/ in front)
bs2.reject! { |b| bs.include?(b.split('/')[1..-1].join('/')) }
# deletes the branches (removes origin/ in front)
bs2.each { |b| puts `git push origin --delete #{b.split('/')[1..-1].join('/')}` }
I use this method so I can have more control.
git branch -D $(git branch | grep -v "master" | grep -v "develop")
This is remove any branches not named: master or develop.
来源:https://stackoverflow.com/questions/7726949/remove-tracking-branches-no-longer-on-remote