Alex
Alex

Reputation: 34978

How to prune local tracking branches that do not exist on remote anymore?

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

Answers (30)

JamesThomasMoon
JamesThomasMoon

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

Schleis
Schleis

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

EvilCreamsicle
EvilCreamsicle

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

Evert_B
Evert_B

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

jackocnr
jackocnr

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

More info.

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

blackjacx
blackjacx

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

chris31389
chris31389

Reputation: 9356

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 $_}

Explanation

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

Shane Kenyon
Shane Kenyon

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 unmerged

If you want to avoid unmerged branches change -D to -d and handle those manually.

Upvotes: 3

Chui Tey
Chui Tey

Reputation: 5554

On OS X, which supports cut -w

git branch -d $(git branch -vv | grep ': gone]' | cut -w -f 2 )

explanation

delete following branches
git branch -d ...

show removed branches

$ 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

keep the branch name

$ git branch -vv | grep ': gone]' | cut -w -f 2
chore/TECH-456
feature/TECH-678

Upvotes: 5

Ali Farhoudi
Ali Farhoudi

Reputation: 6020

You can do this by a few simple actions:

  • Output your all branches into a temp file:
git branch > branches.tmp
  • Open the file and remove branches to exclude them from being deleted from your local (branches like develop/master/main/...)
  • Pass branches name to xargs by cat command and remove branches:
cat branches.tmp | xargs git branch -D

Upvotes: 4

Ingo Karkat
Ingo Karkat

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

Macadameane
Macadameane

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

James van Rossum
James van Rossum

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

tzachs
tzachs

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.

More info here.

Upvotes: 252

cyan-kinesin
cyan-kinesin

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

PaulH
PaulH

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

spume
spume

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

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

Aniket
Aniket

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

In Powershell:

git branch -D (git branch --merged |% { $_.trim() } )

Upvotes: 6

johnshew
johnshew

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

FelikZ
FelikZ

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

hidralisk
hidralisk

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

Meredith
Meredith

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

Oliver
Oliver

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:

  1. git branch -vv | grep 'gone]' > stale_branches.txt
  2. view the file and remove lines for branches you want to keep (such as master branch!); you don't need to edit the contents of any line
  3. awk '{print $1}' stale_branches.txt | xargs git branch -d

Upvotes: 12

Kgeorge
Kgeorge

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

sendon1982
sendon1982

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

Karl Wilbur
Karl Wilbur

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

twalberg
twalberg

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

wisbucky
wisbucky

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

Related Questions