bundler 1.6.0.pre.1 → 1.6.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bundler might be problematic. Click here for more details.

Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -3
  3. data/Rakefile +12 -7
  4. data/lib/bundler.rb +3 -3
  5. data/lib/bundler/cli.rb +36 -619
  6. data/lib/bundler/cli/binstubs.rb +36 -0
  7. data/lib/bundler/cli/cache.rb +34 -0
  8. data/lib/bundler/cli/check.rb +35 -0
  9. data/lib/bundler/cli/clean.rb +19 -0
  10. data/lib/bundler/cli/common.rb +54 -0
  11. data/lib/bundler/cli/config.rb +84 -0
  12. data/lib/bundler/cli/console.rb +42 -0
  13. data/lib/bundler/cli/exec.rb +37 -0
  14. data/lib/bundler/cli/gem.rb +61 -0
  15. data/lib/bundler/cli/init.rb +33 -0
  16. data/lib/bundler/cli/inject.rb +33 -0
  17. data/lib/bundler/cli/install.rb +123 -0
  18. data/lib/bundler/cli/open.rb +25 -0
  19. data/lib/bundler/cli/outdated.rb +80 -0
  20. data/lib/bundler/cli/package.rb +36 -0
  21. data/lib/bundler/cli/platform.rb +43 -0
  22. data/lib/bundler/cli/show.rb +44 -0
  23. data/lib/bundler/cli/update.rb +73 -0
  24. data/lib/bundler/cli/viz.rb +27 -0
  25. data/lib/bundler/dsl.rb +46 -26
  26. data/lib/bundler/fetcher.rb +50 -4
  27. data/lib/bundler/installer.rb +1 -1
  28. data/lib/bundler/parallel_workers/worker.rb +1 -1
  29. data/lib/bundler/remote_specification.rb +1 -1
  30. data/lib/bundler/resolver.rb +30 -18
  31. data/lib/bundler/source/git.rb +0 -4
  32. data/lib/bundler/source/git/git_proxy.rb +2 -2
  33. data/lib/bundler/source/rubygems.rb +1 -14
  34. data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +1 -1
  35. data/lib/bundler/vendor/thor/base.rb +1 -1
  36. data/lib/bundler/version.rb +1 -1
  37. data/spec/bundler/bundler_spec.rb +46 -47
  38. data/spec/bundler/{cli_rspec.rb → cli_spec.rb} +0 -1
  39. data/spec/bundler/definition_spec.rb +3 -7
  40. data/spec/bundler/dsl_spec.rb +43 -21
  41. data/spec/bundler/gem_helper_spec.rb +152 -123
  42. data/spec/bundler/retry_spec.rb +6 -7
  43. data/spec/bundler/settings_spec.rb +0 -2
  44. data/spec/bundler/source_spec.rb +4 -4
  45. data/spec/commands/newgem_spec.rb +7 -7
  46. data/spec/commands/outdated_spec.rb +11 -0
  47. data/spec/install/gems/dependency_api_spec.rb +41 -0
  48. data/spec/install/gems/simple_case_spec.rb +1 -17
  49. data/spec/other/ext_spec.rb +1 -1
  50. data/spec/support/artifice/endpoint_strict_basic_authentication.rb +18 -0
  51. data/spec/support/builders.rb +43 -42
  52. data/spec/support/permissions.rb +0 -1
  53. data/spec/update/gems_spec.rb +11 -0
  54. metadata +51 -30
@@ -0,0 +1,27 @@
1
+ module Bundler
2
+ class CLI::Viz
3
+ attr_reader :options, :gem_name
4
+ def initialize(options)
5
+ @options = options
6
+ end
7
+
8
+ def run
9
+ require 'graphviz'
10
+ output_file = File.expand_path(options[:file])
11
+ graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format])
12
+ graph.viz
13
+ rescue LoadError => e
14
+ Bundler.ui.error e.inspect
15
+ Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:"
16
+ Bundler.ui.warn "`gem install ruby-graphviz`"
17
+ rescue StandardError => e
18
+ if e.message =~ /GraphViz not installed or dot not in PATH/
19
+ Bundler.ui.error e.message
20
+ Bundler.ui.warn "Please install GraphViz. On a Mac with homebrew, you can run `brew install graphviz`."
21
+ else
22
+ raise
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -18,11 +18,13 @@ module Bundler
18
18
  def initialize
19
19
  @source = nil
20
20
  @sources = []
21
+ @git_sources = {}
21
22
  @dependencies = []
22
23
  @groups = []
23
24
  @platforms = []
24
25
  @env = nil
25
26
  @ruby_version = nil
27
+ add_github_sources
26
28
  end
27
29
 
28
30
  def rubygems_source
@@ -75,7 +77,7 @@ module Bundler
75
77
  options = Hash === args.last ? args.pop.dup : {}
76
78
  version = args
77
79
 
78
- _normalize_options(name, version, options)
80
+ normalize_options(name, version, options)
79
81
 
80
82
  dep = Dependency.new(name, version, options)
81
83
 
@@ -139,8 +141,21 @@ module Bundler
139
141
  @source = nil
140
142
  end
141
143
 
144
+ def git_source(name, &block)
145
+ unless block_given?
146
+ raise InvalidOption, "You need to pass a block to #git_source"
147
+ end
148
+
149
+ if valid_keys.include?(name.to_s)
150
+ raise InvalidOption, "You cannot use #{name} as a git source. It " \
151
+ "is a reserved key. Reserved keys are: #{valid_keys.join(", ")}"
152
+ end
153
+
154
+ @git_sources[name.to_s] = block
155
+ end
156
+
142
157
  def path(path, options = {}, source_options = {}, &blk)
143
- source Source::Path.new(_normalize_hash(options).merge("path" => Pathname.new(path))), source_options, &blk
158
+ source Source::Path.new(normalize_hash(options).merge("path" => Pathname.new(path))), source_options, &blk
144
159
  end
145
160
 
146
161
  def git(uri, options = {}, source_options = {}, &blk)
@@ -155,7 +170,7 @@ module Bundler
155
170
  raise DeprecatedError, msg
156
171
  end
157
172
 
158
- source Source::Git.new(_normalize_hash(options).merge("uri" => uri)), source_options, &blk
173
+ source Source::Git.new(normalize_hash(options).merge("uri" => uri)), source_options, &blk
159
174
  end
160
175
 
161
176
  def to_definition(lockfile, unlock)
@@ -193,30 +208,39 @@ module Bundler
193
208
 
194
209
  private
195
210
 
196
- def _normalize_hash(opts)
197
- # Cannot modify a hash during an iteration in 1.9
211
+ def add_github_sources
212
+ git_source(:github) do |repo_name|
213
+ repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
214
+ "git://github.com/#{repo_name}.git"
215
+ end
216
+
217
+ git_source(:gist){ |repo_name| "https://gist.github.com/#{repo_name}.git" }
218
+ end
219
+
220
+ def normalize_hash(opts)
198
221
  opts.keys.each do |k|
199
- next if String === k
200
- v = opts[k]
201
- opts.delete(k)
202
- opts[k.to_s] = v
222
+ opts[k.to_s] = opts.delete(k) unless k.is_a?(String)
203
223
  end
204
224
  opts
205
225
  end
206
226
 
207
- def _normalize_options(name, version, opts)
208
- _normalize_hash(opts)
227
+ def valid_keys
228
+ @valid_keys ||= %w(group groups git path name branch ref tag require submodules platform platforms type)
229
+ end
230
+
231
+ def normalize_options(name, version, opts)
232
+ normalize_hash(opts)
233
+
234
+ git_names = @git_sources.keys.map(&:to_s)
209
235
 
210
- valid_keys = %w(group groups git gist github path name branch ref tag require submodules platform platforms type)
211
- invalid_keys = opts.keys - valid_keys
236
+ invalid_keys = opts.keys - (valid_keys + git_names)
212
237
  if invalid_keys.any?
213
- plural = invalid_keys.size > 1
214
238
  message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} "
215
- if plural
216
- message << "as options for gem '#{name}', but they are invalid."
217
- else
218
- message << "as an option for gem '#{name}', but it is invalid."
219
- end
239
+ message << if invalid_keys.size > 1
240
+ "as options for gem '#{name}', but they are invalid."
241
+ else
242
+ "as an option for gem '#{name}', but it is invalid."
243
+ end
220
244
 
221
245
  message << " Valid options are: #{valid_keys.join(", ")}"
222
246
  raise InvalidOption, message
@@ -236,13 +260,9 @@ module Bundler
236
260
  raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
237
261
  end
238
262
 
239
- if github = opts.delete("github")
240
- github = "#{github}/#{github}" unless github.include?("/")
241
- opts["git"] = "git://github.com/#{github}.git"
242
- end
243
-
244
- if gist = opts.delete("gist")
245
- opts["git"] = "https://gist.github.com/#{gist}.git"
263
+ git_name = (git_names & opts.keys).last
264
+ if @git_sources[git_name]
265
+ opts["git"] = @git_sources[git_name].call(opts[git_name])
246
266
  end
247
267
 
248
268
  ["git", "path"].each do |type|
@@ -1,5 +1,6 @@
1
1
  require 'bundler/vendored_persistent'
2
2
  require 'securerandom'
3
+ require 'cgi'
3
4
 
4
5
  module Bundler
5
6
 
@@ -27,6 +28,25 @@ module Bundler
27
28
  "using RVM are available at rvm.io/packages/openssl."
28
29
  end
29
30
  end
31
+ # This error is raised if HTTP authentication is required, but not provided.
32
+ class AuthenticationRequiredError < HTTPError
33
+ def initialize(remote_uri)
34
+ super "Authentication is required for #{remote_uri}.\n" \
35
+ "Please supply credentials for this source. You can do this by running:\n" \
36
+ " bundle config #{remote_uri} username:password"
37
+ end
38
+ end
39
+ # This error is raised if HTTP authentication is provided, but incorrect.
40
+ class BadAuthenticationError < HTTPError
41
+ def initialize(remote_uri)
42
+ super "Bad username or password for #{remote_uri}.\n" \
43
+ "Please double-check your credentials and correct them."
44
+ end
45
+ end
46
+
47
+ # Exceptions classes that should bypass retry attempts. If your password didn't work the
48
+ # first time, it's not going to the third time.
49
+ AUTH_ERRORS = [AuthenticationRequiredError, BadAuthenticationError]
30
50
 
31
51
  class << self
32
52
  attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries
@@ -156,7 +176,7 @@ module Bundler
156
176
  # API errors mean we should treat this as a non-API source
157
177
  @use_api = false
158
178
 
159
- specs = Bundler::Retry.new("source fetch").attempts do
179
+ specs = Bundler::Retry.new("source fetch", AUTH_ERRORS).attempts do
160
180
  fetch_all_remote_specs
161
181
  end
162
182
  end
@@ -193,7 +213,7 @@ module Bundler
193
213
 
194
214
  return {@remote_uri => last_spec_list} if query_list.empty?
195
215
 
196
- remote_specs = Bundler::Retry.new("dependency api").attempts do
216
+ remote_specs = Bundler::Retry.new("dependency api", AUTH_ERRORS).attempts do
197
217
  fetch_dependency_remote_specs(query_list)
198
218
  end
199
219
 
@@ -258,8 +278,14 @@ module Bundler
258
278
  def request(uri)
259
279
  Bundler.ui.debug "Fetching from: #{uri}"
260
280
  req = Net::HTTP::Get.new uri.request_uri
261
- req.basic_auth(uri.user, uri.password) if uri.user
281
+ if uri.user
282
+ user = CGI.unescape(uri.user)
283
+ password = uri.password ? CGI.unescape(uri.password) : nil
284
+ req.basic_auth(user, password)
285
+ end
262
286
  response = connection.request(uri, req)
287
+ rescue Net::HTTPUnauthorized, Net::HTTPForbidden
288
+ retry_with_auth { request(uri) }
263
289
  rescue OpenSSL::SSL::SSLError
264
290
  raise CertificateFailureError.new(uri)
265
291
  rescue *HTTP_ERRORS
@@ -297,8 +323,13 @@ module Bundler
297
323
  Bundler.rubygems.sources = ["#{@remote_uri}"]
298
324
  Bundler.rubygems.fetch_all_remote_specs
299
325
  rescue Gem::RemoteFetcher::FetchError, OpenSSL::SSL::SSLError => e
300
- if e.message.match("certificate verify failed")
326
+ case e.message
327
+ when /certificate verify failed/
301
328
  raise CertificateFailureError.new(uri)
329
+ when /401|403/
330
+ # Gemfury uses a 403 for unauthenticated requests instead of a 401, so retry auth
331
+ # on both.
332
+ retry_with_auth { fetch_all_remote_specs }
302
333
  else
303
334
  Bundler.ui.trace e
304
335
  raise HTTPError, "Could not fetch specs from #{uri}"
@@ -333,5 +364,20 @@ module Bundler
333
364
  store
334
365
  end
335
366
 
367
+ # Attempt to retry with HTTP authentication, if it's appropriate to do so. Yields to a block;
368
+ # the caller should use this to re-attempt the failing request with the altered `@remote_uri`.
369
+ def retry_with_auth
370
+ # Authentication has already been attempted and failed.
371
+ raise BadAuthenticationError.new(uri) if @remote_uri.user
372
+
373
+ auth = Bundler.settings[@remote_uri.to_s]
374
+
375
+ # Authentication isn't provided at all, by "bundle config" or in the URI.
376
+ raise AuthenticationRequiredError.new(uri) if auth.nil?
377
+
378
+ @remote_uri.user, @remote_uri.password = *auth.split(":", 2)
379
+ yield
380
+ end
381
+
336
382
  end
337
383
  end
@@ -90,7 +90,7 @@ module Bundler
90
90
  # dependencies might actually affect the installation of a gem.
91
91
  # that said, it's a rare situation (other than rake), and parallel
92
92
  # installation is just SO MUCH FASTER. so we let people opt in.
93
- jobs = [Bundler.settings[:jobs].to_i, 1].max
93
+ jobs = [Bundler.settings[:jobs].to_i-1, 1].max
94
94
  if jobs > 1 && can_install_parallely?
95
95
  install_in_parallel jobs, options[:standalone]
96
96
  else
@@ -22,7 +22,7 @@ module Bundler
22
22
  trap("INT") { @threads.each {|i| i.exit }; stop_workers; exit 1 }
23
23
  end
24
24
 
25
- # Enque a request to be executed in the worker pool
25
+ # Enqueue a request to be executed in the worker pool
26
26
  #
27
27
  # @param obj [String] mostly it is name of spec that should be downloaded
28
28
  def enq(obj)
@@ -5,7 +5,7 @@ module Bundler
5
5
  # Represents a lazily loaded gem specification, where the full specification
6
6
  # is on the source server in rubygems' "quick" index. The proxy object is to
7
7
  # be seeded with what we're given from the source's abbreviated index - the
8
- # full specification will only be fetched when necesary.
8
+ # full specification will only be fetched when necessary.
9
9
  class RemoteSpecification
10
10
  include MatchPlatform
11
11
 
@@ -171,15 +171,23 @@ module Bundler
171
171
  end
172
172
  end
173
173
 
174
- def handle_conflict(current, states)
175
- return unless current
176
- parent = current
177
- state = states.detect { |i| i.name == parent.name }
178
- until (state && state.possibles.any?) || parent.required_by.empty?
179
- parent = parent.required_by.last
180
- state = states.detect { |i| i.name == parent.name }
174
+ def handle_conflict(current, states, existing=nil)
175
+ until current.nil? && existing.nil?
176
+ current_state = find_state(current, states)
177
+ existing_state = find_state(existing, states)
178
+ return current if state_any?(current_state)
179
+ return existing if state_any?(existing_state)
180
+ existing = existing.required_by.last if existing
181
+ current = current.required_by.last if current
181
182
  end
182
- return parent, state
183
+ end
184
+
185
+ def state_any?(state)
186
+ state && state.possibles.any?
187
+ end
188
+
189
+ def find_state(current, states)
190
+ states.detect { |i| current && current.name == i.name }
183
191
  end
184
192
 
185
193
  def other_possible?(conflict, states)
@@ -189,6 +197,7 @@ module Bundler
189
197
  end
190
198
 
191
199
  def find_conflict_state(conflict, states)
200
+ return unless conflict
192
201
  until states.empty? do
193
202
  state = states.pop
194
203
  return state if conflict.name == state.name
@@ -227,20 +236,19 @@ module Bundler
227
236
  end
228
237
 
229
238
  def resolve_conflict(current, states)
230
- # Return a requirment/gem which has other possibles states
239
+ # Return a requirement/gem which has other possibles states
231
240
  # Given the set of constraints placed by requrired_by
232
- parent, _ = handle_conflict(current, states)
233
241
 
234
- debug { " -> Going to: #{parent.name} state" }
242
+ debug { " -> Going to: #{current.name} state" }
235
243
 
236
- # Find the state where the conlict has occured
237
- state = find_conflict_state(parent, states)
244
+ # Find the state where the conflict has occurred
245
+ state = find_conflict_state(current, states)
238
246
 
239
247
  # Resolve the conflicts by rewinding the state
240
248
  # when the conflicted gem was activated
241
249
  reqs, activated, depth = resolve_for_conflict(state)
242
250
 
243
- # Keep the state around if it still has other possiblities
251
+ # Keep the state around if it still has other possibilities
244
252
  states << state unless state.possibles.empty?
245
253
  clear_search_cache
246
254
 
@@ -303,8 +311,11 @@ module Bundler
303
311
  @errors[existing.name] = [existing, current]
304
312
 
305
313
  parent = current.required_by.last
306
- parent, state = handle_conflict(existing.required_by[-2], states) if !other_possible?(parent, states) && existing.respond_to?(:required_by)
307
- parent = current unless state && state.possibles.any?
314
+ if existing.respond_to?(:required_by)
315
+ parent = handle_conflict(current, states, existing.required_by[-2]) unless other_possible?(parent, states)
316
+ else
317
+ parent = handle_conflict(current, states) unless other_possible?(parent, states)
318
+ end
308
319
 
309
320
  raise version_conflict if parent.nil? || parent.name == 'bundler'
310
321
 
@@ -345,7 +356,8 @@ module Bundler
345
356
  # This is not a top-level Gemfile requirement
346
357
  else
347
358
  @errors[current.name] = [nil, current]
348
- reqs, activated, depth = resolve_conflict(current, states)
359
+ parent = handle_conflict(current, states)
360
+ reqs, activated, depth = resolve_conflict(parent, states)
349
361
  next
350
362
  end
351
363
  end
@@ -501,7 +513,7 @@ module Bundler
501
513
  private
502
514
 
503
515
  # Indicates progress by writing a '.' every iteration_rate time which is
504
- # aproximately every second. iteration_rate is calculated in the first
516
+ # approximately every second. iteration_rate is calculated in the first
505
517
  # second of resolve running.
506
518
  def indicate_progress
507
519
  @iteration_counter += 1
@@ -136,10 +136,6 @@ module Bundler
136
136
 
137
137
  # TODO: actually cache git specs
138
138
  def specs(*)
139
- if has_app_cache? && !local?
140
- set_local!(app_cache_path)
141
- end
142
-
143
139
  if requires_checkout? && !@copied
144
140
  git_proxy.checkout
145
141
  git_proxy.copy_to(install_path, submodules)
@@ -27,8 +27,8 @@ module Bundler
27
27
  end
28
28
  end
29
29
 
30
- # The GitProxy is responsible to iteract with git repositories.
31
- # All actions required by the Git source is encapsualted in this
30
+ # The GitProxy is responsible to interact with git repositories.
31
+ # All actions required by the Git source is encapsulated in this
32
32
  # object.
33
33
  class GitProxy
34
34
  attr_accessor :path, :uri, :ref
@@ -4,7 +4,6 @@ require 'rubygems/spec_fetcher'
4
4
 
5
5
  module Bundler
6
6
  class Source
7
- # TODO: Refactor this class
8
7
  class Rubygems < Source
9
8
  API_REQUEST_LIMIT = 100 # threshold for switching back to the modern index instead of fetching every spec
10
9
 
@@ -68,9 +67,7 @@ module Bundler
68
67
  end
69
68
 
70
69
  def install(spec)
71
- if installed_specs[spec].any? && gem_dir_exists?(spec)
72
- return ["Using #{version_message(spec)}", nil]
73
- end
70
+ return ["Using #{version_message(spec)}", nil] if installed_specs[spec].any?
74
71
 
75
72
  # Download the gem to get the spec, because some specs that are returned
76
73
  # by rubygems.org are broken and wrong.
@@ -276,16 +273,6 @@ module Bundler
276
273
  end
277
274
  end
278
275
 
279
- def gem_dir_exists?(spec)
280
- return true if spec.name == "bundler"
281
- # Ruby 2 default gems
282
- return true if spec.loaded_from.include?("specifications/default/")
283
- # Ruby 1.9 default gems
284
- return true if spec.summary =~ /is bundled with Ruby/
285
-
286
- File.directory?(spec.full_gem_path)
287
- end
288
276
  end
289
-
290
277
  end
291
278
  end
@@ -6,6 +6,6 @@ describe <%= config[:constant_name] %> do
6
6
  end
7
7
 
8
8
  it 'should do something useful' do
9
- expect(false).to be true
9
+ expect(false).to eq(true)
10
10
  end
11
11
  end