Last active
March 19, 2018 15:34
-
-
Save jaredatron/5de89925627cc1997625 to your computer and use it in GitHub Desktop.
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 | |
| 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 |
Author
jaredatron
commented
Mar 23, 2016
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