aboutsummaryrefslogtreecommitdiffstats
path: root/lib/bundler/cli/outdated.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/cli/outdated.rb')
-rw-r--r--lib/bundler/cli/outdated.rb255
1 files changed, 255 insertions, 0 deletions
diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb
new file mode 100644
index 0000000000..863d0dd388
--- /dev/null
+++ b/lib/bundler/cli/outdated.rb
@@ -0,0 +1,255 @@
+# frozen_string_literal: true
+require "bundler/cli/common"
+
+module Bundler
+ class CLI::Outdated
+ attr_reader :options, :gems
+
+ def initialize(options, gems)
+ @options = options
+ @gems = gems
+ end
+
+ def run
+ check_for_deployment_mode
+
+ sources = Array(options[:source])
+
+ gems.each do |gem_name|
+ Bundler::CLI::Common.select_spec(gem_name)
+ end
+
+ Bundler.definition.validate_runtime!
+ current_specs = Bundler.ui.silence { Bundler.definition.resolve }
+ current_dependencies = {}
+ Bundler.ui.silence do
+ Bundler.load.dependencies.each do |dep|
+ current_dependencies[dep.name] = dep
+ end
+ end
+
+ definition = if gems.empty? && sources.empty?
+ # We're doing a full update
+ Bundler.definition(true)
+ else
+ Bundler.definition(:gems => gems, :sources => sources)
+ end
+
+ Bundler::CLI::Common.configure_gem_version_promoter(
+ Bundler.definition,
+ options
+ )
+
+ # the patch level options imply strict is also true. It wouldn't make
+ # sense otherwise.
+ strict = options[:strict] ||
+ Bundler::CLI::Common.patch_level_options(options).any?
+
+ filter_options_patch = options.keys &
+ %w(filter-major filter-minor filter-patch)
+
+ definition_resolution = proc do
+ options[:local] ? definition.resolve_with_cache! : definition.resolve_remotely!
+ end
+
+ if options[:parseable]
+ Bundler.ui.silence(&definition_resolution)
+ else
+ definition_resolution.call
+ end
+
+ Bundler.ui.info ""
+ outdated_gems_by_groups = {}
+ outdated_gems_list = []
+
+ # Loop through the current specs
+ gemfile_specs, dependency_specs = current_specs.partition do |spec|
+ current_dependencies.key? spec.name
+ end
+
+ (gemfile_specs + dependency_specs).sort_by(&:name).each do |current_spec|
+ next if !gems.empty? && !gems.include?(current_spec.name)
+
+ dependency = current_dependencies[current_spec.name]
+ active_spec = retrieve_active_spec(strict, definition, current_spec)
+
+ next if active_spec.nil?
+ if filter_options_patch.any?
+ update_present = update_present_via_semver_portions(current_spec, active_spec, options)
+ next unless update_present
+ end
+
+ gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version)
+ next unless gem_outdated || (current_spec.git_version != active_spec.git_version)
+ groups = nil
+ if dependency && !options[:parseable]
+ groups = dependency.groups.join(", ")
+ end
+
+ outdated_gems_list << { :active_spec => active_spec,
+ :current_spec => current_spec,
+ :dependency => dependency,
+ :groups => groups }
+
+ outdated_gems_by_groups[groups] ||= []
+ outdated_gems_by_groups[groups] << { :active_spec => active_spec,
+ :current_spec => current_spec,
+ :dependency => dependency,
+ :groups => groups }
+ end
+
+ if outdated_gems_list.empty?
+ display_nothing_outdated_message(filter_options_patch)
+ else
+ unless options[:parseable]
+ if options[:pre]
+ Bundler.ui.info "Outdated gems included in the bundle (including " \
+ "pre-releases):"
+ else
+ Bundler.ui.info "Outdated gems included in the bundle:"
+ end
+ end
+
+ options_include_groups = [:group, :groups].select do |v|
+ options.keys.include?(v.to_s)
+ end
+
+ if options_include_groups.any?
+ ordered_groups = outdated_gems_by_groups.keys.compact.sort
+ [nil, ordered_groups].flatten.each do |groups|
+ gems = outdated_gems_by_groups[groups]
+ contains_group = if groups
+ groups.split(",").include?(options[:group])
+ else
+ options[:group] == "group"
+ end
+
+ next if (!options[:groups] && !contains_group) || gems.nil?
+
+ unless options[:parseable]
+ if groups
+ Bundler.ui.info "===== Group #{groups} ====="
+ else
+ Bundler.ui.info "===== Without group ====="
+ end
+ end
+
+ gems.each do |gem|
+ print_gem(
+ gem[:current_spec],
+ gem[:active_spec],
+ gem[:dependency],
+ groups,
+ options_include_groups.any?
+ )
+ end
+ end
+ else
+ outdated_gems_list.each do |gem|
+ print_gem(
+ gem[:current_spec],
+ gem[:active_spec],
+ gem[:dependency],
+ gem[:groups],
+ options_include_groups.any?
+ )
+ end
+ end
+
+ exit 1
+ end
+ end
+
+ private
+
+ def retrieve_active_spec(strict, definition, current_spec)
+ if strict
+ active_spec = definition.find_resolved_spec(current_spec)
+ else
+ active_specs = definition.find_indexed_specs(current_spec)
+ if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
+ active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
+ end
+ active_spec = active_specs.last
+ end
+
+ active_spec
+ end
+
+ def display_nothing_outdated_message(filter_options_patch)
+ unless options[:parseable]
+ if filter_options_patch.any?
+ display = filter_options_patch.map do |o|
+ o.sub("filter-", "")
+ end.join(" or ")
+
+ Bundler.ui.info "No #{display} updates to display.\n"
+ else
+ Bundler.ui.info "Bundle up to date!\n"
+ end
+ end
+ end
+
+ def print_gem(current_spec, active_spec, dependency, groups, options_include_groups)
+ spec_version = "#{active_spec.version}#{active_spec.git_version}"
+ spec_version += " (from #{active_spec.loaded_from})" if Bundler.ui.debug? && active_spec.loaded_from
+ current_version = "#{current_spec.version}#{current_spec.git_version}"
+
+ if dependency && dependency.specific?
+ dependency_version = %(, requested #{dependency.requirement})
+ end
+
+ spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, " \
+ "installed #{current_version}#{dependency_version})"
+
+ output_message = if options[:parseable]
+ spec_outdated_info.to_s
+ elsif options_include_groups || !groups
+ " * #{spec_outdated_info}"
+ else
+ " * #{spec_outdated_info} in groups \"#{groups}\""
+ end
+
+ Bundler.ui.info output_message.rstrip
+ end
+
+ def check_for_deployment_mode
+ if Bundler.settings[:frozen]
+ raise ProductionError, "You are trying to check outdated gems in " \
+ "deployment mode. Run `bundle outdated` elsewhere.\n" \
+ "\nIf this is a development machine, remove the " \
+ "#{Bundler.default_gemfile} freeze" \
+ "\nby running `bundle install --no-deployment`."
+ end
+ end
+
+ def update_present_via_semver_portions(current_spec, active_spec, options)
+ current_major = current_spec.version.segments.first
+ active_major = active_spec.version.segments.first
+
+ update_present = false
+ update_present = active_major > current_major if options["filter-major"]
+
+ if !update_present && (options["filter-minor"] || options["filter-patch"]) && current_major == active_major
+ current_minor = get_version_semver_portion_value(current_spec, 1)
+ active_minor = get_version_semver_portion_value(active_spec, 1)
+
+ update_present = active_minor > current_minor if options["filter-minor"]
+
+ if !update_present && options["filter-patch"] && current_minor == active_minor
+ current_patch = get_version_semver_portion_value(current_spec, 2)
+ active_patch = get_version_semver_portion_value(active_spec, 2)
+
+ update_present = active_patch > current_patch
+ end
+ end
+
+ update_present
+ end
+
+ def get_version_semver_portion_value(spec, version_portion_index)
+ version_section = spec.version.segments[version_portion_index, 1]
+ version_section.nil? ? 0 : (version_section.first || 0)
+ end
+ end
+end