user2012677
user2012677

Reputation: 5735

Delete all branches that are no longer on Github

I have branches Branch-Master, Branch-A, Branch-B, Branch-C on GitHub. I usually merge into master and delete the merged branch. This leaves a bunch of branches on my local machine, but not GitHub.

How do I automatically remove my local branches automatically? I want to create a git hook to do this.

When I try

git remote prune origin

and then run git branch, I still see all my branches, even the ones merged and deleted into master.

I know I can do

git branch -d {the_local_branch}

manually, but this does not help when doing a githook or I have multiple branches to remove systematically.

What I don't like about the answers given here: How can I delete all Git branches which have been merged?

Is that they are based off names, and inferring the deleted branch, rather than somehow explicitly passing a branch id, specific to that branch. What I understand in the link provided, is that if I create a branch name that is the same as the deleted branch, it will also be deleted, even though they are different branches.

How do I delete a Git branch locally and remotely?

Does not address my inquiry, as it's solution is already stated above.

I would rather have some type of compare function using branch uid's if they exist.

Upvotes: 3

Views: 478

Answers (3)

FedFranz
FedFranz

Reputation: 1059

The solution to this is to first list all branches with no upstream and then delete them.

This can be done in a one-line instruction:

LANG=en git branch --format='%(if:equals=gone)%(upstream:track,nobracket)%(then)%(refname:short)%(end)' | grep '.' | xargs git branch -D

This command combines the solution given here git branch -D using xargs.

Upvotes: 0

torek
torek

Reputation: 488103

As phd noted in a comment, there is no underlying ID for branches. A branch name is simply a local name in your repository, created by you or at your request, that identifies one specific commit. You can change this name any time, to any string you like as long as it meets the requirements for a branch name. Git won't remember that it used to have some other name.

Now, there are a few additional bits of information: your .git/config file can, and usually will, have in it, for each branch, some annotations such as:

[branch "master"]
    remote = origin
    merge = master

[branch "develop"]
    remote = another-remote
    merge = develop

These settings define the upstream setting of the branch. A branch can have no upstream at all—in which case the two settings are non-existent—or one upstream, in which case the two settings exist and describe the upstream, using the two parts from the old historical method of dealing with this before remote-tracking names were invented.

These configuration sections will automatically get renamed if you use standard Git commands to rename the branch. For instance, after git branch -m develop xyz, you will have:

[branch "xyz"]
    remote = another-remote
    merge = develop

which means that the upstream of your local branch xyz is another-remote/develop. (This assumes a standard fetch = setting for remote another-remote.)

It's possible, with some effort, to read through the upstream setting (if any) of each branch: use git for-each-ref to enumerate all branches, and then, for each branch, use git rev-parse --symbolic-full-name <branch>@{upstream} to see if it has an upstream, and if so, what the upstream name is. Note that you must be a little bit careful here as the "remote" can be set to ., indicating that the upstream of some local branch is another local branch.

Combining this kind of programmatic test ("does branch X have an upstream, and if so, is it actually a remote-tracking name, and what remote-tracking name is it?") with a second test ("after using git remote <remote> prune or git fetch <remote> --prune, does the remote-tracking name exist?") may get you what you want. You'll need to do some coding (bash scripting, for instance). Making it optimal, or work from within a hook, is hard, but the overall task is pretty straightforward:

git for-each-ref --format='%(refname:short)' refs/heads |
while read branch; do
    # make sure it has an upstream set -- if not, ignore
    upstream=$(git rev-parse --symbolic-full-name ${branch}@{upstream}) || continue
    # make sure the upstream is a remote-tracking name
    case $upstream in
    refs/remotes/*) ;;
    *) continue;;
    esac
    # get the name of the remote
    remote=${upstream#refs/remotes/}
    remote=${remote%%/*}
    # prune remote-tracking branches
    git remote $remote prune
    # and test whether the upstream still exists
    git rev-parse $upstream && continue

    # $branch has upstream $upstream which is gone, so:
    echo git branch -D $branch
done

This is quite untested, and is missing important bits like keeping it quiet about branches it's not going to delete (each rev-parse will spew messages to stdout and/or stderr). It also doesn't actually delete any branches: since that can be rather irrecoverable, so one would want to test it thoroughly first, and only then remove the echo.

Upvotes: 1

Krantisinh
Krantisinh

Reputation: 1729

You can delete all branches at one go (except the one which is checked out) by one single command:

git branch --list | xargs git branch -D

If you want to specify some pattern to match the branch name, you can do that too:

git branch --list k* | xargs git branch -D

And with the help of

git remote prune origin

you can delete remote branches in your system that are no longer referenced.

Hope this helps !

Upvotes: 0

Related Questions