summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThe Bundler Bot <bot@bundler.io>2018-07-02 04:27:33 +0000
committerColby Swandale <me@colby.fyi>2018-10-09 22:33:21 +1100
commita4f707d29d4536342dba2bd82f79686f1df5cae6 (patch)
treea6c12f70efe9db127e6bc7d7f6d42fdd2063bb6f
parent8f244e7aca8a7e2419018f4b4956edadc33afefe (diff)
downloadbundler-a4f707d29d4536342dba2bd82f79686f1df5cae6.tar.gz
Auto merge of #6513 - agrim123:agr-bundler-remove, r=indirect
Add `bundle remove` Features of the command implemented: - Multiple gems support ```bash $ bundle remove rack rails ``` - Remove any empty block that might occur after removing the gem or otherwise present Things yet to implement: - Add `rm` alias. _Optional_ - [x] Add `--install` flag to remove gems from `.bundle`. - [x] Handling multiple gems on the same line. - [x] Handle gem spec - [x] Handle eval_gemfile cases ([one](https://github.com/bundler/bundler/pull/6513#discussion_r195632603) case left) Closes #6506 (cherry picked from commit 431a3f76868bdb9d4c94be25b28b0c7f26ee9f6e)
-rw-r--r--lib/bundler/cli.rb11
-rw-r--r--lib/bundler/cli/remove.rb18
-rw-r--r--lib/bundler/dependency.rb4
-rw-r--r--lib/bundler/dsl.rb3
-rw-r--r--lib/bundler/injector.rb167
-rw-r--r--lib/bundler/shared_helpers.rb6
-rw-r--r--man/bundle-remove.ronn23
-rw-r--r--spec/commands/remove_spec.rb571
-rw-r--r--spec/support/matchers.rb4
9 files changed, 793 insertions, 14 deletions
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index fda09dd0eb..dd4301959a 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -166,6 +166,17 @@ module Bundler
Check.new(options).run
end
+ desc "remove [GEM [GEM ...]]", "Removes gems from the Gemfile"
+ long_desc <<-D
+ Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If the gem is not found, Bundler prints a error message and if gem could not be removed due to any reason Bundler will display a warning.
+ D
+ method_option "install", :type => :boolean, :banner =>
+ "Runs 'bundle install' after removing the gems from the Gemfile"
+ def remove(*gems)
+ require "bundler/cli/remove"
+ Remove.new(gems, options).run
+ end
+
desc "install [OPTIONS]", "Install the current environment to the system"
long_desc <<-D
Install will install all of the gems in the current bundle, making them available
diff --git a/lib/bundler/cli/remove.rb b/lib/bundler/cli/remove.rb
new file mode 100644
index 0000000000..cd6a2cec28
--- /dev/null
+++ b/lib/bundler/cli/remove.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Remove
+ def initialize(gems, options)
+ @gems = gems
+ @options = options
+ end
+
+ def run
+ raise InvalidOption, "Please specify gems to remove." if @gems.empty?
+
+ Injector.remove(@gems, {})
+
+ Installer.install(Bundler.root, Bundler.definition) if @options["install"]
+ end
+ end
+end
diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb
index 8fd59bd809..8840ad6a9c 100644
--- a/lib/bundler/dependency.rb
+++ b/lib/bundler/dependency.rb
@@ -7,8 +7,7 @@ require "bundler/rubygems_ext"
module Bundler
class Dependency < Gem::Dependency
attr_reader :autorequire
- attr_reader :groups
- attr_reader :platforms
+ attr_reader :groups, :platforms, :gemfile
PLATFORM_MAP = {
:ruby => Gem::Platform::RUBY,
@@ -88,6 +87,7 @@ module Bundler
@platforms = Array(options["platforms"])
@env = options["env"]
@should_include = options.fetch("should_include", true)
+ @gemfile = options["gemfile"]
@autorequire = Array(options["require"] || []) if options.key?("require")
end
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index 8681163277..7a7667b4e6 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -16,7 +16,7 @@ module Bundler
VALID_PLATFORMS = Bundler::Dependency::PLATFORM_MAP.keys.freeze
VALID_KEYS = %w[group groups git path glob name branch ref tag require submodules
- platform platforms type source install_if].freeze
+ platform platforms type source install_if gemfile].freeze
attr_reader :gemspecs
attr_accessor :dependencies
@@ -93,6 +93,7 @@ module Bundler
def gem(name, *args)
options = args.last.is_a?(Hash) ? args.pop.dup : {}
+ options["gemfile"] = @gemfile
version = args || [">= 0"]
normalize_options(name, version, options)
diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb
index fd7db8762b..2497439edd 100644
--- a/lib/bundler/injector.rb
+++ b/lib/bundler/injector.rb
@@ -2,13 +2,18 @@
module Bundler
class Injector
- def self.inject(new_deps, options = {})
- injector = new(new_deps, options)
+ def self.inject(deps, options = {})
+ injector = new(deps, options)
injector.inject(Bundler.default_gemfile, Bundler.default_lockfile)
end
- def initialize(new_deps, options = {})
- @new_deps = new_deps
+ def self.remove(gems, options = {})
+ injector = new(gems, options)
+ injector.remove(Bundler.default_gemfile, Bundler.default_lockfile)
+ end
+
+ def initialize(deps, options = {})
+ @deps = deps
@options = options
end
@@ -28,19 +33,18 @@ module Bundler
builder.eval_gemfile(gemfile_path)
# don't inject any gems that are already in the Gemfile
- @new_deps -= builder.dependencies
+ @deps -= builder.dependencies
# add new deps to the end of the in-memory Gemfile
- # Set conservative versioning to false because
- # we want to let the resolver resolve the version first
- builder.eval_gemfile("injected gems", build_gem_lines(false)) if @new_deps.any?
+ # Set conservative versioning to false because we want to let the resolver resolve the version first
+ builder.eval_gemfile("injected gems", build_gem_lines(false)) if @deps.any?
# resolve to see if the new deps broke anything
@definition = builder.to_definition(lockfile_path, {})
@definition.resolve_remotely!
# since nothing broke, we can add those gems to the gemfile
- append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @new_deps.any?
+ append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @deps.any?
# since we resolved successfully, write out the lockfile
@definition.lock(Bundler.default_lockfile)
@@ -49,7 +53,21 @@ module Bundler
Bundler.reset_paths!
# return an array of the deps that we added
- @new_deps
+ @deps
+ end
+ end
+
+ # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies.
+ # @param [Pathname] lockfile_path The lockfile from which to remove dependencies.
+ # @return [Array]
+ def remove(gemfile_path, lockfile_path)
+ # remove gems from each gemfiles we have
+ Bundler.definition.gemfiles.each do |path|
+ deps = remove_deps(path)
+
+ show_warning("No gems were removed from the gemfile.") if deps.empty?
+
+ deps.each {|dep| Bundler.ui.confirm "#{SharedHelpers.pretty_dependency(dep, false)} was removed." }
end
end
@@ -76,7 +94,7 @@ module Bundler
end
def build_gem_lines(conservative_versioning)
- @new_deps.map do |d|
+ @deps.map do |d|
name = d.name.dump
requirement = if conservative_versioning
@@ -101,5 +119,132 @@ module Bundler
f.puts new_gem_lines
end
end
+
+ # evalutes a gemfile to remove the specified gem
+ # from it.
+ def remove_deps(gemfile_path)
+ initial_gemfile = IO.readlines(gemfile_path)
+
+ Bundler.ui.info "Removing gems from #{gemfile_path}"
+
+ # evaluate the Gemfile we have
+ builder = Dsl.new
+ builder.eval_gemfile(gemfile_path)
+
+ removed_deps = remove_gems_from_dependencies(builder, @deps, gemfile_path)
+
+ # abort the opertion if no gems were removed
+ # no need to operate on gemfile furthur
+ return [] if removed_deps.empty?
+
+ cleaned_gemfile = remove_gems_from_gemfile(@deps, gemfile_path)
+
+ SharedHelpers.write_to_gemfile(gemfile_path, cleaned_gemfile)
+
+ # check for errors
+ # including extra gems being removed
+ # or some gems not being removed
+ # and return the actual removed deps
+ cross_check_for_errors(gemfile_path, builder.dependencies, removed_deps, initial_gemfile)
+ end
+
+ # @param [Dsl] builder Dsl object of current Gemfile.
+ # @param [Array] gems Array of names of gems to be removed.
+ # @param [Pathname] path of the Gemfile
+ # @return [Array] removed_deps Array of removed dependencies.
+ def remove_gems_from_dependencies(builder, gems, gemfile_path)
+ removed_deps = []
+
+ gems.each do |gem_name|
+ deleted_dep = builder.dependencies.find {|d| d.name == gem_name }
+
+ if deleted_dep.nil?
+ raise GemfileError, "`#{gem_name}` is not specified in #{gemfile_path} so it could not be removed."
+ end
+
+ builder.dependencies.delete(deleted_dep)
+
+ removed_deps << deleted_dep
+ end
+
+ removed_deps
+ end
+
+ # @param [Array] gems Array of names of gems to be removed.
+ # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies.
+ def remove_gems_from_gemfile(gems, gemfile_path)
+ patterns = /gem\s+(['"])#{Regexp.union(gems)}\1|gem\s*\((['"])#{Regexp.union(gems)}\2\)/
+
+ # remove lines which match the regex
+ new_gemfile = IO.readlines(gemfile_path).reject {|line| line.match(patterns) }
+
+ # remove lone \n and append them with other strings
+ new_gemfile.each_with_index do |_line, index|
+ if new_gemfile[index + 1] == "\n"
+ new_gemfile[index] += new_gemfile[index + 1]
+ new_gemfile.delete_at(index + 1)
+ end
+ end
+
+ %w[group source env install_if].each {|block| remove_nested_blocks(new_gemfile, block) }
+
+ new_gemfile.join.chomp
+ end
+
+ # @param [Array] gemfile Array of gemfile contents.
+ # @param [String] block_name Name of block name to look for.
+ def remove_nested_blocks(gemfile, block_name)
+ nested_blocks = 0
+
+ # count number of nested blocks
+ gemfile.each_with_index {|line, index| nested_blocks += 1 if !gemfile[index + 1].nil? && gemfile[index + 1].include?(block_name) && line.include?(block_name) }
+
+ while nested_blocks >= 0
+ nested_blocks -= 1
+
+ gemfile.each_with_index do |line, index|
+ next unless !line.nil? && line.include?(block_name)
+ if gemfile[index + 1] =~ /^\s*end\s*$/
+ gemfile[index] = nil
+ gemfile[index + 1] = nil
+ end
+ end
+
+ gemfile.compact!
+ end
+ end
+
+ # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies.
+ # @param [Array] original_deps Array of original dependencies.
+ # @param [Array] removed_deps Array of removed dependencies.
+ # @param [Array] initial_gemfile Contents of original Gemfile before any operation.
+ def cross_check_for_errors(gemfile_path, original_deps, removed_deps, initial_gemfile)
+ # evalute the new gemfile to look for any failure cases
+ builder = Dsl.new
+ builder.eval_gemfile(gemfile_path)
+
+ # record gems which were removed but not requested
+ extra_removed_gems = original_deps - builder.dependencies
+
+ # if some extra gems were removed then raise error
+ # and revert Gemfile to original
+ unless extra_removed_gems.empty?
+ SharedHelpers.write_to_gemfile(gemfile_path, initial_gemfile.join)
+
+ raise InvalidOption, "Gems could not be removed. #{extra_removed_gems.join(", ")} would also have been removed. Bundler cannot continue."
+ end
+
+ # record gems which could not be removed due to some reasons
+ errored_deps = builder.dependencies.select {|d| d.gemfile == gemfile_path } & removed_deps.select {|d| d.gemfile == gemfile_path }
+
+ show_warning "#{errored_deps.map(&:name).join(", ")} could not be removed." unless errored_deps.empty?
+
+ # return actual removed dependencies
+ removed_deps - errored_deps
+ end
+
+ def show_warning(message)
+ Bundler.ui.info Bundler.ui.add_color(message, :yellow)
+ end
end
end
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb
index 505bd0843a..79883b5253 100644
--- a/lib/bundler/shared_helpers.rb
+++ b/lib/bundler/shared_helpers.rb
@@ -197,10 +197,12 @@ module Bundler
def pretty_dependency(dep, print_source = false)
msg = String.new(dep.name)
msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default
+
if dep.is_a?(Bundler::Dependency)
platform_string = dep.platforms.join(", ")
msg << " " << platform_string if !platform_string.empty? && platform_string != Gem::Platform::RUBY
end
+
msg << " from the `#{dep.source}` source" if print_source && dep.source
msg
end
@@ -223,6 +225,10 @@ module Bundler
Digest(name)
end
+ def write_to_gemfile(gemfile_path, contents)
+ filesystem_access(gemfile_path) {|g| File.open(g, "w") {|file| file.puts contents } }
+ end
+
private
def validate_bundle_path
diff --git a/man/bundle-remove.ronn b/man/bundle-remove.ronn
new file mode 100644
index 0000000000..40a239b4a2
--- /dev/null
+++ b/man/bundle-remove.ronn
@@ -0,0 +1,23 @@
+bundle-remove(1) -- Removes gems from the Gemfile
+===========================================================================
+
+## SYNOPSIS
+
+`bundle remove [GEM [GEM ...]] [--install]`
+
+## DESCRIPTION
+
+Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If a gem cannot be removed, a warning is printed. If a gem is already absent from the Gemfile, and error is raised.
+
+## OPTIONS
+
+* `--install`:
+ Runs `bundle install` after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s).
+
+Example:
+
+bundle remove rails
+
+bundle remove rails rack
+
+bundle remove rails rack --install
diff --git a/spec/commands/remove_spec.rb b/spec/commands/remove_spec.rb
new file mode 100644
index 0000000000..37594b1ece
--- /dev/null
+++ b/spec/commands/remove_spec.rb
@@ -0,0 +1,571 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle remove" do
+ context "when no gems are specified" do
+ it "throws error" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ G
+
+ bundle "remove"
+
+ expect(out).to include("Please specify gems to remove.")
+ end
+ end
+
+ context "when --install flag is specified" do
+ it "removes gems from .bundle" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ bundle! "remove rack --install"
+
+ expect(out).to include("rack was removed.")
+ expect(the_bundle).to_not include_gems "rack"
+ end
+ end
+
+ describe "remove single gem from gemfile" do
+ context "when gem is present in gemfile" do
+ it "shows success for removed gem" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when gem is not present in gemfile" do
+ it "shows warning for gem that could not be removed" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile")} so it could not be removed.")
+ end
+ end
+ end
+
+ describe "remove mutiple gems from gemfile" do
+ context "when all gems are present in gemfile" do
+ it "shows success fir all removed gems" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle! "remove rack rails"
+
+ expect(out).to include("rack was removed.")
+ expect(out).to include("rails was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when some gems are not present in the gemfile" do
+ it "shows warning for those not present and success for those that can be removed" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rails"
+ gem "minitest"
+ gem "rspec"
+ G
+
+ bundle "remove rails rack minitest"
+
+ expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile")} so it could not be removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rails"
+ gem "minitest"
+ gem "rspec"
+ G
+ end
+ end
+ end
+
+ context "with inline groups" do
+ it "removes the specified gem" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", :group => [:dev]
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ describe "with group blocks" do
+ context "when single group block with gem to be removed is present" do
+ it "removes the group block" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ gem "rspec"
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when an empty block is also present" do
+ it "removes all empty blocks" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ gem "rspec"
+ end
+
+ group :dev do
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when the gem belongs to mutiple groups" do
+ it "removes the groups" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test, :serioustest do
+ gem "rspec"
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when the gem is present in mutiple groups" do
+ it "removes all empty blocks" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :one do
+ gem "rspec"
+ end
+
+ group :two do
+ gem "rspec"
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+ end
+
+ describe "nested group blocks" do
+ context "when all the groups will be empty after removal" do
+ it "removes the empty nested blocks" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ group :serioustest do
+ gem "rspec"
+ end
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when outer group will not be empty after removal" do
+ it "removes only empty blocks" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ gem "rack-test"
+
+ group :serioustest do
+ gem "rspec"
+ end
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ gem "rack-test"
+
+ end
+ G
+ end
+ end
+
+ context "when inner group will not be empty after removal" do
+ it "removes only empty blocks" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ group :serioustest do
+ gem "rspec"
+ gem "rack-test"
+ end
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ group :serioustest do
+ gem "rack-test"
+ end
+ end
+ G
+ end
+ end
+ end
+
+ describe "arbitrary gemfile" do
+ context "when mutiple gems are present in same line" do
+ it "shows warning for gems not removed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"; gem "rails"
+ G
+
+ bundle "remove rails"
+
+ expect(out).to include("Gems could not be removed. rack (>= 0) would also have been removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"; gem "rails"
+ G
+ end
+ end
+
+ context "when some gems could not be removed" do
+ it "shows warning for gems not removed and success for those removed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem"rack"
+ gem"rspec"
+ gem "rails"
+ gem "minitest"
+ G
+
+ bundle! "remove rails rack rspec minitest"
+
+ expect(out).to include("rails was removed.")
+ expect(out).to include("minitest was removed.")
+ expect(out).to include("rack, rspec could not be removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ gem"rack"
+ gem"rspec"
+ G
+ end
+ end
+ end
+
+ context "with sources" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "rspec"
+ end
+ end
+
+ it "removes gems and empty source blocks" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+
+ source "file://#{gem_repo3}" do
+ gem "rspec"
+ end
+ G
+
+ bundle! "install"
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+ end
+ end
+
+ describe "with eval_gemfile" do
+ context "when gems are present in both gemfiles" do
+ it "removes the gems" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+
+ gem "rack"
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ end
+ end
+
+ context "when gems are present in other gemfile" do
+ it "removes the gems" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ G
+
+ bundle! "remove rack"
+
+ expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"")
+ expect(out).to include("rack was removed.")
+ end
+ end
+
+ context "when gems to be removed are not specified in any of the gemfiles" do
+ it "throws error for the gems not present" do
+ # an empty gemfile
+ # indicating the gem is not present in the gemfile
+ create_file "Gemfile-other", <<-G
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile")} so it could not be removed.")
+ end
+ end
+
+ context "when the gem is present in parent file but not in gemfile specified by eval_gemfile" do
+ it "removes the gem" do
+ create_file "Gemfile-other", <<-G
+ gem "rails"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile-other")} so it could not be removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ G
+ end
+ end
+
+ context "when gems can not be removed from other gemfile" do
+ it "shows error" do
+ create_file "Gemfile-other", <<-G
+ gem "rails"; gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(out).to include("Gems could not be removed. rails (>= 0) would also have been removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ G
+ end
+ end
+
+ context "when gems could not be removed from parent gemfile" do
+ it "shows error" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rails"; gem "rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("Gems could not be removed. rails (>= 0) would also have been removed.")
+ expect(bundled_app("Gemfile-other").read).to include("gem \"rack\"")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rails"; gem "rack"
+ G
+ end
+ end
+
+ context "when gem present in gemfiles but could not be removed from one from one of them" do
+ it "removes gem which can be removed and shows warning for file from which it can not be removed" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem"rack"
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"")
+ end
+ end
+ end
+
+ context "with install_if" do
+ it "removes gems inside blocks and empty blocks" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ install_if(lambda { false }) do
+ gem "rack"
+ end
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "with env" do
+ it "removes gems inside blocks and empty blocks" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ env "BUNDLER_TEST" do
+ gem "rack"
+ end
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "with gemspec" do
+ it "should not remove the gem" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("foo.gemspec", "")
+ s.add_dependency "rack"
+ end
+
+ install_gemfile(<<-G)
+ source "file://#{gem_repo1}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ bundle! "remove foo"
+
+ expect(out).to include("foo could not be removed.")
+ end
+ end
+end
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
index 0244927bdc..8e17be3a02 100644
--- a/spec/support/matchers.rb
+++ b/spec/support/matchers.rb
@@ -238,5 +238,9 @@ module Spec
def lockfile_should_be(expected)
expect(bundled_app("Gemfile.lock")).to read_as(normalize_uri_file(strip_whitespace(expected)))
end
+
+ def gemfile_should_be(expected)
+ expect(bundled_app("Gemfile")).to read_as(strip_whitespace(expected))
+ end
end
end