Nyambaa
Nyambaa

Reputation: 41877

How do I delete all Git branches which have been merged?

How do I delete branches which have already been merged? Can I delete them all at once, instead of deleting each branch one-by-one?

Upvotes: 2737

Views: 1031526

Answers (30)

carbonr
carbonr

Reputation: 6067

This is what I do to remove local branches after merging PRs. This relies on the idea that the remote branch was there and then deleted, for e.g after merging.

git fetch --prune
git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -d

here prune updates my local git to make it aware that the remove branch is gone

then filter the ones with [gone] in the name and removes them.

Upvotes: 0

Adam Dymitruk
Adam Dymitruk

Reputation: 129762

NOTE: You can add other branches to exclude like master and dev if your workflow has those as a possible ancestor. Usually I branch off of a "sprint-start" tag and master, dev and qa are not ancestors.


First, list locally-tracking branches that were merged in remote (consider using -r flag to list all remote-tracking branches).

git branch --merged

You might see a few branches you don't want to remove. We can add arguments to skip important branches that we don't want to delete like master or a develop. The following command will skip the master/main branch and anything that has 'dev' in it.

git branch --merged | grep -Ev "(^\*|^\+|master|main|dev)"

The first part (^\*|^+) excludes the current branch and any branch checked out in another worktree.

If you want to skip a branch, you can add it to the grep command as below. The branch skip_branch_name will not be deleted.

git branch --merged | grep -Ev "(^\*|^\+|master|main|dev|skip_branch_name)"

To delete all local branches that are already merged into the currently checked out branch:

git branch --merged | grep -Ev "(^\*|^\+|master|main|dev)" | xargs --no-run-if-empty git branch -d

You can see that master and dev are excluded in case they are an ancestor.


You can delete a merged local branch with:

git branch -d branchname

To force deletion of an unmerged branch, use:

git branch -D branchname

To delete it from the remote use:

git push --delete origin branchname

git push origin :branchname    # for really old git

Once you delete the branch from the remote, you can prune to get rid of remote tracking branches with:

git remote prune origin

or prune individual remote tracking branches, as the other answer suggests, with:

git branch -dr branchname

Upvotes: 4076

Dan K.K.
Dan K.K.

Reputation: 6094

You can use gbda alias/function if you're using OhMyZSH with git plugin.

Upvotes: 14

pataruco
pataruco

Reputation: 11

I know this is already an answered question, but I wrote it as fish and Zsh functions

Zsh

Add this snippet to your ~/.zshrc


git-clean(){
  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
}

Fish

Create a fish function file ~/.config/fish/functions/git_clean.fish

function git-clean
 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}')
   git branch -D $branch
 end
end

Restart the shell with

exec "$SHELL"

Upvotes: 0

Ismael
Ismael

Reputation: 16730

This also works to delete all merged branches except master.

git branch --merged | grep -v '^* master$' | grep -v '^  master$' | grep -v '^* main$' | grep -v '^  main$' | xargs git branch -d

Edit: Also ignoring main branch as it's being used these days

Upvotes: 96

DevWL
DevWL

Reputation: 18860

On Windows with git bash installed egrep -v will not work

git branch --merged | grep -E -v "(master|test|dev)" | xargs git branch -d

where grep -E -v is equivalent of egrep -v

Use -d to remove already merged branches or -D to remove unmerged branches

Upvotes: 7

Timmmm
Timmmm

Reputation: 97058

So many bad answers here. This is what you probably want:

git branch --delete $(git branch --merged master --no-contains master --format='%(refname:short)')

This selects all local branches that have been merged into master (including master), and that aren't descendent of master (which excludes master itself). The --format is necessary because by default Git prints asterisks for the currently checked out branches. You might be able to do this with git for-each-ref too but it seems more complicated (it lists remote branches too).

You don't want:

  • git branch --merged (without master): This will list branches that have been "merged" into your currently checked out commit (i.e. HEAD), which is probably unexpected and not what you want.
  • | grep -v master: There's no need for this; you can just use --no-contains.
  • | xargs: Again, no need. git branch --delete can delete more than one branch.

Update: I've been using this for a while and it works pretty well. There are two minor flaws:

  1. If there are no branches to delete it will give you an error. That's fine for interactive use IMO. You shouldn't be using this in a script anyway since it's Bash and Bash should not be used for scripting.

  2. It won't delete branches that point to the same commit as master. I have a tool that automatically rebases all my branches, and for ones that have been merged it can leave them in this state. This may not matter to you.

Upvotes: 41

Eduardo Lucio
Eduardo Lucio

Reputation: 2487

Same @Adam Dymitruk 's answer, but with some improvements/fixes...

How do I delete all Git branches which have been merged?

First, list locally/remotely tracking branches that were merged in remote...

NOTE: You might see few branches that you don't want to remove. We can add few arguments ("grep") to skip important branches that you don't want to delete like "master" or a "hom"...

FOR LOCALLY-TRACKING BRANCHES

git branch --merged | grep -vE '(master|hom)$'

FOR REMOTE-TRACKING BRANCHES

git branch --remotes --merged | grep -vE 'origin/(master|hom)$'

To delete all locally/remotely tracking branches that are already merged into the CURRENTLY CHECKED OUT BRANCH (normally "main" or "master")...

FOR LOCALLY-TRACKING BRANCHES

git branch --merged | grep -vE '(master|hom)$' | xargs git branch --delete

FOR REMOTE-TRACKING BRANCHES

git branch --remotes --merged | grep -vE 'origin/(master|hom)$' | sed 's/ origin\///' | xargs git push --delete origin

TIP: To use multiple resulting commands use "xargs -n 1".

NOTE: If it's not merged (WARNING! ⚠️), use git branch --delete --force.


Improvements

  • The "grep" commands are safer because they match the names "by the whole" and by the end of the string. In other words, less risk of matching the part of the branch name.

  • The command...

git branch --remotes --merged | grep -vE 'origin/(master|hom)$' | sed 's/ origin\///' | xargs git push --delete origin

...effectively deletes branches remotely.

  • More organized and succinct procedure.

Upvotes: 1

Raman
Raman

Reputation: 19675

My Bash script contribution is based loosely on mmrobin's answer.

It takes some useful parameters specifying includes and excludes, or to examine/remove only local or remote branches instead of both.

#!/bin/bash

# exclude branches regex, configure as "(remote|branch1|branch2|etc)$"
excludes_default="(upstream|master|main|next|maint.*|proposed.*)$"
excludes=
includes=
base=master
merged="--merged"
local=1
remote=1

while [ $# -gt 0 ]; do
  case "$1" in
  -i) shift; includes="$includes $1" ;;
  -e) shift; excludes="$excludes $1" ;;
  -b) shift; base="$1" ;;
  --no-local) local=0 ;;
  --no-remote) remote=0 ;;
  --all) merged= ;;
  *) echo "Unknown argument $1"; exit 1 ;;
  esac
  shift   # next option
done

if [ "$excludes" == "" ]; then
  excludes="__NOTHING__"
else
  excludes="($(echo $excludes | sed -e 's/ /|/g'))"
fi

if [ "$includes" == "" ]; then
  includes=".*"
else
  includes="($(echo $includes | sed -e 's/ /|/g'))"
fi

current_branch=$(git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
if [ "$current_branch" != "$base" ]; then
  echo "ERROR: You are on branch $current_branch, NOT $base. Change branch or specify base with -b."
  exit 1
fi
echo -e "NOTE: no fetch performed, do it manually...\n"

remotes=$(git remote)
remote_branches=$(git branch --no-color --format="%(refname)" -r $merged \
  | grep -v HEAD | grep -v "/$current_branch$" | grep -v -E "$excludes" | grep -v -E "$excludes_default" | grep -E "$includes" \
  | sed -e 's|refs/remotes/||g' -e 's|refs/heads/||g')
local_branches=$(git branch --no-color --format="%(refname)" $merged \
  | grep -v HEAD | grep -v "$current_branch$" | grep -v -E "$excludes" | grep -v -E "$excludes_default" | grep -E "$includes" \
  | sed -e 's|refs/remotes/||g' -e 's|refs/heads/||g')

if [ -z "$remote_branches" ] && [ -z "$local_branches" ]; then
  echo "No existing branches have been merged into $current_branch."
else
  echo "This will remove the following branches:"
  if [ "$remote" == 1 -a -n "$remote_branches" ]; then
    echo "$remote_branches"
  fi
  if [ "$local" == 1 -a -n "$local_branches" ]; then
    echo "$local_branches"
  fi
  read -p "Continue? (y/n): " -n 1 choice
  echo
  if [ "$choice" == "y" ] || [ "$choice" == "Y" ]; then
    if [ "$remote" == 1 ]; then
      # Remove remote branches
      if [ -z "$remotes" ]; then
        echo "No remote branches removed."
      fi
      for remote in $remotes
      do
        branches=$(echo -n "$remote_branches" | grep "$remote/" | sed "s/$remote\/?\(.*\)/:\1 /g" | tr '\n' ' ')
        git push $remote $branches 
      done
    fi

    if [ "$local" == 1 ]; then
      # Remove local branches
      locals=$(echo -n "$local_branches" | tr '\n' ' ')
      if [ -z "$locals" ]; then
        echo "No local branches removed."
      else
        git branch -d "$locals"
      fi
    fi
  fi
fi

Upvotes: 0

ted
ted

Reputation: 4985

The following creates an alias git cleanup with one optional parameter branch. By default it uses the default branch of remote origin. Every branch that has been merged into this will be deleted. There also is a -d /--dryrun option to show what would be deleted.

Setup

git config --global alias.cleanup '!COMMAND="git branch -D"; while [[ $# -gt 0 ]]; do case "$1" in -d|--dryrun) COMMAND="echo"; shift; ;; *) MAIN_BRANCH="$1"; shift;; esac; done; MAIN_BRANCH="${MAIN_BRANCH:-$(git symbolic-ref refs/remotes/origin/HEAD)}"; git for-each-ref --merged="$MAIN_BRANCH" --no-contains="$MAIN_BRANCH" --format="%(refname:short)" refs/heads/ | xargs -n1 -r $COMMAND;#'

Usage:

git cleanup             # delete all branches that have been merged into origin/HEAD
git cleanup master2     # delete all branches that have been merged into master2
git cleanup master2 -d  # do a dryrun (show names of branches that would be delted)

Readable code

Who can read that "oneliner"? Well here you go

COMMAND="git branch -D";
while [[ $# -gt 0 ]]; do
  case "$1" in
    -d|--dryrun)
      COMMAND="echo"; 
      shift; 
      ;; 
    *)
      MAIN_BRANCH="$1";
      shift
    ;;
  esac;
done;
MAIN_BRANCH="${MAIN_BRANCH:-$(git symbolic-ref refs/remotes/origin/HEAD)}"; 
git for-each-ref --merged="$MAIN_BRANCH" --no-contains="$MAIN_BRANCH" --format="%(refname:short)" refs/heads/ | xargs -n1 -r $COMMAND;
#

What is different to the other answers?

  • uses --no-contains option to filter out the identity branch (branch into which all other branches that are deleted have been merged) instead of grep -v (this works better if you are specific about your branch and specify refs/heads/master for example)
  • uses the remote HEAD to check for the default branch name
  • has a parameter to specify which branch to use as merge master
  • has a dryrun option

Upvotes: -1

l0b0
l0b0

Reputation: 58978

tl;dr: git branch --format='%(if:notequals=main)%(refname:short)%(then)%(if:notequals=master)%(refname:short)%(then)%(refname:short)%(end)%(end)' --merged | xargs --max-args=1 --no-run-if-empty echo git branch --delete, then remove the echo if you're happy to actually delete the listed branches.

Look ma, no tools!

Git can filter branches without using external tools, meaning there's a cross-platform, relatively simple way to exclude the default branch from searches. To list branches merged into the "main" branch, excluding the "main" branch itself: git branch --format='%(if:notequals=main)%(refname:short)%(then)%(refname:short)%(end)' --merged=main. For the "master" branch: git branch --format='%(if:notequals=master)%(refname:short)%(then)%(refname:short)%(end)' --merged=master

We can also combine the two, printing the branch name only if it matches neither "main" nor "master": git branch --format='%(if:notequals=main)%(refname:short)%(then)%(if:notequals=master)%(refname:short)%(then)%(refname:short)%(end)%(end)' --merged

The last one comes with a small caveat: you should check out the default branch (either "main" or "master", in this case) first, because --merged without a value means "merged into HEAD", which could be pointing to one of the branches you want to delete. But trying to delete the current local branch won't work anyway, so this is not a risk if that's what you're doing.

Another advantage of this approach is that there's no extra leading spaces or asterisk to filter out.

These commands will print an empty line rather than nothing for the excluded branch, but xargs takes care of that. If you want to process the lines using something other than xargs you might need special handling for the empty lines (like passing through sed '/^$/d')

See git help for-each-ref for more info about --format.

Upvotes: 1

Viktor Ivliiev
Viktor Ivliiev

Reputation: 1334

My favorite and simple script:

git branch --merged | grep -E -v "(master|main|develop|other)" | xargs git branch -d

Upvotes: 19

Klas Mellbourn
Klas Mellbourn

Reputation: 44437

For those of you that are on Windows and prefer PowerShell scripts, here is one that deletes local merged branches:

function Remove-MergedBranches
{
  git branch --merged |
    ForEach-Object { $_.Trim() } |
    Where-Object { $_ -NotMatch "^\*" } |
    Where-Object { -not ( $_ -Like "*master" -or $_ -Like "*main" ) } |
    ForEach-Object { git branch -d $_ }
}

Or the short version:

git branch --merged | %{$_.trim()}  | ?{$_ -notmatch 'dev' -and $_ -notmatch 'master' -and $_ -notmatch 'main'} | %{git branch -d $_.trim()}

Upvotes: 92

earlonrails
earlonrails

Reputation: 5182

Based on some of these answers I made my own Bash script to do it too!

It uses git branch --merged and git branch -d to delete the branches that have been merged and prompts you for each of the branches before deleting.

merged_branches () {
    local current_branch=$(git rev-parse --abbrev-ref HEAD)
    for branch in $(git branch --merged | cut -c3-)
      do
        echo "Branch $branch is already merged into $current_branch."
        echo "Would you like to delete it? [Y]es/[N]o "
        read REPLY
        if [[ $REPLY =~ ^[Yy] ]]; then
            git branch -d $branch
        fi
    done
}

Upvotes: 6

real_ate
real_ate

Reputation: 11299

Just extending Adam's answer a little bit:

Add this to your Git configuration by running git config -e --global

[alias]
    cleanup = "!git branch --merged | grep  -v '\\*\\|master\\|develop' | xargs -n 1 -r git branch -d"

And then you can delete all the local merged branches doing a simple git cleanup.

Upvotes: 297

Elmer
Elmer

Reputation: 9437

I use this:

git branch --delete $(git branch --format '%(refname:short)' --merged | grep --invert-match 'main\|master\|branch-to-skip')

It lists all merged branched in the specified format, then it feeds that list to git branch --delete.

Upvotes: 12

Ilya Serbis
Ilya Serbis

Reputation: 22333

git cleanup script from the git-toolbelt

Deletes all branches that have already been merged into master or develop. Keeps other branches lying around. Will be most conservative with deletions.

Removes branches both locally and in the origin remote.

Upvotes: 2

Guido Bouman
Guido Bouman

Reputation: 3285

You'll want to exclude the master, main & develop branches from those commands.

Local git clear:

git branch --merged | grep -v '\*\|master\|main\|develop' | xargs -n 1 git branch -d

Remote git clear:

git branch -r --merged | grep -v '\*\|master\|main\|develop' | sed 's/origin\///' | xargs -n 1 git push --delete origin

Sync local registry of remote branches:

git fetch -p

Upvotes: 147

eddies
eddies

Reputation: 7493

I've used Adam's answer for years now. That said, that there are some cases where it wasn't behaving as I expected:

  1. branches that contained the word "master" were ignored, e.g. "notmaster" or "masterful", rather than only the master branch
  2. branches that contained the word "dev" were ignored, e.g. "dev-test", rather than only the dev branch
  3. deleting branches that are reachable from the HEAD of the current branch (that is, not necessarily master)
  4. in detached HEAD state, deleting every branch reachable from the current commit

1 & 2 were straightforward to address, with just a change to the regex. 3 depends on the context of what you want (i.e. only delete branches that haven't been merged into master or against your current branch). 4 has the potential to be disastrous (although recoverable with git reflog), if you unintentionally ran this in detached HEAD state.

Finally, I wanted this to all be in a one-liner that didn't require a separate (Bash|Ruby|Python) script.

TL;DR

Create a git alias "sweep" that accepts an optional -f flag:

git config --global alias.sweep '!git branch --merged $([[ $1 != "-f" ]] \
&& git rev-parse master) | egrep -v "(^\*|^\s*(master|develop)$)" \
| xargs git branch -d'

and invoke it with:

git sweep

or:

git sweep -f

The long, detailed answer

It was easiest for me to create an example git repo with some branches and commits to test the correct behavior:

Create a new git repo with a single commit

mkdir sweep-test && cd sweep-test && git init
echo "hello" > hello
git add . && git commit -am "initial commit"

Create some new branches

git branch foo && git branch bar && git branch develop && git branch notmaster && git branch masterful
git branch --list
  bar
  develop
  foo
* master
  masterful
  notmaster

Desired behavior: select all merged branches except: master, develop or current

The original regex misses the branches "masterful" and "notmaster" :

git checkout foo
git branch --merged | egrep -v "(^\*|master|dev)"
  bar

With the updated regex (which now excludes "develop" rather than "dev"):

git branch --merged | egrep -v "(^\*|^\s*(master|develop)$)"
bar
masterful
notmaster

Switch to branch foo, make a new commit, then checkout a new branch, foobar, based on foo:

echo "foo" > foo
git add . && git commit -am "foo"
git checkout -b foobar
echo "foobar" > foobar
git add . && git commit -am "foobar"

My current branch is foobar, and if I re-run the above command to list the branches I want to delete, the branch "foo" is included even though it hasn't been merged into master:

git branch --merged | egrep -v "(^\*|^\s*(master|develop)$)"
  bar
  foo
  masterful
  notmaster

However, if I run the same command on master, the branch "foo" is not included:

git checkout master && git branch --merged | egrep -v "(^\*|^\s*(master|develop)$)"
  bar
  masterful
  notmaster

And this is simply because git branch --merged defaults to the HEAD of the current branch if not otherwise specified. At least for my workflow, I don't want to delete local branches unless they've been merged to master, so I prefer the following variant using git rev-parse:

git checkout foobar
git branch --merged $(git rev-parse master) | egrep -v "(^\*|^\s*(master|develop)$)"
  bar
  masterful
  notmaster

Detached HEAD state

Relying on the default behavior of git branch --merged has even more significant consequences in detached HEAD state:

git checkout foobar
git checkout HEAD~0
git branch --merged | egrep -v "(^\*|^\s*(master|develop)$)"
  bar
  foo
  foobar
  masterful
  notmaster

This would have deleted the branch I was just on, "foobar" along with "foo", which is almost certainly not the desired outcome. With our revised command, however:

git branch --merged $(git rev-parse master) | egrep -v "(^\*|^\s*(master|develop)$)"
  bar
  masterful
  notmaster

One line, including the actual delete

git branch --merged $(git rev-parse master) | egrep -v "(^\*|^\s*(master|develop)$)" | xargs git branch -d

All wrapped up into a git alias "sweep":

git config --global alias.sweep '!git branch --merged $([[ $1 != "-f" ]] \
&& git rev-parse master) | egrep -v "(^\*|^\s*(master|develop)$)" \
| xargs git branch -d'

The alias accepts an optional -f flag. The default behavior is to only delete branches that have been merged into master, but the -f flag will delete branches that have been merged into the current branch.

git sweep
Deleted branch bar (was 9a56952).
Deleted branch masterful (was 9a56952).
Deleted branch notmaster (was 9a56952).
git sweep -f
Deleted branch foo (was 2cea1ab).

Upvotes: 69

lucianosousa
lucianosousa

Reputation: 8374

The simplest way I found to do it removing only local branches, not remote ones:

$ git branch --merged | grep -v master | xargs -n 1 git branch -D

This command will delete only branches already merged in your master one. Be careful if you don't want to delete other branches, such as a staging.

Upvotes: 3

kenorb
kenorb

Reputation: 166879

Try the following command:

git branch -d $(git branch --merged | grep -vw $(git rev-parse --abbrev-ref HEAD))

By using git rev-parse will get the current branch name in order to exclude it. If you got the error, that means there are no local branches to remove.

To do the same with remote branches (change origin with your remote name), try:

git push origin -vd $(git branch -r --merged | grep -vw $(git rev-parse --abbrev-ref HEAD) | cut -d/ -f2)

In case you've multiple remotes, add grep origin | before cut to filter only the origin.

If above command fails, try to delete the merged remote-tracking branches first:

git branch -rd $(git branch -r --merged | grep -vw $(git rev-parse --abbrev-ref HEAD))

Then git fetch the remote again and use the previous git push -vdcommand again.

If you're using it often, consider adding as aliases into your ~/.gitconfig file.

In case you've removed some branches by mistake, use git reflog to find the lost commits.

Upvotes: 5

bedrin
bedrin

Reputation: 4584

If you are using branching model like HubFlow or GitFlow you can use this command to remove the merged feature branches:

git branch --merged | grep feature.* | grep -v "\*" | xargs -n 1 git branch -d

Upvotes: 0

RedEyed
RedEyed

Reputation: 2135

Just created python script for that:

import sys
from shutil import which
import logging
from subprocess import check_output, call

logger = logging.getLogger(__name__)

if __name__ == '__main__':
    if which("git") is None:
        logger.error("git is not found!")
        sys.exit(-1)

    branches = check_output("git branch -r --merged".split()).strip().decode("utf8").splitlines()
    current = check_output("git branch --show-current".split()).strip().decode("utf8")
    blacklist = ["master", current]

    for b in branches:
        b = b.split("/")[-1]

        if b in blacklist:
            continue
        else:
            if input(f"Do you want to delete branch: '{b}' [y/n]\n").lower() == "y":
                call(f"git branch -D {b}".split())
                call(f"git push --delete origin {b}".split())

Upvotes: 1

Mariusz Pawelski
Mariusz Pawelski

Reputation: 28962

If you're on Windows you can use Windows Powershell or Powershell 7 with Out-GridView to have a nice list of branches and select with mouse which one you want to delete:

git branch --format "%(refname:short)" --merged  | Out-GridView -PassThru | % { git branch -d $_ }

enter image description here after clicking OK Powershell will pass this branches names to git branch -d command and delete them enter image description here

Upvotes: 22

Prashant
Prashant

Reputation: 1064

I've been using the following method to remove merged local AND remote branches in one cmd.

I have the following in my bashrc file:

function rmb {
  current_branch=$(git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
  if [ "$current_branch" != "master" ]; then
    echo "WARNING: You are on branch $current_branch, NOT master."
  fi
  echo "Fetching merged branches..."
  git remote prune origin
  remote_branches=$(git branch -r --merged | grep -v '/master$' | grep -v "/$current_branch$")
  local_branches=$(git branch --merged | grep -v 'master$' | grep -v "$current_branch$")
  if [ -z "$remote_branches" ] && [ -z "$local_branches" ]; then
    echo "No existing branches have been merged into $current_branch."
  else
    echo "This will remove the following branches:"
    if [ -n "$remote_branches" ]; then
      echo "$remote_branches"
    fi
    if [ -n "$local_branches" ]; then
      echo "$local_branches"
    fi
    read -p "Continue? (y/n): " -n 1 choice
    echo
    if [ "$choice" == "y" ] || [ "$choice" == "Y" ]; then
      # Remove remote branches
      git push origin `git branch -r --merged | grep -v '/master$' | grep -v "/$current_branch$" | sed 's/origin\//:/g' | tr -d '\n'`
      # Remove local branches
      git branch -d `git branch --merged | grep -v 'master$' | grep -v "$current_branch$" | sed 's/origin\///g' | tr -d '\n'`
    else
      echo "No branches removed."
    fi
  fi
}

original source

This doesn't delete the master branch, but removes merged local AND remote branches. Once you have this in you rc file, just run rmb, you're shown a list of merged branches that will be cleaned and asked for confirmation on the action. You can modify the code to not ask for confirmation as well, but it's probably good to keep it in.

Upvotes: 4

psuzzi
psuzzi

Reputation: 2277

Note: I am not happy with previous answers, (not working on all systems, not working on remote, not specifying the --merged branch, not filtering exactly). So, I add my own answer.

There are two main cases:

Local

You want to delete local branches that are already merged to another local branch. During the deletion, you want to keep some important branches, like master, develop, etc.

git branch --format "%(refname:short)" --merged master | grep -E -v '^master$|^feature/develop$' | xargs -n 1 git branch -d

Notes:

  • git branch output --format ".." is to strip whitespaces and allow exact grep matching
  • grep -E is used instead of egrep, so it works also in systems without egrep (i.e.: git for windows).
  • grep -E -v '^master$|^feature/develop$' is to specify local branches that I don't want to delete
  • xargs -n 1 git branch -d: perform the deletion of local branches (it won't work for remote ones)
  • of course you get an error if you try deleting the branch currently checked-out. So, I suggest to switch to master beforehand.

Remote

You want to delete remote branches that are already merged to another remote branch. During the deletion, you want to keep some important branches, like HEAD, master, releases, etc.

git branch -r --format "%(refname:short)" --merged origin/master | grep -E -v '^*HEAD$|^*/master$|^*release' | cut -d/ -f2- | xargs -n 1 git push --delete origin

Notes:

  • for remote, we use the -r option and provide the full branch name: origin/master
  • grep -E -v '^*HEAD$|^*/master$|^*release' is to match the remote branches that we don't want to delete.
  • cut -d/ -f2- : remove the unneeded 'origin/' prefix that otherwise is printed out by the git branch command.
  • xargs -n 1 git push --delete origin : perform the deletion of remote branches.

Upvotes: 13

Paulo Fidalgo
Paulo Fidalgo

Reputation: 22331

Given you want to delete the merged branches, you need to delete the remote-tracking branches only, unless you state otherwise.

So to delete those branches you can do it by

git branch --remote --merged origin/master | egrep -v "(^\*|master|development)" | cut -b 10- | xargs git push --delete origin

This will delete all merged branches (merged to master) except master and development.

Upvotes: 0

kuboon
kuboon

Reputation: 10201

To delete all branches on remote that are already merged:

git branch -r --merged | grep -v master | sed 's/origin\//:/' | xargs -n 1 git push origin

In more recent versions of Git

git branch -r --merged | grep -v master | sed 's/origin\///' | xargs -n 1 git push --delete origin

UPDATE (by @oliver; since does not fit in comment, but enough answers already): if you are on branch ABC then ABC will appear in the results of git branch -r --merged because the branch is not specified, so branch defaults to current branch, and a branch always qualifies as merged to itself (because there are no differences between a branch and itself!).

So either specify the branch:

git branch -r --merged master | grep -v master ...

OR first checkout master:

git checkout master | git branch -r --merged | grep -v ...

Upvotes: 604

folex
folex

Reputation: 5297

For me git branch --merged doesn't show branches that were merged via GitHub PR. I'm not sure of the reasons, but I use the following line to delete all local branches that do not have remote tracking branch:

diff <(git branch --format "%(refname:short)") <(git branch -r | grep -v HEAD | cut -d/ -f2-) | grep '<' | cut -c 3- | xargs git branch -D

Explanation:

  • git branch --format "%(refname:short)" gives a list of local branches
  • git branch -r | grep -v HEAD | cut -d/ -f2- gives a list of remote branches, filtering out HEAD
  • diff <(...) <(...) gives a diff of output of two commands inside parentheses
  • grep '<' filters branches that exist in first list, but not in the second
  • cut -c 3- gives line starting from 3rd character, thus removing prefix <
  • xargs git branch -D executes git branch -D against each branch name

Alternatively, you can avoid grep -v '<' like this:

diff --old-line-format="%L" --new-line-format="" --unchanged-line-format="" <(git branch --format "%(refname:short)") <(git branch -r | grep -v HEAD | cut -d/ -f2-) | xargs git branch -D

Upvotes: 3

holys
holys

Reputation: 14809

git-delete-merged-branches from git-extras repo.

https://github.com/tj/git-extras/blob/master/Commands.md#git-delete-merged-branches

Upvotes: 1

Related Questions