brew/Library/Homebrew/cask/cmd/automerge.rb

128 lines
3.5 KiB
Ruby
Raw Normal View History

2019-02-15 12:33:43 +01:00
# frozen_string_literal: true
require "cask/cmd/abstract_internal_command"
require "tap"
require "utils/formatter"
require "utils/github"
module Cask
class Cmd
class Automerge < AbstractInternalCommand
OFFICIAL_CASK_TAPS = [
"homebrew/cask",
"homebrew/cask-drivers",
"homebrew/cask-eid",
"homebrew/cask-fonts",
"homebrew/cask-versions",
].freeze
def run
taps = OFFICIAL_CASK_TAPS.map(&Tap.public_method(:fetch))
access = taps.all? { |tap| GitHub.write_access?(tap.full_name) }
raise "This command may only be run by Homebrew maintainers." unless access
2019-02-15 12:33:43 +01:00
Homebrew.install_gem! "git_diff"
require "git_diff"
failed = []
taps.each do |tap|
2019-02-15 12:33:43 +01:00
open_pull_requests = GitHub.pull_requests(tap.full_name, state: :open, base: "master")
open_pull_requests.each do |pr|
next unless passed_ci(pr)
next unless check_diff(pr)
number = pr["number"]
sha = pr.dig("head", "sha")
print "#{Formatter.url(pr["html_url"])} "
2019-02-15 18:48:37 +01:00
retried = false
2019-02-15 12:33:43 +01:00
begin
GitHub.merge_pull_request(
tap.full_name,
number: number, sha: sha,
merge_method: :squash,
commit_message: "Squashed and auto-merged via `brew cask automerge`."
)
puts "#{Tty.bold}#{Formatter.success("")}#{Tty.reset}"
rescue
2019-02-15 18:48:37 +01:00
unless retried
retried = true
sleep 5
retry
end
2019-02-15 12:33:43 +01:00
puts "#{Tty.bold}#{Formatter.error("")}#{Tty.reset}"
failed << pr["html_url"]
end
end
end
return if failed.empty?
2019-02-15 12:33:43 +01:00
$stderr.puts
2019-04-08 12:47:15 -04:00
raise CaskError, "Failed to merge the following PRs:\n#{failed.join("\n")}"
2019-02-15 12:33:43 +01:00
end
def passed_ci(pr)
statuses = GitHub.open_api(pr["statuses_url"])
latest_pr_status = statuses.select { |status| status["context"] == "continuous-integration/travis-ci/pr" }
.max_by { |status| Time.parse(status["updated_at"]) }
latest_pr_status&.fetch("state") == "success"
end
def check_diff(pr)
diff_url = pr["diff_url"]
output, _, status = curl_output("--location", diff_url)
return false unless status.success?
diff = GitDiff.from_string(output)
diff_is_single_cask(diff) && diff_only_version_or_checksum_changed(diff)
end
def diff_is_single_cask(diff)
return false unless diff.files.count == 1
2019-02-15 12:33:43 +01:00
file = diff.files.first
return false unless file.a_path == file.b_path
2019-02-15 12:33:43 +01:00
file.a_path.match?(%r{\ACasks/[^/]+\.rb\Z})
end
def diff_only_version_or_checksum_changed(diff)
lines = diff.files.flat_map(&:hunks).flat_map(&:lines)
additions = lines.select(&:addition?)
deletions = lines.select(&:deletion?)
changed_lines = deletions + additions
return false if additions.count != deletions.count
return false if additions.count > 2
changed_lines.all? { |line| diff_line_is_version(line.to_s) || diff_line_is_sha256(line.to_s) }
end
def diff_line_is_sha256(line)
line.match?(/\A[+-]\s*sha256 '[0-9a-f]{64}'\Z/)
end
def diff_line_is_version(line)
line.match?(/\A[+-]\s*version '[^']+'\Z/)
end
def self.help
"automatically merge “simple” Cask pull requests"
end
end
end
end