bundler 1.1.5 → 1.2.0.pre

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 (46) hide show
  1. data/.travis.yml +10 -7
  2. data/CHANGELOG.md +27 -8
  3. data/ISSUES.md +20 -16
  4. data/README.md +2 -0
  5. data/Rakefile +6 -5
  6. data/bin/bundle +5 -3
  7. data/lib/bundler.rb +33 -13
  8. data/lib/bundler/capistrano.rb +1 -1
  9. data/lib/bundler/cli.rb +108 -20
  10. data/lib/bundler/definition.rb +76 -20
  11. data/lib/bundler/deployment.rb +4 -4
  12. data/lib/bundler/dsl.rb +26 -25
  13. data/lib/bundler/fetcher.rb +4 -13
  14. data/lib/bundler/gem_helper.rb +17 -5
  15. data/lib/bundler/graph.rb +10 -10
  16. data/lib/bundler/installer.rb +34 -2
  17. data/lib/bundler/ruby_version.rb +94 -0
  18. data/lib/bundler/runtime.rb +1 -1
  19. data/lib/bundler/settings.rb +18 -13
  20. data/lib/bundler/source.rb +316 -150
  21. data/lib/bundler/templates/newgem/README.md.tt +1 -1
  22. data/lib/bundler/vendor/thor/parser/options.rb +0 -3
  23. data/lib/bundler/vendored_thor.rb +1 -2
  24. data/lib/bundler/version.rb +1 -1
  25. data/man/bundle-config.ronn +8 -0
  26. data/man/bundle-install.ronn +6 -0
  27. data/man/bundle-package.ronn +3 -3
  28. data/man/gemfile.5.ronn +16 -3
  29. data/spec/bundler/dsl_spec.rb +23 -26
  30. data/spec/bundler/gem_helper_spec.rb +31 -0
  31. data/spec/cache/gems_spec.rb +10 -1
  32. data/spec/cache/git_spec.rb +114 -2
  33. data/spec/cache/path_spec.rb +85 -9
  34. data/spec/install/gems/dependency_api_spec.rb +21 -42
  35. data/spec/install/git_spec.rb +149 -1
  36. data/spec/lock/lockfile_spec.rb +1 -1
  37. data/spec/other/config_spec.rb +120 -22
  38. data/spec/other/newgem_spec.rb +2 -0
  39. data/spec/other/platform_spec.rb +881 -0
  40. data/spec/support/helpers.rb +12 -1
  41. data/spec/support/platforms.rb +33 -0
  42. data/spec/support/rubygems_hax/platform.rb +12 -1
  43. data/spec/update/gems_spec.rb +12 -0
  44. metadata +9 -8
  45. data/lib/bundler/vendored_persistent.rb +0 -3
  46. data/spec/install/deprecated_spec.rb +0 -36
@@ -0,0 +1,94 @@
1
+ module Bundler
2
+ class RubyVersion
3
+ attr_reader :version, :engine, :engine_version
4
+
5
+ def initialize(version, engine, engine_version)
6
+ # The parameters to this method must satisfy the
7
+ # following constraints, which are verified in
8
+ # the DSL:
9
+ #
10
+ # * If an engine is specified, an engine version
11
+ # must also be specified
12
+ # * If an engine version is specified, an engine
13
+ # must also be specified
14
+ # * If the engine is "ruby", the engine version
15
+ # must not be specified, or the engine version
16
+ # specified must match the version.
17
+
18
+ @version = version
19
+ @engine = engine || "ruby"
20
+ @engine_version = engine_version || version
21
+ end
22
+
23
+ def to_s
24
+ output = "ruby #{version}"
25
+ output << " (#{engine} #{engine_version})" unless engine == "ruby"
26
+
27
+ output
28
+ end
29
+
30
+ def ==(other)
31
+ version == other.version &&
32
+ engine == other.engine &&
33
+ engine_version == other.engine_version
34
+ end
35
+
36
+ # Returns a tuple of thsee things:
37
+ # [diff, this, other]
38
+ # The priority of attributes are
39
+ # 1. engine
40
+ # 2. ruby_version
41
+ # 3. engine_version
42
+ def diff(other)
43
+ if engine != other.engine
44
+ [ :engine, engine, other.engine ]
45
+ elsif version != other.version
46
+ [ :version, version, other.version ]
47
+ elsif engine_version != other.engine_version
48
+ [ :engine_version, engine_version, other.engine_version ]
49
+ else
50
+ nil
51
+ end
52
+ end
53
+ end
54
+
55
+ # A subclass of RubyVersion that implements version,
56
+ # engine and engine_version based upon the current
57
+ # information in the system. It can be used anywhere
58
+ # a RubyVersion object is expected, and can be
59
+ # compared with a RubyVersion object.
60
+ class SystemRubyVersion < RubyVersion
61
+ def initialize(*)
62
+ # override the default initialize, because
63
+ # we will implement version, engine and
64
+ # engine_version dynamically
65
+ end
66
+
67
+ def version
68
+ RUBY_VERSION
69
+ end
70
+
71
+ def engine
72
+ if defined?(RUBY_ENGINE)
73
+ RUBY_ENGINE
74
+ else
75
+ # not defined in ruby 1.8.7
76
+ "ruby"
77
+ end
78
+ end
79
+
80
+ def engine_version
81
+ case engine
82
+ when "ruby"
83
+ RUBY_VERSION
84
+ when "rbx"
85
+ Rubinius::VERSION
86
+ when "jruby"
87
+ JRUBY_VERSION
88
+ else
89
+ raise BundlerError, "That RUBY_ENGINE is not recognized"
90
+ nil
91
+ end
92
+ end
93
+ end
94
+ end
@@ -97,7 +97,7 @@ module Bundler
97
97
  def cache
98
98
  FileUtils.mkdir_p(cache_path) unless File.exists?(cache_path)
99
99
 
100
- Bundler.ui.info "Updating .gem files in vendor/cache"
100
+ Bundler.ui.info "Updating files in vendor/cache"
101
101
  specs.each do |spec|
102
102
  next if spec.name == 'bundler'
103
103
  spec.source.cache(spec) if spec.source.respond_to?(:cache)
@@ -2,8 +2,8 @@ module Bundler
2
2
  class Settings
3
3
  def initialize(root)
4
4
  @root = root
5
- @local_config = load_config(local_config_file)
6
- @global_config = load_config(global_config_file)
5
+ @local_config = (File.exist?(local_config_file) && yaml = YAML.load_file(local_config_file)) ? yaml : {}
6
+ @global_config = (File.exist?(global_config_file) && yaml = YAML.load_file(global_config_file)) ? yaml : {}
7
7
  end
8
8
 
9
9
  def [](key)
@@ -15,6 +15,8 @@ module Bundler
15
15
  set_key(key, value, @local_config, local_config_file)
16
16
  end
17
17
 
18
+ alias :set_local :[]=
19
+
18
20
  def delete(key)
19
21
  @local_config.delete(key_for(key))
20
22
  end
@@ -32,9 +34,19 @@ module Bundler
32
34
  end
33
35
  end
34
36
 
37
+ def local_overrides
38
+ repos = {}
39
+ all.each do |k|
40
+ if k =~ /^local\./
41
+ repos[$'] = self[k]
42
+ end
43
+ end
44
+ repos
45
+ end
46
+
35
47
  def locations(key)
48
+ key = key_for(key)
36
49
  locations = {}
37
-
38
50
  locations[:local] = @local_config[key] if @local_config.key?(key)
39
51
  locations[:env] = ENV[key] if ENV[key]
40
52
  locations[:global] = @global_config[key] if @global_config.key?(key)
@@ -71,8 +83,9 @@ module Bundler
71
83
 
72
84
  # @local_config["BUNDLE_PATH"] should be prioritized over ENV["BUNDLE_PATH"]
73
85
  def path
74
- path = ENV[key_for(:path)] || @global_config[key_for(:path)]
75
- return path if path && !@local_config.key?(key_for(:path))
86
+ key = key_for(:path)
87
+ path = ENV[key] || @global_config[key]
88
+ return path if path && !@local_config.key?(key)
76
89
 
77
90
  if path = self[:path]
78
91
  "#{path}/#{Bundler.ruby_scope}"
@@ -111,13 +124,5 @@ module Bundler
111
124
  def local_config_file
112
125
  Pathname.new("#{@root}/config")
113
126
  end
114
-
115
- def load_config(config_file)
116
- if config_file.exist? && !config_file.size.zero?
117
- yaml = YAML.load_file(config_file)
118
- end
119
- yaml || {}
120
- end
121
-
122
127
  end
123
128
  end
@@ -4,7 +4,7 @@ require "rubygems/installer"
4
4
  require "rubygems/spec_fetcher"
5
5
  require "rubygems/format"
6
6
  require "digest/sha1"
7
- require "open3"
7
+ require "fileutils"
8
8
 
9
9
  module Bundler
10
10
  module Source
@@ -264,12 +264,37 @@ module Bundler
264
264
  Bundler.rubygems.sources = old
265
265
  end
266
266
  end
267
-
268
267
  end
269
268
 
269
+
270
270
  class Path
271
- attr_reader :path, :options
272
- # Kind of a hack, but needed for the lock file parser
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
273
298
  attr_writer :name
274
299
  attr_accessor :version
275
300
 
@@ -287,8 +312,12 @@ module Bundler
287
312
  @path = @path.expand_path(Bundler.root) unless @path.relative?
288
313
  end
289
314
 
290
- @name = options["name"]
315
+ @name = options["name"]
291
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
292
321
  end
293
322
 
294
323
  def remote!
@@ -330,9 +359,46 @@ module Bundler
330
359
  File.basename(path.expand_path(Bundler.root).to_s)
331
360
  end
332
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
+
333
400
  def load_spec_files
334
401
  index = Index.new
335
-
336
402
  expanded_path = path.expand_path(Bundler.root)
337
403
 
338
404
  if File.directory?(expanded_path)
@@ -368,61 +434,10 @@ module Bundler
368
434
  index
369
435
  end
370
436
 
371
- def local_specs(*)
372
- @local_specs ||= load_spec_files
373
- end
374
-
375
- class Installer < Bundler::GemInstaller
376
- def initialize(spec, options = {})
377
- @spec = spec
378
- @bin_dir = Bundler.requires_sudo? ? "#{Bundler.tmp}/bin" : "#{Bundler.rubygems.gem_dir}/bin"
379
- @gem_dir = Bundler.rubygems.path(spec.full_gem_path)
380
- @wrappers = options[:wrappers] || true
381
- @env_shebang = options[:env_shebang] || true
382
- @format_executable = options[:format_executable] || false
383
- end
384
-
385
- def generate_bin
386
- return if spec.executables.nil? || spec.executables.empty?
387
-
388
- if Bundler.requires_sudo?
389
- FileUtils.mkdir_p("#{Bundler.tmp}/bin") unless File.exist?("#{Bundler.tmp}/bin")
390
- end
391
- super
392
- if Bundler.requires_sudo?
393
- Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/bin"
394
- spec.executables.each do |exe|
395
- Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.rubygems.gem_dir}/bin/"
396
- end
397
- end
398
- end
399
- end
400
-
401
- def install(spec)
402
- Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
403
- # Let's be honest, when we're working from a path, we can't
404
- # really expect native extensions to work because the whole point
405
- # is to just be able to modify what's in that path and go. So, let's
406
- # not put ourselves through the pain of actually trying to generate
407
- # the full gem.
408
- Installer.new(spec).generate_bin
409
- end
410
-
411
- alias specs local_specs
412
-
413
- def cache(spec)
414
- unless path.expand_path(Bundler.root).to_s.index(Bundler.root.to_s) == 0
415
- Bundler.ui.warn " * #{spec.name} at `#{path}` will not be cached."
416
- end
417
- end
418
-
419
- private
420
-
421
437
  def relative_path
422
438
  if path.to_s.match(%r{^#{Regexp.escape Bundler.root.to_s}})
423
439
  return path.relative_path_from(Bundler.root)
424
440
  end
425
-
426
441
  path
427
442
  end
428
443
 
@@ -442,7 +457,7 @@ module Bundler
442
457
 
443
458
  gem_file = Dir.chdir(gem_dir){ Gem::Builder.new(spec).build }
444
459
 
445
- installer = Installer.new(spec, :env_shebang => false)
460
+ installer = Path::Installer.new(spec, :env_shebang => false)
446
461
  run_hooks(:pre_install, installer)
447
462
  installer.build_extensions
448
463
  run_hooks(:post_build, installer)
@@ -479,20 +494,162 @@ module Bundler
479
494
  end
480
495
 
481
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
+
482
632
  attr_reader :uri, :ref, :options, :submodules
483
633
 
484
634
  def initialize(options)
485
- super
635
+ @options = options
636
+ @glob = options["glob"] || DEFAULT_GLOB
637
+
638
+ @allow_cached = false
639
+ @allow_remote = false
486
640
 
487
- # stringify options that could be set as symbols
641
+ # Stringify options that could be set as symbols
488
642
  %w(ref branch tag revision).each{|k| options[k] = options[k].to_s if options[k] }
489
643
 
490
644
  @uri = options["uri"]
491
645
  @ref = options["ref"] || options["branch"] || options["tag"] || 'master'
492
- @revision = options["revision"]
493
646
  @submodules = options["submodules"]
647
+ @name = options["name"]
648
+ @version = options["version"]
649
+
494
650
  @update = false
495
651
  @installed = nil
652
+ @local = false
496
653
  end
497
654
 
498
655
  def self.from_lock(options)
@@ -522,15 +679,21 @@ module Bundler
522
679
  alias == eql?
523
680
 
524
681
  def to_s
525
- sref = options["ref"] ? shortref_for_display(options["ref"]) : ref
526
- "#{uri} (at #{sref})"
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})"
527
690
  end
528
691
 
529
692
  def name
530
693
  File.basename(@uri, '.git')
531
694
  end
532
695
 
533
- def path
696
+ def install_path
534
697
  @install_path ||= begin
535
698
  git_scope = "#{base_name}-#{shortref_for_path(revision)}"
536
699
 
@@ -542,32 +705,86 @@ module Bundler
542
705
  end
543
706
  end
544
707
 
708
+ alias :path :install_path
709
+
545
710
  def unlock!
546
- @revision = nil
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 options["branch"]
721
+ raise GitError, "Cannot use local override for #{name} at #{path} because " \
722
+ ":branch is not specified in Gemfile. Specify a branch or check " \
723
+ "`bundle config --delete` to remove the local override"
724
+ end
725
+
726
+ unless path.exist?
727
+ raise GitError, "Cannot use local override for #{name} because #{path} " \
728
+ "does not exist. Check `bundle config --delete` to remove the local override"
729
+ end
730
+
731
+ set_local!(path)
732
+
733
+ # Create a new git proxy without the cached revision
734
+ # so the Gemfile.lock always picks up the new revision.
735
+ @git_proxy = GitProxy.new(path, uri, ref)
736
+
737
+ if git_proxy.branch != options["branch"]
738
+ raise GitError, "Local override for #{name} at #{path} is using branch " \
739
+ "#{git_proxy.branch} but Gemfile specifies #{options["branch"]}"
740
+ end
741
+
742
+ changed = cached_revision && cached_revision != git_proxy.revision
743
+
744
+ if changed && !git_proxy.contains?(cached_revision)
745
+ raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(cached_revision)} " \
746
+ "but the current branch in your local override for #{name} does not contain such commit. " \
747
+ "Please make sure your branch is up to date."
748
+ end
749
+
750
+ changed
547
751
  end
548
752
 
549
753
  # TODO: actually cache git specs
550
754
  def specs(*)
551
- if allow_git_ops? && !@update
552
- # Start by making sure the git cache is up to date
553
- cache
554
- checkout
755
+ if has_app_cache? && !local?
756
+ set_local!(app_cache_path)
757
+ end
758
+
759
+ if requires_checkout? && !@update
760
+ git_proxy.checkout
761
+ git_proxy.copy_to(install_path, submodules)
555
762
  @update = true
556
763
  end
764
+
557
765
  local_specs
558
766
  end
559
767
 
560
768
  def install(spec)
561
769
  Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
562
-
563
- unless @installed
770
+ if requires_checkout? && !@installed
564
771
  Bundler.ui.debug " * Checking out revision: #{ref}"
565
- checkout if allow_git_ops?
772
+ git_proxy.copy_to(install_path, submodules)
566
773
  @installed = true
567
774
  end
568
775
  generate_bin(spec)
569
776
  end
570
777
 
778
+ def cache(spec)
779
+ return unless Bundler.settings[:cache_all]
780
+ return if path.expand_path(Bundler.root).to_s.index(Bundler.root.to_s) == 0
781
+ cached!
782
+ FileUtils.rm_rf(app_cache_path)
783
+ git_proxy.checkout if requires_checkout?
784
+ git_proxy.copy_to(app_cache_path, @submodules)
785
+ FileUtils.rm_rf(app_cache_path.join(".git"))
786
+ end
787
+
571
788
  def load_spec_files
572
789
  super
573
790
  rescue PathError, GitError
@@ -585,23 +802,29 @@ module Bundler
585
802
  end
586
803
  end
587
804
  end
805
+
588
806
  private
589
807
 
590
- def git(command)
591
- if allow_git_ops?
592
- out = %x{git #{command}}
808
+ def set_local!(path)
809
+ @local = true
810
+ @local_specs = @git_proxy = nil
811
+ @cache_path = @install_path = path
812
+ end
813
+
814
+ def has_app_cache?
815
+ cached_revision && super
816
+ end
593
817
 
594
- if $?.exitstatus != 0
595
- msg = "Git error: command `git #{command}` in directory #{Dir.pwd} has failed."
596
- msg << "\nIf this error persists you could try removing the cache directory '#{cache_path}'" if cached?
597
- raise GitError, msg
598
- end
599
- out
600
- else
601
- raise GitError, "Bundler is trying to run a `git #{command}` at runtime. You probably need to run `bundle install`. However, " \
602
- "this error message could probably be more useful. Please submit a ticket at http://github.com/carlhuda/bundler/issues " \
603
- "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}"
604
- end
818
+ def app_cache_path
819
+ @app_cache_path ||= Bundler.app_cache.join("#{base_name}-#{shortref_for_path(cached_revision || revision)}")
820
+ end
821
+
822
+ def local?
823
+ @local
824
+ end
825
+
826
+ def requires_checkout?
827
+ allow_git_ops? && !local?
605
828
  end
606
829
 
607
830
  def base_name
@@ -628,82 +851,25 @@ module Bundler
628
851
  Digest::SHA1.hexdigest(input)
629
852
  end
630
853
 
631
- # Escape the URI for git commands
632
- def uri_escaped
633
- if Bundler::WINDOWS
634
- # Windows quoting requires double quotes only, with double quotes
635
- # inside the string escaped by being doubled.
636
- '"' + uri.gsub('"') {|s| '""'} + '"'
637
- else
638
- # Bash requires single quoted strings, with the single quotes escaped
639
- # by ending the string, escaping the quote, and restarting the string.
640
- "'" + uri.gsub("'") {|s| "'\\''"} + "'"
641
- end
642
- end
643
-
644
- def cache
645
- if cached?
646
- return if has_revision_cached?
647
- Bundler.ui.info "Updating #{uri}"
648
- in_cache do
649
- git %|fetch --force --quiet --tags #{uri_escaped} "refs/heads/*:refs/heads/*"|
650
- end
651
- else
652
- Bundler.ui.info "Fetching #{uri}"
653
- FileUtils.mkdir_p(cache_path.dirname)
654
- git %|clone #{uri_escaped} "#{cache_path}" --bare --no-hardlinks|
655
- end
656
- end
657
-
658
- def checkout
659
- unless File.exist?(path.join(".git"))
660
- FileUtils.mkdir_p(path.dirname)
661
- FileUtils.rm_rf(path)
662
- git %|clone --no-checkout "#{cache_path}" "#{path}"|
663
- File.chmod((0777 & ~File.umask), path)
664
- end
665
- Dir.chdir(path) do
666
- git %|fetch --force --quiet --tags "#{cache_path}"|
667
- git "reset --hard #{revision}"
668
-
669
- if @submodules
670
- git "submodule init"
671
- git "submodule update"
672
- end
673
- end
674
- end
675
-
676
- def has_revision_cached?
677
- return unless @revision
678
- in_cache { git %|cat-file -e #{@revision}| }
679
- true
680
- rescue GitError
681
- false
682
- end
683
-
684
854
  def allow_git_ops?
685
855
  @allow_remote || @allow_cached
686
856
  end
687
857
 
858
+ def cached_revision
859
+ options["revision"]
860
+ end
861
+
688
862
  def revision
689
- @revision ||= begin
690
- if allow_git_ops?
691
- in_cache { git("rev-parse #{ref}").strip }
692
- else
693
- raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application"
694
- end
695
- end
863
+ git_proxy.revision
696
864
  end
697
865
 
698
866
  def cached?
699
867
  cache_path.exist?
700
868
  end
701
869
 
702
- def in_cache(&blk)
703
- cache unless cached?
704
- Dir.chdir(cache_path, &blk)
870
+ def git_proxy
871
+ @git_proxy ||= GitProxy.new(cache_path, uri, ref, cached_revision){ allow_git_ops? }
705
872
  end
706
873
  end
707
-
708
874
  end
709
875
  end