forked from Homebrew/brew
/
bump.rb
293 lines (247 loc) 路 9.78 KB
/
bump.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# typed: true
# frozen_string_literal: true
require "cli/parser"
require "livecheck/livecheck"
module Homebrew
extend T::Sig
module_function
sig { returns(CLI::Parser) }
def bump_args
Homebrew::CLI::Parser.new do
description <<~EOS
Display out-of-date brew formulae and the latest version available. If the
returned current and livecheck versions differ or when querying specific
formulae, also displays whether a pull request has been opened with the URL.
EOS
switch "--full-name",
description: "Print formulae/casks with fully-qualified names."
switch "--no-pull-requests",
description: "Do not retrieve pull requests from GitHub."
switch "--formula", "--formulae",
description: "Check only formulae."
switch "--cask", "--casks",
description: "Check only casks."
switch "--open-pr",
description: "Open a pull request for the new version if none have been opened yet."
flag "--limit=",
description: "Limit number of package results returned."
flag "--start-with=",
description: "Letter or word that the list of package results should alphabetically follow."
conflicts "--cask", "--formula"
conflicts "--no-pull-requests", "--open-pr"
named_args [:formula, :cask]
end
end
def bump
args = bump_args.parse
if args.limit.present? && !args.formula? && !args.cask?
raise UsageError, "`--limit` must be used with either `--formula` or `--cask`."
end
formulae_and_casks = if args.formula?
args.named.to_formulae
elsif args.cask?
args.named.to_casks
else
args.named.to_formulae_and_casks
end
formulae_and_casks = formulae_and_casks&.sort_by do |formula_or_cask|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end
limit = args.limit.to_i if args.limit.present?
unless Utils::Curl.curl_supports_tls13?
begin
ensure_formula_installed!("curl", reason: "Repology queries") unless HOMEBREW_BREWED_CURL_PATH.exist?
rescue FormulaUnavailableError
opoo "A newer `curl` is required for Repology queries."
end
end
if formulae_and_casks.present?
Livecheck.load_other_tap_strategies(formulae_and_casks)
ambiguous_casks = []
if !args.formula? && !args.cask?
ambiguous_casks = formulae_and_casks \
.group_by { |item| Livecheck.package_or_resource_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.select { |item| item.is_a?(Cask::Cask) }
end
ambiguous_names = []
unless args.full_name?
ambiguous_names =
(formulae_and_casks - ambiguous_casks).group_by { |item| Livecheck.package_or_resource_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
end
formulae_and_casks.each_with_index do |formula_or_cask, i|
puts if i.positive?
use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask, full_name: use_full_name)
repository = if formula_or_cask.is_a?(Formula)
if formula_or_cask.head_only?
ohai name
puts "Formula is HEAD-only."
next
end
Repology::HOMEBREW_CORE
else
Repology::HOMEBREW_CASK
end
package_data = if formula_or_cask.is_a?(Formula) && formula_or_cask.versioned_formula?
nil
else
Repology.single_package_query(name, repository: repository)
end
retrieve_and_display_info_and_open_pr(
formula_or_cask,
name,
package_data&.values&.first,
args: args,
ambiguous_cask: ambiguous_casks.include?(formula_or_cask),
)
end
else
api_response = {}
unless args.cask?
api_response[:formulae] =
Repology.parse_api_response(limit, args.start_with, repository: Repology::HOMEBREW_CORE)
end
unless args.formula?
api_response[:casks] =
Repology.parse_api_response(limit, args.start_with, repository: Repology::HOMEBREW_CASK)
end
api_response.each_with_index do |(package_type, outdated_packages), idx|
repository = if package_type == :formulae
Repology::HOMEBREW_CORE
else
Repology::HOMEBREW_CASK
end
puts if idx.positive?
oh1 package_type.capitalize if api_response.size > 1
outdated_packages.each_with_index do |(_name, repositories), i|
break if limit && i >= limit
homebrew_repo = repositories.find do |repo|
repo["repo"] == repository
end
next if homebrew_repo.blank?
formula_or_cask = begin
if repository == Repology::HOMEBREW_CORE
Formula[homebrew_repo["srcname"]]
else
Cask::CaskLoader.load(homebrew_repo["srcname"])
end
rescue
next
end
name = Livecheck.package_or_resource_name(formula_or_cask)
ambiguous_cask = begin
formula_or_cask.is_a?(Cask::Cask) && !args.cask? && Formula[name]
rescue FormulaUnavailableError
false
end
puts if i.positive?
retrieve_and_display_info_and_open_pr(
formula_or_cask,
name,
repositories,
args: args,
ambiguous_cask: ambiguous_cask,
)
end
end
end
end
def livecheck_result(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask)
referenced_formula_or_cask, =
Livecheck.resolve_livecheck_reference(formula_or_cask, full_name: false, debug: false)
# Check skip conditions for a referenced formula/cask
if referenced_formula_or_cask
skip_info = Livecheck::SkipConditions.referenced_skip_information(
referenced_formula_or_cask,
name,
full_name: false,
verbose: false,
)
end
skip_info ||= Livecheck::SkipConditions.skip_information(formula_or_cask, full_name: false, verbose: false)
if skip_info.present?
return "#{skip_info[:status]}#{" - #{skip_info[:messages].join(", ")}" if skip_info[:messages].present?}"
end
version_info = Livecheck.latest_version(
formula_or_cask,
referenced_formula_or_cask: referenced_formula_or_cask,
json: true, full_name: false, verbose: true, debug: false
)
return "unable to get versions" if version_info.blank?
latest = version_info[:latest]
Version.new(latest)
rescue => e
"error: #{e}"
end
def retrieve_pull_requests(formula_or_cask, name, state:, version: nil)
tap_remote_repo = formula_or_cask.tap&.remote_repo || formula_or_cask.tap&.full_name
pull_requests = GitHub.fetch_pull_requests(name, tap_remote_repo, state: state, version: version)
if pull_requests.try(:any?)
pull_requests = pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ")
end
pull_requests
end
def retrieve_and_display_info_and_open_pr(formula_or_cask, name, repositories, args:, ambiguous_cask: false)
if formula_or_cask.is_a?(Formula)
current_version = formula_or_cask.stable.version
type = :formula
version_name = "formula version"
else
current_version = Version.new(formula_or_cask.version)
type = :cask
version_name = "cask version "
end
livecheck_latest = livecheck_result(formula_or_cask)
repology_latest = if repositories.present?
Repology.latest_version(repositories)
else
"not found"
end
new_version = if livecheck_latest.is_a?(Version) && livecheck_latest > current_version
livecheck_latest
elsif repology_latest.is_a?(Version) && repology_latest > current_version && !formula_or_cask.livecheckable?
repology_latest
end.presence
open_pull_requests = if !args.no_pull_requests? && (args.named.present? || new_version)
retrieve_pull_requests(formula_or_cask, name, state: "open")
end.presence
closed_pull_requests = if !args.no_pull_requests? && !open_pull_requests && new_version.present?
# if we haven't already found open requests, try for an exact match across closed requests
retrieve_pull_requests(formula_or_cask, name, state: "closed", version: new_version)
end.presence
title_name = ambiguous_cask ? "#{name} (cask)" : name
title = if current_version == repology_latest &&
current_version == livecheck_latest
"#{title_name} #{Tty.green}is up to date!#{Tty.reset}"
else
title_name
end
ohai title
puts <<~EOS
Current #{version_name}: #{current_version}
Latest livecheck version: #{livecheck_latest}
Latest Repology version: #{repology_latest}
Open pull requests: #{open_pull_requests || "none"}
Closed pull requests: #{closed_pull_requests || "none"}
EOS
return unless args.open_pr?
if repology_latest.is_a?(Version) &&
repology_latest > current_version &&
repology_latest > livecheck_latest &&
formula_or_cask.livecheckable?
puts "#{title_name} was not bumped to the Repology version because it's livecheckable."
end
return unless new_version
return if open_pull_requests
return if closed_pull_requests
system HOMEBREW_BREW_FILE, "bump-#{type}-pr", "--no-browse",
"--message=Created by `brew bump`", "--version=#{new_version}", name
end
end