Merge pull request #9423 from reitermarkus/bump-unversioned-casks
Add `bump-unversioned-casks` command.
This commit is contained in:
commit
e416668a25
@ -15,7 +15,7 @@ module Cask
|
||||
#
|
||||
# @api private
|
||||
class Pkg < AbstractArtifact
|
||||
attr_reader :pkg_relative_path, :path, :stanza_options
|
||||
attr_reader :path, :stanza_options
|
||||
|
||||
def self.from_args(cask, path, **stanza_options)
|
||||
stanza_options.assert_valid_keys!(:allow_untrusted, :choices)
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
module Cask
|
||||
class Cask
|
||||
def artifacts; end
|
||||
|
||||
def homepage; end
|
||||
end
|
||||
end
|
||||
|
||||
@ -40,6 +40,10 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
def time_file_size
|
||||
downloader.resolved_time_file_size
|
||||
end
|
||||
|
||||
def clear_cache
|
||||
downloader.clear_cache
|
||||
end
|
||||
|
||||
@ -30,7 +30,8 @@ module Cask
|
||||
def initialize(cask, command: SystemCommand, force: false,
|
||||
skip_cask_deps: false, binaries: true, verbose: false,
|
||||
require_sha: false, upgrade: false,
|
||||
installed_as_dependency: false, quarantine: true)
|
||||
installed_as_dependency: false, quarantine: true,
|
||||
verify_download_integrity: true)
|
||||
@cask = cask
|
||||
@command = command
|
||||
@force = force
|
||||
@ -42,6 +43,7 @@ module Cask
|
||||
@upgrade = upgrade
|
||||
@installed_as_dependency = installed_as_dependency
|
||||
@quarantine = quarantine
|
||||
@verify_download_integrity = verify_download_integrity
|
||||
end
|
||||
|
||||
attr_predicate :binaries?, :force?, :skip_cask_deps?, :require_sha?,
|
||||
@ -150,13 +152,10 @@ module Cask
|
||||
s.freeze
|
||||
end
|
||||
|
||||
sig { returns(Pathname) }
|
||||
def download
|
||||
return @downloaded_path if @downloaded_path
|
||||
|
||||
odebug "Downloading"
|
||||
@downloaded_path = Download.new(@cask, quarantine: quarantine?).fetch
|
||||
odebug "Downloaded to -> #{@downloaded_path}"
|
||||
@downloaded_path
|
||||
@download ||= Download.new(@cask, quarantine: quarantine?)
|
||||
.fetch(verify_download_integrity: @verify_download_integrity)
|
||||
end
|
||||
|
||||
def verify_has_sha
|
||||
@ -171,15 +170,15 @@ module Cask
|
||||
|
||||
def primary_container
|
||||
@primary_container ||= begin
|
||||
download
|
||||
UnpackStrategy.detect(@downloaded_path, type: @cask.container&.type, merge_xattrs: true)
|
||||
downloaded_path = download
|
||||
UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true)
|
||||
end
|
||||
end
|
||||
|
||||
def extract_primary_container
|
||||
def extract_primary_container(to: @cask.staged_path)
|
||||
odebug "Extracting primary container"
|
||||
|
||||
odebug "Using container class #{primary_container.class} for #{@downloaded_path}"
|
||||
odebug "Using container class #{primary_container.class} for #{primary_container.path}"
|
||||
|
||||
basename = CGI.unescape(File.basename(@cask.url.path))
|
||||
|
||||
@ -191,16 +190,16 @@ module Cask
|
||||
FileUtils.chmod_R "+rw", tmpdir/nested_container, force: true, verbose: verbose?
|
||||
|
||||
UnpackStrategy.detect(tmpdir/nested_container, merge_xattrs: true)
|
||||
.extract_nestedly(to: @cask.staged_path, verbose: verbose?)
|
||||
.extract_nestedly(to: to, verbose: verbose?)
|
||||
end
|
||||
else
|
||||
primary_container.extract_nestedly(to: @cask.staged_path, basename: basename, verbose: verbose?)
|
||||
primary_container.extract_nestedly(to: to, basename: basename, verbose: verbose?)
|
||||
end
|
||||
|
||||
return unless quarantine?
|
||||
return unless Quarantine.available?
|
||||
|
||||
Quarantine.propagate(from: @downloaded_path, to: @cask.staged_path)
|
||||
Quarantine.propagate(from: primary_container.path, to: to)
|
||||
end
|
||||
|
||||
def install_artifacts
|
||||
|
||||
@ -72,9 +72,10 @@ module Homebrew
|
||||
new_version = Cask::DSL::Version.new(new_version)
|
||||
new_base_url = args.url
|
||||
new_hash = args.sha256
|
||||
new_hash = :no_check if new_hash == ":no_check"
|
||||
|
||||
old_version = cask.version
|
||||
old_hash = cask.sha256.to_s
|
||||
old_hash = cask.sha256
|
||||
|
||||
tap_full_name = cask.tap&.full_name
|
||||
default_remote_branch = cask.tap.path.git_origin_branch if cask.tap
|
||||
@ -95,7 +96,7 @@ module Homebrew
|
||||
elsif old_version.latest?
|
||||
opoo "No --url= argument specified!" unless new_base_url
|
||||
elsif new_version.latest?
|
||||
opoo "Ignoring specified --sha256= argument." if new_hash
|
||||
opoo "Ignoring specified --sha256= argument." if new_hash && new_check != :no_check
|
||||
elsif Version.new(new_version) < Version.new(old_version)
|
||||
odie <<~EOS
|
||||
You need to bump this cask manually since changing the
|
||||
@ -136,7 +137,9 @@ module Homebrew
|
||||
]
|
||||
end
|
||||
|
||||
if !new_version.latest? && (new_hash.nil? || cask.languages.present?)
|
||||
if new_version.latest?
|
||||
new_hash = :no_check
|
||||
elsif new_hash.nil? || cask.languages.present?
|
||||
tmp_contents = Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
|
||||
replacement_pairs.uniq.compact,
|
||||
read_only_run: true,
|
||||
@ -172,15 +175,14 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
replacement_pairs << if old_version.latest?
|
||||
p old_hash
|
||||
|
||||
replacement_pairs << if old_version.latest? || new_version.latest? || new_hash == :no_check
|
||||
hash_regex = old_hash == :no_check ? ":no_check" : "[\"']#{Regexp.escape(old_hash.to_s)}[\"']"
|
||||
|
||||
[
|
||||
"sha256 :no_check",
|
||||
"sha256 \"#{new_hash}\"",
|
||||
]
|
||||
elsif new_version.latest?
|
||||
[
|
||||
"sha256 \"#{old_hash}\"",
|
||||
"sha256 :no_check",
|
||||
/sha256\s+#{hash_regex}/m,
|
||||
"sha256 #{new_hash == :no_check ? ":no_check" : "\"#{new_hash}\""}",
|
||||
]
|
||||
else
|
||||
[
|
||||
|
||||
175
Library/Homebrew/dev-cmd/bump-unversioned-casks.rb
Normal file
175
Library/Homebrew/dev-cmd/bump-unversioned-casks.rb
Normal file
@ -0,0 +1,175 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cask/download"
|
||||
require "cask/installer"
|
||||
require "cask/cask_loader"
|
||||
require "cli/parser"
|
||||
require "tap"
|
||||
require "unversioned_cask_checker"
|
||||
|
||||
module Homebrew
|
||||
extend T::Sig
|
||||
|
||||
extend SystemCommand::Mixin
|
||||
|
||||
sig { returns(CLI::Parser) }
|
||||
def self.bump_unversioned_casks_args
|
||||
Homebrew::CLI::Parser.new do
|
||||
usage_banner <<~EOS
|
||||
`bump-unversioned-casks` [<options>] [<tap>]
|
||||
|
||||
Check all casks with unversioned URLs in a given <tap> for updates.
|
||||
EOS
|
||||
switch "-n", "--dry-run",
|
||||
description: "Do everything except caching state and opening pull requests."
|
||||
flag "--limit=",
|
||||
description: "Maximum runtime in minutes."
|
||||
flag "--state-file=",
|
||||
description: "File for caching state."
|
||||
|
||||
named 1
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def self.bump_unversioned_casks
|
||||
args = bump_unversioned_casks_args.parse
|
||||
|
||||
state_file = if args.state_file.present?
|
||||
Pathname(args.state_file).expand_path
|
||||
else
|
||||
HOMEBREW_CACHE/"bump_unversioned_casks.json"
|
||||
end
|
||||
state_file.dirname.mkpath
|
||||
|
||||
tap = Tap.fetch(args.named.first)
|
||||
|
||||
state = state_file.exist? ? JSON.parse(state_file.read) : {}
|
||||
|
||||
cask_files = tap.cask_files
|
||||
unversioned_cask_files = cask_files.select do |cask_file|
|
||||
url = cask_file.each_line do |line|
|
||||
url = line[/\s*url\s+"([^"]+)"\s*/, 1]
|
||||
break url if url
|
||||
end
|
||||
|
||||
url.present? && url.exclude?('#{')
|
||||
end.sort
|
||||
|
||||
unversioned_casks = unversioned_cask_files.map { |path| Cask::CaskLoader.load(path) }
|
||||
|
||||
ohai "Unversioned Casks: #{unversioned_casks.count}"
|
||||
|
||||
checked, unchecked = unversioned_casks.partition { |c| state.key?(c.full_name) }
|
||||
|
||||
queue = Queue.new
|
||||
|
||||
# Start with random casks which have not been checked.
|
||||
unchecked.shuffle.each do |c|
|
||||
queue.enq c
|
||||
end
|
||||
|
||||
# Continue with previously checked casks, ordered by when they were last checked.
|
||||
checked.sort_by { |c| state.dig(c.full_name, "check_time") }.each do |c|
|
||||
queue.enq c
|
||||
end
|
||||
|
||||
limit = args.limit.presence&.to_i
|
||||
end_time = Time.now + limit.minutes if limit
|
||||
|
||||
until queue.empty? || (end_time && end_time < Time.now)
|
||||
cask = queue.deq
|
||||
|
||||
key = cask.full_name
|
||||
|
||||
new_state = bump_unversioned_cask(cask, state: state.fetch(key, {}), dry_run: args.dry_run?)
|
||||
|
||||
next unless new_state
|
||||
|
||||
state[key] = new_state
|
||||
|
||||
state_file.atomic_write JSON.generate(state) unless args.dry_run?
|
||||
end
|
||||
end
|
||||
|
||||
sig do
|
||||
params(cask: Cask::Cask, state: T::Hash[String, T.untyped], dry_run: T.nilable(T::Boolean))
|
||||
.returns(T.nilable(T::Hash[String, T.untyped]))
|
||||
end
|
||||
def self.bump_unversioned_cask(cask, state:, dry_run:)
|
||||
ohai "Checking #{cask.full_name}"
|
||||
|
||||
unversioned_cask_checker = UnversionedCaskChecker.new(cask)
|
||||
|
||||
unless unversioned_cask_checker.single_app_cask? || unversioned_cask_checker.single_pkg_cask?
|
||||
opoo "Skipping, not a single-app or PKG cask."
|
||||
return
|
||||
end
|
||||
|
||||
last_check_time = state["check_time"]&.yield_self { |t| Time.parse(t) }
|
||||
|
||||
check_time = Time.now
|
||||
if last_check_time && check_time < (last_check_time + 1.day)
|
||||
opoo "Skipping, already checked within the last 24 hours."
|
||||
return
|
||||
end
|
||||
|
||||
last_sha256 = state["sha256"]
|
||||
last_time = state["time"]&.yield_self { |t| Time.parse(t) }
|
||||
last_file_size = state["file_size"]
|
||||
|
||||
download = Cask::Download.new(cask)
|
||||
time, file_size = begin
|
||||
download.time_file_size
|
||||
rescue
|
||||
[nil, nil]
|
||||
end
|
||||
|
||||
if last_time != time || last_file_size != file_size
|
||||
begin
|
||||
cached_download = unversioned_cask_checker.installer.download
|
||||
rescue => e
|
||||
onoe e
|
||||
return
|
||||
end
|
||||
|
||||
sha256 = cached_download.sha256
|
||||
|
||||
if last_sha256 != sha256 && (version = unversioned_cask_checker.guess_cask_version)
|
||||
if cask.version == version
|
||||
oh1 "Cask #{cask} is up-to-date at #{version}"
|
||||
else
|
||||
bump_cask_pr_args = [
|
||||
"bump-cask-pr",
|
||||
"--version", version.to_s,
|
||||
"--sha256", ":no_check",
|
||||
"--message", "Automatic update via `brew bump-unversioned-casks`.",
|
||||
cask.sourcefile_path
|
||||
]
|
||||
|
||||
if dry_run
|
||||
bump_cask_pr_args << "--dry-run"
|
||||
oh1 "Would bump #{cask} from #{cask.version} to #{version}"
|
||||
else
|
||||
oh1 "Bumping #{cask} from #{cask.version} to #{version}"
|
||||
end
|
||||
|
||||
begin
|
||||
system_command! HOMEBREW_BREW_FILE, args: bump_cask_pr_args
|
||||
rescue ErrorDuringExecution => e
|
||||
onoe e
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
"sha256" => sha256,
|
||||
"check_time" => check_time.iso8601,
|
||||
"time" => time&.iso8601,
|
||||
"file_size" => file_size,
|
||||
}
|
||||
end
|
||||
end
|
||||
@ -356,7 +356,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
|
||||
ohai "Downloading #{url}"
|
||||
|
||||
resolved_url, _, url_time = resolve_url_basename_time(url)
|
||||
resolved_url, _, url_time, = resolve_url_basename_time_file_size(url)
|
||||
|
||||
fresh = if cached_location.exist? && url_time
|
||||
url_time <= cached_location.mtime
|
||||
@ -398,14 +398,19 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
rm_rf(temporary_path)
|
||||
end
|
||||
|
||||
def resolved_time_file_size
|
||||
_, _, time, file_size = resolve_url_basename_time_file_size(url)
|
||||
[time, file_size]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolved_url_and_basename
|
||||
resolved_url, basename, = resolve_url_basename_time(url)
|
||||
resolved_url, basename, = resolve_url_basename_time_file_size(url)
|
||||
[resolved_url, basename]
|
||||
end
|
||||
|
||||
def resolve_url_basename_time(url)
|
||||
def resolve_url_basename_time_file_size(url)
|
||||
@resolved_info_cache ||= {}
|
||||
return @resolved_info_cache[url] if @resolved_info_cache.include?(url)
|
||||
|
||||
@ -458,9 +463,15 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
|
||||
.map { |t| t.match?(/^\d+$/) ? Time.at(t.to_i) : Time.parse(t) }
|
||||
.last
|
||||
|
||||
file_size =
|
||||
lines.map { |line| line[/^Content-Length:\s*(\d+)/i, 1] }
|
||||
.compact
|
||||
.map(&:to_i)
|
||||
.last
|
||||
|
||||
basename = filenames.last || parse_basename(redirect_url)
|
||||
|
||||
@resolved_info_cache[url] = [redirect_url, basename, time]
|
||||
@resolved_info_cache[url] = [redirect_url, basename, time, file_size]
|
||||
end
|
||||
|
||||
def _fetch(url:, resolved_url:)
|
||||
@ -526,7 +537,7 @@ class CurlApacheMirrorDownloadStrategy < CurlDownloadStrategy
|
||||
@combined_mirrors = [*@mirrors, *backup_mirrors]
|
||||
end
|
||||
|
||||
def resolve_url_basename_time(url)
|
||||
def resolve_url_basename_time_file_size(url)
|
||||
if url == self.url
|
||||
super("#{apache_mirrors["preferred"]}#{apache_mirrors["path_info"]}")
|
||||
else
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cmd/shared_examples/args_parse"
|
||||
|
||||
describe "Homebrew.bump_unversioned_casks_args" do
|
||||
it_behaves_like "parseable arguments"
|
||||
end
|
||||
28
Library/Homebrew/test/unversioned_cask_checker_spec.rb
Normal file
28
Library/Homebrew/test/unversioned_cask_checker_spec.rb
Normal file
@ -0,0 +1,28 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "unversioned_cask_checker"
|
||||
|
||||
describe Homebrew::UnversionedCaskChecker do
|
||||
describe "::decide_between_versions" do
|
||||
expected_mappings = {
|
||||
[nil, nil] => nil,
|
||||
["1.2", nil] => "1.2",
|
||||
[nil, "1.2.3"] => "1.2.3",
|
||||
["1.2", "1.2.3"] => "1.2.3",
|
||||
["1.2.3", "1.2"] => "1.2.3",
|
||||
["1.2.3", "8312"] => "1.2.3,8312",
|
||||
["2021", "2006"] => "2021,2006",
|
||||
["1.0", "1"] => "1.0",
|
||||
["1.0", "0"] => "1.0",
|
||||
["1.2.3.4000", "4000"] => "1.2.3.4000",
|
||||
}
|
||||
|
||||
expected_mappings.each do |(short_version, version), expected_version|
|
||||
it "maps (#{short_version}, #{version}) to #{expected_version}" do
|
||||
expect(described_class.decide_between_versions(short_version, version))
|
||||
.to eq expected_version
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
167
Library/Homebrew/unversioned_cask_checker.rb
Normal file
167
Library/Homebrew/unversioned_cask_checker.rb
Normal file
@ -0,0 +1,167 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cask/cask"
|
||||
require "cask/installer"
|
||||
|
||||
module Homebrew
|
||||
# Check unversioned casks for updates by extracting their
|
||||
# contents and guessing the version from contained files.
|
||||
#
|
||||
# @api private
|
||||
class UnversionedCaskChecker
|
||||
extend T::Sig
|
||||
|
||||
sig { returns(Cask::Cask) }
|
||||
attr_reader :cask
|
||||
|
||||
sig { params(cask: Cask::Cask).void }
|
||||
def initialize(cask)
|
||||
@cask = cask
|
||||
end
|
||||
|
||||
sig { returns(Cask::Installer) }
|
||||
def installer
|
||||
@installer ||= Cask::Installer.new(cask, verify_download_integrity: false)
|
||||
end
|
||||
|
||||
sig { returns(T::Array[Cask::Artifact::App]) }
|
||||
def apps
|
||||
@apps ||= @cask.artifacts.select { |a| a.is_a?(Cask::Artifact::App) }
|
||||
end
|
||||
|
||||
sig { returns(T::Array[Cask::Artifact::Pkg]) }
|
||||
def pkgs
|
||||
@pkgs ||= @cask.artifacts.select { |a| a.is_a?(Cask::Artifact::Pkg) }
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def single_app_cask?
|
||||
apps.count == 1
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def single_pkg_cask?
|
||||
pkgs.count == 1
|
||||
end
|
||||
|
||||
sig { params(info_plist_path: Pathname).returns(T.nilable(String)) }
|
||||
def self.version_from_info_plist(info_plist_path)
|
||||
plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", info_plist_path]).plist
|
||||
|
||||
short_version = plist["CFBundleShortVersionString"].presence
|
||||
version = plist["CFBundleVersion"].presence
|
||||
|
||||
return decide_between_versions(short_version, version) if short_version && version
|
||||
end
|
||||
|
||||
sig { params(package_info_path: Pathname).returns(T.nilable(String)) }
|
||||
def self.version_from_package_info(package_info_path)
|
||||
contents = package_info_path.read
|
||||
|
||||
short_version = contents[/CFBundleShortVersionString="([^"]*)"/, 1].presence
|
||||
version = contents[/CFBundleVersion="([^"]*)"/, 1].presence
|
||||
|
||||
return decide_between_versions(short_version, version) if short_version && version
|
||||
end
|
||||
|
||||
sig do
|
||||
params(short_version: T.nilable(String), version: T.nilable(String))
|
||||
.returns(T.nilable(String))
|
||||
end
|
||||
def self.decide_between_versions(short_version, version)
|
||||
return short_version if short_version == version
|
||||
|
||||
short_version_match = short_version&.match?(/\A\d+(\.\d+)+\Z/)
|
||||
version_match = version&.match?(/\A\d+(\.\d+)+\Z/)
|
||||
|
||||
if short_version_match && version_match
|
||||
return version if version.length > short_version.length && version.start_with?("#{short_version}.")
|
||||
return short_version if short_version.length > version.length && short_version.start_with?("#{version}.")
|
||||
end
|
||||
|
||||
if short_version&.match?(/\A\d+(\.\d+)*\Z/) && version&.match?(/\A\d+\Z/)
|
||||
return short_version if short_version.start_with?("#{version}.") || short_version.end_with?(".#{version}")
|
||||
|
||||
return "#{short_version},#{version}"
|
||||
end
|
||||
|
||||
short_version || version
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def guess_cask_version
|
||||
if apps.empty? && pkgs.empty?
|
||||
opoo "Cask #{cask} does not contain any apps or PKG installers."
|
||||
return
|
||||
end
|
||||
|
||||
Dir.mktmpdir do |dir|
|
||||
dir = Pathname(dir)
|
||||
|
||||
installer.yield_self do |i|
|
||||
i.extract_primary_container(to: dir)
|
||||
rescue ErrorDuringExecution => e
|
||||
onoe e
|
||||
return nil
|
||||
end
|
||||
|
||||
info_plist_paths = apps.flat_map do |app|
|
||||
Pathname.glob(dir/"**"/app.source.basename/"Contents"/"Info.plist")
|
||||
end
|
||||
|
||||
info_plist_paths.each do |info_plist_path|
|
||||
if (version = self.class.version_from_info_plist(info_plist_path))
|
||||
return version
|
||||
end
|
||||
end
|
||||
|
||||
pkg_paths = pkgs.flat_map do |pkg|
|
||||
Pathname.glob(dir/"**"/pkg.path.basename)
|
||||
end
|
||||
|
||||
pkg_paths.each do |pkg_path|
|
||||
packages =
|
||||
system_command!("installer", args: ["-plist", "-pkginfo", "-pkg", pkg_path])
|
||||
.plist
|
||||
.map { |package| package.fetch("Package") }
|
||||
.uniq
|
||||
|
||||
Dir.mktmpdir do |extract_dir|
|
||||
extract_dir = Pathname(extract_dir)
|
||||
FileUtils.rmdir extract_dir
|
||||
|
||||
begin
|
||||
system_command! "pkgutil", args: ["--expand-full", pkg_path, extract_dir]
|
||||
rescue ErrorDuringExecution => e
|
||||
onoe "Failed to extract #{pkg_path.basename}: #{e}"
|
||||
next
|
||||
end
|
||||
|
||||
package_info_path = extract_dir/"PackageInfo"
|
||||
if package_info_path.exist?
|
||||
if (version = self.class.version_from_package_info(package_info_path))
|
||||
return version
|
||||
end
|
||||
elsif packages.count == 1
|
||||
onoe "#{pkg_path.basename} does not contain a `PackageInfo` file."
|
||||
end
|
||||
|
||||
opoo "#{pkg_path.basename} contains multiple packages: (#{packages.join(", ")})" if packages.count != 1
|
||||
|
||||
$stderr.puts Pathname.glob(extract_dir/"**/*")
|
||||
.map { |path|
|
||||
regex = %r{\A(.*?\.(app|qlgenerator|saver|plugin|kext|bundle|osax))/.*\Z}
|
||||
path.to_s.sub(regex, '\1')
|
||||
}.uniq
|
||||
ensure
|
||||
Cask::Utils.gain_permissions_remove(extract_dir)
|
||||
extract_dir.mkpath
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -736,11 +736,12 @@ module GitHub
|
||||
EOS
|
||||
user_message = args.message
|
||||
if user_message
|
||||
pr_message += <<~EOS
|
||||
pr_message = <<~EOS
|
||||
#{user_message}
|
||||
|
||||
---
|
||||
|
||||
#{user_message}
|
||||
#{pr_message}
|
||||
EOS
|
||||
end
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ bump
|
||||
bump-cask-pr
|
||||
bump-formula-pr
|
||||
bump-revision
|
||||
bump-unversioned-casks
|
||||
cask
|
||||
cat
|
||||
cleanup
|
||||
|
||||
@ -899,6 +899,17 @@ present, "revision 1" will be added.
|
||||
* `--message`:
|
||||
Append *`message`* to the default commit message.
|
||||
|
||||
### `bump-unversioned-casks` [*`options`*] [*`tap`*]
|
||||
|
||||
Check all casks with unversioned URLs in a given *`tap`* for updates.
|
||||
|
||||
* `-n`, `--dry-run`:
|
||||
Do everything except caching state and opening pull requests.
|
||||
* `--limit`:
|
||||
Maximum runtime in minutes.
|
||||
* `--state-file`:
|
||||
File for caching state.
|
||||
|
||||
### `cat` *`formula`*|*`cask`*
|
||||
|
||||
Display the source of a *`formula`* or *`cask`*.
|
||||
|
||||
@ -1245,6 +1245,21 @@ Print what would be done rather than doing it\.
|
||||
\fB\-\-message\fR
|
||||
Append \fImessage\fR to the default commit message\.
|
||||
.
|
||||
.SS "\fBbump\-unversioned\-casks\fR [\fIoptions\fR] [\fItap\fR]"
|
||||
Check all casks with unversioned URLs in a given \fItap\fR for updates\.
|
||||
.
|
||||
.TP
|
||||
\fB\-n\fR, \fB\-\-dry\-run\fR
|
||||
Do everything except caching state and opening pull requests\.
|
||||
.
|
||||
.TP
|
||||
\fB\-\-limit\fR
|
||||
Maximum runtime in minutes\.
|
||||
.
|
||||
.TP
|
||||
\fB\-\-state\-file\fR
|
||||
File for caching state\.
|
||||
.
|
||||
.SS "\fBcat\fR \fIformula\fR|\fIcask\fR"
|
||||
Display the source of a \fIformula\fR or \fIcask\fR\.
|
||||
.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user