From f96b8e713875fee683299cda6000834fae594255 Mon Sep 17 00:00:00 2001 From: nandahkrishna Date: Wed, 2 Dec 2020 18:04:22 +0530 Subject: [PATCH 1/6] livecheck: add GithubLatest strategy --- Library/Homebrew/livecheck/strategy.rb | 1 + .../livecheck/strategy/github_latest.rb | 59 +++++++++++++++++++ .../livecheck/strategy/github_latest_spec.rb | 26 ++++++++ 3 files changed, 86 insertions(+) create mode 100644 Library/Homebrew/livecheck/strategy/github_latest.rb create mode 100644 Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb diff --git a/Library/Homebrew/livecheck/strategy.rb b/Library/Homebrew/livecheck/strategy.rb index 766512e08f..8a7b403dd4 100644 --- a/Library/Homebrew/livecheck/strategy.rb +++ b/Library/Homebrew/livecheck/strategy.rb @@ -78,6 +78,7 @@ end require_relative "strategy/apache" require_relative "strategy/bitbucket" require_relative "strategy/git" +require_relative "strategy/github_latest" require_relative "strategy/gnome" require_relative "strategy/gnu" require_relative "strategy/hackage" diff --git a/Library/Homebrew/livecheck/strategy/github_latest.rb b/Library/Homebrew/livecheck/strategy/github_latest.rb new file mode 100644 index 0000000000..38c3aea144 --- /dev/null +++ b/Library/Homebrew/livecheck/strategy/github_latest.rb @@ -0,0 +1,59 @@ +# typed: false +# frozen_string_literal: true + +module Homebrew + module Livecheck + module Strategy + # The {GithubLatest} strategy identifies versions of software at + # github.com by checking a repository's latest release page. + # + # GitHub URLs take a few differemt formats: + # + # * `https://github.com/example/example/releases/download/1.2.3/example-1.2.3.zip` + # * `https://github.com/example/example/archive/v1.2.3.tar.gz` + # + # This strategy is used when latest releases are marked for software hosted + # on GitHub. It is necessary to use `strategy :github_latest` in a `livecheck` + # block for Livecheck to use this strategy. + # + # The default regex identifies versions from `href` attributes containing the + # tag name. + # + # @api public + class GithubLatest + NICE_NAME = "GitHub Latest" + + # The `Regexp` used to determine if the strategy applies to the URL. + URL_MATCH_REGEX = /github.com/i.freeze + + # Whether the strategy can be applied to the provided URL. + # + # @param url [String] the URL to match against + # @return [Boolean] + def self.match?(url) + URL_MATCH_REGEX.match?(url) + end + + # Generates a URL and regex (if one isn't provided) and passes them + # to {PageMatch.find_versions} to identify versions in the content. + # + # @param url [String] the URL of the content to check + # @param regex [Regexp] a regex used for matching versions in content + # @return [Hash] + def self.find_versions(url, regex = nil) + %r{github\.com/(?[\da-z\-]+)/(?[\da-z_\-]+)}i =~ url + + # The page containing the latest release + page_url = "https://github.com/#{username}/#{repository}/releases/latest" + + # The default regex applies to most repositories, but may have to be + # replaced with a specific regex when the tag names contain the package + # name or other characters apart from the version. + regex ||= %r{href=.*?/tag/v?(\d+(?:\.\d+)+)["' >]}i + + Homebrew::Livecheck::Strategy::PageMatch.find_versions(page_url, regex) + end + end + end + end +end diff --git a/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb b/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb new file mode 100644 index 0000000000..2d1d4cc401 --- /dev/null +++ b/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb @@ -0,0 +1,26 @@ +# typed: false +# frozen_string_literal: true + +require "livecheck/strategy/github_latest" + +describe Homebrew::Livecheck::Strategy::GithubLatest do + subject(:github_latest) { described_class } + + let(:github_download_url) { "https://github.com/example/example/releases/download/1.2.3/example-1.2.3.zip" } + let(:github_archive_url) { "https://github.com/example/example/archive/v1.2.3.tar.gz" } + let(:non_github_url) { "https://brew.sh/test" } + + describe "::match?" do + it "returns true if the argument provided is a GitHub Download URL" do + expect(github_latest.match?(github_download_url)).to be true + end + + it "returns true if the argument provided is a GitHub Archive URL" do + expect(github_latest.match?(github_archive_url)).to be true + end + + it "returns false if the argument provided is not a GitHub URL" do + expect(github_latest.match?(non_github_url)).to be false + end + end +end From d173b57c4203915365b967d1c61fad49bd325536 Mon Sep 17 00:00:00 2001 From: nandahkrishna Date: Sat, 5 Dec 2020 20:35:01 +0530 Subject: [PATCH 2/6] github_latest: modify strategy and tests --- .../livecheck/strategy/github_latest.rb | 13 ++++++++----- .../livecheck/strategy/github_latest_spec.rb | 19 +++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Library/Homebrew/livecheck/strategy/github_latest.rb b/Library/Homebrew/livecheck/strategy/github_latest.rb index 38c3aea144..d521305d9e 100644 --- a/Library/Homebrew/livecheck/strategy/github_latest.rb +++ b/Library/Homebrew/livecheck/strategy/github_latest.rb @@ -7,10 +7,11 @@ module Homebrew # The {GithubLatest} strategy identifies versions of software at # github.com by checking a repository's latest release page. # - # GitHub URLs take a few differemt formats: + # GitHub URLs take a few different formats: # - # * `https://github.com/example/example/releases/download/1.2.3/example-1.2.3.zip` + # * `https://github.com/example/example/releases/download/1.2.3/example-1.2.3.tar.gz` # * `https://github.com/example/example/archive/v1.2.3.tar.gz` + # * `https://github.com/downloads/example/example/example-1.2.3.tar.gz` # # This strategy is used when latest releases are marked for software hosted # on GitHub. It is necessary to use `strategy :github_latest` in a `livecheck` @@ -21,10 +22,12 @@ module Homebrew # # @api public class GithubLatest - NICE_NAME = "GitHub Latest" + NICE_NAME = "GitHub - Latest" + + PRIORITY = 0 # The `Regexp` used to determine if the strategy applies to the URL. - URL_MATCH_REGEX = /github.com/i.freeze + URL_MATCH_REGEX = %r{//github\.com(?:/downloads)?(?:/[^/]+){2}}i.freeze # Whether the strategy can be applied to the provided URL. # @@ -41,7 +44,7 @@ module Homebrew # @param regex [Regexp] a regex used for matching versions in content # @return [Hash] def self.find_versions(url, regex = nil) - %r{github\.com/(?[\da-z\-]+)/(?[\da-z_\-]+)}i =~ url + %r{github\.com/(?:downloads/)?(?[^/]+)/(?[^/]+)}i =~ url.sub(/\.git$/i, "") # The page containing the latest release page_url = "https://github.com/#{username}/#{repository}/releases/latest" diff --git a/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb b/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb index 2d1d4cc401..a6be06a295 100644 --- a/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb @@ -6,17 +6,24 @@ require "livecheck/strategy/github_latest" describe Homebrew::Livecheck::Strategy::GithubLatest do subject(:github_latest) { described_class } - let(:github_download_url) { "https://github.com/example/example/releases/download/1.2.3/example-1.2.3.zip" } - let(:github_archive_url) { "https://github.com/example/example/archive/v1.2.3.tar.gz" } + let(:github_release_artifact_url) { + "https://github.com/example/example/releases/download/1.2.3/example-1.2.3.zip" + } + let(:github_tag_archive_url) { "https://github.com/example/example/archive/v1.2.3.tar.gz" } + let(:github_repository_upload_url) { "https://github.com/downloads/example/example/example-1.2.3.tar.gz" } let(:non_github_url) { "https://brew.sh/test" } describe "::match?" do - it "returns true if the argument provided is a GitHub Download URL" do - expect(github_latest.match?(github_download_url)).to be true + it "returns true if the argument provided is a GitHub release artifact URL" do + expect(github_latest.match?(github_release_artifact_url)).to be true end - it "returns true if the argument provided is a GitHub Archive URL" do - expect(github_latest.match?(github_archive_url)).to be true + it "returns true if the argument provided is a GitHub tag archive URL" do + expect(github_latest.match?(github_tag_archive_url)).to be true + end + + it "returns true if the argument provided is a GitHub repository upload URL" do + expect(github_latest.match?(github_repository_upload_url)).to be true end it "returns false if the argument provided is not a GitHub URL" do From e37da963410ace813ecd4da158e8296b5cc1c1ce Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Sat, 5 Dec 2020 11:40:08 -0500 Subject: [PATCH 3/6] GithubLatest: Align test URL with example --- Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb b/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb index a6be06a295..d15088049a 100644 --- a/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy/github_latest_spec.rb @@ -7,7 +7,7 @@ describe Homebrew::Livecheck::Strategy::GithubLatest do subject(:github_latest) { described_class } let(:github_release_artifact_url) { - "https://github.com/example/example/releases/download/1.2.3/example-1.2.3.zip" + "https://github.com/example/example/releases/download/1.2.3/example-1.2.3.tar.gz" } let(:github_tag_archive_url) { "https://github.com/example/example/archive/v1.2.3.tar.gz" } let(:github_repository_upload_url) { "https://github.com/downloads/example/example/example-1.2.3.tar.gz" } From 7ef88f1966235c59216ec31836abedd6487f7cfc Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Sat, 5 Dec 2020 11:44:28 -0500 Subject: [PATCH 4/6] Livecheck: Skip URL processing for GithubLatest --- Library/Homebrew/livecheck/livecheck.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/livecheck/livecheck.rb b/Library/Homebrew/livecheck/livecheck.rb index 9531265260..07cba1f076 100644 --- a/Library/Homebrew/livecheck/livecheck.rb +++ b/Library/Homebrew/livecheck/livecheck.rb @@ -25,6 +25,11 @@ module Homebrew lolg.it ].freeze + STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL = [ + :github_latest, + :page_match, + ].freeze + UNSTABLE_VERSION_KEYWORDS = %w[ alpha beta @@ -381,8 +386,8 @@ module Homebrew next end - # Do not preprocess the URL when livecheck.strategy is set to :page_match - url = if livecheck_strategy == :page_match + # Only preprocess the URL when it's appropriate + url = if STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL.include?(livecheck_strategy) original_url else preprocess_url(original_url) From 524272aed03bbb5765535ff2dadf54b310108dee Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Sat, 5 Dec 2020 11:49:47 -0500 Subject: [PATCH 5/6] Livecheck: Rework non-positive PRIORITY handling --- Library/Homebrew/livecheck/livecheck.rb | 6 +++++- Library/Homebrew/livecheck/strategy.rb | 21 +++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Library/Homebrew/livecheck/livecheck.rb b/Library/Homebrew/livecheck/livecheck.rb index 07cba1f076..d27c6b51e4 100644 --- a/Library/Homebrew/livecheck/livecheck.rb +++ b/Library/Homebrew/livecheck/livecheck.rb @@ -393,7 +393,11 @@ module Homebrew preprocess_url(original_url) end - strategies = Strategy.from_url(url, livecheck_regex.present?) + strategies = Strategy.from_url( + url, + livecheck_strategy: livecheck_strategy, + regex_provided: livecheck_regex.present?, + ) strategy = Strategy.from_symbol(livecheck_strategy) strategy ||= strategies.first strategy_name = @livecheck_strategy_names[strategy] diff --git a/Library/Homebrew/livecheck/strategy.rb b/Library/Homebrew/livecheck/strategy.rb index 8a7b403dd4..c2c6f5ca40 100644 --- a/Library/Homebrew/livecheck/strategy.rb +++ b/Library/Homebrew/livecheck/strategy.rb @@ -52,19 +52,28 @@ module Homebrew # Returns an array of strategies that apply to the provided URL. # # @param url [String] the URL to check for matching strategies - # @param regex_provided [Boolean] whether a regex is provided in a + # @param livecheck_strategy [Symbol] a {Strategy} symbol from the + # `livecheck` block + # @param regex_provided [Boolean] whether a regex is provided in the # `livecheck` block # @return [Array] - def from_url(url, regex_provided = nil) + def from_url(url, livecheck_strategy: nil, regex_provided: nil) usable_strategies = strategies.values.select do |strategy| - # Ignore strategies with a priority of 0 or lower - next if strategy.const_defined?(:PRIORITY) && !strategy::PRIORITY.positive? + if strategy == PageMatch + # Only treat the `PageMatch` strategy as usable if a regex is + # present in the `livecheck` block + next unless regex_provided + elsif strategy.const_defined?(:PRIORITY) && + !strategy::PRIORITY.positive? && + from_symbol(livecheck_strategy) != strategy + # Ignore strategies with a priority of 0 or lower, unless the + # strategy is specified in the `livecheck` block + next + end strategy.respond_to?(:match?) && strategy.match?(url) end - usable_strategies << strategies[:page_match] if strategies.key?(:page_match) && regex_provided - # Sort usable strategies in descending order by priority, using the # DEFAULT_PRIORITY when a strategy doesn't contain a PRIORITY constant usable_strategies.sort_by do |strategy| From 28bfa0c93f4400e714c2ef54bb290e3e17baf6d2 Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Sat, 5 Dec 2020 11:53:50 -0500 Subject: [PATCH 6/6] GithubLatest: Rework documentation comments --- .../livecheck/strategy/github_latest.rb | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/Library/Homebrew/livecheck/strategy/github_latest.rb b/Library/Homebrew/livecheck/strategy/github_latest.rb index d521305d9e..d3afcddbf4 100644 --- a/Library/Homebrew/livecheck/strategy/github_latest.rb +++ b/Library/Homebrew/livecheck/strategy/github_latest.rb @@ -5,7 +5,7 @@ module Homebrew module Livecheck module Strategy # The {GithubLatest} strategy identifies versions of software at - # github.com by checking a repository's latest release page. + # github.com by checking a repository's "latest" release page. # # GitHub URLs take a few different formats: # @@ -13,17 +13,30 @@ module Homebrew # * `https://github.com/example/example/archive/v1.2.3.tar.gz` # * `https://github.com/downloads/example/example/example-1.2.3.tar.gz` # - # This strategy is used when latest releases are marked for software hosted - # on GitHub. It is necessary to use `strategy :github_latest` in a `livecheck` - # block for Livecheck to use this strategy. + # A repository's `/releases/latest` URL normally redirects to a release + # tag (e.g., `/releases/tag/1.2.3`). When there isn't a "latest" release, + # it will redirect to the `/releases` page. # - # The default regex identifies versions from `href` attributes containing the - # tag name. + # This strategy should only be used when we know the upstream repository + # has a "latest" release and the tagged release is appropriate to use + # (e.g., "latest" isn't wrongly pointing to an unstable version, not + # picking up the actual latest version, etc.). The strategy can only be + # applied by using `strategy :github_latest` in a `livecheck` block. + # + # The default regex identifies versions like `1.2.3`/`v1.2.3` in `href` + # attributes containing the tag URL (e.g., + # `/example/example/releases/tag/v1.2.3`). This is a common tag format + # but a modified regex can be provided in a `livecheck` block to override + # the default if a repository uses a different format (e.g., + # `example-1.2.3`, `1.2.3d`, `1.2.3-4`, etc.). # # @api public class GithubLatest NICE_NAME = "GitHub - Latest" + # A priority of zero causes livecheck to skip the strategy. We do this + # for {GithubLatest} so we can selectively apply the strategy using + # `strategy :github_latest` in a `livecheck` block. PRIORITY = 0 # The `Regexp` used to determine if the strategy applies to the URL. @@ -46,12 +59,10 @@ module Homebrew def self.find_versions(url, regex = nil) %r{github\.com/(?:downloads/)?(?[^/]+)/(?[^/]+)}i =~ url.sub(/\.git$/i, "") - # The page containing the latest release + # Example URL: `https://github.com/example/example/releases/latest` page_url = "https://github.com/#{username}/#{repository}/releases/latest" - # The default regex applies to most repositories, but may have to be - # replaced with a specific regex when the tag names contain the package - # name or other characters apart from the version. + # The default regex is the same for all URLs using this strategy regex ||= %r{href=.*?/tag/v?(\d+(?:\.\d+)+)["' >]}i Homebrew::Livecheck::Strategy::PageMatch.find_versions(page_url, regex)