Last active
January 3, 2019 19:14
-
-
Save cblackburn-ajla/affc04253d6b322fd99721c77e6fc355 to your computer and use it in GitHub Desktop.
Determine orphaned branches that have been merged into another branch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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