Last active
August 31, 2021 16:00
-
-
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.
This file contains 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 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