Merge pull request #18455 from samford/livecheck/move-preprocess-url-into-strategies
livecheck: move `#preprocess_url` into strategies
This commit is contained in:
commit
490a553fec
@ -13,31 +13,6 @@ module Homebrew
|
|||||||
# command. These methods print the requested livecheck information
|
# command. These methods print the requested livecheck information
|
||||||
# for formulae.
|
# for formulae.
|
||||||
module Livecheck
|
module Livecheck
|
||||||
GITEA_INSTANCES = T.let(%w[
|
|
||||||
codeberg.org
|
|
||||||
gitea.com
|
|
||||||
opendev.org
|
|
||||||
tildegit.org
|
|
||||||
].freeze, T::Array[String])
|
|
||||||
private_constant :GITEA_INSTANCES
|
|
||||||
|
|
||||||
GOGS_INSTANCES = T.let(%w[
|
|
||||||
lolg.it
|
|
||||||
].freeze, T::Array[String])
|
|
||||||
private_constant :GOGS_INSTANCES
|
|
||||||
|
|
||||||
STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL = T.let([
|
|
||||||
:extract_plist,
|
|
||||||
:github_latest,
|
|
||||||
:header_match,
|
|
||||||
:json,
|
|
||||||
:page_match,
|
|
||||||
:sparkle,
|
|
||||||
:xml,
|
|
||||||
:yaml,
|
|
||||||
].freeze, T::Array[Symbol])
|
|
||||||
private_constant :STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL
|
|
||||||
|
|
||||||
UNSTABLE_VERSION_KEYWORDS = T.let(%w[
|
UNSTABLE_VERSION_KEYWORDS = T.let(%w[
|
||||||
alpha
|
alpha
|
||||||
beta
|
beta
|
||||||
@ -573,48 +548,6 @@ module Homebrew
|
|||||||
urls.compact.uniq
|
urls.compact.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
# Preprocesses and returns the URL used by livecheck.
|
|
||||||
sig { params(url: String).returns(String) }
|
|
||||||
def self.preprocess_url(url)
|
|
||||||
begin
|
|
||||||
uri = Addressable::URI.parse url
|
|
||||||
rescue Addressable::URI::InvalidURIError
|
|
||||||
return url
|
|
||||||
end
|
|
||||||
|
|
||||||
host = uri.host
|
|
||||||
path = uri.path
|
|
||||||
return url if host.nil? || path.nil?
|
|
||||||
|
|
||||||
host = "github.com" if host == "github.s3.amazonaws.com"
|
|
||||||
path = path.delete_prefix("/").delete_suffix(".git")
|
|
||||||
scheme = uri.scheme
|
|
||||||
|
|
||||||
if host == "github.com"
|
|
||||||
return url if path.match? %r{/releases/latest/?$}
|
|
||||||
|
|
||||||
owner, repo = path.delete_prefix("downloads/").split("/")
|
|
||||||
url = "#{scheme}://#{host}/#{owner}/#{repo}.git"
|
|
||||||
elsif GITEA_INSTANCES.include?(host)
|
|
||||||
return url if path.match? %r{/releases/latest/?$}
|
|
||||||
|
|
||||||
owner, repo = path.split("/")
|
|
||||||
url = "#{scheme}://#{host}/#{owner}/#{repo}.git"
|
|
||||||
elsif GOGS_INSTANCES.include?(host)
|
|
||||||
owner, repo = path.split("/")
|
|
||||||
url = "#{scheme}://#{host}/#{owner}/#{repo}.git"
|
|
||||||
# sourcehut
|
|
||||||
elsif host == "git.sr.ht"
|
|
||||||
owner, repo = path.split("/")
|
|
||||||
url = "#{scheme}://#{host}/#{owner}/#{repo}"
|
|
||||||
# GitLab (gitlab.com or self-hosted)
|
|
||||||
elsif path.include?("/-/archive/")
|
|
||||||
url = url.sub(%r{/-/archive/.*$}i, ".git")
|
|
||||||
end
|
|
||||||
|
|
||||||
url
|
|
||||||
end
|
|
||||||
|
|
||||||
# livecheck should fetch a URL using brewed curl if the formula/cask
|
# livecheck should fetch a URL using brewed curl if the formula/cask
|
||||||
# contains a `stable`/`url` or `head` URL `using: :homebrew_curl` that
|
# contains a `stable`/`url` or `head` URL `using: :homebrew_curl` that
|
||||||
# shares the same root domain.
|
# shares the same root domain.
|
||||||
@ -705,12 +638,7 @@ module Homebrew
|
|||||||
|
|
||||||
checked_urls = []
|
checked_urls = []
|
||||||
urls.each_with_index do |original_url, i|
|
urls.each_with_index do |original_url, i|
|
||||||
# Only preprocess the URL when it's appropriate
|
url = original_url
|
||||||
url = if STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL.include?(livecheck_strategy)
|
|
||||||
original_url
|
|
||||||
else
|
|
||||||
preprocess_url(original_url)
|
|
||||||
end
|
|
||||||
next if checked_urls.include?(url)
|
next if checked_urls.include?(url)
|
||||||
|
|
||||||
strategies = Strategy.from_url(
|
strategies = Strategy.from_url(
|
||||||
@ -722,6 +650,11 @@ module Homebrew
|
|||||||
strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first
|
strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first
|
||||||
strategy_name = livecheck_strategy_names[strategy]
|
strategy_name = livecheck_strategy_names[strategy]
|
||||||
|
|
||||||
|
if strategy.respond_to?(:preprocess_url)
|
||||||
|
url = strategy.preprocess_url(url)
|
||||||
|
next if checked_urls.include?(url)
|
||||||
|
end
|
||||||
|
|
||||||
if debug
|
if debug
|
||||||
puts
|
puts
|
||||||
if livecheck_url.is_a?(Symbol)
|
if livecheck_url.is_a?(Symbol)
|
||||||
@ -921,10 +854,6 @@ module Homebrew
|
|||||||
checked_urls = []
|
checked_urls = []
|
||||||
urls.each_with_index do |original_url, i|
|
urls.each_with_index do |original_url, i|
|
||||||
url = original_url.gsub(Constants::LATEST_VERSION, formula_latest)
|
url = original_url.gsub(Constants::LATEST_VERSION, formula_latest)
|
||||||
|
|
||||||
# Only preprocess the URL when it's appropriate
|
|
||||||
url = preprocess_url(url) unless STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL.include?(livecheck_strategy)
|
|
||||||
|
|
||||||
next if checked_urls.include?(url)
|
next if checked_urls.include?(url)
|
||||||
|
|
||||||
strategies = Strategy.from_url(
|
strategies = Strategy.from_url(
|
||||||
@ -936,6 +865,11 @@ module Homebrew
|
|||||||
strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first
|
strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first
|
||||||
strategy_name = livecheck_strategy_names[strategy]
|
strategy_name = livecheck_strategy_names[strategy]
|
||||||
|
|
||||||
|
if strategy.respond_to?(:preprocess_url)
|
||||||
|
url = strategy.preprocess_url(url)
|
||||||
|
next if checked_urls.include?(url)
|
||||||
|
end
|
||||||
|
|
||||||
if debug
|
if debug
|
||||||
puts
|
puts
|
||||||
if livecheck_url.is_a?(Symbol)
|
if livecheck_url.is_a?(Symbol)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# typed: strict
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "addressable"
|
||||||
require "system_command"
|
require "system_command"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
@ -26,6 +27,9 @@ module Homebrew
|
|||||||
class Git
|
class Git
|
||||||
extend SystemCommand::Mixin
|
extend SystemCommand::Mixin
|
||||||
|
|
||||||
|
# Used to cache processed URLs, to avoid duplicating effort.
|
||||||
|
@processed_urls = T.let({}, T::Hash[String, String])
|
||||||
|
|
||||||
# The priority of the strategy on an informal scale of 1 to 10 (from
|
# The priority of the strategy on an informal scale of 1 to 10 (from
|
||||||
# lowest to highest).
|
# lowest to highest).
|
||||||
PRIORITY = 8
|
PRIORITY = 8
|
||||||
@ -34,12 +38,75 @@ module Homebrew
|
|||||||
# regex isn't provided.
|
# regex isn't provided.
|
||||||
DEFAULT_REGEX = /\D*(.+)/
|
DEFAULT_REGEX = /\D*(.+)/
|
||||||
|
|
||||||
|
GITEA_INSTANCES = T.let(%w[
|
||||||
|
codeberg.org
|
||||||
|
gitea.com
|
||||||
|
opendev.org
|
||||||
|
tildegit.org
|
||||||
|
].freeze, T::Array[String])
|
||||||
|
private_constant :GITEA_INSTANCES
|
||||||
|
|
||||||
|
GOGS_INSTANCES = T.let(%w[
|
||||||
|
lolg.it
|
||||||
|
].freeze, T::Array[String])
|
||||||
|
private_constant :GOGS_INSTANCES
|
||||||
|
|
||||||
|
# Processes and returns the URL used by livecheck.
|
||||||
|
sig { params(url: String).returns(String) }
|
||||||
|
def self.preprocess_url(url)
|
||||||
|
processed_url = @processed_urls[url]
|
||||||
|
return processed_url if processed_url
|
||||||
|
|
||||||
|
begin
|
||||||
|
uri = Addressable::URI.parse url
|
||||||
|
rescue Addressable::URI::InvalidURIError
|
||||||
|
return url
|
||||||
|
end
|
||||||
|
|
||||||
|
host = uri.host
|
||||||
|
path = uri.path
|
||||||
|
return url if host.nil? || path.nil?
|
||||||
|
|
||||||
|
host = "github.com" if host == "github.s3.amazonaws.com"
|
||||||
|
path = path.delete_prefix("/").delete_suffix(".git")
|
||||||
|
scheme = uri.scheme
|
||||||
|
|
||||||
|
if host == "github.com"
|
||||||
|
return url if path.match? %r{/releases/latest/?$}
|
||||||
|
|
||||||
|
owner, repo = path.delete_prefix("downloads/").split("/")
|
||||||
|
processed_url = "#{scheme}://#{host}/#{owner}/#{repo}.git"
|
||||||
|
elsif GITEA_INSTANCES.include?(host)
|
||||||
|
return url if path.match? %r{/releases/latest/?$}
|
||||||
|
|
||||||
|
owner, repo = path.split("/")
|
||||||
|
processed_url = "#{scheme}://#{host}/#{owner}/#{repo}.git"
|
||||||
|
elsif GOGS_INSTANCES.include?(host)
|
||||||
|
owner, repo = path.split("/")
|
||||||
|
processed_url = "#{scheme}://#{host}/#{owner}/#{repo}.git"
|
||||||
|
# sourcehut
|
||||||
|
elsif host == "git.sr.ht"
|
||||||
|
owner, repo = path.split("/")
|
||||||
|
processed_url = "#{scheme}://#{host}/#{owner}/#{repo}"
|
||||||
|
# GitLab (gitlab.com or self-hosted)
|
||||||
|
elsif path.include?("/-/archive/")
|
||||||
|
processed_url = url.sub(%r{/-/archive/.*$}i, ".git")
|
||||||
|
end
|
||||||
|
|
||||||
|
if processed_url && (processed_url != url)
|
||||||
|
@processed_urls[url] = processed_url
|
||||||
|
else
|
||||||
|
url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Whether the strategy can be applied to the provided URL.
|
# Whether the strategy can be applied to the provided URL.
|
||||||
#
|
#
|
||||||
# @param url [String] the URL to match against
|
# @param url [String] the URL to match against
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
sig { params(url: String).returns(T::Boolean) }
|
sig { params(url: String).returns(T::Boolean) }
|
||||||
def self.match?(url)
|
def self.match?(url)
|
||||||
|
url = preprocess_url(url)
|
||||||
(DownloadStrategyDetector.detect(url) <= GitDownloadStrategy) == true
|
(DownloadStrategyDetector.detect(url) <= GitDownloadStrategy) == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -256,89 +256,4 @@ RSpec.describe Homebrew::Livecheck do
|
|||||||
expect(livecheck.use_homebrew_curl?(f_homebrew_curl, "test")).to be(false)
|
expect(livecheck.use_homebrew_curl?(f_homebrew_curl, "test")).to be(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "::preprocess_url" do
|
|
||||||
let(:github_git_url_with_extension) { "https://github.com/Homebrew/brew.git" }
|
|
||||||
|
|
||||||
it "returns the unmodified URL for an unparsable URL" do
|
|
||||||
# Modeled after the `head` URL in the `ncp` formula
|
|
||||||
expect(livecheck.preprocess_url(":something:cvs:@cvs.brew.sh:/cvs"))
|
|
||||||
.to eq(":something:cvs:@cvs.brew.sh:/cvs")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the unmodified URL for a GitHub URL ending in .git" do
|
|
||||||
expect(livecheck.preprocess_url(github_git_url_with_extension))
|
|
||||||
.to eq(github_git_url_with_extension)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a GitHub URL not ending in .git" do
|
|
||||||
expect(livecheck.preprocess_url("https://github.com/Homebrew/brew"))
|
|
||||||
.to eq(github_git_url_with_extension)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the unmodified URL for a GitHub /releases/latest URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://github.com/Homebrew/brew/releases/latest"))
|
|
||||||
.to eq("https://github.com/Homebrew/brew/releases/latest")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a GitHub AWS URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://github.s3.amazonaws.com/downloads/Homebrew/brew/1.0.0.tar.gz"))
|
|
||||||
.to eq(github_git_url_with_extension)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a github.com/downloads/... URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://github.com/downloads/Homebrew/brew/1.0.0.tar.gz"))
|
|
||||||
.to eq(github_git_url_with_extension)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a GitHub tag archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://github.com/Homebrew/brew/archive/1.0.0.tar.gz"))
|
|
||||||
.to eq(github_git_url_with_extension)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a GitHub release archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://github.com/Homebrew/brew/releases/download/1.0.0/brew-1.0.0.tar.gz"))
|
|
||||||
.to eq(github_git_url_with_extension)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a gitlab.com archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://gitlab.com/Homebrew/brew/-/archive/1.0.0/brew-1.0.0.tar.gz"))
|
|
||||||
.to eq("https://gitlab.com/Homebrew/brew.git")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a self-hosted GitLab archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://brew.sh/Homebrew/brew/-/archive/1.0.0/brew-1.0.0.tar.gz"))
|
|
||||||
.to eq("https://brew.sh/Homebrew/brew.git")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a Codeberg archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://codeberg.org/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
|
||||||
.to eq("https://codeberg.org/Homebrew/brew.git")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a Gitea archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://gitea.com/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
|
||||||
.to eq("https://gitea.com/Homebrew/brew.git")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for an Opendev archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://opendev.org/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
|
||||||
.to eq("https://opendev.org/Homebrew/brew.git")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a tildegit archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://tildegit.org/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
|
||||||
.to eq("https://tildegit.org/Homebrew/brew.git")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a LOL Git archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://lolg.it/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
|
||||||
.to eq("https://lolg.it/Homebrew/brew.git")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the Git repository URL for a sourcehut archive URL" do
|
|
||||||
expect(livecheck.preprocess_url("https://git.sr.ht/~Homebrew/brew/archive/1.0.0.tar.gz"))
|
|
||||||
.to eq("https://git.sr.ht/~Homebrew/brew")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -37,6 +37,91 @@ RSpec.describe Homebrew::Livecheck::Strategy::Git do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "::preprocess_url" do
|
||||||
|
let(:github_git_url_with_extension) { "https://github.com/Homebrew/brew.git" }
|
||||||
|
|
||||||
|
it "returns the unmodified URL for an unparsable URL" do
|
||||||
|
# Modeled after the `head` URL in the `ncp` formula
|
||||||
|
expect(git.preprocess_url(":something:cvs:@cvs.brew.sh:/cvs"))
|
||||||
|
.to eq(":something:cvs:@cvs.brew.sh:/cvs")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the unmodified URL for a GitHub URL ending in .git" do
|
||||||
|
expect(git.preprocess_url(github_git_url_with_extension))
|
||||||
|
.to eq(github_git_url_with_extension)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a GitHub URL not ending in .git" do
|
||||||
|
expect(git.preprocess_url("https://github.com/Homebrew/brew"))
|
||||||
|
.to eq(github_git_url_with_extension)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the unmodified URL for a GitHub /releases/latest URL" do
|
||||||
|
expect(git.preprocess_url("https://github.com/Homebrew/brew/releases/latest"))
|
||||||
|
.to eq("https://github.com/Homebrew/brew/releases/latest")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a GitHub AWS URL" do
|
||||||
|
expect(git.preprocess_url("https://github.s3.amazonaws.com/downloads/Homebrew/brew/1.0.0.tar.gz"))
|
||||||
|
.to eq(github_git_url_with_extension)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a github.com/downloads/... URL" do
|
||||||
|
expect(git.preprocess_url("https://github.com/downloads/Homebrew/brew/1.0.0.tar.gz"))
|
||||||
|
.to eq(github_git_url_with_extension)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a GitHub tag archive URL" do
|
||||||
|
expect(git.preprocess_url("https://github.com/Homebrew/brew/archive/1.0.0.tar.gz"))
|
||||||
|
.to eq(github_git_url_with_extension)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a GitHub release archive URL" do
|
||||||
|
expect(git.preprocess_url("https://github.com/Homebrew/brew/releases/download/1.0.0/brew-1.0.0.tar.gz"))
|
||||||
|
.to eq(github_git_url_with_extension)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a gitlab.com archive URL" do
|
||||||
|
expect(git.preprocess_url("https://gitlab.com/Homebrew/brew/-/archive/1.0.0/brew-1.0.0.tar.gz"))
|
||||||
|
.to eq("https://gitlab.com/Homebrew/brew.git")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a self-hosted GitLab archive URL" do
|
||||||
|
expect(git.preprocess_url("https://brew.sh/Homebrew/brew/-/archive/1.0.0/brew-1.0.0.tar.gz"))
|
||||||
|
.to eq("https://brew.sh/Homebrew/brew.git")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a Codeberg archive URL" do
|
||||||
|
expect(git.preprocess_url("https://codeberg.org/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
||||||
|
.to eq("https://codeberg.org/Homebrew/brew.git")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a Gitea archive URL" do
|
||||||
|
expect(git.preprocess_url("https://gitea.com/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
||||||
|
.to eq("https://gitea.com/Homebrew/brew.git")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for an Opendev archive URL" do
|
||||||
|
expect(git.preprocess_url("https://opendev.org/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
||||||
|
.to eq("https://opendev.org/Homebrew/brew.git")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a tildegit archive URL" do
|
||||||
|
expect(git.preprocess_url("https://tildegit.org/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
||||||
|
.to eq("https://tildegit.org/Homebrew/brew.git")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a LOL Git archive URL" do
|
||||||
|
expect(git.preprocess_url("https://lolg.it/Homebrew/brew/archive/brew-1.0.0.tar.gz"))
|
||||||
|
.to eq("https://lolg.it/Homebrew/brew.git")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the Git repository URL for a sourcehut archive URL" do
|
||||||
|
expect(git.preprocess_url("https://git.sr.ht/~Homebrew/brew/archive/1.0.0.tar.gz"))
|
||||||
|
.to eq("https://git.sr.ht/~Homebrew/brew")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "::match?" do
|
describe "::match?" do
|
||||||
it "returns true for a Git repository URL" do
|
it "returns true for a Git repository URL" do
|
||||||
expect(git.match?(git_url)).to be true
|
expect(git.match?(git_url)).to be true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user