class BotGithub

Public Class Methods

new() click to toggle source
# File lib/bot_github.rb, line 10
def initialize
  @client = Octokit::Client.new :access_token => BotConfig.instance.github_access_token
  @client.login
end

Public Instance Methods

sync() click to toggle source
# File lib/bot_github.rb, line 15
def sync
  puts "\nStarting Github Xcode Bot Builder #{Time.now}\n-----------------------------------------------------------"
  # Check to see if we're already running and skip this run if so
  running_instances = `ps aux | grep [b]ot-sync-github | grep -v bin/sh`.split("\n")
  if (running_instances.count > 1)
    $stderr.puts "Skipping run since bot-sync-github is already running"
    PP.pp(running_instances, STDERR)
    exit 1
  end

  bot_statuses = BotBuilder.instance.status_of_all_bots
  bots_processed = []
  pull_requests.each do |pr|
    # Check if a bot exists for this PR
    bot = bot_statuses[pr.bot_short_name_without_version]
    bots_processed << pr.bot_short_name
    if (bot.nil?)
      # Create a new bot
      BotBuilder.instance.create_bot(pr.bot_short_name, pr.bot_long_name, pr.branch,
                                     BotConfig.instance.scm_path,
                                     BotConfig.instance.xcode_project_or_workspace,
                                     BotConfig.instance.xcode_scheme,
                                     BotConfig.instance.xcode_devices)
      create_status_new_build(pr)
    else
      github_state_cur = latest_github_state(pr).state # :unknown :pending :success :error :failure
      github_state_new = convert_bot_status_to_github_state(bot)
      if (github_state_new == :pending && github_state_cur != github_state_new)
        # User triggered a new build by clicking Integrate on the Xcode server interface
        create_status(pr, github_state_new, convert_bot_status_to_github_description(bot), bot.status_url)
      elsif (github_state_new != :unknown && github_state_cur != github_state_new)
        # Build has passed or failed so update status and comment on the issue
        create_comment_for_bot_status(pr, bot)
        create_status(pr, github_state_new, convert_bot_status_to_github_description(bot), bot.status_url)
      elsif (github_state_cur == :unknown || user_requested_retest(pr, bot))
        # Unknown state occurs when there's a new commit so trigger a new build
        BotBuilder.instance.start_bot(bot.guid)
        create_status_new_build(pr)
      else
        puts "PR #{pr.number} (#{github_state_cur}) is up to date for bot #{bot.short_name}"
      end
    end
  end

  # Delete bots that no longer have open pull requests
  bots_unprocessed = bot_statuses.keys - bots_processed
  bots_unprocessed.each do |bot_short_name|
    bot = bot_statuses[bot_short_name]
    BotBuilder.instance.delete_bot(bot.guid) unless !is_managed_bot(bot)
  end

  puts "-----------------------------------------------------------\nFinished Github Xcode Bot Builder #{Time.now}\n"
end

Private Instance Methods

bot_long_name(pr) click to toggle source
# File lib/bot_github.rb, line 185
def bot_long_name(pr)
  github_repo = BotConfig.instance.github_repo
  "PR #{pr.number} #{pr.title} #{github_repo}"
end
bot_short_name(pr) click to toggle source
# File lib/bot_github.rb, line 190
def bot_short_name(pr)
  short_name = "#{pr.number}-#{pr.branch}".gsub(/[^[:alnum:]]/, '_') + bot_short_name_suffix
  short_name
end
bot_short_name_suffix() click to toggle source
# File lib/bot_github.rb, line 206
def bot_short_name_suffix
  github_repo = BotConfig.instance.github_repo.downcase
  ('_' + github_repo + '_v').gsub(/[^[:alnum:]]/, '_')
end
bot_short_name_without_version(pr) click to toggle source

For duplicate bot names xcode server appends a version bot_short_name_v, bot_short_name_v1, bot_short_name_v2. This method converts bot_short_name_v2 to bot_short_name_v

# File lib/bot_github.rb, line 197
def bot_short_name_without_version(pr)
  bot_short_name(pr).sub(/_v\d*$/, '_v')
end
convert_bot_status_to_github_description(bot) click to toggle source
# File lib/bot_github.rb, line 71
def convert_bot_status_to_github_description(bot)
  bot_run_status = bot.latest_run_status # :unknown :running :completed
  bot_run_sub_status = bot.latest_run_sub_status # :unknown :build-failed :build-errors :test-failures :warnings :analysis-issues :succeeded
  github_description = bot_run_status == :running ? "Build Triggered." : ""
  if (bot_run_status == :completed || bot_run_status == :failed)
    github_description = bot_run_sub_status.to_s.split('-').map(&:capitalize).join(' ') + "."
  end
  github_description
end
convert_bot_status_to_github_state(bot) click to toggle source
# File lib/bot_github.rb, line 81
def convert_bot_status_to_github_state(bot)
  bot_run_status = bot.latest_run_status # :unknown :running :completed
  bot_run_sub_status = bot.latest_run_sub_status # :unknown :build-failed :build-errors :test-failures :warnings :analysis-issues :succeeded
  github_state = bot_run_status == :running ? :pending : :unknown
  if (bot_run_status == :completed || bot_run_status == :failed)
    github_state = case bot_run_sub_status
                     when :"test-failures", :"warnings", :"analysis-issues"
                       :failure
                     when :"succeeded"
                       :success
                     else
                       :error
                   end
  end
  github_state
end
create_comment_for_bot_status(pr, bot) click to toggle source
# File lib/bot_github.rb, line 98
def create_comment_for_bot_status(pr, bot)
  message = "Build " + convert_bot_status_to_github_state(bot).to_s.capitalize + ": " + convert_bot_status_to_github_description(bot)
  message += "\n#{bot.status_url}"
  @client.add_comment(BotConfig.instance.github_repo, pr.number, message)
  puts "PR #{pr.number} added comment \"#{message}\""
end
create_status(pr, github_state, description = nil, target_url = nil) click to toggle source
# File lib/bot_github.rb, line 109
def create_status(pr, github_state, description = nil, target_url = nil)
  options = {}
  if (!description.nil?)
    options['description'] = description
  end
  if (!target_url.nil?)
    options['target_url'] = target_url
  end
  @client.create_status(BotConfig.instance.github_repo, pr.sha, github_state.to_s, options)
  puts "PR #{pr.number} status updated to \"#{github_state}\" with description \"#{description}\""
end
create_status_new_build(pr) click to toggle source
# File lib/bot_github.rb, line 105
def create_status_new_build(pr)
  create_status(pr, :pending, "Build Triggered.")
end
is_managed_bot(bot) click to toggle source
# File lib/bot_github.rb, line 201
def is_managed_bot(bot)
  # Check the suffix of the bot to see if it matches the bot_short_name_suffix
  bot.short_name =~ /#{bot_short_name_suffix}\d*$/
end
latest_github_state(pr) click to toggle source
# File lib/bot_github.rb, line 121
def latest_github_state(pr)
  statuses = @client.statuses(BotConfig.instance.github_repo, pr.sha)
  status = OpenStruct.new
  if (statuses.count == 0)
    status.state = :unknown
    status.updated_at = Time.now
  else
    status.state = statuses[0].state.to_sym
    status.updated_at = statuses[0].updated_at
  end
  status
end
pull_requests() click to toggle source
# File lib/bot_github.rb, line 134
def pull_requests
  github_repo = BotConfig.instance.github_repo
  responses = @client.pull_requests(github_repo)
  prs = []
  responses.each do |response|
    pr = OpenStruct.new
    pr.sha = response.head.sha
    pr.branch = response.head.ref
    pr.title = response.title
    pr.state = response.state
    pr.number = response.number
    pr.updated_at = response.updated_at
    pr.bot_short_name = bot_short_name(pr)
    pr.bot_short_name_without_version = bot_short_name_without_version(pr)
    pr.bot_long_name = bot_long_name(pr)
    prs << pr
  end
  prs
end
user_requested_retest(pr, bot) click to toggle source
# File lib/bot_github.rb, line 154
def user_requested_retest(pr, bot)
  should_retest = false
  github_repo = BotConfig.instance.github_repo

  # Check for a user retest request comment
  comments = @client.issue_comments(github_repo, pr.number)
  latest_retest_time = Time.at(0)
  found_retest_comment = false
  comments.each do |comment|
    if (comment.body =~ /retest/i)
      latest_retest_time = comment.updated_at
      found_retest_comment = true
    end
  end

  return should_retest unless found_retest_comment

  # Get the latest status time
  latest_status_time = latest_github_state(pr)
  if (latest_status_time.nil? || latest_status_time.updated_at.nil?)
    latest_status_time = Time.at(0)
  end

  if (latest_retest_time > latest_status_time.updated_at)
    should_retest = true
    puts "PR #{pr.number} user requested a retest"
  end

  should_retest
end