Skip to content

Instantly share code, notes, and snippets.

@carlzulauf
Created June 6, 2018 20:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save carlzulauf/638f8c641e63668098012178953f1e36 to your computer and use it in GitHub Desktop.
Save carlzulauf/638f8c641e63668098012178953f1e36 to your computer and use it in GitHub Desktop.
Clean stale local git branches
#!/usr/bin/env ruby
require 'time'
require 'optparse'
class Cleaner
attr_accessor :whitelist, :blacklist, :cutoff_days, :confirm, :dry_run, :keep, :skip, :remove
def initialize(options = {})
@whitelist = %W(master develop #{local_branch}).uniq
@blacklist = []
@cutoff_days = 30
@confirm = false
@dry_run = false
@keep = []
@skip = []
@remove = []
end
def perform
partition_branches
summarize
remove_branches
end
def partition_branches
local_refs.each do |last_sha, branch_name, updated_str|
updated_at = Time.parse(updated_str)
if whitelisted?(branch_name)
skip << branch_name
elsif updated_at > cutoff
keep << branch_name
elsif removable?(branch_name)
remove << branch_name
else
skip << branch_name
end
end
end
def whitelisted?(name)
whitelist.each do |pattern|
case pattern
when String
return true if name == pattern
when Regexp
return true if name =~ pattern
end
end
return false
end
def removable?(branch)
return true if blacklist.empty?
blacklist.each do |pattern|
return true if branch =~ pattern
end
return false
end
def summarize
puts "Whitelisted branches to keep:"
skip.each { |b| puts " #{b}" }
puts
puts "Recently (past #{cutoff_days} days) updated branches to keep:"
keep.each { |b| puts " #{b}" }
puts
puts "Branches to remove:"
remove.each { |b| puts " #{b}" }
end
def remove_branches
return if dry_run
return unless confirm || user_confirmed?
remove.each do |branch|
puts "Removing #{branch}"
`git branch -D #{branch}`
end
end
def user_confirmed?
puts "Are you sure you want to remove these branches? ([Y]es/[N]o)"
answer = gets.strip
answer =~ /^y(es)?$/i
end
def local_branch
`git name-rev --name-only HEAD`.strip
end
def local_refs
`git for-each-ref refs/heads --format='%(objectname)\t%(refname:short)\t%(creatordate)'`.lines.map { |l| l.split("\t").map(&:strip) }
end
def cutoff
@cutoff ||= Time.now - (60*60*24*cutoff_days)
end
def self.parse(args)
opts = self.new
parser = OptionParser.new do |o|
o.banner = "Usage: git clean-local [options]"
o.on("-y", "--yes", "--confirm", "Perform branch removals") do
opts.confirm = true
end
o.on("--no", "--dry-run", "Show branches which will be removed, but don't remove them. Default.") do
opts.dry_run = true
end
o.on("--whitelist=WHITELIST", "Comma seperated list of branch patterns to skip") do |list|
opts.whitelist += list.split(",").map do |whitelist_item|
/#{whitelist_item}/
end
end
o.on("--blacklist=BLACKLIST", "Comma separated list of branch patterns to remove") do |list|
opts.blacklist += list.split(",").map do |blacklist_item|
/#{blacklist_item}/
end
end
o.on("--cutoff=DAYS", "Keep branches that have been updated in past number of days") do |days|
opts.cutoff_days = days.to_i
end
o.on("-h", "--help") do
puts o
exit
end
end
parser.parse!(args)
opts
end
end
Cleaner.parse(ARGV).perform
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment