class Object

Constants

CI_TEST_ARGS
ERROR_DISTANCE
GERGICH_GIT_PATH
GERGICH_REVIEW_LABEL
GERGICH_USER
GergichError
MASTER_BOUNCER_REVIEW_LABEL
PROJECT
WARN_DISTANCE

TODO: time-based thresholds

Public Instance Methods

error(text) click to toggle source
# File lib/gergich/cli.rb, line 24
def error(text)
  $stderr.puts "\e[31mError:\e[0m #{text}"
  $stderr.puts usage
  exit 1
end
help_command(commands) click to toggle source
# File lib/gergich/cli.rb, line 30
def help_command(commands)
  {
    action: ->(subcommand = "help") {
      subcommand_info = commands[subcommand]
      if !subcommand_info
        error "Unrecognized command `#{subcommand}`"
      elsif (help_text = subcommand_info[:help])
        info help_text.respond_to?(:call) ? help_text.call : help_text
      else
        error "No help available for `#{subcommand}`"
      end
    },
    help: -> {
      indentation = commands.keys.map(&:size).max
      commands_help = commands
        .to_a
        .sort_by(&:first)
        .map { |key, data|
          "#{key.ljust(indentation)} - #{data[:summary]}" if data[:summary]
        }
        .compact
      usage(commands_help.join("\n"))
    }
  }
end
info(text) click to toggle source
# File lib/gergich/cli.rb, line 9
def info(text)
  puts text
  exit
end
maybe_bounce_commit!(commit) click to toggle source
# File lib/gergich/cli/master_bouncer.rb, line 27
def maybe_bounce_commit!(commit)
  draft = Gergich::Draft.new(commit)
  draft.reset!

  distance = Gergich.git("rev-list origin/master ^#{commit.ref} --count").to_i
  detail = "#{distance} commits behind master"

  score = 0
  message = nil
  if distance > ERROR_DISTANCE
    score = -2
    message = "This commit is probably not safe to merge (#{detail}). You'll " \
              "need to rebase it to ensure all the tests still pass."
  elsif distance > WARN_DISTANCE
    score = -1
    message = "This commit may not be safe to merge (#{detail}). Please " \
              "rebase to make sure all the tests still pass."
  end

  review = Gergich::Review.new(commit, draft)
  current_score = review.current_score

  puts "#{detail}, " + (score == current_score ?
                        "score still #{score}" :
                        "changing score from #{current_score} to #{score}")

  # since we run on a daily cron, we might be checking the same patchset
  # many times, so bail if nothing has changed
  return if score == current_score

  draft.add_label MASTER_BOUNCER_REVIEW_LABEL, score
  draft.add_message message if message

  # otherwise we always publish ... even in the score=0 case it's
  # important, as we might be undoing a previous negative score.
  # similarly, over time the same patchset will become more out of date,
  # so we allow_repost (so to speak) so we can add increasingly negative
  # reviews
  review.publish!(allow_repost: true)
end
potentially_mergeable_changes() click to toggle source
# File lib/gergich/cli/master_bouncer.rb, line 16
def potentially_mergeable_changes
  url = "/changes/?q=status:open+" \
        "p:#{PROJECT}+" \
        "label:Verified=1+" \
        "is:mergeable+" \
        "branch:master" \
        "&o=CURRENT_REVISION"
  changes = Gergich::API.get(url)
  changes.reject { |c| c["subject"] =~ /\Awip($|\W)/i }
end
run_app(commands) click to toggle source
# File lib/gergich/cli.rb, line 68
def run_app(commands)
  commands["help"] = help_command(commands)
  command = ARGV.shift || "help"

  if commands[command]
    begin
      action = commands[command][:action]
      run_command(action)
    rescue GergichError
      error $ERROR_INFO.message
    rescue StandardError
      error "Unhandled exception: #{$ERROR_INFO}\n#{$ERROR_INFO.backtrace.join("\n")}"
    end
  else
    error "Unrecognized command `#{command}`"
  end
end
run_ci_test!(all_commands) click to toggle source
# File lib/gergich/cli/gergich.rb, line 25
def run_ci_test!(all_commands)
  ENV["GERGICH_CHANGE_NAME"] = "0"
  commands_to_test = all_commands - %w[citest reset publish status]
  commands_to_test << "status" # put it at the end, so we maximize the stuff it tests

  commands = commands_to_test.map { |command| [command, CI_TEST_ARGS[command] || []] }
  commands.concat(all_commands.map { |command| ["help", [command]] })

  # after running our test commands, reset and publish frd:
  commands << ["reset"]
  # Note that while gergich should always be able to vote on these labels, he may be used to
  # vone on other branches depending on project usage and project-specific permissions
  commands << ["label", ["Lint-Review", 1]]
  commands << ["label", ["Code-Review", 1]] # Not all projects have Lint-Review
  commands << ["message", ["\`gergich citest\` checks out :thumbsup: :mj:"]]
  commands << ["publish"]

  commands.each do |command, args = []|
    arglist = args.map { |arg| Shellwords.escape(arg.to_s) }
    output = `bundle exec gergich #{command} #{arglist.join(" ")} 2>&1`
    unless $CHILD_STATUS.success?
      error "`gergich citest` failed on step `#{command}`:\n\n#{output.gsub(/^/, '  ')}\n"
    end
  end
end
run_command(action) click to toggle source
# File lib/gergich/cli.rb, line 56
def run_command(action)
  params = action.parameters
  params.each_with_index do |(type, name), i|
    error "No <#{name}> specified" if i >= ARGV.size && type == :req
  end
  if ARGV.size > params.size
    extra_args = ARGV[params.size, ARGV.size].map { |a| "`#{Shellwords.escape(a)}`" }
    error "Extra arg(s) #{extra_args.join(' ')}"
  end
  action.call(*ARGV)
end
script_name() click to toggle source
# File lib/gergich/cli.rb, line 14
def script_name
  $PROGRAM_NAME.sub(%r{.*/}, "")
end
usage(content = nil) click to toggle source
# File lib/gergich/cli.rb, line 18
def usage(content = nil)
  "Usage: #{script_name} <command> [<args>...]\n" +
    (content ? "\n#{content}\n\n" : "") +
    "Tip: run `#{script_name} help <command>` for more info"
end