Support plugin and Theme compatibility version manifests (#9995)

Adds a new rake task `plugin:checkout_compatible_all` and
`plugin:checkout_compatible[plugin-name]` that check out compatible plugin
versions.

Supports a .discourse-compatibility file in the root of plugins and themes that
list out a plugin's compatibility with certain discourse versions:

eg: .discourse-compatibility
```
2.5.0.beta6: some-git-hash
2.4.4.beta4: some-git-tag
2.2.0: git-reference
```

This ensures older Discourse installs are able to find and install older
versions of plugins without intervention, through the manifest only.

It iterates through the versions in descending order. If the current Discourse
version matches an item in the manifest, it checks out the listed plugin target.
If the Discourse version is greater than an item in the manifest, it checks out
the next highest version listed in the manifest.

If no versions match, it makes no change.
This commit is contained in:
Jeff Wong 2020-07-06 11:48:00 -10:00
parent c33847b30d
commit 271d6319ce
4 changed files with 140 additions and 0 deletions

View File

@ -88,6 +88,45 @@ task 'plugin:update', :plugin do |t, args|
abort('Unable to pull latest version of plugin') unless update_status
end
desc 'pull compatible plugin versions for all plugins'
task 'plugin:pull_compatible_all' do |t|
# Loop through each directory
plugins = Dir.glob(File.expand_path('plugins/*')).select { |f| File.directory? f }
# run plugin:pull_compatible
plugins.each do |plugin|
next unless File.directory?(plugin + "/.git")
Rake::Task['plugin:pull_compatible'].invoke(plugin)
Rake::Task['plugin:pull_compatible'].reenable
end
end
desc 'pull a compatible plugin version'
task 'plugin:pull_compatible', :plugin do |t, args|
plugin = ENV['PLUGIN'] || ENV['plugin'] || args[:plugin]
plugin_path = plugin
plugin = File.basename(plugin)
unless File.directory?(plugin_path)
if File.directory?('plugins/' + plugin)
plugin_path = File.expand_path('plugins/' + plugin)
else
abort('Plugin ' + plugin + ' not found')
end
end
checkout_version = Discourse.find_compatible_git_resource(plugin_path)
# Checkout value of the version compat
if checkout_version
puts "checking out compatible #{plugin} version: #{checkout_version}"
update_status = system("git -C '#{plugin_path}' reset --hard #{checkout_version}")
abort('Unable to checkout a compatible plugin version') unless update_status
else
puts "#{plugin} is already at latest compatible version"
end
end
desc 'install all plugin gems'
task 'plugin:install_all_gems' do |t|
plugins = Dir.glob(File.expand_path('plugins/*')).select { |f| File.directory? f }

View File

@ -23,6 +23,12 @@ class ThemeStore::GitImporter
else
import_public!
end
if version = Discourse.find_compatible_git_resource(@temp_folder)
Discourse::Utils.execute_command(chdir: @temp_folder) do |runner|
Rails.logger.warn "git reset --hard #{version}"
return runner.exec("git", "reset", "--hard", version)
end
end
end
def diff_local_changes(remote_theme_id)

View File

@ -3,6 +3,8 @@
module Discourse
VERSION_REGEXP = /\A\d+\.\d+\.\d+(\.beta\d+)?\z/ unless defined? ::Discourse::VERSION_REGEXP
VERSION_COMPATIBILITY_FILENAME = ".discourse-compatibility"
# work around reloader
unless defined? ::Discourse::VERSION
module VERSION #:nodoc:
@ -18,4 +20,40 @@ module Discourse
def self.has_needed_version?(current, needed)
Gem::Version.new(current) >= Gem::Version.new(needed)
end
# lookup an external resource (theme/plugin)'s best compatible version
# compatible resource files are YAML, in the format:
# `discourse_version: plugin/theme git reference.` For example:
# 2.5.0.beta6: c4a6c17
# 2.5.0.beta4: d1d2d3f
# 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)
return unless version_list
version_list = YAML.load(version_list).sort_by { |version, pin| Gem::Version.new(version) }.reverse
# 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(::Discourse::VERSION::STRING) # Exact version match - return it
checkout_version = target
break
elsif Gem::Version.new(core_compat) < Gem::Version.new(::Discourse::VERSION::STRING) # Core is on a higher version than listed, use a later version
break
end
checkout_version = target
end
checkout_version
end
# Find a compatible resource from a git repo
def self.find_compatible_git_resource(path)
return unless File.directory?("#{path}/.git")
compat_resource, std_error, s = Open3.capture3("git -C '#{path}' show HEAD@{upstream}:#{Discourse::VERSION_COMPATIBILITY_FILENAME}")
Discourse.find_compatible_resource(compat_resource) if s.success?
end
end

View File

@ -46,4 +46,61 @@ describe Discourse::VERSION do
end
end
context "compatible_resource" do
after do
# Cleanup versions
::Discourse::VERSION::STRING = [::Discourse::VERSION::MAJOR, ::Discourse::VERSION::MINOR, ::Discourse::VERSION::TINY, ::Discourse::VERSION::PRE].compact.join('.')
end
shared_examples "test compatible resource" do
it "returns nil when the current version is above all pinned versions" do
::Discourse::VERSION::STRING = "2.6.0"
expect(Discourse.find_compatible_resource(version_list)).to be_nil
end
it "returns the correct version if matches exactly" do
::Discourse::VERSION::STRING = "2.5.0.beta4"
expect(Discourse.find_compatible_resource(version_list)).to eq("twofivebetafour")
end
it "returns the closest matching version" do
::Discourse::VERSION::STRING = "2.4.6.beta12"
expect(Discourse.find_compatible_resource(version_list)).to eq("twofivebetatwo")
end
it "returns the lowest version possible when using an older version" do
::Discourse::VERSION::STRING = "1.4.6.beta12"
expect(Discourse.find_compatible_resource(version_list)).to eq("twofourtwobetaone")
end
end
it "returns nil when nil" do
expect(Discourse.find_compatible_resource(nil)).to be_nil
end
context "with a regular compatible list" do
let(:version_list) { <<~VERSION_LIST
2.5.0.beta6: twofivebetasix
2.5.0.beta4: twofivebetafour
2.5.0.beta2: twofivebetatwo
2.4.4.beta6: twofourfourbetasix
2.4.2.beta1: twofourtwobetaone
VERSION_LIST
}
include_examples "test compatible resource"
end
context "handle a compatible resource out of order" do
let(:version_list) { <<~VERSION_LIST
2.4.2.beta1: twofourtwobetaone
2.5.0.beta4: twofivebetafour
2.5.0.beta6: twofivebetasix
2.5.0.beta2: twofivebetatwo
2.4.4.beta6: twofourfourbetasix
VERSION_LIST
}
include_examples "test compatible resource"
end
end
end