Class: Cask::Cmd::Automerge

Inherits:
AbstractInternalCommand show all
Defined in:
brew/Library/Homebrew/cask/cmd/automerge.rb

Constant Summary collapse

OFFICIAL_CASK_TAPS =
[
  "homebrew/cask",
  "homebrew/cask-drivers",
  "homebrew/cask-eid",
  "homebrew/cask-fonts",
  "homebrew/cask-versions",
].freeze

Instance Attribute Summary

Attributes inherited from AbstractCommand

#args

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from AbstractInternalCommand

command_name, visible

Methods inherited from AbstractCommand

abstract?, command_name, #initialize, run, visible

Methods included from Homebrew::Search

#query_regexp, #search_casks, #search_descriptions, #search_formulae, #search_taps

Methods included from Options

#process_arguments

Constructor Details

This class inherits a constructor from Cask::Cmd::AbstractCommand

Class Method Details

.helpObject



122
123
124
# File 'brew/Library/Homebrew/cask/cmd/automerge.rb', line 122

def self.help
  "automatically merge “simple” Cask pull requests"
end

Instance Method Details

#check_diff(pr) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
# File 'brew/Library/Homebrew/cask/cmd/automerge.rb', line 80

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

#diff_is_single_cask(diff) ⇒ Object



92
93
94
95
96
97
98
99
# File 'brew/Library/Homebrew/cask/cmd/automerge.rb', line 92

def diff_is_single_cask(diff)
  return false unless diff.files.count == 1

  file = diff.files.first
  return false unless file.a_path == file.b_path

  file.a_path.match?(%r{\ACasks/[^/]+\.rb\Z})
end

#diff_line_is_sha256(line) ⇒ Object



114
115
116
# File 'brew/Library/Homebrew/cask/cmd/automerge.rb', line 114

def diff_line_is_sha256(line)
  line.match?(/\A[+-]\s*sha256 '[0-9a-f]{64}'\Z/)
end

#diff_line_is_version(line) ⇒ Object



118
119
120
# File 'brew/Library/Homebrew/cask/cmd/automerge.rb', line 118

def diff_line_is_version(line)
  line.match?(/\A[+-]\s*version '[^']+'\Z/)
end

#diff_only_version_or_checksum_changed(diff) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
# File 'brew/Library/Homebrew/cask/cmd/automerge.rb', line 101

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

#passed_ci(pr) ⇒ Object



71
72
73
74
75
76
77
78
# File 'brew/Library/Homebrew/cask/cmd/automerge.rb', line 71

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

#runObject

Raises:



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'brew/Library/Homebrew/cask/cmd/automerge.rb', line 19

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

  Homebrew.install_gem! "git_diff"
  require "git_diff"

  failed = []

  taps.each do |tap|
    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"])} "

      retried = false

      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
        unless retried
          retried = true
          sleep 5
          retry
        end

        puts "#{Tty.bold}#{Formatter.error("")}#{Tty.reset}"
        failed << pr["html_url"]
      end
    end
  end

  return if failed.empty?

  $stderr.puts
  raise CaskError, "Failed to merge the following PRs:\n#{failed.join("\n")}"
end