Skip to content

Instantly share code, notes, and snippets.

@cblackburn-ajla
Last active January 3, 2019 19:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cblackburn-ajla/affc04253d6b322fd99721c77e6fc355 to your computer and use it in GitHub Desktop.
Save cblackburn-ajla/affc04253d6b322fd99721c77e6fc355 to your computer and use it in GitHub Desktop.
Determine orphaned branches that have been merged into another branch.
#!/usr/bin/env ruby
#
# Get a list of merged branches
#
# You need to checkout the target remote branch before running. In other words,
# if your target branch is 'master', you have to have it on your local hard disk
# before you run this script, otherwise you will get an error like: `fatal:
# malformed object name master`. Git needs to have the branch available locally
# with latest changes pulled in order to find the branches that have/have-not
# been merged into it.
#
# To create a list of branch-names only, without date info
#
# git-merged-branches.rb --branch production --exclude-days 180 | cut -f1 -d'|'
#
# For help run: git-merged-branches.rb --help
require 'optimist'
require 'fileutils'
require 'colored'
# require 'pry'
class GitMergedBranches
attr_accessor :branch_count, :options, :elapsed, :excluded_branches, :matched_count
include FileUtils
class BranchEntry
attr_accessor :name, :last_commit_date, :relative_last_commit, :last_commiter
def initialize(name, last_commit_date, relative_last_commit, last_commiter)
self.name = name
self.last_commit_date = last_commit_date
self.relative_last_commit = relative_last_commit
self.last_commiter = last_commiter
end
def <=>(other)
last_commit_date <=> other.last_commit_date
end
def to_s
"#{name} | #{last_commit_date} | #{relative_last_commit} by #{last_commiter}"
end
end
class << self
def collect_args(*_args)
opts = Optimist.options do
opt(
:branch,
'Base branch - list branches merged into this branch. Uses current branch if not specified.',
type: :string, short: 'b', required: false
)
opt(
:exclude_days,
'Exclude branches that have no commits within this many days',
type: :integer, short: 'x', required: false, default: 0
)
opt(
:color,
'Use colored output',
type: :boolean, short: 'c', required: false, default: true
)
end
# Set branch to current if not given on command line
opts[:branch] ||= `git rev-parse --abbrev-ref HEAD`.chomp
opts
end
def run
start_time = Time.now
opts = collect_args(ARGV)
instance = GitMergedBranches.new(opts)
instance.process
instance.elapsed = Time.now - start_time
instance.report_summary
end
end
def initialize(opts)
self.branch_count = 0
self.excluded_branches = []
self.options = opts
end
def color(string, clr)
if options[:color]
puts(string.send(clr))
else
puts(string)
end
end
def report_summary
puts
color(">>> Processed #{branch_count} branches in [#{elapsed}] seconds", :red)
color(">>> Branches with NO commits in the last #{options[:exclude_days]} days: [#{matched_count}]", :red)
color(">>> Branches with commits in the last #{options[:exclude_days]} days: [#{excluded_branches.count}]", :red)
end
def process
self.matched_count = matched_branches.count
color(">>> #{matched_count} remote branches that have been merged into `#{options[:branch]}` with no commits in the last #{options[:exclude_days]} days:", :green)
puts
puts matched_branches.sort
end
def merged_branches
@branches ||= begin
current_origin = nil
cmd = "git branch -r --merged #{options[:branch]}"
merged_list = `#{cmd}`.split("\n").collect(&:strip)
raise "Error running: #{cmd}. See output above ^^^" unless $?.exitstatus == 0
# find and delete the HEAD pointer and current branch origin
head_pointer = merged_list.grep(/ -> /).first
current_origin = head_pointer.split(/ -> /).last if head_pointer
merged_list.delete_if { |elem| [head_pointer, current_origin].include?(elem) }
end
end
def matched_branches
today = Date.today
@sorted ||= merged_branches.map do |branch|
self.branch_count += 1
date_strings = `git show --format="%ci|%cr" #{branch} | head -n 1`.chomp.split('|')
last_commit_date = Date.strptime(date_strings.first, '%Y-%m-%d')
days_old = (today - last_commit_date).to_i
if recent_branch?(days_old)
excluded_branches << branch
nil
else
BranchEntry.new(branch, last_commit_date, date_strings.last, last_commiter(branch))
end
end.compact
end
# Determine if a branch has any commits within the last options[:exclude_days] days
def recent_branch?(branch_age)
branch_age.to_i <= options[:exclude_days].to_i
end
def last_commiter(branch)
cmd = %(git show --format="%ai %ar by %an" #{branch} | head -n 1)
data = `#{cmd}`.split
data.last
end
end
GitMergedBranches.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment