1

My need

Hi, I'm looking for a command to delete all local branches already pushed to origin.

I especially want to keep all branches with commits not pushed yet to their respective remote branches.

Reason

git prune does a part of the job by clearing branches while the remote is deleted, but as I have many feature branches I would need to keep only the branches which have not been fully pushed to remote, to avoid having a long list of local branches in my repo, restricting them to those actually in works.

Thank you!

Romain Valeri
  • 19,645
  • 3
  • 36
  • 61
Pleymor
  • 2,611
  • 1
  • 32
  • 44

2 Answers2

3
git remote update # `git fetch --all` but it allows configurable skipping, see the docs
git for-each-ref refs/heads --format='
        [[ "%(upstream)" != "" ]] &&    # there is an upstream branch
        git merge-base --is-ancestor %(refname) %(upstream) &&  # which has this tip
        echo delete %(refname)
' # | sh # | git update-ref --stdin

Generating commands for the shell is useful and fun -- if you c&p the above it won't actually do anything to your repo, its output is instead the commands to check for whether a branch is in the upstream; delete a # and it'll execute the checks instead of just printing them, try it.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • 1
    What is this `[[ ]]` syntax? Could you share a link to some doc to learn about it? I don't get what it is / how it works. (Also, `# | sh # | sh` .... is this an in-code comment?) – Romain Valeri Jul 08 '19 at 08:51
  • [`man bash`](https://linux.die.net/man/1/bash), skip to `[[ expression ]]`. It's a bashism that was originated in `ksh` and works in `zsh`. Not in POSIX. – phd Jul 08 '19 at 10:22
  • 1
    [Stack overflow question regarding difference between single/double brackets](https://stackoverflow.com/q/669452/151344) – Alderath Jul 08 '19 at 10:37
  • @RomainValeri `[[` is a "new" feature that's been so widely supported for so long (coming up 20 years now in all the major shells that actually get used -- I'm wagering there are no distros that try to force `dash` as their interactive shell) I've started using it as standard, say `help [[`. – jthill Jul 08 '19 at 14:00
  • @jthill Thanks a lot for the explanation, but I'm still baffled by a few things here... among which : shell commands *inside* a format pattern?! How is this even working (I don't doubt it does, I'm just at a loss as to why/how) ? (also, your sentence about getting rid of a "#" refered to the last line, right? What is `# | sh` doing? why is it doubled?) Clearly too many questions, I know, I'm sorry :-) – Romain Valeri Jul 08 '19 at 14:15
  • @RomainValeri try it -- see the added explanation. Delete a `#` and it'll execute the commands instead of printing them. In this case, those executed commands will themselves print the actual branch deletions (because the executed commands include `echo git branch -d %(refname)`) so to actually delete the branches you have to delete both `#`'s on the last line. I'm just trying to shoehorn in some lessons on shell use, as the answer says, it's a useful technique. – jthill Jul 08 '19 at 14:26
  • Thank you @jthill, amazing! It looks almost good, I just have to bring a minor modification, because the command `git for-each-ref refs/heads --format='[[ "%(upstream)" != "" ]] && git merge-base --is-ancestor %(refname) %(upstream) && echo git branch -d % (refname)' | sh` tries to execute commands like `git branch -d refs/heads/my-super-branch` instead of `git branch -d my-super-branch`, so I guess a little `sed` to add should do the trick – Pleymor Jul 08 '19 at 14:39
  • gakk. Fixed. f-e-r has a `:short` format option for that, no need for the sed, sorry. – jthill Jul 08 '19 at 14:41
3

Although it looks like a blind elephant in regards to the nimble and smart solution suggested by jthill, I share here the brutal approach I tend to use so far for the same need you describe :

# nuke all branches* (which have no unique commit)
git branch -d $(git for-each-ref --format="%(refname:short) refs/heads")

# for "main" branches (I mean permanent, like master), recreate them if needed from remote
git checkout master

This is the one-shot version, but an alias can easily be made from it for convenience.

Note the -d flag which, as opposed to -D, means "soft deletion" and refuses to delete branches with unmerged commits (safecheck ignored by -D).


* here, git might (depending on the state of the current branch) complain about not being able to delete the branch you're on. Not a big deal in direct command mode, but a bit annoying in an alias. Just checkout a mock branch beforehand and delete it afterwards. Since we're nuking everything to be sure, I tend to name the branch from-orbit.

Romain Valeri
  • 19,645
  • 3
  • 36
  • 61
  • I didn't know this difference between `-d` and `-D`, it just does the trick, thank you! – Pleymor Jul 08 '19 at 14:43
  • 2
    Upvoted because this is probably what I'd actually do from the command line, error messages and all. In scripts I've got this fetish for not redirecting stderr when I have any choice so I'd use the explicit checking. – jthill Jul 08 '19 at 15:14