64
==> git branch -a
* master
  test
  remotes/origin/master
  remotes/origin/test

when someone delete the remotes/origin/test,I still can see it on my computer.

I know I can do this and remove the test

==> git remote prune
==> git branch -d test
==> git branch -a
* master
  remotes/origin/master

But if I have more local branch, and they are not on remote, so how can I remove them quickly?

Dozer
  • 5,025
  • 11
  • 36
  • 52
  • 2
    Possible duplicate of [Remove tracking branches no longer on remote](https://stackoverflow.com/questions/7726949/remove-tracking-branches-no-longer-on-remote) – rds Oct 03 '18 at 19:02

9 Answers9

84

This is how I remove local branches that are not longer relevant:

git branch --merged origin/master | xargs git branch -d

You may need to tweak it according to your specific configuration (e.g. see comments below to exclude particular branches), but the first command here before the pipe should give you a list of all your local branches that have been merged into your master branch.

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
eck
  • 985
  • 6
  • 6
  • 50
    For fear of removing local master, I modified this to be: `git branch --merged origin/master | grep -v master | xargs git branch -d` – thexfactor Feb 17 '15 at 01:15
  • 15
    Nitpicky change to the above comment, but to avoid removing other branches besides "master" accidentally (i.e. "develop" in Gitflow), it would probably be better to not invert the grep command and instead do something like: `git branch --merged origin/develop | grep "^\s*feature/" | xargs git branch -d` – Steve Ardis Apr 02 '15 at 17:16
  • 1
    Another nitpicky amendment if you want to delete everything but remote... let's make sure we _only_ exclude "master" and not "mastery" or "postmaster" by adding "-w" to grep: `git branch --merged origin/master | grep -vw master | xargs git branch -d` – nofinator Oct 05 '22 at 20:31
  • 1
    This solution only works where all branches are eventually merged into a main branch called "master". This is not case in workflows that have long lived release branches, that never get merged to main. Also note that these days the default branch is called "origin/main" instead of "origin/master" – AliA Feb 23 '23 at 23:25
  • For Windows users who end up here, I have the following version for your because xargs doesn't exits there. *PowerShell flavor:* `git branch --merged origin/main | ForEach-Object { git branch -d $_.Trim() }` which can also be reduced to the following: `git branch --merged origin/main | % { git branch -d $_.Trim() }` There are other possibilities, but let's stop with PowerShell here. *CMD flavor* `FOR /F "tokens=*" %i IN ('git branch --merged origin/main') DO git branch -d %i` – Reza Aug 27 '23 at 12:13
54

A simple prune will not delete the local branch.

Here is another approach to achieve a real deletion. Be sure to execute "git fetch -p" first to get the latest status of the remote repositories.

git branch -vv | grep ': gone]'|  grep -v "\*" | awk '{ print $1; }' | xargs -r git branch -d

This will check all local branches and their origin and will delete all local branches whose origin was deleted.

In detail:

git branch -vv

will list your local branches and show information about the remote branch, saying “gone” if it is not present anymore.

grep ': gone]'

will fetch the branches that match the “ gone]” phrase.

grep -v "\*"

will fetch only lines that do not contain an asterisk. This will ignore the branch you are currently on and also prevent that the “git branch -d” is executed with a “*” at the end which would result in deleting all your local branches

awk '{print $1}'

will fetch the output until the first white space, which will result in the local branch name.

xargs git branch -d

will use the output (branch name) and append it to the “git branch -d” command to finally delete the branch. If you also want to delete branches that are not fully merged, you can use a capital “D” instead of “d” to force delete.

kcm
  • 1,118
  • 1
  • 10
  • 17
  • Worked on macOS, but some branches which had no remote source has been left. Interesting.. – Johnny Five Jan 23 '19 at 08:59
  • @JohnnyFive just execute a plain "git branch -vv" and check if the branches are marked with "gone". you might have to do a git fetch again. – kcm Feb 14 '19 at 09:11
  • Getting `xargs: illegal option -- r` – Leo Aug 22 '19 at 15:46
  • 1
    @Leo This error occurs if you are executing the command on a non-GNU Linux system. Only GNU Linux based systems have the -r option which is responsible for skipping the execution if the input arguments (in this case the branch name) is empty. You can remove the option from the command, but might get some invalid argument errors that you can ignore. – kcm Aug 23 '19 at 09:55
  • Had exactly this issue, worked than with git branch -vv | grep ': gone]'| grep -v "\*" | awk '{ print $1; }' | xargs git branch -D – Denis Mar 19 '21 at 05:43
  • 2
    I like to throw a `-p` argument to `xargs` (especially if you use the `-D` option for `git branch`) to have an explicit confirmation for each command that is run. To have that, modify the last command with `xargs -rp git branch -D` – Qortex May 16 '22 at 10:45
  • 1
    I like this answer because it works for branches that have been squash merged on the remote, rather than just merged branches. – snowe Jun 27 '22 at 16:50
  • 1
    NB: This is prone to localisation. So be sure to use LANG=C git branch -vv | ... – MKesper Jan 05 '23 at 07:23
37

According to the git-fetch manual page, git fetch -p will "After fetching, remove any remote-tracking branches which no longer exist on the remote.` If you have local branches tracking those remote branches, you may need to prune those manually.

twalberg
  • 59,951
  • 11
  • 89
  • 84
  • 6
    but how to remove local branch not on remote? the `-p` only remove the remote-tracking – Dozer May 16 '13 at 14:47
  • 7
    Can you reliably distinguish between a local branch that was set up to track a remote branch vs. one that is for local development? If my local branch is named `foo`, was it originally created to track `remotes/origin/foo`, or is it a local branch that I created to test some new idea? If you can answer that question reliably for every local branch, you can `git branch -D` the ones you don't want. If you can't, trying to do it "automatically" will be destructive. – twalberg May 16 '13 at 15:10
  • 1
    [The manual](https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt--p) says that `--prune` will _"remove any remote-tracking references that no longer exist on the remote"_. Therefore a local branch that does not track a remote branch will not be affected. – user368683 Nov 06 '21 at 09:20
  • This is clearly the simplest and most effective way of doing it. This should be the accepted answer. – Antoine Viallon Feb 17 '23 at 09:44
  • This only deletes the references. The branches remain. – Dan Friedman May 19 '23 at 17:02
10

I ended up with something very similar to kcm's approach. 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 a 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: As Matteo pointed out in his earlier comment on another answer, use of the -D flag to git branch can be very dangerous. So, in the config command 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.

Karl Wilbur
  • 5,898
  • 3
  • 44
  • 54
8

I wrote a simple shell script called git-dangling-branches for this purpose. If you specify -D option, it will delete all local branches which don't have refs/remotes/origin/<branch_name>. Of course, you should be careful when you do that.

#!/bin/bash -e
if [[ "$1" == '-D' ]]; then
  DELETE=1
else
  DELETE=0
fi

REMOTE_BRANCHES="`mktemp`"
LOCAL_BRANCHES="`mktemp`"
DANGLING_BRANCHES="`mktemp`"
git for-each-ref --format="%(refname)" refs/remotes/origin/ | \
  sed 's#^refs/remotes/origin/##' > "$REMOTE_BRANCHES"
git for-each-ref --format="%(refname)" refs/heads/ | \
  sed 's#^refs/heads/##' > "$LOCAL_BRANCHES"
grep -vxF -f "$REMOTE_BRANCHES" "$LOCAL_BRANCHES" | \
  sort -V > "$DANGLING_BRANCHES"
rm -f "$REMOTE_BRANCHES" "$LOCAL_BRANCHES"

if [[ $DELETE -ne 0 ]]; then
  cat "$DANGLING_BRANCHES" | while read -r B; do
    git branch -D "$B"
  done
else
  cat "$DANGLING_BRANCHES"
fi
rm -f "$DANGLING_BRANCHES"
trustin
  • 12,231
  • 6
  • 42
  • 52
  • 2
    This is the answer that I wanted. I'm interested in clearing out local branches that that don't exist with the same name in github. The other answers remove remote-tracking branches, and remove all merged branches, but that's not what I wanted. So thanks for this. I changed -D to -d before running it, just in case. – Chris Lear Nov 06 '14 at 10:57
  • This is fantastic. I had to change `mktemp` to touch with actual filenames because `mktemp` wasn't supported in my OS, but this is absolutely wonderful. – earl3s Jul 07 '16 at 20:02
  • I also added this: `git remote prune origin $DRY_RUN` at the top where `$DRY_RUN` is set to `--dry-run` if `-D` is not passed. Cleans my system up nicely. – earl3s Jul 07 '16 at 20:03
5

Fast

git fetch -p
git branch -v | grep "\[gone\]"

Then manually check and delete branches in the output.


Detailed

The simplest manual solution based on the answers in this thread as well as here, is to remove all remote-tracking references that no longer exist on the remote with

git fetch -p

then see what has been removed from remote-tracking, but is still in your local working tree with

git branch -v

and manually delete branches that have [gone] in the output.

To get a concise list run

git branch -v | grep "\[gone\]"
young_souvlaki
  • 1,886
  • 4
  • 24
  • 28
  • This also can be affected by localisation. Be sure to use LANG=C git branch -v | ... – MKesper Jan 05 '23 at 07:24
  • One line solution: `git fetch -p && LC_ALL=C git branch --format "%(refname:lstrip=-2) %(upstream:track)" | grep -wF '[gone]' | awk '{ print $1 }' | xargs -n1 git branch -d` – Antoine Viallon Feb 17 '23 at 09:51
  • More generally, if the system is not in english, then one can issue: `git branch -v | grep "\[*\]" | awk '{print $1}' | xargs git branch -D`, admitting that nothing besides "gone" is being displayed for removed branches. – Rick Stanley Apr 06 '23 at 16:13
1

There is the very simple

git branch --merged | xargs git branch -d

which does the same as the more complex options shown previously, that is only removing branches whose remotes are deleted

roob_test
  • 11
  • 2
0

You can do this by iterating over the refs, I used following command to remove all the local branches which dont have remote branches and it worked. Warning: this operation does a force delete non fully merged branches, use with care.

git branch -D `git for-each-ref --format="%(fieldName)" refs/heads/<branch-name-pattern>`

%(fieldName) = refname:short)

refs/heads/ = can be suffixed if you have a common prefix/suffix in branch names ex: refs/heads/*abc*

Refer this for more information git-for-each-ref(1) Manual Page

Laksitha Ranasingha
  • 4,321
  • 1
  • 28
  • 33
  • With your solution you will forcibly remove also branches that are not fully merged, pretty dangerous! – Matteo Nov 30 '17 at 09:49
  • "git for-each-ref --format=%(refname:short) refs/heads" will list all local branches, irrespective of weather they are on remote or not. And then "git branch -D" will delete them even when they have un-commited changes. This is not a proper answer to the question – AliA Feb 24 '23 at 01:49
0

First, do:

git fetch && git remote prune origin

And then:

git branch -a | grep -v ${$(git branch -a | grep remotes | cut -d/" -f3-)/#/-e} | xargs git branch -D

  • List all remotes without remote/origin prefix: git branch -a | grep remotes | cut -d'/' -f3-
  • Remove all listed from git: git branch -a | grep v ${<all remotes without prefix>/#/-e}
  • Remove from local needs -D cause not all have to be marked as merged. For example, squash and merge in Github does not mark them as merged.
Pando85
  • 34
  • 4
  • 1
    I get `pipe braceparam cmdsubst pipe pipe dquote>` and have no idea what it means. – Leo Aug 22 '19 at 15:47
  • 1
    `unexpected EOF while looking for matching `"'` sure about that cut syntax? – dcsan May 27 '22 at 23:32
  • This pipeline does not work. Even if it worked it is not correct answer because "git branch -a" lists all branches and then the second part of pipeline is trying to filter out the entries from this list that have "remotes" in them. This will give the list of all local branches not just the ones that are not in remote – AliA Feb 24 '23 at 02:51