Remove tracking branches no longer on remote

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

Note that the above uses the "porcelain" command git branch to get the upstream status.

Another way to obtain this status is to use the "plumbing" command git for-each-ref with the interpolation variable %(upstream:track), which will be [gone] just like above.

This approach is somewhat safer, because there is no risk of accidentally matching on part of the commit message.

git fetch -p && for branch in $(git for-each-ref --format '%(refname) %(upstream:track)' refs/heads | awk '$2 == "[gone]" {sub("refs/heads/", "", $1); 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:

  1. Check out your default branch. Usually git checkout master
  2. 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 branch output 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 (or git fetch -p, which is an alias, or git prune remote origin which 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.)