bin/analyze
#! /usr/bin/env ruby
# frozen_string_literal: true
require 'octokit'
require 'optparse'
require 'yaml'
require_relative '../lib/hubtrics'
parser = Hubtrics::Parser.new(banner: "Usage: #{File.basename(__FILE__)} [options]")
options = parser.parse(ARGV)
begin
puts Paint["Analyzing #{options[:repository]}...", :blue]
client = Hubtrics.client(options[:client])
pulls = client.pulls(options[:repository], state: 'open')
query = "repo:#{options[:repository]} is:open is:pr"
approved_pulls = client.search_issues("#{query} review:approved").items.map(&:number)
rejected_pulls = client.search_issues("#{query} review:changes_requested").items.map(&:number)
pulls.each do |pull|
pull = Hubtrics::PullRequest.fetch(options[:repository], pull.number)
title_rules = options.dig(:rules, :pulls, :title).map { |rule| Hubtrics::Rules::Rule.new(rule) }
if title_rules.none? { |rule| rule.valid?(pull.title) }
puts Paint["Pull #{pull.number} failed title validation: #{pull.title}", :red]
comment = <<~COMMENT
_Pull closed automatically due to title validation failure._
`#{pull.title}` is not formatted correctly. Please review the [coding guidelines](https://github.com/verifyvalid/guidelines/blob/master/docs/coding-guidelines/git.md#pull-requests) if you are unsure of the format expected.
COMMENT
client.add_comment(options[:repository], pull.number, comment)
client.close_pull_request(options[:repository], pull.number)
next
end
original_labels = pull.labels
labels = original_labels.dup
labels = labels.reject { |label| label =~ /^conflict-with-parent|outdated$/ }
labels << 'conflict-with-parent' if pull.mergeable_state == 'dirty'
labels << 'outdated' if pull.mergeable_state == 'behind'
labels = labels.reject { |label| label =~ /^auto-tests-/ }
labels <<
case pull.status
when 'passing' then 'auto-tests-passing'
when 'failing' then 'auto-tests-failing'
when 'pending' then 'auto-tests-in-progress'
end
labels = labels.reject { |label| label =~ /^review-(approved|rejected|incomplete|in-progress)/ }
labels <<
if labels.include?('review-in-progress')
nil
elsif approved_pulls.include?(pull.number)
'review-approved'
elsif rejected_pulls.include?(pull.number)
'review-rejected'
end
labels = labels.reject { |label| label =~ /^(preproduction|production|feature-branch|Release)$/ }
if pull.head.to_s =~ /^v\d{2}(\.\d+)+RC\d?$/
labels << 'Release'
elsif %w[production preproduction].include?(pull.base.to_s)
labels << pull.base.to_s
elsif pull.base.to_s != 'master' && pull.base.to_s !~ /^v\d{2}(\.\d+)+RC\d?$/ && !labels.include?('Release')
labels << 'feature-branch'
end
labels = labels.reject { |label| label =~ /^heavy-churn$/ }
if labels.none? { |label| %w[Release project-branch feature-branch].include?(label) } && pull.commits > 30 ||
labels.none? { |label| %w[Release project-branch].include?(label) } && pull.comments + pull.review_comments > 30
labels << 'heavy-churn'
end
labels = labels.reject { |label| label =~ /^stale$/ }
if pull.updated_at < (Date.today - 14).to_time.utc
labels << 'stale'
end
# Clean up the labels
labels = labels.compact.sort.uniq
next if original_labels == labels
if options[:dry_run]
puts "Update #{pull.number}: #{original_labels} with #{labels.sort}"
else
client.replace_all_labels(pull.repository, pull.number, labels)
color =
if %w[review-approved auto-tests-passing].all? { |l| labels.include?(l) }
:green
elsif %w[review-rejected auto-tests-failing].all? { |l| labels.include?(l) }
:red
elsif %w[review-rejected auto-tests-failing].any? { |l| labels.include?(l) }
:yellow
end
puts Paint["Updated #{pull.number} with #{labels}", color]
end
end
puts Paint["Analysis of #{options[:repository]} complete.\n", :blue]
rescue Octokit::TooManyRequests => e
puts Paint[e.message, :red]
puts Paint[e.response_headers.inspect, :red]
end