#!/usr/bin/env ruby # frozen_string_literal: true require 'date' require 'optparse' require 'pry' require_relative '../lib/github_repo_statistics/version' require_relative '../lib/github_repo_statistics/github_repo_statistics' options = {} OptionParser.new do |opts| opts.banner = 'Usage: github_repo_statistics [options]' opts.on('--debug', 'Enable debug mode') do options[:debug] = true end opts.on('--ci', 'Do not print the info messages for better CI text parsing [default: false]') do options[:ci] = true end opts.on('--codeowners', 'Print CODEOWNERS info [default: false]') do options[:codeowners] = true end opts.on('--hotspot-files', 'Print the found hotspot files (big files touched by many) [default: false]') do options[:hotspot_files] = true end opts.on('--excluded-contributors STRING', 'Comma-delimited list of excluded contributors [example: WEB,RAILS,MOBILE]') do |exclusions| options[:exclusions] = exclusions end opts.on('--excluded-files STRING', 'Comma-delimited list of excluded keywords [example: ViewController,AppDelegate.swift]') do |excluded_files| options[:excluded_files] = excluded_files end opts.on('--excluded-prs STRING', 'Comma-delimited list of excluded PRs [example: 1234,1235,2222]') do |excluded_prs| options[:excluded_prs] = excluded_prs end opts.on('--excluded-contributor-names STRING', 'Comma-delimited list of excluded Contributros [example: Name1,Name2]') do |excluded_contributor_names| options[:excluded_contributor_names] = excluded_contributor_names end opts.on('--steps STRING', 'Number of steps the script will go into the past [default: 1]') do |steps| options[:steps] = steps end opts.on('--duration-in-days STRING', 'Number of days to aggregate the changes for [default: 30]') do |duration_in_days| options[:duration_in_days] = duration_in_days end opts.on('--path STRING', 'Path to the directory or file to calculate the ownership [default: "."]') do |path| options[:path] = path end opts.on('--team-regex STRING', 'Regex that will identify the team name [default: "[A-Za-z]+"]') do |team_regex| options[:team_regex] = team_regex end opts.on('--codeowners-path STRING', 'Path to CODEOWNERS file [default: .github/CODEOWNERS]') do |codeowners_path| options[:codeowners_path] = codeowners_path end opts.on('--big-file-size STRING', 'The amount of lines in the file to be considered big [default: 250]') do |big_file_size| options[:big_file_size] = big_file_size end opts.on('--default-branch STRING', 'The default branch to pull and run metrics for [default: master]') do |default_branch| options[:default_branch] = default_branch end opts.on('--code-extensions STRING', 'The file extensions that consider to be code [default: ".kt,.swift"]') do |code_extension| options[:code_extension] = code_extension end opts.on('--percentile STRING', 'The percentile of file changes that are considered to be hotspot [default: "100"]') do |percentile| options[:percentile] = percentile end opts.on('--team-to-focus STRING', 'The team identifier to focus for the metric collection [default: ""]') do |team_to_focus| options[:team_to_focus] = team_to_focus end opts.on('--codeowner-to-focus STRING', 'The codeowner identifier to focus for the metric collection [default: ""]') do |codeowner_to_focus| options[:codeowner_to_focus] = codeowner_to_focus end opts.on('--output-to-files', 'Puts the output for hotspot and codeowners into files instead of the STDOUT (useful for CI and big amount of data) [default:false]') do options[:file_output] = true end opts.on('--hotspot-check', 'Enables a hotspot check [default:false]') do options[:hotspot_check] = true end opts.on('-v', '--version', 'Display the version of the gem') do puts "github_repo_statistics version #{GithubRepoStatistics::VERSION}" exit end opts.on('-h', '--help', 'Display this help message') do puts opts puts <<~EXAMPLES Examples: github_repo_statistics --path src/test --exclusions WEB,RAILS --steps 2 --duration-in-days 90 --hotspot-files --debug EXAMPLES exit end end.parse! EXCLUSIONS = options[:exclusions]&.split(',') REPO_PATH = options[:path] || '.' TEAM_REGEX = options[:team_regex] || '[A-Za-z]+' CODEOWNERS_PATH = options[:codeowners_path] || '.github/CODEOWNERS' BIG_FILE_SIZE = options[:big_file_size] || 250 CI = options[:ci] || false DEFAULT_BRANCH = options[:default_branch] || 'master' CODEOWNERS = options[:codeowners] || false HOTSPOT = options[:hotspot_files] || false FILE_OUTPUT = options[:file_output] || false CODE_EXTENSIONS = options[:code_extension] ? options[:code_extension].split(',') : ['.swift', '.kt'] EXCLUDED_FILES = options[:excluded_files] EXCLUDED_PRS = options[:excluded_prs] TEAM_TO_FOCUS = options[:team_to_focus] CODEOWNER_TO_FOCUS = options[:codeowner_to_focus] PERCENTILE = options[:percentile] || '100' EXCLUDED_CONTRIBUTOR_NAMES = options[:excluded_contributor_names] unless CI puts "\nDirectory: #{REPO_PATH}\n" puts "Time period that data is aggregated by: #{options[:duration_in_days]} days" puts "Steps to jump in the past: #{options[:steps].to_i}" puts "Runs against: #{DEFAULT_BRANCH}" puts "Code extensions: #{CODE_EXTENSIONS}" puts "Regex to detect the teams identifiers: #{TEAM_REGEX}" puts "Excluded contributors: #{EXCLUSIONS}\n" if EXCLUSIONS puts "Excluded file keywords: #{EXCLUDED_FILES.split(',')}\n" if EXCLUDED_FILES puts "Lines of code limit (big files) for the hotspot calculation: #{BIG_FILE_SIZE}" puts "Hotspot detailed output is: #{options[:hotspot_files] ? 'on' : 'off'}\n" puts "CODEOWNERS output is: #{options[:codeowners] ? 'on' : 'off'}\n" puts "Output to file mode is: #{options[:file_output] ? 'on' : 'off'}\n" puts "CI mode is: #{options[:ci] ? 'on' : 'off'}\n" puts "Debug mode is: #{options[:debug] ? 'on' : 'off'}\n\n" end if options[:hotspot_check] feature_branch = `git rev-parse --abbrev-ref HEAD`.chomp puts "Feature branch: #{feature_branch}" changed_files = `git diff --name-only origin/#{DEFAULT_BRANCH}...origin/#{feature_branch} | grep #{REPO_PATH} | grep '\.swift'`.split puts "Changed files: #{changed_files}" return if changed_files.empty? branch_hotspot_files = GithubRepoStatistics.new(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH, begin_time: DateTime.now, steps: options[:steps].to_i, debug: options[:debug]).hotspot_check(files: changed_files, branch: feature_branch) puts "branch_hotspot_files = #{branch_hotspot_files}" return if branch_hotspot_files.empty? system("git checkout #{DEFAULT_BRANCH}", [:out] => File::NULL) master_hotspot_files = GithubRepoStatistics.new(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH, begin_time: DateTime.now, steps: options[:steps].to_i, debug: options[:debug]).hotspot_check(files: branch_hotspot_files, branch: DEFAULT_BRANCH) puts "master_hotspot_files = #{master_hotspot_files}" if branch_hotspot_files.count > master_hotspot_files.count message = "A new hotspot was introduced in this PR. Please consult @hellofresh/squad-ios-foundation to address it. Files: #{(branch_hotspot_files - master_hotspot_files).join(', ')}" File.write('./failure.txt', message) raise message end else system("git checkout #{DEFAULT_BRANCH}", [:out] => File::NULL) system('git pull', %i[out err] => File::NULL) GithubRepoStatistics.new(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH, begin_time: DateTime.now, steps: options[:steps].to_i, debug: options[:debug]).contribution_message end