ManageIQ/multi_repo

View on GitHub
lib/multi_repo/helpers/pull_request_blaster_outer.rb

Summary

Maintainability
A
4 hrs
Test Coverage
require 'pathname'

module MultiRepo::Helpers
  class PullRequestBlasterOuter
    attr_reader :repo, :base, :head, :script, :dry_run, :message, :title

    def initialize(repo, base:, head:, script:, dry_run:, message:, title: nil, **)
      @repo    = repo
      @base    = base
      @head    = head
      @script  = begin
        s = Pathname.new(script).expand_path
        raise "File not found #{s}" unless s.exist?
        s.to_s
      end
      @dry_run = dry_run
      @message = message
      @title   = (title || message)[0, 72]
    end

    def blast
      repo.git.fetch

      unless repo.git.remote_branch?("origin", base)
        puts "!! Skipping #{repo.name}: 'origin/#{base}' not found".light_yellow
        return
      end

      repo.git.hard_checkout(head, "origin/#{base}")
      run_script

      result = false
      if !changes_found?
        puts
        puts "!! Skipping #{repo.name}: No changes found".light_yellow
        result = "no changes".light_yellow
      else
        commit_changes
        show_commit
        puts

        if dry_run
          puts "** dry-run: Skipping opening pull request".light_black
          result = "dry run".light_black
        else
          print "Do you want to open a pull request on #{repo.name} with the above changes? (y/N): "
          answer = $stdin.gets.chomp
          if answer.upcase.start_with?("Y")
            fork_repo unless forked?
            push_branch
            result = open_pull_request
          else
            puts "!! Skipping #{repo.name}: User ignored".light_yellow
            result = "ignored".light_yellow
          end
        end
      end
      result
    end

    private

    def github
      @github ||= MultiRepo::Service::Github.new(dry_run: dry_run)
    end

    def forked?
      # NOTE: There is an assumption here that the fork's name will match the source's name.
      #   Ideally there would be a "forked from" field in the repo metadata, but there isn't.
      github.client.repos(github.client.login, :type => "forks").any? { |m| m.name == repo.short_name }
    end

    def fork_repo
      github.client.fork(repo.name)
      until forked?
        print "."
        sleep 3
      end
    end

    def run_script
      repo.chdir do
        Bundler.with_unbundled_env do
          parts = []
          parts << "GITHUB_REPO=#{repo.name}"
          parts << "DRY_RUN=true" if dry_run
          parts << script
          cmd = parts.join(" ")

          unless system(cmd)
            puts "!! Script execution failed.".light_red
            exit $?.exitstatus
          end
        end
      end
    end

    def changes_found?
      repo.git.client.capturing.status("--porcelain").chomp.present?
    end

    def commit_changes
      repo.git.client.add("-v", ".")
      repo.git.client.commit("-m", message)
    end

    def show_commit
      repo.git.client.show
    end

    def blast_remote
      "pr_blaster_outer"
    end

    def blast_remote_url
      # NOTE: Similar to `forked?`, there is an assumption here that the fork's name will match the source's name.
      "git@github.com:#{github.client.login}/#{repo.short_name}.git"
    end

    def pr_head
      "#{github.client.login}:#{head}"
    end

    def push_branch
      repo.git.client.remote("add", blast_remote, blast_remote_url) unless repo.git.remote?(blast_remote)
      repo.git.client.push("-f", blast_remote, "#{head}:#{head}")
    end

    def open_pull_request
      pr = github.client.create_pull_request(repo.name, base, pr_head, title, title)
      pr.html_url
    rescue => err
      raise unless err.message.include?("A pull request already exists")
      puts "!! Skipping #{repo.name}: #{err.message}".light_yellow
    end
  end
end