diff options
Diffstat (limited to 'lib/bundler/cli/outdated.rb')
-rw-r--r-- | lib/bundler/cli/outdated.rb | 255 |
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 |