bundler 1.6.0.pre.1 → 1.6.0.pre.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bundler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -3
- data/Rakefile +12 -7
- data/lib/bundler.rb +3 -3
- data/lib/bundler/cli.rb +36 -619
- data/lib/bundler/cli/binstubs.rb +36 -0
- data/lib/bundler/cli/cache.rb +34 -0
- data/lib/bundler/cli/check.rb +35 -0
- data/lib/bundler/cli/clean.rb +19 -0
- data/lib/bundler/cli/common.rb +54 -0
- data/lib/bundler/cli/config.rb +84 -0
- data/lib/bundler/cli/console.rb +42 -0
- data/lib/bundler/cli/exec.rb +37 -0
- data/lib/bundler/cli/gem.rb +61 -0
- data/lib/bundler/cli/init.rb +33 -0
- data/lib/bundler/cli/inject.rb +33 -0
- data/lib/bundler/cli/install.rb +123 -0
- data/lib/bundler/cli/open.rb +25 -0
- data/lib/bundler/cli/outdated.rb +80 -0
- data/lib/bundler/cli/package.rb +36 -0
- data/lib/bundler/cli/platform.rb +43 -0
- data/lib/bundler/cli/show.rb +44 -0
- data/lib/bundler/cli/update.rb +73 -0
- data/lib/bundler/cli/viz.rb +27 -0
- data/lib/bundler/dsl.rb +46 -26
- data/lib/bundler/fetcher.rb +50 -4
- data/lib/bundler/installer.rb +1 -1
- data/lib/bundler/parallel_workers/worker.rb +1 -1
- data/lib/bundler/remote_specification.rb +1 -1
- data/lib/bundler/resolver.rb +30 -18
- data/lib/bundler/source/git.rb +0 -4
- data/lib/bundler/source/git/git_proxy.rb +2 -2
- data/lib/bundler/source/rubygems.rb +1 -14
- data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +1 -1
- data/lib/bundler/vendor/thor/base.rb +1 -1
- data/lib/bundler/version.rb +1 -1
- data/spec/bundler/bundler_spec.rb +46 -47
- data/spec/bundler/{cli_rspec.rb → cli_spec.rb} +0 -1
- data/spec/bundler/definition_spec.rb +3 -7
- data/spec/bundler/dsl_spec.rb +43 -21
- data/spec/bundler/gem_helper_spec.rb +152 -123
- data/spec/bundler/retry_spec.rb +6 -7
- data/spec/bundler/settings_spec.rb +0 -2
- data/spec/bundler/source_spec.rb +4 -4
- data/spec/commands/newgem_spec.rb +7 -7
- data/spec/commands/outdated_spec.rb +11 -0
- data/spec/install/gems/dependency_api_spec.rb +41 -0
- data/spec/install/gems/simple_case_spec.rb +1 -17
- data/spec/other/ext_spec.rb +1 -1
- data/spec/support/artifice/endpoint_strict_basic_authentication.rb +18 -0
- data/spec/support/builders.rb +43 -42
- data/spec/support/permissions.rb +0 -1
- data/spec/update/gems_spec.rb +11 -0
- 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
|
data/lib/bundler/dsl.rb
CHANGED
@@ -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
|
-
|
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(
|
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(
|
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
|
197
|
-
|
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
|
-
|
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
|
208
|
-
|
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
|
-
|
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
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
240
|
-
|
241
|
-
opts["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|
|
data/lib/bundler/fetcher.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/bundler/installer.rb
CHANGED
@@ -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
|
-
#
|
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
|
8
|
+
# full specification will only be fetched when necessary.
|
9
9
|
class RemoteSpecification
|
10
10
|
include MatchPlatform
|
11
11
|
|
data/lib/bundler/resolver.rb
CHANGED
@@ -171,15 +171,23 @@ module Bundler
|
|
171
171
|
end
|
172
172
|
end
|
173
173
|
|
174
|
-
def handle_conflict(current, states)
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
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
|
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: #{
|
242
|
+
debug { " -> Going to: #{current.name} state" }
|
235
243
|
|
236
|
-
# Find the state where the
|
237
|
-
state = find_conflict_state(
|
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
|
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
|
-
|
307
|
-
|
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
|
-
|
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
|
-
#
|
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
|
data/lib/bundler/source/git.rb
CHANGED
@@ -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
|
31
|
-
# All actions required by the Git source is
|
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?
|
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
|