From 00903f6b1194b2a4be74d41f9aaa8c8bff0a6495 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 25 Jul 2023 14:04:39 +0100 Subject: [PATCH] DEV: Support version operators in .discourse-compatibility (#22714) This adds support for the `<=` and `<` version operators in `.discourse-compatibility` files. This allows for more flexibility (e.g. targeting the entire 3.1.x stable release via `< 3.2.0.beta1`), and should also make compatibility files to be more readable. If an operator is not specified we default to `<=`, which matches the old behavior. --- lib/version.rb | 42 ++++++++++++++++++++++++----------- spec/lib/version_spec.rb | 48 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/lib/version.rb b/lib/version.rb index ce2101b14de..26db10bb5fa 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -31,7 +31,7 @@ module Discourse # 2.5.0.beta2: bbffee # 2.4.4.beta6: some-other-branch-ref # 2.4.2.beta1: v1-tag - def self.find_compatible_resource(version_list, version = ::Discourse::VERSION::STRING) + def self.find_compatible_resource(version_list, target_version = ::Discourse::VERSION::STRING) return unless version_list.present? begin @@ -41,21 +41,37 @@ module Discourse raise InvalidVersionListError unless version_list.is_a?(Hash) - version_list = version_list.sort_by { |v, pin| Gem::Version.new(v) }.reverse + version_list = + version_list + .transform_keys do |v| + Gem::Requirement.parse(v) + rescue Gem::Requirement::BadRequirementError => e + raise InvalidVersionListError, "Invalid version specifier: #{v}" + end + .sort_by do |parsed_requirement, _| + operator, version = parsed_requirement + [version, operator == "<" ? 0 : 1] + end - # If plugin compat version is listed as less than current Discourse version, take the version/hash listed before. - checkout_version = nil - version_list.each do |core_compat, target| - if Gem::Version.new(core_compat) == Gem::Version.new(version) # Exact version match - return it - checkout_version = target - break - elsif Gem::Version.new(core_compat) < Gem::Version.new(version) # Core is on a higher version than listed, use a later version - break + parsed_target_version = Gem::Version.new(target_version) + + lowest_matching_entry = + version_list.find do |parsed_requirement, target| + req_operator, req_version = parsed_requirement + req_operator = "<=" if req_operator == "=" + + if !%w[<= <].include?(req_operator) + raise InvalidVersionListError, + "Invalid version specifier operator for '#{req_operator} #{req_version}'. Operator must be one of <= or <" + end + + resolved_requirement = Gem::Requirement.new("#{req_operator} #{req_version.to_s}") + resolved_requirement.satisfied_by?(parsed_target_version) end - checkout_version = target - end - return if checkout_version.nil? + return if lowest_matching_entry.nil? + + checkout_version = lowest_matching_entry[1] begin Discourse::Utils.execute_command "git", diff --git a/spec/lib/version_spec.rb b/spec/lib/version_spec.rb index 1b8fd67dcf6..6e54d1315ba 100644 --- a/spec/lib/version_spec.rb +++ b/spec/lib/version_spec.rb @@ -103,6 +103,54 @@ RSpec.describe Discourse::VERSION do YML include_examples "test compatible resource" end + + context "with different version operators" do + let(:version_list) { <<~YML } + <= 3.2.0.beta1: lteBeta1 + 3.2.0.beta2: lteBeta2 + < 3.2.0.beta4: ltBeta4 + <= 3.2.0.beta4: lteBeta4 + YML + + it "supports <= operator" do + expect(Discourse.find_compatible_resource(version_list, "3.2.0.beta1")).to eq("lteBeta1") + expect(Discourse.find_compatible_resource(version_list, "3.2.0.beta0")).to eq("lteBeta1") + end + + it "defaults to <= operator" do + expect(Discourse.find_compatible_resource(version_list, "3.2.0.beta2")).to eq("lteBeta2") + end + + it "supports < operator" do + expect(Discourse.find_compatible_resource(version_list, "3.2.0.beta3")).to eq("ltBeta4") + expect(Discourse.find_compatible_resource(version_list, "3.2.0.beta4")).not_to eq("ltBeta4") + end + + it "prioritises <= over <, regardless of file order" do + expect(Discourse.find_compatible_resource(version_list, "3.2.0.beta3")).to eq("ltBeta4") + expect( + Discourse.find_compatible_resource(version_list.lines.reverse.join("\n"), "3.2.0.beta3"), + ).to eq("ltBeta4") + end + + it "raises error for >= operator" do + expect { Discourse.find_compatible_resource(">= 3.1.0: test", "3.1.0") }.to raise_error( + Discourse::InvalidVersionListError, + ) + end + + it "raises error for ~> operator" do + expect { Discourse.find_compatible_resource("~> 3.1.0: test", "3.1.0") }.to raise_error( + Discourse::InvalidVersionListError, + ) + end + + it "raises error for invalid version" do + expect { Discourse.find_compatible_resource("1.1.: test", "3.1.0") }.to raise_error( + Discourse::InvalidVersionListError, + ) + end + end end describe ".find_compatible_git_resource" do