Skip to content

Instantly share code, notes, and snippets.

@jaredatron
Last active March 19, 2018 15:34
Show Gist options
  • Select an option

  • Save jaredatron/5de89925627cc1997625 to your computer and use it in GitHub Desktop.

Select an option

Save jaredatron/5de89925627cc1997625 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require 'time'
require 'colored'
require 'parallel'
require 'optparse'
require 'facter'
require 'json'
require 'active_support/core_ext/numeric/time'
# require 'pry-byebug'
class GitBranchDetails
NotAGitRepository = Class.new(StandardError)
GOOD_TRAVIS_STATES = %w( created passed started ).freeze
NEUTRAL_TRAVIS_STATES = [ "not found", "canceled" ].freeze
BAD_TRAVIS_STATES = %w( failed errored ).freeze
EXPECTED_TRAVIS_STATES = (GOOD_TRAVIS_STATES + NEUTRAL_TRAVIS_STATES + BAD_TRAVIS_STATES).freeze
SPECIAL_BRANCHES = %w(
master
production
staging
beta
integration
).freeze
def self.parse_arguments(arguments)
branch_names = []
options = {}
args = arguments.dup
until args.empty?
break if arguments.first.start_with?('-')
branch_names << arguments.shift
end
option_parser = OptionParser.new do |opts|
opts.banner = "Usage: git branch-details [branch_name, ...] [options]"
opts.on('--[no-]travis', 'Check status of each branch on travis (can be slow)') do |should_run_travis|
options[:travis] = should_run_travis
end
opts.on('-r', '--[no-]remote', "Run on remote branches") do |remote|
options[:remote] = remote
end
opts.on('--author=', 'only show branches matching a specific author pattern') do |author|
options[:author] = author
end
opts.on('--recent', 'only show recently changed branches') do |recent|
options[:recent] = recent
end
opts.on('--vebose', 'print vebose output') do |vebose|
options[:vebose] = recent
end
end
option_parser.parse! args
[ branch_names, options ]
end
def initialize(branch_names=nil, options={})
@branch_names = branch_names unless branch_names.empty?
@check_travis = options.key?(:travis) ? options[:travis] : true
@remote_branches = options.key?(:remote) ? options[:remote] : true
@author = options[:author]
@recent = options[:recent]
@vebose = options[:vebose]
@thread_count = Facter.value('processors')['count']
end
def print_report
ensure_git_repository!
branches
column_widths = columns.each_with_object({}) do |column, hash|
row_sizes = branches.map do |branch|
value = branch[column]
value.nil? ? 0 : value.length
end
hash[column] = (row_sizes + [column.length]).max + 5
end
print_format = column_widths.map { |_, width| "%-#{width}s" }.join(" ") + "\n"
x = print_format % columns
puts x
puts '-' * x.length
branches.each do |branch|
format_strings = []
values = []
columns.each do |column|
unformatted_value = branch[column]
unformatted_width = unformatted_value.nil? ? 0 : branch[column].length
color = color_of_value(column, unformatted_value)
formatted_value = unformatted_value.nil? ? nil : unformatted_value.send(color)
formatted_width = formatted_value.nil? ? 0 : formatted_value.length
column_width = column_widths[column] + formatted_width - unformatted_width
format_strings << "%-#{column_width}s"
values << formatted_value
end
print_format = format_strings.join(" ") + "\n"
printf print_format, *values
end
puts ""
true
rescue NotAGitRepository
puts ""
puts "This directory is not a git repository".red
puts ""
false
end
private
def columns
@columns ||= begin
cols = [
'name',
'latest commit',
'author name',
'merged into origin/master',
]
cols << 'travis' if @check_travis
cols
end
@columns
end
def color_of_value(column, value)
color = begin
case column
when 'name'
case value
when current_branch_name
:green
when *SPECIAL_BRANCHES
:yellow
end
when 'travis'
case value
when *GOOD_TRAVIS_STATES
:green
when *NEUTRAL_TRAVIS_STATES
:yellow
when *BAD_TRAVIS_STATES
:red
end
end
end
color ||= :to_s
color
end
def ensure_git_repository!
git_status = `git status 2>&1`
raise NotAGitRepository, "#{self.class.name} must be run from a git repository" if git_status =~ /^fatal: Not a git repository/
end
def current_branch_name
@current_branch_name ||= `git rev-parse --abbrev-ref HEAD`.chomp
@current_branch_name
end
def branch_names
@branch_names ||= begin
git_branch_result = `git branch #{'-r ' if @remote_branches}`.chomp.gsub(/^../, '').split("\n")
git_branch_result.reject! { |branch_name| branch_name.include?(' -> ')}
git_branch_result.reject! { |branch_name| branch_name =~ %r{/} } unless @remote_branches
git_branch_result
end
@branch_names
end
def get_branch_info(branch_name)
data = `git log -1 --format="%h,%an,%ae,%ci,%cr" #{branch_name}`.chomp.split(',')
data = ["sha", "author name", "author email", "latest commit time", "latest commit"].zip(data).to_h
data['name'] = branch_name
data['latest commit time'] = Time.parse(data['latest commit time'])
data['merged into origin/master'] = `git branch -r --contains #{branch_name}`.chomp.gsub(/^../, '').split("\n").include?('origin/master') ? 'yes' : 'no'
data['travis'] = get_travis_state(branch_name) if @check_travis
data
end
def get_travis_state(branch_name)
travis_show = `travis show #{branch_name} 2>&1`
if travis_show =~ /^resource not found/i
return "not found"
end
travis_state = travis_show[/State:\s+(.+)\n/, 1]
travis_state = "unknown state #{travis_state.inspect}" unless EXPECTED_TRAVIS_STATES.include?(travis_state)
travis_state
end
def branches
@branches ||= begin
message =
if @check_travis
"* Loading branch information and travis status"
else
"* Loading branch information"
end
print message if @vebose
data = Parallel.map(branch_names, in_threads: @thread_count) do |branch_name|
print "." if @vebose
get_branch_info(branch_name)
end
puts "" if @vebose
if @author
data.select! do |branch|
branch['author name'].downcase.include?(@author.to_s.downcase) ||
branch['author email'].downcase.include?(@author.to_s.downcase)
end
end
if @recent
data.select! do |branch|
branch['latest commit time'] > 1.week.ago
end
end
data.sort_by! { |b| b['latest commit time'] }.reverse!
end
end
end
if __FILE__ == $PROGRAM_NAME
branch_names, options = GitBranchDetails.parse_arguments(ARGV)
success = GitBranchDetails.new(branch_names, options).print_report
exit success ? 0 : 1
end
@jaredatron
Copy link
Author

☃ git branch-details
name                                 latest_commit_ago      author_name       merged into origin/master
-------------------------------------------------------------------------------------------------------------
retry-ajax-503                       7 minutes ago          Jared Grippe      no
apply-button-reporting-too-big       38 minutes ago         Jared Grippe      yes
apply-button-in-pilot-detection      56 minutes ago         Jared Grippe      no
fix-catchall-routing-issue           85 minutes ago         Jared Grippe      yes
lint-spec                            88 minutes ago         Jared Grippe      no
production                           24 hours ago           Jared Grippe      no
master                               24 hours ago           Jared Grippe      yes
background-job-spec-helpers          27 hours ago           Jared Grippe      yes
staging                              2 weeks ago            Jared Grippe      no

@benjaminhoffman
Copy link

do you have any instructions on how to run this with zsh? thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment