1291

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?

MatthewMartin
  • 32,326
  • 33
  • 105
  • 164
Alex
  • 32,506
  • 16
  • 106
  • 171
  • 11
    Possible duplicate of [Remove local branches no longer on remote](http://stackoverflow.com/questions/7726949/remove-local-branches-no-longer-on-remote) – kolen Mar 30 '17 at 18:18
  • 6
    One-liner, cross platform, doesn't look like the cat slept on your keyboard: `npx git-removed-branches` (dry-run) or `npx git-removed-branches --prune` (for real). You need to already have _node.js_ installed. See [answers](https://stackoverflow.com/a/45699402/11545) [below](https://stackoverflow.com/a/57748218/11545) for details. – Cristian Diaconescu Apr 20 '20 at 22:04
  • I usually think these things should be done deliberately not automatic otherwise you open yourself to deleting something you didn't want to delete. So I'd stick with git branch -d localBranchName and git push origin --delete remoteBranchName – Epirocks Nov 16 '20 at 12:51
  • 3
    For IntelliJ users the following plugin does exactly what you need: https://plugins.jetbrains.com/plugin/10059-git-branch-cleaner – Daniel Feb 14 '22 at 09:00

39 Answers39

1403

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.

danopz
  • 3,310
  • 5
  • 31
  • 42
Schleis
  • 41,516
  • 7
  • 68
  • 87
  • 22
    This worked for me perfectly. Somehow, ```git fetch -p``` is not always enough? – Stefan Hendriks Aug 30 '13 at 20:22
  • 28
    Unfortunately this doesn't work in Git Bash on Windows. `sh.exe": cannot make pipe for process substitution: Function not implemented sh.exe": egrep -v -f /dev/fd/0: No such file or directory fatal: branch name required` Any ideas? – Ludder Feb 07 '14 at 11:30
  • 2
    This is due `/dev/fd/0` not existing for the stdout in windows, I am not sure what the equivalent would be. – Schleis Feb 13 '14 at 16:03
  • Great command set. However, if you only have one branch, the command will not `grep -v` out the master line. You can fix this with a simple master culling like so: `git branch -r | awk '{print }' | grep -v 'master' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin | grep -v 'master') | xargs git branch -d` – chris.wood Jul 05 '14 at 23:17
  • @Schleis I think `/dev/fd/0` is stdin not stdout. – 2rs2ts Sep 24 '14 at 23:20
  • 3
    Great command. As you said, the branch not fully merged are not deleted. The option -D in `git branch -D` instead of the final `git branch -d` will help if these branches are not relevant to your case. – Raphaël Dec 16 '14 at 10:22
  • 4
    fatal: branch name required – zgoda Mar 16 '15 at 10:58
  • 1
    @zgoda that would happen if you don't have any branches that would be deleted. To check, use everything up to the last pipe in to xargs and see what comes out. – Schleis Mar 16 '15 at 13:13
  • I love this, but the problem is that for some reason it didn't get rid of even half of my branches. It did get rid of many, but not all the branches that I was expecting. Do you have any idea why? – CtheGood Aug 05 '15 at 13:58
  • 1
    Did you perform a `git fetch --prune` before running this? Or are your branches not tracking a remote? This command only removes the branches whose remote tracking branch doesn't exist anymore. – Schleis Aug 05 '15 at 14:12
  • 2
    For those on Windows, the approach proposed in the answer by @wisbucky worked for me: http://stackoverflow.com/a/30494276/315702 – Mark McClelland Sep 17 '15 at 19:25
  • 2
    I'm getting an error: `egrep: brackets ([ ]) not balanced` when running this command? – mohammedkhan Jan 21 '16 at 11:02
  • 2
    This does not seem to work for me. In my case it is missing some branches. It seems to me that `branch -vv` writes `[origin/some_branch: gone]` for branches where the remote tracking branch has been removed by the prune? So, the following seems to work for me: `git branch -vv | grep -e ": gone]" | awk '{print $1}' | xargs git branch -d` – Thomas Arildsen Feb 02 '16 at 09:48
  • Oh, I just realised this is exactly what was proposed in http://stackoverflow.com/a/30494276/865169 – Thomas Arildsen Feb 02 '16 at 09:50
  • 2
    Add option `-r` to `xargs` to ignore when there are no branches to remove. – bryant1410 Aug 11 '16 at 19:13
  • 56
    To add it as an alias: `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'` – bryant1410 Aug 11 '16 at 19:13
  • 230
    Is there any proposed command for a future git release that will do this? This command looks like my cat has been on my laptop – Bron Davies Sep 14 '16 at 20:02
  • Absolutely. This should be built into git. – Jez Feb 16 '17 at 17:28
  • 1
    This is why I love Linux (and Bash)!! – Thom Parkin May 05 '17 at 15:25
  • 1
    Sure @ThomParkin I love them too, but you love them because somehow an extremely convolved command makes up for a being able to perform a common task that Git should handle itself? Though I love Git, it's amazing the overhead it requires for any development team that should be focused on development to remember simple, standard usage. – Project707 Sep 14 '17 at 05:06
  • 1
    Although this works in deleting those branches, I'm getting a bunch of errors for when the pattern matches stuff that isn't a branch, e.g.: "error: branch 'blabla' not found", where 'blabla' is all kinds of simple words or characters, sometimes brackets or just the word branch or handle, as if it took a branch name apart and took every piece of it as its own branch for some reason. – batjko Sep 15 '17 at 08:00
  • @Project707 Point taken. I cannot [entirely] disagree. – Thom Parkin Sep 30 '17 at 17:59
  • @Project707: I find the convoluted commands eventually boil down to requiring a Turing complete scripting language anyway. – Joshua Dec 05 '17 at 22:16
  • 43
    using `git branch -D` at the end of the command worked better for us, as we tend to squash commits on merge, which gets us `The branch x is not fully merged` errors when running with `-d`. – Mateus Gondim Mar 09 '18 at 13:40
  • 1
    For really detailed information, check this out: [explainshell.com](https://explainshell.com/explain?cmd=git+branch+-r+%7C+awk+%27%7Bprint+%241%7D%27+%7C+egrep+-v+-f+%2Fdev%2Ffd%2F0+%3C%28git+branch+-vv+%7C+grep+origin%29+%7C+awk+%27%7Bprint+%241%7D%27+%7C+xargs+git+branch+-d) – ChW Apr 16 '18 at 09:46
  • 2
    I got `error: The branch my_local_test_branch is not fully merged`. So, I added `-D` instead of `-d` to clean up the _unmerged_ `my_local_branch` The complete command: `git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs git branch -D` – m_drinks_coffee Jul 10 '18 at 12:34
  • 3
    @mani_jena I would be cautious about using `-D` here. It might remove some topic branch work you have locally that you haven't yet pushed to `origin`! – thekingoftruth Oct 10 '18 at 18:37
  • 1
    I just don't want local branches that are tracking remote branches that don't exist anymore, here's the alias I use: `alias git_branch_cleanup="git fetch -p && git branch -v | grep '[gone]' | awk '{print $1}' | xargs git branch -D"` – Marco Chiappetta Jan 04 '19 at 19:01
  • I got an error when making it an alias `awk: syntax error at source line 1`, so I moved it to `/usr/local/bin` so it can run it easily and without issues. – Sean Mar 17 '19 at 17:36
  • 1
    @MarcoChiappetta I got your point, but, what if the branch was never tracked by any remote? I believe in this case, using the `-D` could get rid of WIP. – aledpardo Aug 30 '19 at 18:51
  • @Ludder you can use Windows Subsystem on Linux for this. I just tested it and it worked. https://learn.microsoft.com/en-us/windows/wsl/install-win10 – Eric Humphrey Dec 02 '19 at 22:14
  • 3
    Works fine in Git Bash for Windows now. Seems the problem that existed earlier was fixed. – Adrian H. Feb 06 '20 at 13:12
  • Works for me, caveat a couple items: 1) set the name of the remote correctly (if not origin), 2) If your branches are not merged (for example, you do squash merges) use "-D" instead of "-d" in the last step. – Liam Feb 12 '20 at 16:13
  • 5
    Why isn't there something like 'git delete-untracked-branches', why on earth do we need to learn this convoluted nonsense. – HamsterWithPitchfork Jul 16 '20 at 10:12
  • Totally unnecessary why would you use that instead of two commands, one to prune remote, and one to delete the local branch? There's a lot to be said for keeping things simple and transparent. – Epirocks Nov 16 '20 at 12:47
  • @ludder This works for Git Bash on Windows 10 without WSL for me, it can probably be shortened: `git branch | sed 's/\x1b[[0-9]*m//g' | cut -c3- | cut -f1 -d' ' | awk '{print $1}' | grep -E -v $(git branch -r | sed 's/origin\///g' | sed 's/\x1b[[0-9]*m//g' | cut -c3- | cut -f1 -d' ' | xargs | sed 's/ /|/g' | awk '{print $1}') | awk '{print $1}' | xargs git branch -D` Code Credits: schleis, this answer, mateus-gondim, a comment on this answer, greg-hewgill, https://stackoverflow.com/a/3795522/6533028 and oliv, https://stackoverflow.com/a/36550261/6533028 – propatience Apr 06 '21 at 15:10
  • 1
    I like this variation `git fetch -p && awk '/origin/&&/gone/{print $1}' <(git branch -vv) | xargs git branch -D` – Gilad Halevy Mar 10 '22 at 08:52
633

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 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 git branch -d

More info.

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
jackocnr
  • 17,068
  • 10
  • 54
  • 63
  • 5
    Worked perfect, I think! Could anyone explain if this answer does anything differently than the accepted answer? – Andrew Oct 07 '15 at 16:00
  • Is it possible to turn this into a global git alias? I tried this but it didn't work: `git config --global alias.cleaner "branch --merged master | grep -v 'master$' | xargs branch -d"` – mgcdanny Oct 12 '15 at 15:21
  • Branches ending on 'master' won't be deleted. I use `git branch --merged master | grep -v '^ master$' | xargs git branch -d` just because it matches how it is formatted in git branch output – Dmitry Naumov Apr 01 '16 at 12:42
  • This is great, I would say an improvement would be to remove master from the `git branch --merged master` command. This allows you to base this on the current branch. In my case (we're using git flow) I wanted to delete all branches that weren't merged into develop (my current branch). – KarlPurk Apr 08 '16 at 10:20
  • 13
    Minor improvement: use `xargs -r git branch -d` so that nothing is run if there are no branches to delete. Note that the `-r` flag [may not be available everywhere](http://stackoverflow.com/questions/18812766/dont-call-xargs-if-the-previous-command-doesnt-return-anything) – Jacob Wang Jul 11 '16 at 02:20
  • 1
    This is great but I do not see how it answers the question. If I delete a branch on the remote that is not merged into master then this command will delete a bunch of other local branches but leave the branch whose remote tracking branch was deleted. No? – dumbledad Jan 11 '17 at 11:56
  • 8
    Since grep may not match due to git colors: `git branch --merged master --no-color | grep -v '^* master$' | xargs -n1 -r git branch -d` – Mike D Jan 17 '17 at 16:14
  • 6
    @NickRes seeing as you asked so nicely, `git branch --merged master` lists branches that are merged into master, then the middle grep part excludes master itself (we don't want to delete master!), and the last xargs part executes `git branch -d` (delete branch) on each of the results. Or you could just read the More info link provided in the answer ;) – jackocnr Mar 14 '17 at 23:09
  • 18
    Improvement: `git branch --merged master | egrep -v '^\s*\*?\s*master$' | xargs git branch -d`. Output from git v2.10.1 will display "* master" when master is checked out. I get rid of master both with or without an asterisk. – Esteban May 16 '17 at 19:12
  • 1
    @Esteban similar fix added - catches your case. (jackocnr hope you don't mind the edit - it wasn't excluding master on git 2.14 as it was) – CupawnTae Oct 10 '17 at 11:56
  • 2
    @mgcdanny (and anyone else interested): in order to make this a global `git` alias, you need to do (in `~/.gitconfig` or other config file): `cleaner = !git branch --merged master | grep -v '^[ *]*master$' | xargs git branch -d` – Harpo Jaeger May 13 '18 at 14:48
  • I am using `git branch --merged develop | egrep -v 'develop$|master$ | xargs -r git branch -d` on my develop branch. Not sure how best practice the specific regular expression is, but thanks for the help. – Alwyn Schoeman Mar 07 '19 at 18:51
  • 1
    This does work only if remote branch exists. If the branch is removed after merged into the master - this approach won't work. – Vladyslav Nikolaiev Jan 13 '20 at 09:30
  • 1
    Now that github [encourages the primary branch to be called `main`](https://github.com/github/renaming), the command written by @Esteban would be `git branch --merged main | egrep -v '^\s*\*?\s*main$' | xargs git branch -d` – Matthew Cox Jan 20 '21 at 22:12
  • @MatthewCox thanks for adding a comment that will help quite a few people. However, it's GitHub specific. – Esteban Jan 21 '21 at 22:37
  • @Esteban GitLab ist also changing "master" to "main" https://about.gitlab.com/blog/2021/03/10/new-git-default-branch-name/ – Jakob Liskow Mar 11 '21 at 18:07
  • 1
    My own improvement: `( git branch --merged origin/main; git branch --merged origin/master ) 2>/dev/null | tr -d '*' | grep -Ev '^ *(main|master)$' | xargs -r git branch -d` This is agnostic towards master or main (as long as no madman created them both...), uses origin instead of local main in case you haven't pulled it, removes asterisk from _all_ branches in case main is not the one currently checked out (nobody is using asterisks as part of their branch names... right?) and includes `-r` for xargs in case no branches are listed. – SvenS Jun 11 '21 at 09:35
  • *agnostic towards master or main (as long as no madman created them both...)* Heh, heh, this actually happens easily when you use 2 remotes and one has the woke policy and the other does not... – Stijn de Witt Mar 23 '22 at 09:32
  • 1
    @Andrew this deletes merged branches. There're other reasons for a local branch to not have a remote counterpart. – AncientSwordRage Jun 15 '22 at 13:03
262

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...

twalberg
  • 59,951
  • 11
  • 89
  • 84
  • 132
    This doesn't delete the local branches, only the remote pointers to the branches – Jaap Nov 01 '13 at 13:11
  • 7
    It deletes only these local branches, which does not exist in remote AND you never did a checkout on them. – Jarek Jakubowski Mar 11 '15 at 10:59
  • 17
    You should read the question more carefully as Jaap notes. People voting for this answer should also read what the question is really about. – Søren Boisen May 19 '15 at 17:52
  • 11
    Yes, this was actually exactly what I wanted to do! @SørenBoisen I hope in the last two years you've had time to revisit that opinion of yours... If content posted on SO should always be only related to what sometimes really confused OPs ask, we would remove a lot of information. Please don't presume to instruct people how they can use this site, other than the official guidelines, and they clearly do not prevent orthogonal yet useful answers. – Félix Adriyel Gagnon-Grenier Jun 11 '17 at 20:40
  • 8
    @FélixGagnon-Grenier Here is my issue with this answer: 1) The question is crystal clear, OP is clearly not confused. 2) This answer does not mention that it does not answer the question! 3) The question already states a way to prune remote-tracking branches which no longer exist, so it adds zero value. – Søren Boisen Jun 12 '17 at 09:14
  • 2
    Answers are for more than the op. Nearly a hundred people find this useful. Of course it adds value!! – Félix Adriyel Gagnon-Grenier Jun 12 '17 at 11:21
  • 1
    git fetch --prune does what I wanted it to do (trimming local branches that don't exist in the remote) - and the more upvoted bash-based answers don't. Therefore, I think this adds value and is worthy of an upvote. – starbeamrainbowlabs Jun 15 '17 at 08:31
  • 2
    Even if the local branch names do not match the upstream branches, Git still tracks that these branches track an upstream branch and this could be used to delete branches. In `.git/config`, there are entries for each such branch and its upstream name. Thus Git could use this information for pruning of local branches. It's simply a missing feature. – siride Nov 08 '17 at 20:06
  • 1
    @siride Exactly, not only does this answer not answer the question, it presents a whole ton of incorrect information regarding remote tracking and how git works. – DylanYoung May 08 '20 at 16:18
195

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.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
tzachs
  • 4,331
  • 1
  • 28
  • 28
176

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.
    
wisbucky
  • 33,218
  • 10
  • 150
  • 101
  • 20
    You can even make the command shorter with `git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -d` – chiborg Jul 30 '15 at 11:52
  • 11
    Just pointing out that you should be on master before doing this, because `git branch -d` compares branches against `HEAD`. – Steve Bennett Oct 15 '15 at 00:38
  • Excellent! I looked and looked for this. I wish I had seen your answer before I ended up figuring it out on my own. It would have saved me a few minutes. Here's my other answer: http://stackoverflow.com/questions/15661853/list-all-local-branches-without-a-remote/34969726#34969726 – Karl Wilbur Jan 23 '16 at 22:36
  • 1
    I ended up with a similar git alias, used cut since awk did not work for some reason: `prunelocal = !sh -c \"git checkout -q master && git fetch -q --prune && git branch -vv | git grep ': gone]' | cut -d' ' -f3 | xargs git branch -d\"` – Zitrax May 09 '16 at 11:40
  • This only worked after I did `git fetch --prune`. If I tried to combine that with the rest, it broke, trying to delete all the "branches" whose names were the output of the fetch. – Mirror318 Aug 12 '16 at 04:19
  • Very useful, specially when remote branches are rebased or squashed instead of merged. Adding prune, this will delete any branches make as "gone": ```git fetch -p; git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -D``` – Eneko Alonso Jan 20 '17 at 23:51
  • 5
    Oh dude... wisbucky you NEED to update your answer to tell people they should be on master. Just did this from a different branch. @stevebennet2 thanks for the informative comment – GrayedFox May 31 '17 at 13:29
  • `git branch -vv | awk '/^[^*].*: gone]/{print $1}''` will not show `*` if current branch is also gone. – kyb Sep 01 '18 at 07:19
  • 2
    The `: gone]` part is a localized string. For french version you must use `: disparue]`. – alexandre-rousseau Aug 17 '20 at 13:35
  • 3
    For anyone deciding to use `xargs git branch -D`, note that `-D` flag is essentially a "force-delete" but is necessary for deleting branches where the remote branch was squash-merged or rebased, so as a good middleground, I would suggest prompting for user confirmation before each delete (safer); using `xargs -p git branch -D` – ksgj1 Mar 14 '22 at 01:44
  • To get it work correctly run this command first: `git remote prune origin` – kgzdev Jan 11 '23 at 12:49
94

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

0xC0000022L
  • 20,597
  • 9
  • 86
  • 152
chris31389
  • 8,414
  • 7
  • 55
  • 66
  • Nice example. alternatively use `git remote prune origin`, also select-string doesn't seem to match with the latest version of git. Consider using `ConvertFrom-String -TemplateContent` – Bernie White May 24 '18 at 02:50
  • 4
    Looks like the latest version of git adds some trailing spaces, so the Split doesn't work to get the branch name. Do .toString().Trim().Split(" ") and it will – Chris Hynes Oct 26 '18 at 12:25
  • Here is mine: `git checkout master; git pull; git remote prune origin; git branch --merged | select-string -notmatch "master" | % { $_.toString().Trim().Split(" ")[0]} | % {git branch -d $_}` – Omzig Feb 26 '19 at 20:29
  • @Omzig What does that do differently? Can you give an explanation> – chris31389 Feb 27 '19 at 08:39
  • 1
    @chris31389, i have the pull in there to get everything that i am missing from master, and i changed the branch to --merged and not master, i want to make sure that i only delete the --merged branches. It just makes me feel better ;) – Omzig Feb 27 '19 at 20:33
  • 1
    This works quite nicely for PowerShell Core i.e., it's a cross platform solution. – Robb Vandaveer Jul 23 '19 at 15:41
  • 1
    I got: `error: The branch 'bugfix/foobar' is not fully merged. If you are sure you want to delete it, run 'git branch -D bugfix/foobar'.`, so for me it worked using a upper case d at the end `{git branch -D $_}` – Kraego Feb 22 '22 at 09:03
  • I had to run `[Console]::OutputEncoding = [System.Text.Encoding]::Utf8` before the command so that branches with non ansi characters get deleted as well, see https://stackoverflow.com/questions/59690793/the-powershell-call-operator-set-the-output-encoding – dba Jan 06 '23 at 13:26
70

One can configure Git to automatically remove references to deleted remote branches when fetching:

git config --global fetch.prune true

When calling git fetch or git pull afterwards, references to deleted remote branches get removed automatically.

wedesoft
  • 2,781
  • 28
  • 25
58

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)

johnshew
  • 615
  • 5
  • 5
50

It will list the local branches whose remote tracking branch is deleted from remote

$ git remote prune origin --dry-run

If you want to de-reference these local branches from local which are un tracked

$ git remote prune origin
027
  • 1,553
  • 13
  • 23
  • 27
    This doesn't answer the question, this is what the OP already knew. – Ken Williams Nov 11 '16 at 18:39
  • 6
    I also agree, but when you search "How to prune local tracking branches that do not exist on remote anymore", this is the top link and this answer matches with the title (not if one has read the full description in question), that is the reason for up votes:) – 027 Jul 21 '17 at 07:03
  • This does not remove the local branches. Still there after running this – Xin Sep 18 '18 at 22:59
  • 1
    @Xin try running the command without --dry-run argument as it will not actually execute the command, but show you what it will change. – Torsten Ojaperv Nov 23 '18 at 13:50
  • This is precisely what I needed. I had an old branch showing up as a remote branch locally that didn't exist on the server. – EmpathicSage May 30 '23 at 05:57
24

If using Windows and Powershell, you can use the following to delete all local branches that have been merged into the branch currently checked out:

git branch --merged | ? {$_[0] -ne '*'} | % {$_.trim()} | % {git branch -d $_}

Explanation

  • Lists the current branch and the branches that have been merged into it
  • Filters out the current branch
  • Cleans up any leading or trailing spaces from the git output for each remaining branch name
  • Deletes the merged local branches

It's worth running git branch --merged by itself first just to make sure it's only going to remove what you expect it to.

(Ported/automated from http://railsware.com/blog/2014/08/11/git-housekeeping-tutorial-clean-up-outdated-branches-in-local-and-remote-repositories/.)

Gnat
  • 2,861
  • 1
  • 21
  • 30
  • This will delete all merged branches versus just those that are marked as deleted on the remote. This could be undesirable if you have environment or release branches that have been merged but we want to keep around (like `uat` `prod` etc). – Shanerk Apr 13 '23 at 14:30
20

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.

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

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.

FelikZ
  • 2,976
  • 4
  • 32
  • 41
13

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.

S'chn T'gai Spock
  • 1,203
  • 18
  • 16
  • Even smaller (as described in another comment) `git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -d` – mrroboaat Jun 05 '20 at 08:13
  • I had to make the final `-d` a `-D`, otherwise your second line would give me this output on each branch: `error: The branch 'your-branch' is not fully merged. If you are sure you want to delete it, run 'git branch -D your-branch'.` – NateH06 Feb 24 '21 at 15:06
10

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
Oliver
  • 27,510
  • 9
  • 72
  • 103
9

To remove remote branches:

$git remote prune origin

To remove local branches that are already merged:

$git branch -D $(git branch --merged)
Arsen Khachaturyan
  • 7,904
  • 4
  • 42
  • 42
Aniket
  • 251
  • 2
  • 2
7

In Powershell:

git branch -D (git branch --merged |% { $_.trim() } )
Arsen Khachaturyan
  • 7,904
  • 4
  • 42
  • 42
Raúl Salinas-Monteagudo
  • 3,412
  • 1
  • 24
  • 22
5

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
hidralisk
  • 706
  • 6
  • 18
5

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"
Macadameane
  • 148
  • 2
  • 9
4

not sure how to do it all at once, but git git branch -d <branchname> will delete a local branch ONLY if it is completely merged. Note the lowercase d.

git branch -D <branchname> (note the capital D) will delete a local branch regardless of its merged status.

NHDaly
  • 7,390
  • 4
  • 40
  • 45
4

Late to the feast, but you can use the --no-contains option to avoid removing your master branch when using branch --merged

git branch --no-contains master --merged master | xargs git branch -d
Evert_B
  • 193
  • 5
3

Schleis' variant does not work for me (Ubuntu 12.04), so let me propose my (clear and shiny :) variants:

Variant 1 (I would prefer this option):

git for-each-ref --format='%(refname:short) %(upstream)' refs/heads/ | awk '$2 !~/^refs\/remotes/' | xargs git branch -D 

Variant 2:

a. Dry-run:

comm -23 <( git branch | grep -v "/" | grep -v "*" | sort ) <( git br -r | awk -F '/' '{print $2}' | sort ) | awk '{print "git branch -D " $1}'

b. Remove branches:

comm -23 <( git branch | grep -v "/" | grep -v "*" | sort ) <( git br -r | awk -F '/' '{print $2}' | sort ) | xargs git branch -D
daniilyar
  • 2,602
  • 3
  • 22
  • 25
3

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

sendon1982
  • 9,982
  • 61
  • 44
  • You don't really need to filter out any branches (ex master). If it's "merged" then that just means it'll remove your local copy, but "git checkout dev" will create a local branch from remote if not already present – csga5000 Sep 24 '18 at 21:34
  • But the local branch list are still there even if they are merged. This command is to remove these branches which have been deleted from remote. – sendon1982 Sep 25 '18 at 02:44
  • asterisk on the beginning means current branch, when you run that command on different branch will not works well – OzzyCzech Mar 26 '19 at 11:15
3

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

spume
  • 1,704
  • 1
  • 14
  • 19
  • Note you have to checkout a branch that tracks an existing remote branch, else the command right before piping to `xargs` will return `*` instead of the branch name. My suggestion is to use `git checkout master; git branch -vv --merged | grep ': gone]' | awk '{print $1}' | xargs git branch -d` – Tian Jul 09 '20 at 02:03
3

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
Ali Farhoudi
  • 5,350
  • 7
  • 26
  • 44
3

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
Chui Tey
  • 5,436
  • 2
  • 35
  • 44
3

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.

Shanerk
  • 5,175
  • 2
  • 40
  • 36
2

Based on the answers above I came with this one line solution:

git remote prune origin; git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs git branch -d
gagarwal
  • 4,224
  • 2
  • 20
  • 27
2

Using a variant on @wisbucky's answer, I added the following as an alias to my ~/.gitconfig file:

pruneitgood = "!f() { \
    git remote prune origin; \
    git branch -vv | perl -nae 'system(qw(git branch -d), $F[0]) if $F[3] eq q{gone]}'; \
}; f"

With this, a simple git pruneitgood will clean up both local & remote branches that are no longer needed after merges.

Ken Williams
  • 22,756
  • 10
  • 85
  • 147
2

Following is an adaptation of @wisbucky's answer for Windows users:

for /f "tokens=1" %i in ('git branch -vv ^| findstr ": gone]"') DO git branch %i -d

I use posh-git and unfortunately PS doesn't like the naked for, so I created a plain 'ol command script named PruneOrphanBranches.cmd:

@ECHO OFF
for /f "tokens=1" %%i in ('git branch -vv ^| findstr ": gone]"') DO CALL :ProcessBranch %%i %1

GOTO :EOF

:ProcessBranch
IF /I "%2%"=="-d" (
    git branch %1 %2
) ELSE (
    CALL :OutputMessage %1
)
GOTO :EOF

:OutputMessage
ECHO Will delete branch [%1] 
GOTO :EOF

:EOF

Call it with no parameters to see a list, and then call it with "-d" to perform the actual deletion or "-D" for any branches that are not fully merged but which you want to delete anyway.

Sam C
  • 303
  • 2
  • 4
  • This didn't work for me, somehow the `:` or `: ` (with a space) in `findstr` caused it to match everything every time--I almost deleted master. However, using a regex worked fine: `git branch -vv ^| findstr /R " \[origin/[0-9]+: gone\] "` – Josh Apr 03 '17 at 14:10
2

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.

Kgeorge
  • 136
  • 5
1

Try this in git bash, to fetch and prune references to deleted branches, and then prune the local branches that were tracking the removed ones:

git fetch -p && git branch -d `git branch -vv | grep ': gone]' | awk '{print $1}' | xargs`

Remember to checkout first a branch that won't be deleted, so that does not block the deleting of the branch.

Guillermo Gutiérrez
  • 17,273
  • 17
  • 89
  • 116
0

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
Meredith
  • 3,928
  • 4
  • 33
  • 58
0

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.

PaulH
  • 2,918
  • 2
  • 15
  • 31
0

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.

cyan-kinesin
  • 529
  • 1
  • 4
  • 18
0

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

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
-1

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]
Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
  • The link no longer works. Also, won't this conflict the the stock `git prune`? – Macadameane May 17 '22 at 19:06
  • 1
    @Macadameane Sorry for the broken link; I had moved the script into the `bin/` subdirectory. I've also renamed it to `git-rprune` to avoid that name clash; actually, I never invoke that command directly, but use an alias `oprune=rprune origin`. I'd never seen the built-in `git prune` so far, as it's an uncommon low-level command. – Ingo Karkat May 19 '22 at 07:04
-2

Delete any branch that isn't up to date with master

git co master && git branch | sed s/\*/\ / | xargs git branch -d 2> /dev/null
Jason Waldrip
  • 5,038
  • 8
  • 36
  • 59
-3

I'm pretty sure that git remote prune origin is what you want.

You can run it as git remote prune origin --dry-run to see what it would do without making any changes.

Jason Antman
  • 2,620
  • 2
  • 24
  • 26
-11

Using the GUI? Manual procedure, but quick and easy.

$ git gui

Select "Branch -> Delete". You can select multiple branches with ctrl-click (windows) and remove all of them.

Pedro Ballesteros
  • 1,006
  • 9
  • 11