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

后端 未结 30 1057
离开以前
离开以前 2020-11-22 14:22

I have many Git branches. How do I delete branches which have already been merged? Is there an easy way to delete them all instead of deleting them one by one?

相关标签:
30条回答
  • 2020-11-22 14:58

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

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

    0 讨论(0)
  • 2020-11-22 14:59

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

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

    0 讨论(0)
  • 2020-11-22 15:00

    This also works to delete all merged branches except master.

    git branch --merged | grep -v '^* master$' | grep -v '^  master$' | xargs git branch -d
    
    0 讨论(0)
  • 2020-11-22 15:00

    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.

    0 讨论(0)
  • 2020-11-22 15:01

    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:

    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).
    
    0 讨论(0)
  • 2020-11-22 15:01

    Git Sweep does a great job of this.

    0 讨论(0)
提交回复
热议问题