rkh-bundler 1.2.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. data/.gitignore +22 -0
  2. data/.travis.yml +42 -0
  3. data/CHANGELOG.md +1105 -0
  4. data/ISSUES.md +67 -0
  5. data/LICENSE +23 -0
  6. data/README.md +31 -0
  7. data/Rakefile +208 -0
  8. data/UPGRADING.md +103 -0
  9. data/bin/bundle +31 -0
  10. data/bundler.gemspec +31 -0
  11. data/lib/bundler.rb +353 -0
  12. data/lib/bundler/capistrano.rb +11 -0
  13. data/lib/bundler/cli.rb +693 -0
  14. data/lib/bundler/definition.rb +568 -0
  15. data/lib/bundler/dep_proxy.rb +43 -0
  16. data/lib/bundler/dependency.rb +134 -0
  17. data/lib/bundler/deployment.rb +58 -0
  18. data/lib/bundler/dsl.rb +256 -0
  19. data/lib/bundler/endpoint_specification.rb +78 -0
  20. data/lib/bundler/environment.rb +47 -0
  21. data/lib/bundler/fetcher.rb +225 -0
  22. data/lib/bundler/gem_helper.rb +162 -0
  23. data/lib/bundler/gem_helpers.rb +23 -0
  24. data/lib/bundler/gem_installer.rb +9 -0
  25. data/lib/bundler/gem_tasks.rb +2 -0
  26. data/lib/bundler/graph.rb +148 -0
  27. data/lib/bundler/index.rb +187 -0
  28. data/lib/bundler/installer.rb +190 -0
  29. data/lib/bundler/lazy_specification.rb +79 -0
  30. data/lib/bundler/lockfile_parser.rb +127 -0
  31. data/lib/bundler/match_platform.rb +13 -0
  32. data/lib/bundler/psyched_yaml.rb +15 -0
  33. data/lib/bundler/remote_specification.rb +57 -0
  34. data/lib/bundler/resolver.rb +486 -0
  35. data/lib/bundler/ruby_version.rb +94 -0
  36. data/lib/bundler/rubygems_ext.rb +153 -0
  37. data/lib/bundler/rubygems_integration.rb +394 -0
  38. data/lib/bundler/runtime.rb +233 -0
  39. data/lib/bundler/settings.rb +128 -0
  40. data/lib/bundler/setup.rb +23 -0
  41. data/lib/bundler/shared_helpers.rb +71 -0
  42. data/lib/bundler/source.rb +869 -0
  43. data/lib/bundler/spec_set.rb +137 -0
  44. data/lib/bundler/templates/Executable +16 -0
  45. data/lib/bundler/templates/Executable.standalone +12 -0
  46. data/lib/bundler/templates/Gemfile +4 -0
  47. data/lib/bundler/templates/newgem/Gemfile.tt +4 -0
  48. data/lib/bundler/templates/newgem/LICENSE.tt +22 -0
  49. data/lib/bundler/templates/newgem/README.md.tt +29 -0
  50. data/lib/bundler/templates/newgem/Rakefile.tt +2 -0
  51. data/lib/bundler/templates/newgem/bin/newgem.tt +3 -0
  52. data/lib/bundler/templates/newgem/gitignore.tt +17 -0
  53. data/lib/bundler/templates/newgem/lib/newgem.rb.tt +9 -0
  54. data/lib/bundler/templates/newgem/lib/newgem/version.rb.tt +7 -0
  55. data/lib/bundler/templates/newgem/newgem.gemspec.tt +17 -0
  56. data/lib/bundler/ui.rb +88 -0
  57. data/lib/bundler/vendor/net/http/faster.rb +27 -0
  58. data/lib/bundler/vendor/net/http/persistent.rb +468 -0
  59. data/lib/bundler/vendor/thor.rb +358 -0
  60. data/lib/bundler/vendor/thor/actions.rb +314 -0
  61. data/lib/bundler/vendor/thor/actions/create_file.rb +105 -0
  62. data/lib/bundler/vendor/thor/actions/create_link.rb +57 -0
  63. data/lib/bundler/vendor/thor/actions/directory.rb +93 -0
  64. data/lib/bundler/vendor/thor/actions/empty_directory.rb +134 -0
  65. data/lib/bundler/vendor/thor/actions/file_manipulation.rb +270 -0
  66. data/lib/bundler/vendor/thor/actions/inject_into_file.rb +109 -0
  67. data/lib/bundler/vendor/thor/base.rb +576 -0
  68. data/lib/bundler/vendor/thor/core_ext/file_binary_read.rb +9 -0
  69. data/lib/bundler/vendor/thor/core_ext/hash_with_indifferent_access.rb +75 -0
  70. data/lib/bundler/vendor/thor/core_ext/ordered_hash.rb +100 -0
  71. data/lib/bundler/vendor/thor/error.rb +30 -0
  72. data/lib/bundler/vendor/thor/group.rb +273 -0
  73. data/lib/bundler/vendor/thor/invocation.rb +168 -0
  74. data/lib/bundler/vendor/thor/parser.rb +4 -0
  75. data/lib/bundler/vendor/thor/parser/argument.rb +67 -0
  76. data/lib/bundler/vendor/thor/parser/arguments.rb +161 -0
  77. data/lib/bundler/vendor/thor/parser/option.rb +120 -0
  78. data/lib/bundler/vendor/thor/parser/options.rb +172 -0
  79. data/lib/bundler/vendor/thor/rake_compat.rb +66 -0
  80. data/lib/bundler/vendor/thor/runner.rb +309 -0
  81. data/lib/bundler/vendor/thor/shell.rb +88 -0
  82. data/lib/bundler/vendor/thor/shell/basic.rb +302 -0
  83. data/lib/bundler/vendor/thor/shell/color.rb +108 -0
  84. data/lib/bundler/vendor/thor/shell/html.rb +121 -0
  85. data/lib/bundler/vendor/thor/task.rb +113 -0
  86. data/lib/bundler/vendor/thor/util.rb +229 -0
  87. data/lib/bundler/vendor/thor/version.rb +3 -0
  88. data/lib/bundler/vendored_thor.rb +7 -0
  89. data/lib/bundler/version.rb +6 -0
  90. data/lib/bundler/vlad.rb +11 -0
  91. data/man/bundle-config.ronn +130 -0
  92. data/man/bundle-exec.ronn +111 -0
  93. data/man/bundle-install.ronn +335 -0
  94. data/man/bundle-package.ronn +59 -0
  95. data/man/bundle-update.ronn +176 -0
  96. data/man/bundle.ronn +83 -0
  97. data/man/gemfile.5.ronn +324 -0
  98. data/man/index.txt +6 -0
  99. data/spec/bundler/dsl_spec.rb +48 -0
  100. data/spec/bundler/gem_helper_spec.rb +174 -0
  101. data/spec/bundler/source_spec.rb +25 -0
  102. data/spec/cache/gems_spec.rb +239 -0
  103. data/spec/cache/git_spec.rb +124 -0
  104. data/spec/cache/path_spec.rb +103 -0
  105. data/spec/cache/platform_spec.rb +57 -0
  106. data/spec/install/deploy_spec.rb +211 -0
  107. data/spec/install/gems/c_ext_spec.rb +48 -0
  108. data/spec/install/gems/dependency_api_spec.rb +402 -0
  109. data/spec/install/gems/env_spec.rb +107 -0
  110. data/spec/install/gems/flex_spec.rb +313 -0
  111. data/spec/install/gems/groups_spec.rb +268 -0
  112. data/spec/install/gems/packed_spec.rb +84 -0
  113. data/spec/install/gems/platform_spec.rb +208 -0
  114. data/spec/install/gems/post_install_spec.rb +47 -0
  115. data/spec/install/gems/resolving_spec.rb +72 -0
  116. data/spec/install/gems/simple_case_spec.rb +814 -0
  117. data/spec/install/gems/standalone_spec.rb +260 -0
  118. data/spec/install/gems/sudo_spec.rb +74 -0
  119. data/spec/install/gems/win32_spec.rb +26 -0
  120. data/spec/install/gemspec_spec.rb +170 -0
  121. data/spec/install/git_spec.rb +796 -0
  122. data/spec/install/invalid_spec.rb +35 -0
  123. data/spec/install/path_spec.rb +405 -0
  124. data/spec/install/upgrade_spec.rb +26 -0
  125. data/spec/lock/git_spec.rb +35 -0
  126. data/spec/lock/lockfile_spec.rb +809 -0
  127. data/spec/other/check_spec.rb +265 -0
  128. data/spec/other/clean_spec.rb +492 -0
  129. data/spec/other/config_spec.rb +138 -0
  130. data/spec/other/console_spec.rb +54 -0
  131. data/spec/other/exec_spec.rb +229 -0
  132. data/spec/other/ext_spec.rb +37 -0
  133. data/spec/other/help_spec.rb +39 -0
  134. data/spec/other/init_spec.rb +40 -0
  135. data/spec/other/newgem_spec.rb +87 -0
  136. data/spec/other/open_spec.rb +35 -0
  137. data/spec/other/outdated_spec.rb +93 -0
  138. data/spec/other/platform_spec.rb +881 -0
  139. data/spec/other/show_spec.rb +88 -0
  140. data/spec/quality_spec.rb +62 -0
  141. data/spec/realworld/edgecases_spec.rb +177 -0
  142. data/spec/resolver/basic_spec.rb +20 -0
  143. data/spec/resolver/platform_spec.rb +82 -0
  144. data/spec/runtime/executable_spec.rb +120 -0
  145. data/spec/runtime/load_spec.rb +107 -0
  146. data/spec/runtime/platform_spec.rb +90 -0
  147. data/spec/runtime/require_spec.rb +261 -0
  148. data/spec/runtime/setup_spec.rb +755 -0
  149. data/spec/runtime/with_clean_env_spec.rb +80 -0
  150. data/spec/spec_helper.rb +98 -0
  151. data/spec/support/artifice/endopint_marshal_fail_basic_authentication.rb +13 -0
  152. data/spec/support/artifice/endpoint.rb +54 -0
  153. data/spec/support/artifice/endpoint_500.rb +37 -0
  154. data/spec/support/artifice/endpoint_api_missing.rb +16 -0
  155. data/spec/support/artifice/endpoint_basic_authentication.rb +13 -0
  156. data/spec/support/artifice/endpoint_extra.rb +27 -0
  157. data/spec/support/artifice/endpoint_extra_missing.rb +15 -0
  158. data/spec/support/artifice/endpoint_fallback.rb +18 -0
  159. data/spec/support/artifice/endpoint_marshal_fail.rb +11 -0
  160. data/spec/support/artifice/endpoint_redirect.rb +15 -0
  161. data/spec/support/builders.rb +604 -0
  162. data/spec/support/fakeweb/rack-1.0.0.marshal +2 -0
  163. data/spec/support/fakeweb/windows.rb +23 -0
  164. data/spec/support/helpers.rb +317 -0
  165. data/spec/support/indexes.rb +112 -0
  166. data/spec/support/matchers.rb +77 -0
  167. data/spec/support/path.rb +73 -0
  168. data/spec/support/platforms.rb +86 -0
  169. data/spec/support/ruby_ext.rb +20 -0
  170. data/spec/support/rubygems_ext.rb +37 -0
  171. data/spec/support/rubygems_hax/platform.rb +22 -0
  172. data/spec/support/sudo.rb +21 -0
  173. data/spec/update/gems_spec.rb +134 -0
  174. data/spec/update/git_spec.rb +196 -0
  175. data/spec/update/source_spec.rb +51 -0
  176. metadata +338 -0
@@ -0,0 +1,869 @@
1
+ require "uri"
2
+ require 'rubygems/user_interaction'
3
+ require "rubygems/installer"
4
+ require "rubygems/spec_fetcher"
5
+ require "rubygems/format"
6
+ require "digest/sha1"
7
+ require "fileutils"
8
+
9
+ module Bundler
10
+ module Source
11
+ # TODO: Refactor this class
12
+ class Rubygems
13
+ FORCE_MODERN_INDEX_LIMIT = 100 # threshold for switching back to the modern index instead of fetching every spec
14
+
15
+ attr_reader :remotes, :caches
16
+ attr_accessor :dependency_names
17
+
18
+ def initialize(options = {})
19
+ @options = options
20
+ @remotes = (options["remotes"] || []).map { |r| normalize_uri(r) }
21
+ @fetchers = {}
22
+ @allow_remote = false
23
+ @allow_cached = false
24
+
25
+ @caches = [ Bundler.app_cache ] +
26
+ Bundler.rubygems.gem_path.map{|p| File.expand_path("#{p}/cache") }
27
+ end
28
+
29
+ def remote!
30
+ @allow_remote = true
31
+ end
32
+
33
+ def cached!
34
+ @allow_cached = true
35
+ end
36
+
37
+ def hash
38
+ Rubygems.hash
39
+ end
40
+
41
+ def eql?(o)
42
+ Rubygems === o
43
+ end
44
+
45
+ alias == eql?
46
+
47
+ def options
48
+ { "remotes" => @remotes.map { |r| r.to_s } }
49
+ end
50
+
51
+ def self.from_lock(options)
52
+ s = new(options)
53
+ Array(options["remote"]).each { |r| s.add_remote(r) }
54
+ s
55
+ end
56
+
57
+ def to_lock
58
+ out = "GEM\n"
59
+ out << remotes.map {|r| " remote: #{r}\n" }.join
60
+ out << " specs:\n"
61
+ end
62
+
63
+ def to_s
64
+ remote_names = self.remotes.map { |r| r.to_s }.join(', ')
65
+ "rubygems repository #{remote_names}"
66
+ end
67
+ alias_method :name, :to_s
68
+
69
+ def specs
70
+ @specs ||= fetch_specs
71
+ end
72
+
73
+ def install(spec)
74
+ if installed_specs[spec].any?
75
+ Bundler.ui.info "Using #{spec.name} (#{spec.version}) "
76
+ return
77
+ end
78
+
79
+ Bundler.ui.info "Installing #{spec.name} (#{spec.version}) "
80
+ path = cached_gem(spec)
81
+ if Bundler.requires_sudo?
82
+ install_path = Bundler.tmp
83
+ bin_path = install_path.join("bin")
84
+ else
85
+ install_path = Bundler.rubygems.gem_dir
86
+ bin_path = Bundler.system_bindir
87
+ end
88
+
89
+ Bundler.rubygems.preserve_paths do
90
+ Bundler::GemInstaller.new(path,
91
+ :install_dir => install_path.to_s,
92
+ :bin_dir => bin_path.to_s,
93
+ :ignore_dependencies => true,
94
+ :wrappers => true,
95
+ :env_shebang => true
96
+ ).install
97
+ end
98
+
99
+ if spec.post_install_message
100
+ Installer.post_install_messages[spec.name] = spec.post_install_message
101
+ end
102
+
103
+ # SUDO HAX
104
+ if Bundler.requires_sudo?
105
+ Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/gems"
106
+ Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/specifications"
107
+ Bundler.sudo "cp -R #{Bundler.tmp}/gems/#{spec.full_name} #{Bundler.rubygems.gem_dir}/gems/"
108
+ Bundler.sudo "cp -R #{Bundler.tmp}/specifications/#{spec.full_name}.gemspec #{Bundler.rubygems.gem_dir}/specifications/"
109
+ spec.executables.each do |exe|
110
+ Bundler.mkdir_p Bundler.system_bindir
111
+ Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.system_bindir}"
112
+ end
113
+ end
114
+
115
+ spec.loaded_from = "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec"
116
+ end
117
+
118
+ def cache(spec)
119
+ cached_path = cached_gem(spec)
120
+ raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path
121
+ return if File.dirname(cached_path) == Bundler.app_cache.to_s
122
+ Bundler.ui.info " * #{File.basename(cached_path)}"
123
+ FileUtils.cp(cached_path, Bundler.app_cache)
124
+ end
125
+
126
+ def add_remote(source)
127
+ @remotes << normalize_uri(source)
128
+ end
129
+
130
+ def replace_remotes(source)
131
+ return false if source.remotes == @remotes
132
+
133
+ @remotes = []
134
+ source.remotes.each do |r|
135
+ add_remote r.to_s
136
+ end
137
+
138
+ true
139
+ end
140
+
141
+ private
142
+
143
+ def cached_gem(spec)
144
+ possibilities = @caches.map { |p| "#{p}/#{spec.file_name}" }
145
+ cached_gem = possibilities.find { |p| File.exist?(p) }
146
+ unless cached_gem
147
+ raise Bundler::GemNotFound, "Could not find #{spec.file_name} for installation"
148
+ end
149
+ cached_gem
150
+ end
151
+
152
+ def normalize_uri(uri)
153
+ uri = uri.to_s
154
+ uri = "#{uri}/" unless uri =~ %r'/$'
155
+ uri = URI(uri)
156
+ raise ArgumentError, "The source must be an absolute URI" unless uri.absolute?
157
+ uri
158
+ end
159
+
160
+ def fetch_specs
161
+ # remote_specs usually generates a way larger Index than the other
162
+ # sources, and large_idx.use small_idx is way faster than
163
+ # small_idx.use large_idx.
164
+ if @allow_remote
165
+ idx = remote_specs.dup
166
+ else
167
+ idx = Index.new
168
+ end
169
+ idx.use(cached_specs, :override_dupes) if @allow_cached || @allow_remote
170
+ idx.use(installed_specs, :override_dupes)
171
+ idx
172
+ end
173
+
174
+ def installed_specs
175
+ @installed_specs ||= begin
176
+ idx = Index.new
177
+ have_bundler = false
178
+ Bundler.rubygems.all_specs.reverse.each do |spec|
179
+ next if spec.name == 'bundler' && spec.version.to_s != VERSION
180
+ have_bundler = true if spec.name == 'bundler'
181
+ spec.source = self
182
+ idx << spec
183
+ end
184
+
185
+ # Always have bundler locally
186
+ unless have_bundler
187
+ # We're running bundler directly from the source
188
+ # so, let's create a fake gemspec for it (it's a path)
189
+ # gemspec
190
+ bundler = Gem::Specification.new do |s|
191
+ s.name = 'bundler'
192
+ s.version = VERSION
193
+ s.platform = Gem::Platform::RUBY
194
+ s.source = self
195
+ s.authors = ["bundler team"]
196
+ s.loaded_from = File.expand_path("..", __FILE__)
197
+ end
198
+ idx << bundler
199
+ end
200
+ idx
201
+ end
202
+ end
203
+
204
+ def cached_specs
205
+ @cached_specs ||= begin
206
+ idx = installed_specs.dup
207
+
208
+ path = Bundler.app_cache
209
+ Dir["#{path}/*.gem"].each do |gemfile|
210
+ next if gemfile =~ /^bundler\-[\d\.]+?\.gem/
211
+
212
+ begin
213
+ s ||= Bundler.rubygems.spec_from_gem(gemfile)
214
+ rescue Gem::Package::FormatError
215
+ raise GemspecError, "Could not read gem at #{gemfile}. It may be corrupted."
216
+ end
217
+
218
+ s.source = self
219
+ idx << s
220
+ end
221
+ end
222
+
223
+ idx
224
+ end
225
+
226
+ def remote_specs
227
+ @remote_specs ||= begin
228
+ idx = Index.new
229
+ old = Bundler.rubygems.sources
230
+
231
+ sources = {}
232
+ remotes.each do |uri|
233
+ fetcher = Bundler::Fetcher.new(uri)
234
+ specs = fetcher.specs(dependency_names, self)
235
+ sources[fetcher] = specs.size
236
+
237
+ idx.use specs
238
+ end
239
+
240
+ # don't need to fetch all specifications for every gem/version on
241
+ # the rubygems repo if there's no api endpoints to search over
242
+ # or it has too many specs to fetch
243
+ fetchers = sources.keys
244
+ api_fetchers = fetchers.select {|fetcher| fetcher.has_api }
245
+ modern_index_fetchers = fetchers - api_fetchers
246
+ if api_fetchers.any? && modern_index_fetchers.all? {|fetcher| sources[fetcher] < FORCE_MODERN_INDEX_LIMIT }
247
+ # this will fetch all the specifications on the rubygems repo
248
+ unmet_dependency_names = idx.unmet_dependency_names
249
+ unmet_dependency_names -= ['bundler'] # bundler will always be unmet
250
+
251
+ Bundler.ui.debug "Unmet Dependencies: #{unmet_dependency_names}"
252
+ if unmet_dependency_names.any?
253
+ api_fetchers.each do |fetcher|
254
+ idx.use fetcher.specs(unmet_dependency_names, self)
255
+ end
256
+ end
257
+ else
258
+ Bundler::Fetcher.disable_endpoint = true
259
+ api_fetchers.each {|fetcher| idx.use fetcher.specs([], self) }
260
+ end
261
+
262
+ idx
263
+ ensure
264
+ Bundler.rubygems.sources = old
265
+ end
266
+ end
267
+ end
268
+
269
+
270
+ class Path
271
+ class Installer < Bundler::GemInstaller
272
+ def initialize(spec, options = {})
273
+ @spec = spec
274
+ @bin_dir = Bundler.requires_sudo? ? "#{Bundler.tmp}/bin" : "#{Bundler.rubygems.gem_dir}/bin"
275
+ @gem_dir = Bundler.rubygems.path(spec.full_gem_path)
276
+ @wrappers = options[:wrappers] || true
277
+ @env_shebang = options[:env_shebang] || true
278
+ @format_executable = options[:format_executable] || false
279
+ end
280
+
281
+ def generate_bin
282
+ return if spec.executables.nil? || spec.executables.empty?
283
+
284
+ if Bundler.requires_sudo?
285
+ FileUtils.mkdir_p("#{Bundler.tmp}/bin") unless File.exist?("#{Bundler.tmp}/bin")
286
+ end
287
+ super
288
+ if Bundler.requires_sudo?
289
+ Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/bin"
290
+ spec.executables.each do |exe|
291
+ Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.rubygems.gem_dir}/bin/"
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ attr_reader :path, :options
298
+ attr_writer :name
299
+ attr_accessor :version
300
+
301
+ DEFAULT_GLOB = "{,*,*/*}.gemspec"
302
+
303
+ def initialize(options)
304
+ @options = options
305
+ @glob = options["glob"] || DEFAULT_GLOB
306
+
307
+ @allow_cached = false
308
+ @allow_remote = false
309
+
310
+ if options["path"]
311
+ @path = Pathname.new(options["path"])
312
+ @path = @path.expand_path(Bundler.root) unless @path.relative?
313
+ end
314
+
315
+ @name = options["name"]
316
+ @version = options["version"]
317
+
318
+ # Stores the original path. If at any point we move to the
319
+ # cached directory, we still have the original path to copy from.
320
+ @original_path = @path
321
+ end
322
+
323
+ def remote!
324
+ @allow_remote = true
325
+ end
326
+
327
+ def cached!
328
+ @allow_cached = true
329
+ end
330
+
331
+ def self.from_lock(options)
332
+ new(options.merge("path" => options.delete("remote")))
333
+ end
334
+
335
+ def to_lock
336
+ out = "PATH\n"
337
+ out << " remote: #{relative_path}\n"
338
+ out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
339
+ out << " specs:\n"
340
+ end
341
+
342
+ def to_s
343
+ "source at #{@path}"
344
+ end
345
+
346
+ def hash
347
+ self.class.hash
348
+ end
349
+
350
+ def eql?(o)
351
+ o.instance_of?(Path) &&
352
+ path.expand_path(Bundler.root) == o.path.expand_path(Bundler.root) &&
353
+ version == o.version
354
+ end
355
+
356
+ alias == eql?
357
+
358
+ def name
359
+ File.basename(path.expand_path(Bundler.root).to_s)
360
+ end
361
+
362
+ def install(spec)
363
+ Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
364
+ # Let's be honest, when we're working from a path, we can't
365
+ # really expect native extensions to work because the whole point
366
+ # is to just be able to modify what's in that path and go. So, let's
367
+ # not put ourselves through the pain of actually trying to generate
368
+ # the full gem.
369
+ Installer.new(spec).generate_bin
370
+ end
371
+
372
+ def cache(spec)
373
+ return unless Bundler.settings[:cache_all]
374
+ return if @original_path.expand_path(Bundler.root).to_s.index(Bundler.root.to_s) == 0
375
+ FileUtils.rm_rf(app_cache_path)
376
+ FileUtils.cp_r("#{@original_path}/.", app_cache_path)
377
+ end
378
+
379
+ def local_specs(*)
380
+ @local_specs ||= load_spec_files
381
+ end
382
+
383
+ def specs
384
+ if has_app_cache?
385
+ @path = app_cache_path
386
+ end
387
+ local_specs
388
+ end
389
+
390
+ private
391
+
392
+ def app_cache_path
393
+ @app_cache_path ||= Bundler.app_cache.join(name)
394
+ end
395
+
396
+ def has_app_cache?
397
+ SharedHelpers.in_bundle? && app_cache_path.exist?
398
+ end
399
+
400
+ def load_spec_files
401
+ index = Index.new
402
+ expanded_path = path.expand_path(Bundler.root)
403
+
404
+ if File.directory?(expanded_path)
405
+ Dir["#{expanded_path}/#{@glob}"].each do |file|
406
+ spec = Bundler.load_gemspec(file)
407
+ if spec
408
+ spec.loaded_from = file.to_s
409
+ spec.source = self
410
+ index << spec
411
+ end
412
+ end
413
+
414
+ if index.empty? && @name && @version
415
+ index << Gem::Specification.new do |s|
416
+ s.name = @name
417
+ s.source = self
418
+ s.version = Gem::Version.new(@version)
419
+ s.platform = Gem::Platform::RUBY
420
+ s.summary = "Fake gemspec for #{@name}"
421
+ s.relative_loaded_from = "#{@name}.gemspec"
422
+ s.authors = ["no one"]
423
+ if expanded_path.join("bin").exist?
424
+ executables = expanded_path.join("bin").children
425
+ executables.reject!{|p| File.directory?(p) }
426
+ s.executables = executables.map{|c| c.basename.to_s }
427
+ end
428
+ end
429
+ end
430
+ else
431
+ raise PathError, "The path `#{expanded_path}` does not exist."
432
+ end
433
+
434
+ index
435
+ end
436
+
437
+ def relative_path
438
+ if path.to_s.match(%r{^#{Regexp.escape Bundler.root.to_s}})
439
+ return path.relative_path_from(Bundler.root)
440
+ end
441
+ path
442
+ end
443
+
444
+ def generate_bin(spec)
445
+ gem_dir = Pathname.new(spec.full_gem_path)
446
+
447
+ # Some gem authors put absolute paths in their gemspec
448
+ # and we have to save them from themselves
449
+ spec.files = spec.files.map do |p|
450
+ next if File.directory?(p)
451
+ begin
452
+ Pathname.new(p).relative_path_from(gem_dir).to_s
453
+ rescue ArgumentError
454
+ p
455
+ end
456
+ end.compact
457
+
458
+ gem_file = Dir.chdir(gem_dir){ Gem::Builder.new(spec).build }
459
+
460
+ installer = Path::Installer.new(spec, :env_shebang => false)
461
+ run_hooks(:pre_install, installer)
462
+ installer.build_extensions
463
+ run_hooks(:post_build, installer)
464
+ installer.generate_bin
465
+ run_hooks(:post_install, installer)
466
+ rescue Gem::InvalidSpecificationException => e
467
+ Bundler.ui.warn "\n#{spec.name} at #{spec.full_gem_path} did not have a valid gemspec.\n" \
468
+ "This prevents bundler from installing bins or native extensions, but " \
469
+ "that may not affect its functionality."
470
+
471
+ if !spec.extensions.empty? && !spec.email.empty?
472
+ Bundler.ui.warn "If you need to use this package without installing it from a gem " \
473
+ "repository, please contact #{spec.email} and ask them " \
474
+ "to modify their .gemspec so it can work with `gem build`."
475
+ end
476
+
477
+ Bundler.ui.warn "The validation message from Rubygems was:\n #{e.message}"
478
+ ensure
479
+ Dir.chdir(gem_dir){ FileUtils.rm_rf(gem_file) if gem_file && File.exist?(gem_file) }
480
+ end
481
+
482
+ def run_hooks(type, installer)
483
+ hooks_meth = "#{type}_hooks"
484
+ return unless Gem.respond_to?(hooks_meth)
485
+ Gem.send(hooks_meth).each do |hook|
486
+ result = hook.call(installer)
487
+ if result == false
488
+ location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/
489
+ message = "#{type} hook#{location} failed for #{installer.spec.full_name}"
490
+ raise InstallHookError, message
491
+ end
492
+ end
493
+ end
494
+ end
495
+
496
+ class Git < Path
497
+ # The GitProxy is responsible to iteract with git repositories.
498
+ # All actions required by the Git source is encapsualted in this
499
+ # object.
500
+ class GitProxy
501
+ attr_accessor :path, :uri, :ref, :revision
502
+
503
+ def initialize(path, uri, ref, revision=nil, &allow)
504
+ @path = path
505
+ @uri = uri
506
+ @ref = ref
507
+ @revision = revision
508
+ @allow = allow || Proc.new { true }
509
+ end
510
+
511
+ def revision
512
+ @revision ||= allowed_in_path { git("rev-parse #{ref}").strip }
513
+ end
514
+
515
+ def branch
516
+ @branch ||= allowed_in_path do
517
+ git("branch") =~ /^\* (.*)$/ && $1.strip
518
+ end
519
+ end
520
+
521
+ def contains?(commit)
522
+ allowed_in_path do
523
+ result = git_null("branch --contains #{commit}")
524
+ $? == 0 && result =~ /^\* (.*)$/
525
+ end
526
+ end
527
+
528
+ def checkout
529
+ if path.exist?
530
+ return if has_revision_cached?
531
+ Bundler.ui.info "Updating #{uri}"
532
+ in_path do
533
+ git %|fetch --force --quiet --tags #{uri_escaped} "refs/heads/*:refs/heads/*"|
534
+ end
535
+ else
536
+ Bundler.ui.info "Fetching #{uri}"
537
+ FileUtils.mkdir_p(path.dirname)
538
+ git %|clone #{uri_escaped} "#{path}" --bare --no-hardlinks|
539
+ end
540
+ end
541
+
542
+ def copy_to(destination, submodules=false)
543
+ unless File.exist?(destination.join(".git"))
544
+ FileUtils.mkdir_p(destination.dirname)
545
+ FileUtils.rm_rf(destination)
546
+ git %|clone --no-checkout "#{path}" "#{destination}"|
547
+ File.chmod((0777 & ~File.umask), destination)
548
+ end
549
+
550
+ Dir.chdir(destination) do
551
+ git %|fetch --force --quiet --tags "#{path}"|
552
+ git "reset --hard #{@revision}"
553
+
554
+ if submodules
555
+ git "submodule init"
556
+ git "submodule update"
557
+ end
558
+ end
559
+ end
560
+
561
+ private
562
+
563
+ # TODO: Do not rely on /dev/null.
564
+ # Given that open3 is not cross platform until Ruby 1.9.3,
565
+ # the best solution is to pipe to /dev/null if it exists.
566
+ # If it doesn't, everything will work fine, but the user
567
+ # will get the $stderr messages as well.
568
+ def git_null(command)
569
+ if !Bundler::WINDOWS && File.exist?("/dev/null")
570
+ git("#{command} 2>/dev/null", false)
571
+ else
572
+ git(command, false)
573
+ end
574
+ end
575
+
576
+ def git(command, check_errors=true)
577
+ if allow?
578
+ out = %x{git #{command}}
579
+
580
+ if check_errors && $?.exitstatus != 0
581
+ msg = "Git error: command `git #{command}` in directory #{Dir.pwd} has failed."
582
+ msg << "\nIf this error persists you could try removing the cache directory '#{path}'" if path.exist?
583
+ raise GitError, msg
584
+ end
585
+ out
586
+ else
587
+ raise GitError, "Bundler is trying to run a `git #{command}` at runtime. You probably need to run `bundle install`. However, " \
588
+ "this error message could probably be more useful. Please submit a ticket at http://github.com/carlhuda/bundler/issues " \
589
+ "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}"
590
+ end
591
+ end
592
+
593
+ def has_revision_cached?
594
+ return unless @revision
595
+ in_path { git("cat-file -e #{@revision}") }
596
+ true
597
+ rescue GitError
598
+ false
599
+ end
600
+
601
+ # Escape the URI for git commands
602
+ def uri_escaped
603
+ if Bundler::WINDOWS
604
+ # Windows quoting requires double quotes only, with double quotes
605
+ # inside the string escaped by being doubled.
606
+ '"' + uri.gsub('"') {|s| '""'} + '"'
607
+ else
608
+ # Bash requires single quoted strings, with the single quotes escaped
609
+ # by ending the string, escaping the quote, and restarting the string.
610
+ "'" + uri.gsub("'") {|s| "'\\''"} + "'"
611
+ end
612
+ end
613
+
614
+ def allow?
615
+ @allow.call
616
+ end
617
+
618
+ def in_path(&blk)
619
+ checkout unless path.exist?
620
+ Dir.chdir(path, &blk)
621
+ end
622
+
623
+ def allowed_in_path
624
+ if allow?
625
+ in_path { yield }
626
+ else
627
+ raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application"
628
+ end
629
+ end
630
+ end
631
+
632
+ attr_reader :uri, :ref, :options, :submodules
633
+
634
+ def initialize(options)
635
+ @options = options
636
+ @glob = options["glob"] || DEFAULT_GLOB
637
+
638
+ @allow_cached = false
639
+ @allow_remote = false
640
+
641
+ # Stringify options that could be set as symbols
642
+ %w(ref branch tag revision).each{|k| options[k] = options[k].to_s if options[k] }
643
+
644
+ @uri = options["uri"]
645
+ @ref = options["ref"] || options["branch"] || options["tag"] || 'master'
646
+ @submodules = options["submodules"]
647
+ @name = options["name"]
648
+ @version = options["version"]
649
+
650
+ @update = false
651
+ @installed = nil
652
+ @local = false
653
+ end
654
+
655
+ def self.from_lock(options)
656
+ new(options.merge("uri" => options.delete("remote")))
657
+ end
658
+
659
+ def to_lock
660
+ out = "GIT\n"
661
+ out << " remote: #{@uri}\n"
662
+ out << " revision: #{revision}\n"
663
+ %w(ref branch tag submodules).each do |opt|
664
+ out << " #{opt}: #{options[opt]}\n" if options[opt]
665
+ end
666
+ out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
667
+ out << " specs:\n"
668
+ end
669
+
670
+ def eql?(o)
671
+ Git === o &&
672
+ uri == o.uri &&
673
+ ref == o.ref &&
674
+ name == o.name &&
675
+ version == o.version &&
676
+ submodules == o.submodules
677
+ end
678
+
679
+ alias == eql?
680
+
681
+ def to_s
682
+ at = if local?
683
+ path
684
+ elsif options["ref"]
685
+ shortref_for_display(options["ref"])
686
+ else
687
+ ref
688
+ end
689
+ "#{uri} (at #{at})"
690
+ end
691
+
692
+ def name
693
+ File.basename(@uri, '.git')
694
+ end
695
+
696
+ def install_path
697
+ @install_path ||= begin
698
+ git_scope = "#{base_name}-#{shortref_for_path(revision)}"
699
+
700
+ if Bundler.requires_sudo?
701
+ Bundler.user_bundle_path.join(Bundler.ruby_scope).join(git_scope)
702
+ else
703
+ Bundler.install_path.join(git_scope)
704
+ end
705
+ end
706
+ end
707
+
708
+ alias :path :install_path
709
+
710
+ def unlock!
711
+ git_proxy.revision = nil
712
+ end
713
+
714
+ def local_override!(path)
715
+ return false if local?
716
+
717
+ path = Pathname.new(path)
718
+ path = path.expand_path(Bundler.root) unless path.relative?
719
+
720
+ unless path.exist?
721
+ raise GitError, "Cannot use local override for #{name} because #{path} " \
722
+ "does not exist. Check `bundle config --delete` to remove the local override"
723
+ end
724
+
725
+ set_local!(path)
726
+
727
+ # Create a new git proxy without the cached revision
728
+ # so the Gemfile.lock always picks up the new revision.
729
+ @git_proxy = GitProxy.new(path, uri, ref)
730
+
731
+ if options["branch"] and git_proxy.branch != options["branch"]
732
+ raise GitError, "Local override for #{name} at #{path} is using branch " \
733
+ "#{git_proxy.branch} but Gemfile specifies #{options["branch"]}"
734
+ end
735
+
736
+ changed = cached_revision && cached_revision != git_proxy.revision
737
+
738
+ if changed && !git_proxy.contains?(cached_revision)
739
+ raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(cached_revision)} " \
740
+ "but the current branch in your local override for #{name} does not contain such commit. " \
741
+ "Please make sure your branch is up to date."
742
+ end
743
+
744
+ changed
745
+ end
746
+
747
+ # TODO: actually cache git specs
748
+ def specs(*)
749
+ if has_app_cache? && !local?
750
+ set_local!(app_cache_path)
751
+ end
752
+
753
+ if requires_checkout? && !@update
754
+ git_proxy.checkout
755
+ git_proxy.copy_to(install_path, submodules)
756
+ @update = true
757
+ end
758
+
759
+ local_specs
760
+ end
761
+
762
+ def install(spec)
763
+ Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
764
+ if requires_checkout? && !@installed
765
+ Bundler.ui.debug " * Checking out revision: #{ref}"
766
+ git_proxy.copy_to(install_path, submodules)
767
+ @installed = true
768
+ end
769
+ generate_bin(spec)
770
+ end
771
+
772
+ def cache(spec)
773
+ return unless Bundler.settings[:cache_all]
774
+ return if path.expand_path(Bundler.root).to_s.index(Bundler.root.to_s) == 0
775
+ cached!
776
+ FileUtils.rm_rf(app_cache_path)
777
+ git_proxy.checkout if requires_checkout?
778
+ git_proxy.copy_to(app_cache_path, @submodules)
779
+ FileUtils.rm_rf(app_cache_path.join(".git"))
780
+ end
781
+
782
+ def load_spec_files
783
+ super
784
+ rescue PathError, GitError
785
+ raise GitError, "#{to_s} is not checked out. Please run `bundle install`"
786
+ end
787
+
788
+ def cache_path
789
+ @cache_path ||= begin
790
+ git_scope = "#{base_name}-#{uri_hash}"
791
+
792
+ if Bundler.requires_sudo?
793
+ Bundler.user_bundle_path.join("cache/git", git_scope)
794
+ else
795
+ Bundler.cache.join("git", git_scope)
796
+ end
797
+ end
798
+ end
799
+
800
+ private
801
+
802
+ def set_local!(path)
803
+ @local = true
804
+ @local_specs = @git_proxy = nil
805
+ @cache_path = @install_path = path
806
+ end
807
+
808
+ def has_app_cache?
809
+ cached_revision && super
810
+ end
811
+
812
+ def app_cache_path
813
+ @app_cache_path ||= Bundler.app_cache.join("#{base_name}-#{shortref_for_path(cached_revision || revision)}")
814
+ end
815
+
816
+ def local?
817
+ @local
818
+ end
819
+
820
+ def requires_checkout?
821
+ allow_git_ops? && !local?
822
+ end
823
+
824
+ def base_name
825
+ File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*},''),".git")
826
+ end
827
+
828
+ def shortref_for_display(ref)
829
+ ref[0..6]
830
+ end
831
+
832
+ def shortref_for_path(ref)
833
+ ref[0..11]
834
+ end
835
+
836
+ def uri_hash
837
+ if uri =~ %r{^\w+://(\w+@)?}
838
+ # Downcase the domain component of the URI
839
+ # and strip off a trailing slash, if one is present
840
+ input = URI.parse(uri).normalize.to_s.sub(%r{/$},'')
841
+ else
842
+ # If there is no URI scheme, assume it is an ssh/git URI
843
+ input = uri
844
+ end
845
+ Digest::SHA1.hexdigest(input)
846
+ end
847
+
848
+ def allow_git_ops?
849
+ @allow_remote || @allow_cached
850
+ end
851
+
852
+ def cached_revision
853
+ options["revision"]
854
+ end
855
+
856
+ def revision
857
+ git_proxy.revision
858
+ end
859
+
860
+ def cached?
861
+ cache_path.exist?
862
+ end
863
+
864
+ def git_proxy
865
+ @git_proxy ||= GitProxy.new(cache_path, uri, ref, cached_revision){ allow_git_ops? }
866
+ end
867
+ end
868
+ end
869
+ end