lib/multi_repo/helpers/pull_request_blaster_outer.rb
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