Skip to content

Instantly share code, notes, and snippets.

@midwire
Last active August 31, 2021 16:00
Show Gist options
  • Save midwire/0b4ece1d6e8a548dfc1f081a5b5cd67c to your computer and use it in GitHub Desktop.
Save midwire/0b4ece1d6e8a548dfc1f081a5b5cd67c to your computer and use it in GitHub Desktop.
A script to list all remote branches that have been merged into a specified branch. Optionally exclude branches with recent commits. Written to help in deleting old merged branches.
#!/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 checked out before you
# run this script, otherwise you will get an error like: `fatal: malformed
# object name master`. Git needs to have the branch checked out 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 'trollop'
require 'fileutils'
require 'colored'
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
def initialize(name, last_commit_date, relative_last_commit)
self.name = name
self.last_commit_date = last_commit_date
self.relative_last_commit = relative_last_commit
end
def <=>(other)
self.last_commit_date <=> other.last_commit_date
end
def to_s
"#{name} | #{last_commit_date} | #{relative_last_commit}"
end
end
class << self
def collect_args(*_args)
opts = Trollop.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 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)
fail "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
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)
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
end
GitMergedBranches.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment