Skip to content

Instantly share code, notes, and snippets.

@billdawson
Created August 5, 2011 00:58
Show Gist options
  • Save billdawson/1126706 to your computer and use it in GitHub Desktop.
Save billdawson/1126706 to your computer and use it in GitHub Desktop.
Delete git timob-* branches if they've been merged with your master branch

Purpose

Gets rid of your local and remote (origin) branches that have "timob-" in their name and that have already been merged into master (or into whatever other branch you want to specify.)

Not to be confused with the actual git prune command.

Usage

$ git branch --merged master | gprune.py

gprune.py has to be executable, of course. Else python gprune.py.

To see what it would do, without being destructive, add --test argument.

What's that?

The git command ...

$ git branch --merged master

... by itself will show all of your branches which have been merged into master. (If you use a different branch name for your "master" -- like I use "appc_master" -- just substitute it in that git command.)

You can pipe that list of branches into this python script. The script then goes through that list and does some safety checks before deciding to delete a branch or not. The flow/logic:

  • git fetch origin is first called to be sure your remote origin branches are updated because we compare them with the local branch (see below).
  • Branch name must contain "timob-" to be considered for deletion. You might want to change that -- I put it in there as extra safety because I'm most interested in only deleting branches that look like pull request branches. Technically, however, it should be safe to delete any branch that has been merged and that passes the rest of the tests below.
  • If you have no remotes/origin branch of the same name, your local branch is deleted using git branch -d [branch name]. I don't use -D. The idea here is that it's safe to delete the branch because a) it's already been merged into master; and b) you don't have a same-named branch up on your origin that could differ from it. If it's your local branch of someone else's branch (i.e., "marshall-timob-4545") and it's anyway merged in master, then we don't really care if it happens to differ from the owner's version of the branch, so it's still safe to delete.
  • If you do have a remotes/origin branch of the same name, git diff --shortstat is run to compare the local and origin branches. If they differ, neither will be deleted -- i.e., nothing will be done -- because it feels like a work-in-progress. If they don't differ, the local will be deleted with git branch -d [branch name] and the remote will be deleted with git push origin :[branch name]. The idea here is that if there is no difference between the remote and the local, then they have both been merged into master (since we know the local has already been merged.) Therefore it's safe to delete them both.
#!/usr/bin/env python
import sys
from subprocess import Popen, PIPE, call, check_call, CalledProcessError
if sys.stdin.isatty():
print >> sys.stderr, "You should run this by piping a list of branches."
print >> sys.stderr, "Example:"
print >> sys.stderr, "$ git branch --merged master | %s" % sys.argv[0]
sys.exit(1)
test_run = "--test" in sys.argv
print "Running 'git fetch origin' to be safe when comparing local branches with origin\n"
call(["git", "fetch", "origin"])
merged_branches = [s.strip() for s in sys.stdin.read().strip().split('\n')]
# list of all branches
p = Popen(["git", "branch", "-a"], stdout=PIPE, stderr=PIPE)
outdata, errdata = p.communicate()
if p.returncode != 0:
print >> sys.stderr, "Exiting because call to git branch -a returned non-zero"
print >> sys.stderr, errdata
sys.exit(1)
all_branches = [b.strip() for b in outdata.strip().split("\n")]
def should_prune_origin(local_branch_name, origin_name):
if origin_name not in all_branches:
return False
# Only prune it if there is no diff
args = ["git", "diff", "--shortstat", b, origin_name]
p = Popen(args, stderr=PIPE, stdout=PIPE)
outdata, errdata = p.communicate()
return p.returncode == 0 and len(outdata.strip()) == 0 and len(errdata.strip()) == 0
did_something = False
error_branches = []
for b in merged_branches:
if "timob-" in b and "/" not in b:
print "Pruning %s" % b
origin_name = "remotes/origin/%s" % b
prune_origin = False
if origin_name in all_branches:
prune_origin = should_prune_origin(b, origin_name)
if not prune_origin:
print "Actually, skipping %s because it differs from %s. Playing it safe." % (b, origin_name)
continue
args = ["git", "branch", "-d", b]
if not test_run:
try:
check_call(args)
did_something = True
except CalledProcessError:
# Don't try to prune origin if local failed
prune_origin = False
error_branches.append(b)
else:
print "would do command:\n%s\n" % " ".join(args)
if prune_origin:
print ""
print "Pruning origin/%s remotely" % b
args = ["git", "push", "origin", ":%s" % b]
if not test_run:
call(args)
else:
print "would do command:\n%s\n" % " ".join(args)
print ""
if not did_something:
print "Didn't prune a thing"
if len(error_branches) > 0:
print "Had problems with these branches:"
print "\n".join(error_branches)
print "Check the output"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment