diff options
author | chrismo <chrismo@clabs.org> | 2016-09-13 19:09:53 -0500 |
---|---|---|
committer | chrismo <chrismo@clabs.org> | 2016-10-21 00:26:27 -0500 |
commit | ad80a9238f4e98e73a9978ca747c4d24fddc7767 (patch) | |
tree | 1ee90f46a5a76424206bb63790479dbf711994cf | |
parent | 7327dc59476c79547170d3ef8162f24b3aea385f (diff) | |
download | bundler-ad80a9238f4e98e73a9978ca747c4d24fddc7767.tar.gz |
Add bundle install conservative updating to update
In the discussion on new 1.13 Conservative Bundle Updates (see
https://github.com/bundler/bundler-features/issues/122), some users
would like to have the same Conservative Updating (see
http://bundler.io/v1.12/man/bundle-install.1.html#CONSERVATIVE-UPDATING)
behavior available in `bundle install` when a declared dependency is
altered in the Gemfile, also available in the `bundle update` command.
This adds a new option called `--conservative` to both `bundle update`
and `bundle lock`. The option only applies on `bundle lock` if the
`--update` option is in use.
The internal flag is more descriptive as to what actually takes place:
It locks any shared dependencies from the gem(s) being updated.
This also promotes the previously added patch level options to being
shown in command-line help, anticipating a 1.14 release, to make these
public and documented.
-rw-r--r-- | lib/bundler/cli.rb | 28 | ||||
-rw-r--r-- | lib/bundler/cli/lock.rb | 2 | ||||
-rw-r--r-- | lib/bundler/cli/update.rb | 3 | ||||
-rw-r--r-- | lib/bundler/definition.rb | 7 | ||||
-rw-r--r-- | spec/bundler/definition_spec.rb | 54 | ||||
-rw-r--r-- | spec/commands/update_spec.rb | 186 |
6 files changed, 189 insertions, 91 deletions
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index f92ecb1c33..6ee9adf431 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -215,14 +215,16 @@ module Bundler "Update ruby specified in Gemfile.lock" method_option "bundler", :type => :string, :lazy_default => "> 0.a", :banner => "Update the locked version of bundler" - method_option "patch", :type => :boolean, :hide => true, :banner => + method_option "patch", :type => :boolean, :banner => "Prefer updating only to next patch version" - method_option "minor", :type => :boolean, :hide => true, :banner => + method_option "minor", :type => :boolean, :banner => "Prefer updating only to next minor version" - method_option "major", :type => :boolean, :hide => true, :banner => + method_option "major", :type => :boolean, :banner => "Prefer updating to next major version (default)" - method_option "strict", :type => :boolean, :hide => true, :banner => + method_option "strict", :type => :boolean, :banner => "Do not allow any gem to be updated past latest --patch/--minor/--major" + method_option "conservative", :type => :boolean, :banner => + "Use bundle install conservative update behavior and do not allowed shared dependencies to be updated." def update(*gems) require "bundler/cli/update" Update.new(options, gems).run @@ -459,14 +461,16 @@ module Bundler "add a new platform to the lockfile" method_option "remove-platform", :type => :array, :default => [], :banner => "remove a platform from the lockfile" - method_option "patch", :type => :boolean, :hide => true, :banner => - "Prefer updating only to next patch version" - method_option "minor", :type => :boolean, :hide => true, :banner => - "Prefer updating only to next minor version" - method_option "major", :type => :boolean, :hide => true, :banner => - "Prefer updating to next major version (default)" - method_option "strict", :type => :boolean, :hide => true, :banner => - "Do not allow any gem to be updated past latest --patch/--minor/--major" + method_option "patch", :type => :boolean, :banner => + "If updating, prefer updating only to next patch version" + method_option "minor", :type => :boolean, :banner => + "If updating, prefer updating only to next minor version" + method_option "major", :type => :boolean, :banner => + "If updating, prefer updating to next major version (default)" + method_option "strict", :type => :boolean, :banner => + "If updating, do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "conservative", :type => :boolean, :banner => + "If updating, use bundle install conservative update behavior and do not allowed shared dependencies to be updated." def lock require "bundler/cli/lock" Lock.new(options).run diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index eb47c9efb0..225a07aa97 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -22,7 +22,7 @@ module Bundler Bundler::Fetcher.disable_endpoint = options["full-index"] update = options[:update] - update = { :gems => update } if update.is_a?(Array) + update = { :gems => update, :lock_shared_dependencies => options[:conservative] } if update.is_a?(Array) definition = Bundler.definition(update) Bundler::CLI::Common.config_gem_version_promoter(Bundler.definition, options) if options[:update] diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 51de98bf34..585fe38e17 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -37,7 +37,8 @@ module Bundler gems.concat(specs.map(&:name)) end - Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby]) + Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby], + :lock_shared_dependencies => options[:conservative]) end Bundler::CLI::Common.config_gem_version_promoter(Bundler.definition, options) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 8a6bf0d17c..8141e6e54e 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -105,8 +105,11 @@ module Bundler add_current_platform unless Bundler.settings[:frozen] @path_changes = converge_paths - eager_unlock = expand_dependencies(@unlock[:gems]) - @unlock[:gems] = @locked_specs.for(eager_unlock).map(&:name) + + unless @unlock[:lock_shared_dependencies] + eager_unlock = expand_dependencies(@unlock[:gems]) + @unlock[:gems] = @locked_specs.for(eager_unlock).map(&:name) + end @gem_version_promoter = create_gem_version_promoter diff --git a/spec/bundler/definition_spec.rb b/spec/bundler/definition_spec.rb index 71e5bb00aa..470b4437f5 100644 --- a/spec/bundler/definition_spec.rb +++ b/spec/bundler/definition_spec.rb @@ -159,23 +159,8 @@ describe Bundler::Definition do end end - context "shared dependent gems" do + context "eager unlock" do before do - build_repo4 do - build_gem "isolated_owner", %w(1.0.1 1.0.2) do |s| - s.add_dependency "isolated_dep", "~> 2.0" - end - build_gem "isolated_dep", %w(2.0.1 2.0.2) - - build_gem "shared_owner_a", %w(3.0.1 3.0.2) do |s| - s.add_dependency "shared_dep", "~> 5.0" - end - build_gem "shared_owner_b", %w(4.0.1 4.0.2) do |s| - s.add_dependency "shared_dep", "~> 5.0" - end - build_gem "shared_dep", %w(5.0.1 5.0.2) - end - gemfile <<-G source "file://#{gem_repo4}" gem 'isolated_owner' @@ -210,19 +195,34 @@ describe Bundler::Definition do L end - it "should unlock isolated and shared dependencies equally" do - # setup for these test costs about 3/4 of a second, much faster to just jam them all in here. - # the global before :each defeats any ability to have re-usable setup for many examples in a - # single context by wiping out the tmp dir and contents. - - unlock_deps_test(%w(isolated_owner), %w(isolated_dep isolated_owner)) - unlock_deps_test(%w(isolated_owner shared_owner_a), %w(isolated_dep isolated_owner shared_dep shared_owner_a)) + it "should not eagerly unlock shared dependency with bundle install conservative updating behavior" do + updated_deps_in_gemfile = [Bundler::Dependency.new("isolated_owner", ">= 0"), + Bundler::Dependency.new("shared_owner_a", "3.0.2"), + Bundler::Dependency.new("shared_owner_b", ">= 0")] + unlock_hash_for_bundle_install = {} + definition = Bundler::Definition.new( + bundled_app("Gemfile.lock"), + updated_deps_in_gemfile, + Bundler::SourceList.new, + unlock_hash_for_bundle_install + ) + locked = definition.send(:converge_locked_specs).map(&:name) + expect(locked.include?("shared_dep")).to be_truthy end - def unlock_deps_test(passed_unlocked, expected_calculated) - definition = Bundler::Definition.new(bundled_app("Gemfile.lock"), [], Bundler::SourceList.new, :gems => passed_unlocked) - unlock_gems = definition.gem_version_promoter.unlock_gems - expect(unlock_gems.sort).to eq expected_calculated + it "should not eagerly unlock shared dependency with bundle update conservative updating behavior" do + updated_deps_in_gemfile = [Bundler::Dependency.new("isolated_owner", ">= 0"), + Bundler::Dependency.new("shared_owner_a", ">= 0"), + Bundler::Dependency.new("shared_owner_b", ">= 0")] + definition = Bundler::Definition.new( + bundled_app("Gemfile.lock"), + updated_deps_in_gemfile, + Bundler::SourceList.new, + :gems => ["shared_owner_a"], :lock_shared_dependencies => true + ) + locked = definition.send(:converge_locked_specs).map(&:name) + expect(locked).to eq %w(isolated_dep isolated_owner shared_dep shared_owner_b) + expect(locked.include?("shared_dep")).to be_truthy end end diff --git a/spec/commands/update_spec.rb b/spec/commands/update_spec.rb index 8a9867d1e9..f5c155e73a 100644 --- a/spec/commands/update_spec.rb +++ b/spec/commands/update_spec.rb @@ -481,81 +481,171 @@ end # these specs are slow and focus on integration and therefore are not exhaustive. unit specs elsewhere handle that. describe "bundle update conservative" do - before do - build_repo4 do - build_gem "foo", %w(1.4.3 1.4.4) do |s| - s.add_dependency "bar", "~> 2.0" + context "patch and minor options" do + before do + build_repo4 do + build_gem "foo", %w(1.4.3 1.4.4) do |s| + s.add_dependency "bar", "~> 2.0" + end + build_gem "foo", %w(1.4.5 1.5.0) do |s| + s.add_dependency "bar", "~> 2.1" + end + build_gem "foo", %w(1.5.1) do |s| + s.add_dependency "bar", "~> 3.0" + end + build_gem "bar", %w(2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0) + build_gem "qux", %w(1.0.0 1.0.1 1.1.0 2.0.0) end - build_gem "foo", %w(1.4.5 1.5.0) do |s| - s.add_dependency "bar", "~> 2.1" + + # establish a lockfile set to 1.4.3 + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo', '1.4.3' + gem 'bar', '2.0.3' + gem 'qux', '1.0.0' + G + + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo' + gem 'qux' + G + end + + context "patch preferred" do + it "single gem updates dependent gem to minor" do + bundle "update --patch foo" + + expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.0" end - build_gem "foo", %w(1.5.1) do |s| - s.add_dependency "bar", "~> 3.0" + + it "update all" do + bundle "update --patch" + + expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1" end - build_gem "bar", %w(2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0) - build_gem "qux", %w(1.0.0 1.0.1 1.1.0 2.0.0) + + it "warns on minor or major increment elsewhere" ## include in prior test end - # establish a lockfile set to 1.4.3 - install_gemfile <<-G - source "file://#{gem_repo4}" - gem 'foo', '1.4.3' - gem 'bar', '2.0.3' - gem 'qux', '1.0.0' - G + context "minor preferred" do + it "single gem updates dependent gem to major" do + bundle "update --minor foo" - # remove 1.4.3 requirement and bar altogether - # to setup update specs below - gemfile <<-G - source "file://#{gem_repo4}" - gem 'foo' - gem 'qux' - G - end + expect(the_bundle).to include_gems "foo 1.5.1", "bar 3.0.0", "qux 1.0.0" + end - context "patch preferred" do - it "single gem updates dependent gem to minor" do - bundle "update --patch foo" + it "warns on major increment elsewhere" ## include in prior test - expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.0" + it "warns when something unlocked doesn't update at all" end - it "update all" do - bundle "update --patch" + context "strict" do + it "patch preferred" do + bundle "update --patch foo bar --strict" - expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1" - end + expect(the_bundle).to include_gems "foo 1.4.4", "bar 2.0.5", "qux 1.0.0" + end + + it "minor preferred" do + bundle "update --minor --strict" - it "warns on minor or major increment elsewhere" ## include in prior test + expect(the_bundle).to include_gems "foo 1.5.0", "bar 2.1.1", "qux 1.1.0" + end + end end - context "minor preferred" do - it "single gem updates dependent gem to major" do - bundle "update --minor foo" + context "eager unlocking" do + before do + build_repo4 do + build_gem "isolated_owner", %w(1.0.1 1.0.2) do |s| + s.add_dependency "isolated_dep", "~> 2.0" + end + build_gem "isolated_dep", %w(2.0.1 2.0.2) - expect(the_bundle).to include_gems "foo 1.5.1", "bar 3.0.0", "qux 1.0.0" + build_gem "shared_owner_a", %w(3.0.1 3.0.2) do |s| + s.add_dependency "shared_dep", "~> 5.0" + end + build_gem "shared_owner_b", %w(4.0.1 4.0.2) do |s| + s.add_dependency "shared_dep", "~> 5.0" + end + build_gem "shared_dep", %w(5.0.1 5.0.2) + end + + gemfile <<-G + source "file://#{gem_repo4}" + gem 'isolated_owner' + + gem 'shared_owner_a' + gem 'shared_owner_b' + G + + lockfile <<-L + GEM + remote: file://#{gem_repo4} + specs: + isolated_dep (2.0.1) + isolated_owner (1.0.1) + isolated_dep (~> 2.0) + shared_dep (5.0.1) + shared_owner_a (3.0.1) + shared_dep (~> 5.0) + shared_owner_b (4.0.1) + shared_dep (~> 5.0) + + PLATFORMS + ruby + + DEPENDENCIES + shared_owner_a + shared_owner_b + isolated_owner + + BUNDLED WITH + 1.13.0 + L end - it "warns on major increment elsewhere" ## include in prior test + it "should eagerly unlock isolated dependency" do + bundle "update isolated_owner" - it "warns when something unlocked doesn't update at all" - end + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.1", "shared_owner_b 4.0.1" + end + + it "should eagerly unlock shared dependency" do + bundle "update shared_owner_a" - context "strict" do - it "patch preferred" do - bundle "update --patch foo bar --strict" + expect(the_bundle).to include_gems "isolated_owner 1.0.1", "isolated_dep 2.0.1", "shared_dep 5.0.2", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" + end + + it "should not eagerly unlock with --conservative" do + bundle "update --conservative shared_owner_a isolated_owner" - expect(the_bundle).to include_gems "foo 1.4.4", "bar 2.0.5", "qux 1.0.0" + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" end - it "minor preferred" do - bundle "update --minor --strict" + it "should match bundle install conservative update behavior when not eagerly unlocking" do + gemfile <<-G + source "file://#{gem_repo4}" + gem 'isolated_owner', '1.0.2' + + gem 'shared_owner_a', '3.0.2' + gem 'shared_owner_b' + G + + bundle "install" - expect(the_bundle).to include_gems "foo 1.5.0", "bar 2.1.1", "qux 1.1.0" + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" end end context "error handling" do + before do + gemfile "" + end + it "raises if too many flags are provided" do bundle "update --patch --minor" |