summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-11-02 23:07:56 +0000
committerhsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-11-02 23:07:56 +0000
commit59c8d50653480bef3f24517296e6ddf937fdf6bc (patch)
treedf10aaf4f3307837fe3d1d129d66f6c0c7586bc5
parent7deb37777a230837e865e0a11fb8d7c1dc6d03ce (diff)
Added bundler as default gems. Revisit [Feature #12733]
* bin/*, lib/bundler/*, lib/bundler.rb, spec/bundler, man/*: Merge from latest stable branch of bundler/bundler repository and added workaround patches. I will backport them into upstream. * common.mk, defs/gmake.mk: Added `test-bundler` task for test suite of bundler. * tool/sync_default_gems.rb: Added sync task for bundler. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@65509 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--LEGAL30
-rw-r--r--NEWS6
-rwxr-xr-xbin/bundle31
-rwxr-xr-xbin/bundle_ruby60
-rwxr-xr-xbin/bundler4
-rw-r--r--common.mk16
-rw-r--r--defs/gmake.mk2
-rw-r--r--doc/maintainers.rdoc3
-rw-r--r--lib/bundler.gemspec55
-rw-r--r--lib/bundler.rb567
-rw-r--r--lib/bundler/build_metadata.rb44
-rw-r--r--lib/bundler/capistrano.rb22
-rw-r--r--lib/bundler/cli.rb790
-rw-r--r--lib/bundler/cli/add.rb35
-rw-r--r--lib/bundler/cli/binstubs.rb49
-rw-r--r--lib/bundler/cli/cache.rb36
-rw-r--r--lib/bundler/cli/check.rb38
-rw-r--r--lib/bundler/cli/clean.rb25
-rw-r--r--lib/bundler/cli/common.rb102
-rw-r--r--lib/bundler/cli/config.rb119
-rw-r--r--lib/bundler/cli/console.rb43
-rw-r--r--lib/bundler/cli/doctor.rb140
-rw-r--r--lib/bundler/cli/exec.rb105
-rw-r--r--lib/bundler/cli/gem.rb252
-rw-r--r--lib/bundler/cli/info.rb50
-rw-r--r--lib/bundler/cli/init.rb47
-rw-r--r--lib/bundler/cli/inject.rb60
-rw-r--r--lib/bundler/cli/install.rb217
-rw-r--r--lib/bundler/cli/issue.rb40
-rw-r--r--lib/bundler/cli/list.rb58
-rw-r--r--lib/bundler/cli/lock.rb63
-rw-r--r--lib/bundler/cli/open.rb26
-rw-r--r--lib/bundler/cli/outdated.rb266
-rw-r--r--lib/bundler/cli/package.rb49
-rw-r--r--lib/bundler/cli/platform.rb46
-rw-r--r--lib/bundler/cli/plugin.rb24
-rw-r--r--lib/bundler/cli/pristine.rb47
-rw-r--r--lib/bundler/cli/remove.rb18
-rw-r--r--lib/bundler/cli/show.rb75
-rw-r--r--lib/bundler/cli/update.rb91
-rw-r--r--lib/bundler/cli/viz.rb31
-rw-r--r--lib/bundler/compact_index_client.rb109
-rw-r--r--lib/bundler/compact_index_client/cache.rb118
-rw-r--r--lib/bundler/compact_index_client/updater.rb116
-rw-r--r--lib/bundler/compatibility_guard.rb14
-rw-r--r--lib/bundler/constants.rb7
-rw-r--r--lib/bundler/current_ruby.rb93
-rw-r--r--lib/bundler/definition.rb993
-rw-r--r--lib/bundler/dep_proxy.rb48
-rw-r--r--lib/bundler/dependency.rb139
-rw-r--r--lib/bundler/deployment.rb69
-rw-r--r--lib/bundler/deprecate.rb44
-rw-r--r--lib/bundler/dsl.rb615
-rw-r--r--lib/bundler/endpoint_specification.rb141
-rw-r--r--lib/bundler/env.rb155
-rw-r--r--lib/bundler/environment_preserver.rb59
-rw-r--r--lib/bundler/errors.rb158
-rw-r--r--lib/bundler/feature_flag.rb72
-rw-r--r--lib/bundler/fetcher.rb312
-rw-r--r--lib/bundler/fetcher/base.rb52
-rw-r--r--lib/bundler/fetcher/compact_index.rb126
-rw-r--r--lib/bundler/fetcher/dependency.rb82
-rw-r--r--lib/bundler/fetcher/downloader.rb84
-rw-r--r--lib/bundler/fetcher/index.rb52
-rw-r--r--lib/bundler/friendly_errors.rb131
-rw-r--r--lib/bundler/gem_helper.rb202
-rw-r--r--lib/bundler/gem_helpers.rb101
-rw-r--r--lib/bundler/gem_remote_fetcher.rb43
-rw-r--r--lib/bundler/gem_tasks.rb7
-rw-r--r--lib/bundler/gem_version_promoter.rb190
-rw-r--r--lib/bundler/gemdeps.rb29
-rw-r--r--lib/bundler/graph.rb152
-rw-r--r--lib/bundler/index.rb213
-rw-r--r--lib/bundler/injector.rb253
-rw-r--r--lib/bundler/inline.rb74
-rw-r--r--lib/bundler/installer.rb318
-rw-r--r--lib/bundler/installer/gem_installer.rb85
-rw-r--r--lib/bundler/installer/parallel_installer.rb233
-rw-r--r--lib/bundler/installer/standalone.rb53
-rw-r--r--lib/bundler/lazy_specification.rb123
-rw-r--r--lib/bundler/lockfile_generator.rb95
-rw-r--r--lib/bundler/lockfile_parser.rb256
-rw-r--r--lib/bundler/match_platform.rb24
-rw-r--r--lib/bundler/mirror.rb223
-rw-r--r--lib/bundler/plugin.rb292
-rw-r--r--lib/bundler/plugin/api.rb81
-rw-r--r--lib/bundler/plugin/api/source.rb306
-rw-r--r--lib/bundler/plugin/dsl.rb53
-rw-r--r--lib/bundler/plugin/events.rb61
-rw-r--r--lib/bundler/plugin/index.rb162
-rw-r--r--lib/bundler/plugin/installer.rb96
-rw-r--r--lib/bundler/plugin/installer/git.rb38
-rw-r--r--lib/bundler/plugin/installer/rubygems.rb27
-rw-r--r--lib/bundler/plugin/source_list.rb27
-rw-r--r--lib/bundler/process_lock.rb24
-rw-r--r--lib/bundler/psyched_yaml.rb37
-rw-r--r--lib/bundler/remote_specification.rb114
-rw-r--r--lib/bundler/resolver.rb373
-rw-r--r--lib/bundler/resolver/spec_group.rb106
-rw-r--r--lib/bundler/retry.rb66
-rw-r--r--lib/bundler/ruby_dsl.rb18
-rw-r--r--lib/bundler/ruby_version.rb152
-rw-r--r--lib/bundler/rubygems_ext.rb210
-rw-r--r--lib/bundler/rubygems_gem_installer.rb99
-rw-r--r--lib/bundler/rubygems_integration.rb898
-rw-r--r--lib/bundler/runtime.rb322
-rw-r--r--lib/bundler/settings.rb463
-rw-r--r--lib/bundler/settings/validator.rb102
-rw-r--r--lib/bundler/setup.rb28
-rw-r--r--lib/bundler/shared_helpers.rb384
-rw-r--r--lib/bundler/similarity_detector.rb63
-rw-r--r--lib/bundler/source.rb94
-rw-r--r--lib/bundler/source/gemspec.rb18
-rw-r--r--lib/bundler/source/git.rb329
-rw-r--r--lib/bundler/source/git/git_proxy.rb262
-rw-r--r--lib/bundler/source/metadata.rb63
-rw-r--r--lib/bundler/source/path.rb249
-rw-r--r--lib/bundler/source/path/installer.rb74
-rw-r--r--lib/bundler/source/rubygems.rb539
-rw-r--r--lib/bundler/source/rubygems/remote.rb69
-rw-r--r--lib/bundler/source_list.rb186
-rw-r--r--lib/bundler/spec_set.rb192
-rw-r--r--lib/bundler/ssl_certs/.document1
-rw-r--r--lib/bundler/ssl_certs/certificate_manager.rb66
-rw-r--r--lib/bundler/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem21
-rw-r--r--lib/bundler/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem23
-rw-r--r--lib/bundler/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem25
-rw-r--r--lib/bundler/stub_specification.rb108
-rw-r--r--lib/bundler/templates/.document1
-rw-r--r--lib/bundler/templates/Executable29
-rw-r--r--lib/bundler/templates/Executable.bundler105
-rw-r--r--lib/bundler/templates/Executable.standalone14
-rw-r--r--lib/bundler/templates/Gemfile7
-rw-r--r--lib/bundler/templates/gems.rb8
-rw-r--r--lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt74
-rw-r--r--lib/bundler/templates/newgem/Gemfile.tt6
-rw-r--r--lib/bundler/templates/newgem/LICENSE.txt.tt21
-rw-r--r--lib/bundler/templates/newgem/README.md.tt47
-rw-r--r--lib/bundler/templates/newgem/Rakefile.tt29
-rw-r--r--lib/bundler/templates/newgem/bin/console.tt14
-rw-r--r--lib/bundler/templates/newgem/bin/setup.tt8
-rw-r--r--lib/bundler/templates/newgem/exe/newgem.tt3
-rw-r--r--lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt3
-rw-r--r--lib/bundler/templates/newgem/ext/newgem/newgem.c.tt9
-rw-r--r--lib/bundler/templates/newgem/ext/newgem/newgem.h.tt6
-rw-r--r--lib/bundler/templates/newgem/gitignore.tt20
-rw-r--r--lib/bundler/templates/newgem/lib/newgem.rb.tt13
-rw-r--r--lib/bundler/templates/newgem/lib/newgem/version.rb.tt7
-rw-r--r--lib/bundler/templates/newgem/newgem.gemspec.tt55
-rw-r--r--lib/bundler/templates/newgem/rspec.tt3
-rw-r--r--lib/bundler/templates/newgem/spec/newgem_spec.rb.tt9
-rw-r--r--lib/bundler/templates/newgem/spec/spec_helper.rb.tt14
-rw-r--r--lib/bundler/templates/newgem/test/newgem_test.rb.tt11
-rw-r--r--lib/bundler/templates/newgem/test/test_helper.rb.tt4
-rw-r--r--lib/bundler/templates/newgem/travis.yml.tt7
-rw-r--r--lib/bundler/ui.rb9
-rw-r--r--lib/bundler/ui/rg_proxy.rb19
-rw-r--r--lib/bundler/ui/shell.rb146
-rw-r--r--lib/bundler/ui/silent.rb69
-rw-r--r--lib/bundler/uri_credentials_filter.rb37
-rw-r--r--lib/bundler/vendor/fileutils/lib/fileutils.rb1638
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo.rb12
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb26
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb57
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb81
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb223
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb36
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb66
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb62
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb63
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb61
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb126
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb46
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb36
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb136
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/errors.rb143
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb6
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb101
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb67
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb837
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb46
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/state.rb58
-rw-r--r--lib/bundler/vendor/net-http-persistent/lib/net/http/faster.rb27
-rw-r--r--lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb1233
-rw-r--r--lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/ssl_reuse.rb129
-rw-r--r--lib/bundler/vendor/thor/lib/thor.rb509
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions.rb321
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions/create_file.rb104
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions/create_link.rb60
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions/directory.rb118
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb143
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb364
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb109
-rw-r--r--lib/bundler/vendor/thor/lib/thor/base.rb679
-rw-r--r--lib/bundler/vendor/thor/lib/thor/command.rb135
-rw-r--r--lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb97
-rw-r--r--lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb12
-rw-r--r--lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb129
-rw-r--r--lib/bundler/vendor/thor/lib/thor/error.rb32
-rw-r--r--lib/bundler/vendor/thor/lib/thor/group.rb281
-rw-r--r--lib/bundler/vendor/thor/lib/thor/invocation.rb177
-rw-r--r--lib/bundler/vendor/thor/lib/thor/line_editor.rb17
-rw-r--r--lib/bundler/vendor/thor/lib/thor/line_editor/basic.rb37
-rw-r--r--lib/bundler/vendor/thor/lib/thor/line_editor/readline.rb88
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser.rb4
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/argument.rb70
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/arguments.rb175
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/option.rb146
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/options.rb221
-rw-r--r--lib/bundler/vendor/thor/lib/thor/rake_compat.rb71
-rw-r--r--lib/bundler/vendor/thor/lib/thor/runner.rb324
-rw-r--r--lib/bundler/vendor/thor/lib/thor/shell.rb81
-rw-r--r--lib/bundler/vendor/thor/lib/thor/shell/basic.rb437
-rw-r--r--lib/bundler/vendor/thor/lib/thor/shell/color.rb149
-rw-r--r--lib/bundler/vendor/thor/lib/thor/shell/html.rb126
-rw-r--r--lib/bundler/vendor/thor/lib/thor/util.rb268
-rw-r--r--lib/bundler/vendor/thor/lib/thor/version.rb3
-rw-r--r--lib/bundler/vendored_fileutils.rb9
-rw-r--r--lib/bundler/vendored_molinillo.rb4
-rw-r--r--lib/bundler/vendored_persistent.rb52
-rw-r--r--lib/bundler/vendored_thor.rb8
-rw-r--r--lib/bundler/version.rb28
-rw-r--r--lib/bundler/version_ranges.rb76
-rw-r--r--lib/bundler/vlad.rb17
-rw-r--r--lib/bundler/worker.rb106
-rw-r--r--lib/bundler/yaml_serializer.rb90
-rw-r--r--man/bundle-add.158
-rw-r--r--man/bundle-add.1.txt52
-rw-r--r--man/bundle-add.ronn40
-rw-r--r--man/bundle-binstubs.140
-rw-r--r--man/bundle-binstubs.1.txt48
-rw-r--r--man/bundle-binstubs.ronn43
-rw-r--r--man/bundle-check.131
-rw-r--r--man/bundle-check.1.txt33
-rw-r--r--man/bundle-check.ronn26
-rw-r--r--man/bundle-clean.124
-rw-r--r--man/bundle-clean.1.txt26
-rw-r--r--man/bundle-clean.ronn18
-rw-r--r--man/bundle-config.1481
-rw-r--r--man/bundle-config.1.txt513
-rw-r--r--man/bundle-config.ronn397
-rw-r--r--man/bundle-doctor.144
-rw-r--r--man/bundle-doctor.1.txt44
-rw-r--r--man/bundle-doctor.ronn33
-rw-r--r--man/bundle-exec.1165
-rw-r--r--man/bundle-exec.1.txt178
-rw-r--r--man/bundle-exec.ronn152
-rw-r--r--man/bundle-gem.180
-rw-r--r--man/bundle-gem.1.txt91
-rw-r--r--man/bundle-gem.ronn78
-rw-r--r--man/bundle-info.120
-rw-r--r--man/bundle-info.1.txt21
-rw-r--r--man/bundle-info.ronn17
-rw-r--r--man/bundle-init.125
-rw-r--r--man/bundle-init.1.txt34
-rw-r--r--man/bundle-init.ronn29
-rw-r--r--man/bundle-inject.133
-rw-r--r--man/bundle-inject.1.txt32
-rw-r--r--man/bundle-inject.ronn22
-rw-r--r--man/bundle-install.1311
-rw-r--r--man/bundle-install.1.txt401
-rw-r--r--man/bundle-install.ronn378
-rw-r--r--man/bundle-list.150
-rw-r--r--man/bundle-list.1.txt43
-rw-r--r--man/bundle-list.ronn33
-rw-r--r--man/bundle-lock.184
-rw-r--r--man/bundle-lock.1.txt93
-rw-r--r--man/bundle-lock.ronn94
-rw-r--r--man/bundle-open.132
-rw-r--r--man/bundle-open.1.txt29
-rw-r--r--man/bundle-open.ronn19
-rw-r--r--man/bundle-outdated.1155
-rw-r--r--man/bundle-outdated.1.txt131
-rw-r--r--man/bundle-outdated.ronn111
-rw-r--r--man/bundle-package.155
-rw-r--r--man/bundle-package.1.txt79
-rw-r--r--man/bundle-package.ronn72
-rw-r--r--man/bundle-platform.161
-rw-r--r--man/bundle-platform.1.txt57
-rw-r--r--man/bundle-platform.ronn42
-rw-r--r--man/bundle-pristine.134
-rw-r--r--man/bundle-pristine.1.txt44
-rw-r--r--man/bundle-pristine.ronn34
-rw-r--r--man/bundle-remove.131
-rw-r--r--man/bundle-remove.1.txt34
-rw-r--r--man/bundle-remove.ronn23
-rw-r--r--man/bundle-show.123
-rw-r--r--man/bundle-show.1.txt27
-rw-r--r--man/bundle-show.ronn21
-rw-r--r--man/bundle-update.1394
-rw-r--r--man/bundle-update.1.txt390
-rw-r--r--man/bundle-update.ronn350
-rw-r--r--man/bundle-viz.139
-rw-r--r--man/bundle-viz.1.txt39
-rw-r--r--man/bundle-viz.ronn30
-rw-r--r--man/bundle.1132
-rw-r--r--man/bundle.1.txt113
-rw-r--r--man/bundle.ronn108
-rw-r--r--man/gemfile.5689
-rw-r--r--man/gemfile.5.ronn521
-rw-r--r--man/gemfile.5.txt653
-rw-r--r--spec/README.md11
-rw-r--r--spec/bundler/bundler/bundler_spec.rb490
-rw-r--r--spec/bundler/bundler/cli_spec.rb173
-rw-r--r--spec/bundler/bundler/compact_index_client/updater_spec.rb55
-rw-r--r--spec/bundler/bundler/definition_spec.rb358
-rw-r--r--spec/bundler/bundler/dep_proxy_spec.rb22
-rw-r--r--spec/bundler/bundler/dsl_spec.rb289
-rw-r--r--spec/bundler/bundler/endpoint_specification_spec.rb71
-rw-r--r--spec/bundler/bundler/env_spec.rb151
-rw-r--r--spec/bundler/bundler/environment_preserver_spec.rb79
-rw-r--r--spec/bundler/bundler/fetcher/base_spec.rb76
-rw-r--r--spec/bundler/bundler/fetcher/compact_index_spec.rb103
-rw-r--r--spec/bundler/bundler/fetcher/dependency_spec.rb287
-rw-r--r--spec/bundler/bundler/fetcher/downloader_spec.rb250
-rw-r--r--spec/bundler/bundler/fetcher/index_spec.rb99
-rw-r--r--spec/bundler/bundler/fetcher_spec.rb161
-rw-r--r--spec/bundler/bundler/friendly_errors_spec.rb270
-rw-r--r--spec/bundler/bundler/gem_helper_spec.rb351
-rw-r--r--spec/bundler/bundler/gem_version_promoter_spec.rb179
-rw-r--r--spec/bundler/bundler/index_spec.rb36
-rw-r--r--spec/bundler/bundler/installer/gem_installer_spec.rb29
-rw-r--r--spec/bundler/bundler/installer/parallel_installer_spec.rb47
-rw-r--r--spec/bundler/bundler/installer/spec_installation_spec.rb62
-rw-r--r--spec/bundler/bundler/lockfile_parser_spec.rb153
-rw-r--r--spec/bundler/bundler/mirror_spec.rb329
-rw-r--r--spec/bundler/bundler/plugin/api/source_spec.rb82
-rw-r--r--spec/bundler/bundler/plugin/api_spec.rb83
-rw-r--r--spec/bundler/bundler/plugin/dsl_spec.rb38
-rw-r--r--spec/bundler/bundler/plugin/events_spec.rb18
-rw-r--r--spec/bundler/bundler/plugin/index_spec.rb186
-rw-r--r--spec/bundler/bundler/plugin/installer_spec.rb104
-rw-r--r--spec/bundler/bundler/plugin/source_list_spec.rb25
-rw-r--r--spec/bundler/bundler/plugin_spec.rb309
-rw-r--r--spec/bundler/bundler/psyched_yaml_spec.rb9
-rw-r--r--spec/bundler/bundler/remote_specification_spec.rb187
-rw-r--r--spec/bundler/bundler/retry_spec.rb81
-rw-r--r--spec/bundler/bundler/ruby_dsl_spec.rb95
-rw-r--r--spec/bundler/bundler/ruby_version_spec.rb524
-rw-r--r--spec/bundler/bundler/rubygems_integration_spec.rb114
-rw-r--r--spec/bundler/bundler/settings/validator_spec.rb111
-rw-r--r--spec/bundler/bundler/settings_spec.rb326
-rw-r--r--spec/bundler/bundler/shared_helpers_spec.rb504
-rw-r--r--spec/bundler/bundler/source/git/git_proxy_spec.rb140
-rw-r--r--spec/bundler/bundler/source/git_spec.rb28
-rw-r--r--spec/bundler/bundler/source/path_spec.rb31
-rw-r--r--spec/bundler/bundler/source/rubygems/remote_spec.rb162
-rw-r--r--spec/bundler/bundler/source/rubygems_spec.rb33
-rw-r--r--spec/bundler/bundler/source_list_spec.rb463
-rw-r--r--spec/bundler/bundler/source_spec.rb154
-rw-r--r--spec/bundler/bundler/spec_set_spec.rb77
-rw-r--r--spec/bundler/bundler/ssl_certs/certificate_manager_spec.rb140
-rw-r--r--spec/bundler/bundler/stub_specification_spec.rb24
-rw-r--r--spec/bundler/bundler/ui/shell_spec.rb73
-rw-r--r--spec/bundler/bundler/ui_spec.rb41
-rw-r--r--spec/bundler/bundler/uri_credentials_filter_spec.rb127
-rw-r--r--spec/bundler/bundler/vendored_persistent_spec.rb78
-rw-r--r--spec/bundler/bundler/version_ranges_spec.rb37
-rw-r--r--spec/bundler/bundler/worker_spec.rb22
-rw-r--r--spec/bundler/bundler/yaml_serializer_spec.rb194
-rw-r--r--spec/bundler/cache/cache_path_spec.rb32
-rw-r--r--spec/bundler/cache/gems_spec.rb304
-rw-r--r--spec/bundler/cache/git_spec.rb214
-rw-r--r--spec/bundler/cache/path_spec.rb139
-rw-r--r--spec/bundler/cache/platform_spec.rb49
-rw-r--r--spec/bundler/commands/add_spec.rb217
-rw-r--r--spec/bundler/commands/binstubs_spec.rb453
-rw-r--r--spec/bundler/commands/check_spec.rb354
-rw-r--r--spec/bundler/commands/clean_spec.rb771
-rw-r--r--spec/bundler/commands/config_spec.rb384
-rw-r--r--spec/bundler/commands/console_spec.rb106
-rw-r--r--spec/bundler/commands/doctor_spec.rb110
-rw-r--r--spec/bundler/commands/exec_spec.rb858
-rw-r--r--spec/bundler/commands/help_spec.rb98
-rw-r--r--spec/bundler/commands/info_spec.rb57
-rw-r--r--spec/bundler/commands/init_spec.rb181
-rw-r--r--spec/bundler/commands/inject_spec.rb117
-rw-r--r--spec/bundler/commands/install_spec.rb587
-rw-r--r--spec/bundler/commands/issue_spec.rb16
-rw-r--r--spec/bundler/commands/licenses_spec.rb38
-rw-r--r--spec/bundler/commands/list_spec.rb131
-rw-r--r--spec/bundler/commands/lock_spec.rb322
-rw-r--r--spec/bundler/commands/newgem_spec.rb912
-rw-r--r--spec/bundler/commands/open_spec.rb92
-rw-r--r--spec/bundler/commands/outdated_spec.rb782
-rw-r--r--spec/bundler/commands/package_spec.rb306
-rw-r--r--spec/bundler/commands/pristine_spec.rb192
-rw-r--r--spec/bundler/commands/remove_spec.rb583
-rw-r--r--spec/bundler/commands/show_spec.rb244
-rw-r--r--spec/bundler/commands/update_spec.rb943
-rw-r--r--spec/bundler/commands/version_spec.rb39
-rw-r--r--spec/bundler/commands/viz_spec.rb149
-rw-r--r--spec/bundler/install/allow_offline_install_spec.rb92
-rw-r--r--spec/bundler/install/binstubs_spec.rb43
-rw-r--r--spec/bundler/install/bundler_spec.rb177
-rw-r--r--spec/bundler/install/deploy_spec.rb423
-rw-r--r--spec/bundler/install/failure_spec.rb125
-rw-r--r--spec/bundler/install/gemfile/eval_gemfile_spec.rb82
-rw-r--r--spec/bundler/install/gemfile/gemspec_spec.rb672
-rw-r--r--spec/bundler/install/gemfile/git_spec.rb1351
-rw-r--r--spec/bundler/install/gemfile/groups_spec.rb384
-rw-r--r--spec/bundler/install/gemfile/install_if.rb44
-rw-r--r--spec/bundler/install/gemfile/lockfile_spec.rb48
-rw-r--r--spec/bundler/install/gemfile/path_spec.rb630
-rw-r--r--spec/bundler/install/gemfile/platform_spec.rb426
-rw-r--r--spec/bundler/install/gemfile/ruby_spec.rb108
-rw-r--r--spec/bundler/install/gemfile/sources_spec.rb619
-rw-r--r--spec/bundler/install/gemfile/specific_platform_spec.rb114
-rw-r--r--spec/bundler/install/gemfile_spec.rb145
-rw-r--r--spec/bundler/install/gems/compact_index_spec.rb940
-rw-r--r--spec/bundler/install/gems/dependency_api_spec.rb760
-rw-r--r--spec/bundler/install/gems/env_spec.rb107
-rw-r--r--spec/bundler/install/gems/flex_spec.rb351
-rw-r--r--spec/bundler/install/gems/mirror_spec.rb39
-rw-r--r--spec/bundler/install/gems/native_extensions_spec.rb90
-rw-r--r--spec/bundler/install/gems/post_install_spec.rb150
-rw-r--r--spec/bundler/install/gems/resolving_spec.rb195
-rw-r--r--spec/bundler/install/gems/standalone_spec.rb337
-rw-r--r--spec/bundler/install/gems/sudo_spec.rb178
-rw-r--r--spec/bundler/install/gems/win32_spec.rb26
-rw-r--r--spec/bundler/install/gemspecs_spec.rb154
-rw-r--r--spec/bundler/install/git_spec.rb65
-rw-r--r--spec/bundler/install/global_cache_spec.rb235
-rw-r--r--spec/bundler/install/path_spec.rb256
-rw-r--r--spec/bundler/install/post_bundle_message_spec.rb206
-rw-r--r--spec/bundler/install/prereleases_spec.rb41
-rw-r--r--spec/bundler/install/process_lock_spec.rb35
-rw-r--r--spec/bundler/install/redownload_spec.rb92
-rw-r--r--spec/bundler/install/security_policy_spec.rb77
-rw-r--r--spec/bundler/install/yanked_spec.rb71
-rw-r--r--spec/bundler/lock/git_spec.rb34
-rw-r--r--spec/bundler/lock/lockfile_bundler_1_spec.rb1386
-rw-r--r--spec/bundler/lock/lockfile_spec.rb1425
-rw-r--r--spec/bundler/other/bundle_ruby_spec.rb155
-rw-r--r--spec/bundler/other/cli_dispatch_spec.rb29
-rw-r--r--spec/bundler/other/compatibility_guard_spec.rb25
-rw-r--r--spec/bundler/other/ext_spec.rb66
-rw-r--r--spec/bundler/other/major_deprecation_spec.rb282
-rw-r--r--spec/bundler/other/platform_spec.rb1312
-rw-r--r--spec/bundler/other/ssl_cert_spec.rb18
-rw-r--r--spec/bundler/plugins/command_spec.rb80
-rw-r--r--spec/bundler/plugins/hook_spec.rb109
-rw-r--r--spec/bundler/plugins/install_spec.rb257
-rw-r--r--spec/bundler/plugins/source/example_spec.rb505
-rw-r--r--spec/bundler/plugins/source_spec.rb108
-rw-r--r--spec/bundler/quality_spec.rb266
-rw-r--r--spec/bundler/realworld/dependency_api_spec.rb44
-rw-r--r--spec/bundler/realworld/double_check_spec.rb40
-rw-r--r--spec/bundler/realworld/edgecases_spec.rb382
-rw-r--r--spec/bundler/realworld/gemfile_source_header_spec.rb53
-rw-r--r--spec/bundler/realworld/mirror_probe_spec.rb144
-rw-r--r--spec/bundler/realworld/parallel_spec.rb74
-rw-r--r--spec/bundler/resolver/basic_spec.rb308
-rw-r--r--spec/bundler/resolver/platform_spec.rb100
-rw-r--r--spec/bundler/runtime/executable_spec.rb190
-rw-r--r--spec/bundler/runtime/gem_tasks_spec.rb44
-rw-r--r--spec/bundler/runtime/inline_spec.rb266
-rw-r--r--spec/bundler/runtime/load_spec.rb111
-rw-r--r--spec/bundler/runtime/platform_spec.rb150
-rw-r--r--spec/bundler/runtime/require_spec.rb452
-rw-r--r--spec/bundler/runtime/setup_spec.rb1445
-rw-r--r--spec/bundler/runtime/with_clean_env_spec.rb151
-rw-r--r--spec/bundler/spec_helper.rb161
-rw-r--r--spec/bundler/support/artifice/compact_index.rb122
-rw-r--r--spec/bundler/support/artifice/compact_index_api_missing.rb18
-rw-r--r--spec/bundler/support/artifice/compact_index_basic_authentication.rb15
-rw-r--r--spec/bundler/support/artifice/compact_index_checksum_mismatch.rb16
-rw-r--r--spec/bundler/support/artifice/compact_index_concurrent_download.rb32
-rw-r--r--spec/bundler/support/artifice/compact_index_creds_diff_host.rb39
-rw-r--r--spec/bundler/support/artifice/compact_index_extra.rb37
-rw-r--r--spec/bundler/support/artifice/compact_index_extra_api.rb52
-rw-r--r--spec/bundler/support/artifice/compact_index_extra_api_missing.rb17
-rw-r--r--spec/bundler/support/artifice/compact_index_extra_missing.rb17
-rw-r--r--spec/bundler/support/artifice/compact_index_forbidden.rb13
-rw-r--r--spec/bundler/support/artifice/compact_index_host_redirect.rb21
-rw-r--r--spec/bundler/support/artifice/compact_index_no_gem.rb13
-rw-r--r--spec/bundler/support/artifice/compact_index_partial_update.rb38
-rw-r--r--spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb34
-rw-r--r--spec/bundler/support/artifice/compact_index_redirects.rb21
-rw-r--r--spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb20
-rw-r--r--spec/bundler/support/artifice/compact_index_wrong_dependencies.rb17
-rw-r--r--spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb20
-rw-r--r--spec/bundler/support/artifice/endopint_marshal_fail_basic_authentication.rb15
-rw-r--r--spec/bundler/support/artifice/endpoint.rb100
-rw-r--r--spec/bundler/support/artifice/endpoint_500.rb19
-rw-r--r--spec/bundler/support/artifice/endpoint_api_forbidden.rb13
-rw-r--r--spec/bundler/support/artifice/endpoint_api_missing.rb18
-rw-r--r--spec/bundler/support/artifice/endpoint_basic_authentication.rb15
-rw-r--r--spec/bundler/support/artifice/endpoint_creds_diff_host.rb39
-rw-r--r--spec/bundler/support/artifice/endpoint_extra.rb33
-rw-r--r--spec/bundler/support/artifice/endpoint_extra_api.rb34
-rw-r--r--spec/bundler/support/artifice/endpoint_extra_missing.rb17
-rw-r--r--spec/bundler/support/artifice/endpoint_fallback.rb19
-rw-r--r--spec/bundler/support/artifice/endpoint_host_redirect.rb17
-rw-r--r--spec/bundler/support/artifice/endpoint_marshal_fail.rb13
-rw-r--r--spec/bundler/support/artifice/endpoint_mirror_source.rb15
-rw-r--r--spec/bundler/support/artifice/endpoint_redirect.rb17
-rw-r--r--spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb20
-rw-r--r--spec/bundler/support/artifice/endpoint_timeout.rb15
-rw-r--r--spec/bundler/support/artifice/fail.rb39
-rw-r--r--spec/bundler/support/artifice/vcr.rb158
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies-gems=bundler/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies/HEAD/request6
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies/HEAD/response24
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/gems/bundler-1.12.3.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/quick/Marshal.4.8/bundler-1.12.3.gemspec.rz/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/specs.4.8.gz/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RedCloth/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/allison/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ansi/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/archive-tar-minitar/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bacon/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-extras/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-git/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rcov/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rspec/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rubyforge/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-zentest/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/camping/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/echoe/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine-le/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fcgi/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/git/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/highline/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http_parser.rb/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/language/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loquacious/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mab/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/markaby/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaid/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitar-cli/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitar/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/newgem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerbar/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/preforker/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rcov/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexical/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubigen/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-openid/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-yadis/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/shotgun/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spicycode-rcov/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/syntax/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tenderlove-frex/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/termios/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-spec/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-1.12.3.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.3.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.8.1.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.0.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.12.1.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-1.0.1.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-1.6.5.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.1.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-1.5.3.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-3.3.3.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.5.3.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-1.4.8.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/thread_safe-0.3.6.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.7.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-1.2.3.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/quick/Marshal.4.8/bundler-1.12.3.gemspec.rz/GET/request7
-rw-r--r--spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/specs.4.8.gz/GET/request7
-rw-r--r--spec/bundler/support/artifice/windows.rb49
-rw-r--r--spec/bundler/support/builders.rb819
-rw-r--r--spec/bundler/support/code_climate.rb26
-rw-r--r--spec/bundler/support/command_execution.rb57
-rw-r--r--spec/bundler/support/hax.rb67
-rw-r--r--spec/bundler/support/helpers.rb600
-rw-r--r--spec/bundler/support/indexes.rb421
-rw-r--r--spec/bundler/support/less_than_proc.rb20
-rw-r--r--spec/bundler/support/manpages.rb14
-rw-r--r--spec/bundler/support/matchers.rb246
-rw-r--r--spec/bundler/support/path.rb126
-rw-r--r--spec/bundler/support/permissions.rb12
-rw-r--r--spec/bundler/support/platforms.rb116
-rw-r--r--spec/bundler/support/rubygems_ext.rb71
-rw-r--r--spec/bundler/support/silent_logger.rb10
-rw-r--r--spec/bundler/support/sometimes.rb57
-rw-r--r--spec/bundler/support/streams.rb19
-rw-r--r--spec/bundler/support/sudo.rb18
-rw-r--r--spec/bundler/support/the_bundle.rb37
-rw-r--r--spec/bundler/update/gemfile_spec.rb66
-rw-r--r--spec/bundler/update/gems/post_install_spec.rb76
-rw-r--r--spec/bundler/update/git_spec.rb374
-rw-r--r--spec/bundler/update/path_spec.rb18
-rw-r--r--spec/bundler/update/redownload_spec.rb36
-rw-r--r--tool/sync_default_gems.rb9
855 files changed, 83604 insertions, 1 deletions
diff --git a/LEGAL b/LEGAL
index 0479751691..4ff1726cba 100644
--- a/LEGAL
+++ b/LEGAL
@@ -848,3 +848,33 @@ test/rubygems:
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
+
+lib/bundler:
+lib/bundler.rb:
+lib/bundler.gemspec:
+spec/bundler:
+man/bundle-*,gemfile.*:
+
+ Portions copyright (c) 2010 Andre Arko
+ Portions copyright (c) 2009 Engine Yard
+
+ MIT License
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/NEWS b/NEWS
index 33d347c176..91637c523b 100644
--- a/NEWS
+++ b/NEWS
@@ -391,6 +391,12 @@ sufficient information, see the ChangeLog file or Redmine
* Add URI::File to handle file URI scheme. [Feature #14035]
+[Bundler]
+
+ * Add Bundler to Standard Library. [Feature #12733]
+
+ * Use 1.17.1. It's latest stable version.
+
=== Compatibility issues (excluding feature bug fixes)
[File]
diff --git a/bin/bundle b/bin/bundle
new file mode 100755
index 0000000000..aaf773745d
--- /dev/null
+++ b/bin/bundle
@@ -0,0 +1,31 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+# Exit cleanly from an early interrupt
+Signal.trap("INT") do
+ Bundler.ui.debug("\n#{caller.join("\n")}") if defined?(Bundler)
+ exit 1
+end
+
+require "bundler"
+# Check if an older version of bundler is installed
+$LOAD_PATH.each do |path|
+ next unless path =~ %r{/bundler-0\.(\d+)} && $1.to_i < 9
+ err = String.new
+ err << "Looks like you have a version of bundler that's older than 0.9.\n"
+ err << "Please remove your old versions.\n"
+ err << "An easy way to do this is by running `gem cleanup bundler`."
+ abort(err)
+end
+
+require "bundler/friendly_errors"
+Bundler.with_friendly_errors do
+ require "bundler/cli"
+
+ # Allow any command to use --help flag to show help for that command
+ help_flags = %w[--help -h]
+ help_flag_used = ARGV.any? {|a| help_flags.include? a }
+ args = help_flag_used ? Bundler::CLI.reformatted_help_args(ARGV) : ARGV
+
+ Bundler::CLI.start(args, :debug => true)
+end
diff --git a/bin/bundle_ruby b/bin/bundle_ruby
new file mode 100755
index 0000000000..df6f8cc8a1
--- /dev/null
+++ b/bin/bundle_ruby
@@ -0,0 +1,60 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require "bundler/shared_helpers"
+
+Bundler::SharedHelpers.major_deprecation(2, "the bundle_ruby executable has been removed in favor of `bundle platform --ruby`")
+
+Signal.trap("INT") { exit 1 }
+
+require "bundler/errors"
+require "bundler/ruby_version"
+require "bundler/ruby_dsl"
+
+module Bundler
+ class Dsl
+ include RubyDsl
+
+ attr_accessor :ruby_version
+
+ def initialize
+ @ruby_version = nil
+ end
+
+ def eval_gemfile(gemfile, contents = nil)
+ contents ||= File.open(gemfile, "rb", &:read)
+ instance_eval(contents, gemfile.to_s, 1)
+ rescue SyntaxError => e
+ bt = e.message.split("\n")[1..-1]
+ raise GemfileError, ["Gemfile syntax error:", *bt].join("\n")
+ rescue ScriptError, RegexpError, NameError, ArgumentError => e
+ e.backtrace[0] = "#{e.backtrace[0]}: #{e.message} (#{e.class})"
+ STDERR.puts e.backtrace.join("\n ")
+ raise GemfileError, "There was an error in your Gemfile," \
+ " and Bundler cannot continue."
+ end
+
+ def source(source, options = {})
+ end
+
+ def gem(name, *args)
+ end
+
+ def group(*args)
+ end
+ end
+end
+
+dsl = Bundler::Dsl.new
+begin
+ dsl.eval_gemfile(Bundler::SharedHelpers.default_gemfile)
+ ruby_version = dsl.ruby_version
+ if ruby_version
+ puts ruby_version
+ else
+ puts "No ruby version specified"
+ end
+rescue Bundler::GemfileError => e
+ puts e.message
+ exit(-1)
+end
diff --git a/bin/bundler b/bin/bundler
new file mode 100755
index 0000000000..d9131fe834
--- /dev/null
+++ b/bin/bundler
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+load File.expand_path("../bundle", __FILE__)
diff --git a/common.mk b/common.mk
index e0ef2e4841..8e7236250d 100644
--- a/common.mk
+++ b/common.mk
@@ -1232,6 +1232,21 @@ yes-test-bundled-gems: test-bundled-gems-run
no-test-bundled-gems:
test-bundled-gems-run: $(PREPARE_BUNDLED_GEMS)
+test-bundler-precheck: $(arch)-fake.rb programs
+
+yes-test-bundler-prepare: test-bundler-precheck
+ $(XRUBY) -C "$(srcdir)" bin/gem install --no-document \
+ --install-dir .bundle --conservative "rspec:~> 3.5"
+
+RSPECOPTS = --format progress
+BUNDLER_SPECS =
+test-bundler: $(TEST_RUNNABLE)-test-bundler
+yes-test-bundler: yes-test-bundler-prepare
+ $(gnumake_recursive)$(Q) \
+ $(XRUBY) -C $(srcdir) -Ispec/bundler .bundle/bin/rspec \
+ --require spec_helper $(RSPECOPTS) spec/bundler/$(BUNDLER_SPECS)
+no-test-bundler:
+
UNICODE_FILES = $(UNICODE_SRC_DATA_DIR)/UnicodeData.txt \
$(UNICODE_SRC_DATA_DIR)/CompositionExclusions.txt \
$(UNICODE_SRC_DATA_DIR)/NormalizationTest.txt \
@@ -1403,6 +1418,7 @@ help: PHONY
" test-all: all ruby tests [TESTOPTS=-j4 TESTS=<test files>]" \
" test-spec: run the Ruby spec suite" \
" test-rubyspec: same as test-spec" \
+ " test-bundler: run the Bundler spec" \
" test-bundled-gems: run the test suite of bundled gems" \
" up: update local copy and autogenerated files" \
" benchmark: benchmark this ruby and COMPARE_RUBY." \
diff --git a/defs/gmake.mk b/defs/gmake.mk
index 8af2106d86..eb8b560bd8 100644
--- a/defs/gmake.mk
+++ b/defs/gmake.mk
@@ -64,7 +64,7 @@ endif
ORDERED_TEST_TARGETS := $(filter $(TEST_TARGETS), \
btest-ruby test-knownbug test-basic \
test-testframework test-ruby test-almost test-all \
- test-spec \
+ test-spec test-bundler-prepare test-bundler \
)
prev_test := $(if $(filter test-spec,$(ORDERED_TEST_TARGETS)),test-spec-precheck)
$(foreach test,$(ORDERED_TEST_TARGETS), \
diff --git a/doc/maintainers.rdoc b/doc/maintainers.rdoc
index 89f1d30617..e7baf933c6 100644
--- a/doc/maintainers.rdoc
+++ b/doc/maintainers.rdoc
@@ -178,6 +178,9 @@ Zachary Scott (zzak)
=== Libraries
+[lib/bundler.rb, lib/bundler/*]
+ Hiroshi SHIBATA (hsbt)
+ https://github.com/bundler/bundler
[lib/cmath.rb]
_unmaintained_
https://github.com/ruby/cmath
diff --git a/lib/bundler.gemspec b/lib/bundler.gemspec
new file mode 100644
index 0000000000..d4bbc6be77
--- /dev/null
+++ b/lib/bundler.gemspec
@@ -0,0 +1,55 @@
+# coding: utf-8
+# frozen_string_literal: true
+
+begin
+ require File.expand_path("../lib/bundler/version", __FILE__)
+rescue LoadError
+ # for Ruby core repository
+ require File.expand_path("../bundler/version", __FILE__)
+end
+
+require "shellwords"
+
+Gem::Specification.new do |s|
+ s.name = "bundler"
+ s.version = Bundler::VERSION
+ s.license = "MIT"
+ s.authors = [
+ "André Arko", "Samuel Giddins", "Colby Swandale", "Hiroshi Shibata",
+ "David Rodríguez", "Grey Baker", "Stephanie Morillo", "Chris Morris", "James Wen", "Tim Moore",
+ "André Medeiros", "Jessica Lynn Suttles", "Terence Lee", "Carl Lerche",
+ "Yehuda Katz"
+ ]
+ s.email = ["team@bundler.io"]
+ s.homepage = "http://bundler.io"
+ s.summary = "The best way to manage your application's dependencies"
+ s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably"
+
+ if s.respond_to?(:metadata=)
+ s.metadata = {
+ "bug_tracker_uri" => "http://github.com/bundler/bundler/issues",
+ "changelog_uri" => "https://github.com/bundler/bundler/blob/master/CHANGELOG.md",
+ "homepage_uri" => "https://bundler.io/",
+ "source_code_uri" => "http://github.com/bundler/bundler/",
+ }
+ end
+
+ if s.version >= Gem::Version.new("2.a".dup)
+ s.required_ruby_version = ">= 2.3.0"
+ s.required_rubygems_version = ">= 2.5.0"
+ else
+ s.required_ruby_version = ">= 1.8.7"
+ s.required_rubygems_version = ">= 1.3.6"
+ end
+
+ s.add_development_dependency "automatiek", "~> 0.1.0"
+ s.add_development_dependency "mustache", "0.99.6"
+ s.add_development_dependency "rake", "~> 10.0"
+ s.add_development_dependency "rdiscount", "~> 2.2"
+ s.add_development_dependency "ronn", "~> 0.7.3"
+ s.add_development_dependency "rspec", "~> 3.6"
+
+ s.files += %w[bundler.gemspec]
+
+ s.require_paths = ["lib"]
+end
diff --git a/lib/bundler.rb b/lib/bundler.rb
new file mode 100644
index 0000000000..1cb3b4fb21
--- /dev/null
+++ b/lib/bundler.rb
@@ -0,0 +1,567 @@
+# frozen_string_literal: true
+
+require "bundler/compatibility_guard"
+
+require "bundler/vendored_fileutils"
+require "pathname"
+require "rbconfig"
+require "thread"
+
+require "bundler/errors"
+require "bundler/environment_preserver"
+require "bundler/plugin"
+require "bundler/rubygems_ext"
+require "bundler/rubygems_integration"
+require "bundler/version"
+require "bundler/constants"
+require "bundler/current_ruby"
+require "bundler/build_metadata"
+
+module Bundler
+ environment_preserver = EnvironmentPreserver.new(ENV, EnvironmentPreserver::BUNDLER_KEYS)
+ ORIGINAL_ENV = environment_preserver.restore
+ ENV.replace(environment_preserver.backup)
+ SUDO_MUTEX = Mutex.new
+
+ autoload :Definition, "bundler/definition"
+ autoload :Dependency, "bundler/dependency"
+ autoload :DepProxy, "bundler/dep_proxy"
+ autoload :Deprecate, "bundler/deprecate"
+ autoload :Dsl, "bundler/dsl"
+ autoload :EndpointSpecification, "bundler/endpoint_specification"
+ autoload :Env, "bundler/env"
+ autoload :Fetcher, "bundler/fetcher"
+ autoload :FeatureFlag, "bundler/feature_flag"
+ autoload :GemHelper, "bundler/gem_helper"
+ autoload :GemHelpers, "bundler/gem_helpers"
+ autoload :GemRemoteFetcher, "bundler/gem_remote_fetcher"
+ autoload :GemVersionPromoter, "bundler/gem_version_promoter"
+ autoload :Graph, "bundler/graph"
+ autoload :Index, "bundler/index"
+ autoload :Injector, "bundler/injector"
+ autoload :Installer, "bundler/installer"
+ autoload :LazySpecification, "bundler/lazy_specification"
+ autoload :LockfileParser, "bundler/lockfile_parser"
+ autoload :MatchPlatform, "bundler/match_platform"
+ autoload :ProcessLock, "bundler/process_lock"
+ autoload :RemoteSpecification, "bundler/remote_specification"
+ autoload :Resolver, "bundler/resolver"
+ autoload :Retry, "bundler/retry"
+ autoload :RubyDsl, "bundler/ruby_dsl"
+ autoload :RubyGemsGemInstaller, "bundler/rubygems_gem_installer"
+ autoload :RubyVersion, "bundler/ruby_version"
+ autoload :Runtime, "bundler/runtime"
+ autoload :Settings, "bundler/settings"
+ autoload :SharedHelpers, "bundler/shared_helpers"
+ autoload :Source, "bundler/source"
+ autoload :SourceList, "bundler/source_list"
+ autoload :SpecSet, "bundler/spec_set"
+ autoload :StubSpecification, "bundler/stub_specification"
+ autoload :UI, "bundler/ui"
+ autoload :URICredentialsFilter, "bundler/uri_credentials_filter"
+ autoload :VersionRanges, "bundler/version_ranges"
+
+ class << self
+ def configure
+ @configured ||= configure_gem_home_and_path
+ end
+
+ def ui
+ (defined?(@ui) && @ui) || (self.ui = UI::Silent.new)
+ end
+
+ def ui=(ui)
+ Bundler.rubygems.ui = ui ? UI::RGProxy.new(ui) : nil
+ @ui = ui
+ end
+
+ # Returns absolute path of where gems are installed on the filesystem.
+ def bundle_path
+ @bundle_path ||= Pathname.new(configured_bundle_path.path).expand_path(root)
+ end
+
+ def configured_bundle_path
+ @configured_bundle_path ||= settings.path.tap(&:validate!)
+ end
+
+ # Returns absolute location of where binstubs are installed to.
+ def bin_path
+ @bin_path ||= begin
+ path = settings[:bin] || "bin"
+ path = Pathname.new(path).expand_path(root).expand_path
+ SharedHelpers.filesystem_access(path) {|p| FileUtils.mkdir_p(p) }
+ path
+ end
+ end
+
+ def setup(*groups)
+ # Return if all groups are already loaded
+ return @setup if defined?(@setup) && @setup
+
+ definition.validate_runtime!
+
+ SharedHelpers.print_major_deprecations!
+
+ if groups.empty?
+ # Load all groups, but only once
+ @setup = load.setup
+ else
+ load.setup(*groups)
+ end
+ end
+
+ def require(*groups)
+ setup(*groups).require(*groups)
+ end
+
+ def load
+ @load ||= Runtime.new(root, definition)
+ end
+
+ def environment
+ SharedHelpers.major_deprecation 2, "Bundler.environment has been removed in favor of Bundler.load"
+ load
+ end
+
+ # Returns an instance of Bundler::Definition for given Gemfile and lockfile
+ #
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
+ # to be updated or true if all gems should be updated
+ # @return [Bundler::Definition]
+ def definition(unlock = nil)
+ @definition = nil if unlock
+ @definition ||= begin
+ configure
+ Definition.build(default_gemfile, default_lockfile, unlock)
+ end
+ end
+
+ def frozen_bundle?
+ frozen = settings[:deployment]
+ frozen ||= settings[:frozen] unless feature_flag.deployment_means_frozen?
+ frozen
+ end
+
+ def locked_gems
+ @locked_gems ||=
+ if defined?(@definition) && @definition
+ definition.locked_gems
+ elsif Bundler.default_lockfile.file?
+ lock = Bundler.read_file(Bundler.default_lockfile)
+ LockfileParser.new(lock)
+ end
+ end
+
+ def ruby_scope
+ "#{Bundler.rubygems.ruby_engine}/#{Bundler.rubygems.config_map[:ruby_version]}"
+ end
+
+ def user_home
+ @user_home ||= begin
+ home = Bundler.rubygems.user_home
+ bundle_home = home ? File.join(home, ".bundle") : nil
+
+ warning = if home.nil?
+ "Your home directory is not set."
+ elsif !File.directory?(home)
+ "`#{home}` is not a directory."
+ elsif !File.writable?(home) && (!File.directory?(bundle_home) || !File.writable?(bundle_home))
+ "`#{home}` is not writable."
+ end
+
+ if warning
+ Kernel.send(:require, "etc")
+ user_home = tmp_home_path(Etc.getlogin, warning)
+ Bundler.ui.warn "#{warning}\nBundler will use `#{user_home}' as your home directory temporarily.\n"
+ user_home
+ else
+ Pathname.new(home)
+ end
+ end
+ end
+
+ def tmp_home_path(login, warning)
+ login ||= "unknown"
+ Kernel.send(:require, "tmpdir")
+ path = Pathname.new(Dir.tmpdir).join("bundler", "home")
+ SharedHelpers.filesystem_access(path) do |tmp_home_path|
+ unless tmp_home_path.exist?
+ tmp_home_path.mkpath
+ tmp_home_path.chmod(0o777)
+ end
+ tmp_home_path.join(login).tap(&:mkpath)
+ end
+ rescue RuntimeError => e
+ raise e.exception("#{warning}\nBundler also failed to create a temporary home directory at `#{path}':\n#{e}")
+ end
+
+ def user_bundle_path(dir = "home")
+ env_var, fallback = case dir
+ when "home"
+ ["BUNDLE_USER_HOME", Pathname.new(user_home).join(".bundle")]
+ when "cache"
+ ["BUNDLE_USER_CACHE", user_bundle_path.join("cache")]
+ when "config"
+ ["BUNDLE_USER_CONFIG", user_bundle_path.join("config")]
+ when "plugin"
+ ["BUNDLE_USER_PLUGIN", user_bundle_path.join("plugin")]
+ else
+ raise BundlerError, "Unknown user path requested: #{dir}"
+ end
+ # `fallback` will already be a Pathname, but Pathname.new() is
+ # idempotent so it's OK
+ Pathname.new(ENV.fetch(env_var, fallback))
+ end
+
+ def user_cache
+ user_bundle_path("cache")
+ end
+
+ def home
+ bundle_path.join("bundler")
+ end
+
+ def install_path
+ home.join("gems")
+ end
+
+ def specs_path
+ bundle_path.join("specifications")
+ end
+
+ def root
+ @root ||= begin
+ SharedHelpers.root
+ rescue GemfileNotFound
+ bundle_dir = default_bundle_dir
+ raise GemfileNotFound, "Could not locate Gemfile or .bundle/ directory" unless bundle_dir
+ Pathname.new(File.expand_path("..", bundle_dir))
+ end
+ end
+
+ def app_config_path
+ if app_config = ENV["BUNDLE_APP_CONFIG"]
+ Pathname.new(app_config).expand_path(root)
+ else
+ root.join(".bundle")
+ end
+ end
+
+ def app_cache(custom_path = nil)
+ path = custom_path || root
+ Pathname.new(path).join(settings.app_cache_path)
+ end
+
+ def tmp(name = Process.pid.to_s)
+ Kernel.send(:require, "tmpdir")
+ Pathname.new(Dir.mktmpdir(["bundler", name]))
+ end
+
+ def rm_rf(path)
+ FileUtils.remove_entry_secure(path) if path && File.exist?(path)
+ rescue ArgumentError
+ message = <<EOF
+It is a security vulnerability to allow your home directory to be world-writable, and bundler can not continue.
+You should probably consider fixing this issue by running `chmod o-w ~` on *nix.
+Please refer to http://ruby-doc.org/stdlib-2.1.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure for details.
+EOF
+ File.world_writable?(path) ? Bundler.ui.warn(message) : raise
+ raise PathError, "Please fix the world-writable issue with your #{path} directory"
+ end
+
+ def settings
+ @settings ||= Settings.new(app_config_path)
+ rescue GemfileNotFound
+ @settings = Settings.new(Pathname.new(".bundle").expand_path)
+ end
+
+ # @return [Hash] Environment present before Bundler was activated
+ def original_env
+ ORIGINAL_ENV.clone
+ end
+
+ # @deprecated Use `original_env` instead
+ # @return [Hash] Environment with all bundler-related variables removed
+ def clean_env
+ Bundler::SharedHelpers.major_deprecation(2, "`Bundler.clean_env` has weird edge cases, use `.original_env` instead")
+ env = original_env
+
+ if env.key?("BUNDLER_ORIG_MANPATH")
+ env["MANPATH"] = env["BUNDLER_ORIG_MANPATH"]
+ end
+
+ env.delete_if {|k, _| k[0, 7] == "BUNDLE_" }
+
+ if env.key?("RUBYOPT")
+ env["RUBYOPT"] = env["RUBYOPT"].sub "-rbundler/setup", ""
+ end
+
+ if env.key?("RUBYLIB")
+ rubylib = env["RUBYLIB"].split(File::PATH_SEPARATOR)
+ rubylib.delete(File.expand_path("..", __FILE__))
+ env["RUBYLIB"] = rubylib.join(File::PATH_SEPARATOR)
+ end
+
+ env
+ end
+
+ def with_original_env
+ with_env(original_env) { yield }
+ end
+
+ def with_clean_env
+ with_env(clean_env) { yield }
+ end
+
+ def clean_system(*args)
+ with_clean_env { Kernel.system(*args) }
+ end
+
+ def clean_exec(*args)
+ with_clean_env { Kernel.exec(*args) }
+ end
+
+ def local_platform
+ return Gem::Platform::RUBY if settings[:force_ruby_platform]
+ Gem::Platform.local
+ end
+
+ def default_gemfile
+ SharedHelpers.default_gemfile
+ end
+
+ def default_lockfile
+ SharedHelpers.default_lockfile
+ end
+
+ def default_bundle_dir
+ SharedHelpers.default_bundle_dir
+ end
+
+ def system_bindir
+ # Gem.bindir doesn't always return the location that RubyGems will install
+ # system binaries. If you put '-n foo' in your .gemrc, RubyGems will
+ # install binstubs there instead. Unfortunately, RubyGems doesn't expose
+ # that directory at all, so rather than parse .gemrc ourselves, we allow
+ # the directory to be set as well, via `bundle config bindir foo`.
+ Bundler.settings[:system_bindir] || Bundler.rubygems.gem_bindir
+ end
+
+ def use_system_gems?
+ configured_bundle_path.use_system_gems?
+ end
+
+ def requires_sudo?
+ return @requires_sudo if defined?(@requires_sudo_ran)
+
+ sudo_present = which "sudo" if settings.allow_sudo?
+
+ if sudo_present
+ # the bundle path and subdirectories need to be writable for RubyGems
+ # to be able to unpack and install gems without exploding
+ path = bundle_path
+ path = path.parent until path.exist?
+
+ # bins are written to a different location on OS X
+ bin_dir = Pathname.new(Bundler.system_bindir)
+ bin_dir = bin_dir.parent until bin_dir.exist?
+
+ # if any directory is not writable, we need sudo
+ files = [path, bin_dir] | Dir[bundle_path.join("build_info/*").to_s] | Dir[bundle_path.join("*").to_s]
+ unwritable_files = files.reject {|f| File.writable?(f) }
+ sudo_needed = !unwritable_files.empty?
+ if sudo_needed
+ Bundler.ui.warn "Following files may not be writable, so sudo is needed:\n #{unwritable_files.map(&:to_s).sort.join("\n ")}"
+ end
+ end
+
+ @requires_sudo_ran = true
+ @requires_sudo = settings.allow_sudo? && sudo_present && sudo_needed
+ end
+
+ def mkdir_p(path, options = {})
+ if requires_sudo? && !options[:no_sudo]
+ sudo "mkdir -p '#{path}'" unless File.exist?(path)
+ else
+ SharedHelpers.filesystem_access(path, :write) do |p|
+ FileUtils.mkdir_p(p)
+ end
+ end
+ end
+
+ def which(executable)
+ if File.file?(executable) && File.executable?(executable)
+ executable
+ elsif paths = ENV["PATH"]
+ quote = '"'.freeze
+ paths.split(File::PATH_SEPARATOR).find do |path|
+ path = path[1..-2] if path.start_with?(quote) && path.end_with?(quote)
+ executable_path = File.expand_path(executable, path)
+ return executable_path if File.file?(executable_path) && File.executable?(executable_path)
+ end
+ end
+ end
+
+ def sudo(str)
+ SUDO_MUTEX.synchronize do
+ prompt = "\n\n" + <<-PROMPT.gsub(/^ {6}/, "").strip + " "
+ Your user account isn't allowed to install to the system RubyGems.
+ You can cancel this installation and run:
+
+ bundle install --path vendor/bundle
+
+ to install the gems into ./vendor/bundle/, or you can enter your password
+ and install the bundled gems to RubyGems using sudo.
+
+ Password:
+ PROMPT
+
+ unless @prompted_for_sudo ||= system(%(sudo -k -p "#{prompt}" true))
+ raise SudoNotPermittedError,
+ "Bundler requires sudo access to install at the moment. " \
+ "Try installing again, granting Bundler sudo access when prompted, or installing into a different path."
+ end
+
+ `sudo -p "#{prompt}" #{str}`
+ end
+ end
+
+ def read_file(file)
+ SharedHelpers.filesystem_access(file, :read) do
+ File.open(file, "r:UTF-8", &:read)
+ end
+ end
+
+ def load_marshal(data)
+ Marshal.load(data)
+ rescue StandardError => e
+ raise MarshalError, "#{e.class}: #{e.message}"
+ end
+
+ def load_gemspec(file, validate = false)
+ @gemspec_cache ||= {}
+ key = File.expand_path(file)
+ @gemspec_cache[key] ||= load_gemspec_uncached(file, validate)
+ # Protect against caching side-effected gemspecs by returning a
+ # new instance each time.
+ @gemspec_cache[key].dup if @gemspec_cache[key]
+ end
+
+ def load_gemspec_uncached(file, validate = false)
+ path = Pathname.new(file)
+ contents = read_file(file)
+ spec = if contents.start_with?("---") # YAML header
+ eval_yaml_gemspec(path, contents)
+ else
+ # Eval the gemspec from its parent directory, because some gemspecs
+ # depend on "./" relative paths.
+ SharedHelpers.chdir(path.dirname.to_s) do
+ eval_gemspec(path, contents)
+ end
+ end
+ return unless spec
+ spec.loaded_from = path.expand_path.to_s
+ Bundler.rubygems.validate(spec) if validate
+ spec
+ end
+
+ def clear_gemspec_cache
+ @gemspec_cache = {}
+ end
+
+ def git_present?
+ return @git_present if defined?(@git_present)
+ @git_present = Bundler.which("git") || Bundler.which("git.exe")
+ end
+
+ def feature_flag
+ @feature_flag ||= FeatureFlag.new(VERSION)
+ end
+
+ def reset!
+ reset_paths!
+ Plugin.reset!
+ reset_rubygems!
+ end
+
+ def reset_paths!
+ @bin_path = nil
+ @bundler_major_version = nil
+ @bundle_path = nil
+ @configured = nil
+ @configured_bundle_path = nil
+ @definition = nil
+ @load = nil
+ @locked_gems = nil
+ @root = nil
+ @settings = nil
+ @setup = nil
+ @user_home = nil
+ end
+
+ def reset_rubygems!
+ return unless defined?(@rubygems) && @rubygems
+ rubygems.undo_replacements
+ rubygems.reset
+ @rubygems = nil
+ end
+
+ private
+
+ def eval_yaml_gemspec(path, contents)
+ Kernel.send(:require, "bundler/psyched_yaml")
+
+ # If the YAML is invalid, Syck raises an ArgumentError, and Psych
+ # raises a Psych::SyntaxError. See psyched_yaml.rb for more info.
+ Gem::Specification.from_yaml(contents)
+ rescue YamlLibrarySyntaxError, ArgumentError, Gem::EndOfYAMLException, Gem::Exception
+ eval_gemspec(path, contents)
+ end
+
+ def eval_gemspec(path, contents)
+ eval(contents, TOPLEVEL_BINDING.dup, path.expand_path.to_s)
+ rescue ScriptError, StandardError => e
+ msg = "There was an error while loading `#{path.basename}`: #{e.message}"
+
+ if e.is_a?(LoadError) && RUBY_VERSION >= "1.9"
+ msg += "\nDoes it try to require a relative path? That's been removed in Ruby 1.9"
+ end
+
+ raise GemspecError, Dsl::DSLError.new(msg, path, e.backtrace, contents)
+ end
+
+ def configure_gem_home_and_path
+ configure_gem_path
+ configure_gem_home
+ bundle_path
+ end
+
+ def configure_gem_path(env = ENV)
+ blank_home = env["GEM_HOME"].nil? || env["GEM_HOME"].empty?
+ if !use_system_gems?
+ # this needs to be empty string to cause
+ # PathSupport.split_gem_path to only load up the
+ # Bundler --path setting as the GEM_PATH.
+ env["GEM_PATH"] = ""
+ elsif blank_home
+ possibles = [Bundler.rubygems.gem_dir, Bundler.rubygems.gem_path]
+ paths = possibles.flatten.compact.uniq.reject(&:empty?)
+ env["GEM_PATH"] = paths.join(File::PATH_SEPARATOR)
+ end
+ end
+
+ def configure_gem_home
+ Bundler::SharedHelpers.set_env "GEM_HOME", File.expand_path(bundle_path, root)
+ Bundler.rubygems.clear_paths
+ end
+
+ # @param env [Hash]
+ def with_env(env)
+ backup = ENV.to_hash
+ ENV.replace(env)
+ yield
+ ensure
+ ENV.replace(backup)
+ end
+ end
+end
diff --git a/lib/bundler/build_metadata.rb b/lib/bundler/build_metadata.rb
new file mode 100644
index 0000000000..a0428f0319
--- /dev/null
+++ b/lib/bundler/build_metadata.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Bundler
+ # Represents metadata from when the Bundler gem was built.
+ module BuildMetadata
+ # begin ivars
+ @release = false
+ # end ivars
+
+ # A hash representation of the build metadata.
+ def self.to_h
+ {
+ "Built At" => built_at,
+ "Git SHA" => git_commit_sha,
+ "Released Version" => release?,
+ }
+ end
+
+ # A string representing the date the bundler gem was built.
+ def self.built_at
+ @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze
+ end
+
+ # The SHA for the git commit the bundler gem was built from.
+ def self.git_commit_sha
+ return @git_commit_sha if @git_commit_sha
+
+ # If Bundler has been installed without its .git directory and without a
+ # commit instance variable then we can't determine its commits SHA.
+ git_dir = File.join(File.expand_path("../../..", __FILE__), ".git")
+ return "unknown" unless File.directory?(git_dir)
+
+ # Otherwise shell out to git.
+ @git_commit_sha = Dir.chdir(File.expand_path("..", __FILE__)) do
+ `git rev-parse --short HEAD`.strip.freeze
+ end
+ end
+
+ # Whether this is an official release build of Bundler.
+ def self.release?
+ @release
+ end
+ end
+end
diff --git a/lib/bundler/capistrano.rb b/lib/bundler/capistrano.rb
new file mode 100644
index 0000000000..1b7145b72b
--- /dev/null
+++ b/lib/bundler/capistrano.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "bundler/shared_helpers"
+Bundler::SharedHelpers.major_deprecation 2,
+ "The Bundler task for Capistrano. Please use http://github.com/capistrano/bundler"
+
+# Capistrano task for Bundler.
+#
+# Add "require 'bundler/capistrano'" in your Capistrano deploy.rb, and
+# Bundler will be activated after each new deployment.
+require "bundler/deployment"
+require "capistrano/version"
+
+if defined?(Capistrano::Version) && Gem::Version.new(Capistrano::Version).release >= Gem::Version.new("3.0")
+ raise "For Capistrano 3.x integration, please use http://github.com/capistrano/bundler"
+end
+
+Capistrano::Configuration.instance(:must_exist).load do
+ before "deploy:finalize_update", "bundle:install"
+ Bundler::Deployment.define_task(self, :task, :except => { :no_release => true })
+ set :rake, lambda { "#{fetch(:bundle_cmd, "bundle")} exec rake" }
+end
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
new file mode 100644
index 0000000000..e658ffce72
--- /dev/null
+++ b/lib/bundler/cli.rb
@@ -0,0 +1,790 @@
+# frozen_string_literal: true
+
+require "bundler"
+require "bundler/vendored_thor"
+
+module Bundler
+ class CLI < Thor
+ require "bundler/cli/common"
+
+ package_name "Bundler"
+
+ AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze
+ PARSEABLE_COMMANDS = %w[
+ check config help exec platform show version
+ ].freeze
+
+ def self.start(*)
+ super
+ rescue Exception => e
+ Bundler.ui = UI::Shell.new
+ raise e
+ ensure
+ Bundler::SharedHelpers.print_major_deprecations!
+ end
+
+ def self.dispatch(*)
+ super do |i|
+ i.send(:print_command)
+ i.send(:warn_on_outdated_bundler)
+ end
+ end
+
+ def initialize(*args)
+ super
+
+ custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile]
+ if custom_gemfile && !custom_gemfile.empty?
+ Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile)
+ Bundler.reset_paths!
+ end
+
+ Bundler.settings.set_command_option_if_given :retry, options[:retry]
+
+ current_cmd = args.last[:current_command].name
+ auto_install if AUTO_INSTALL_CMDS.include?(current_cmd)
+ rescue UnknownArgumentError => e
+ raise InvalidOption, e.message
+ ensure
+ self.options ||= {}
+ unprinted_warnings = Bundler.ui.unprinted_warnings
+ Bundler.ui = UI::Shell.new(options)
+ Bundler.ui.level = "debug" if options["verbose"]
+ unprinted_warnings.each {|w| Bundler.ui.warn(w) }
+
+ if ENV["RUBYGEMS_GEMDEPS"] && !ENV["RUBYGEMS_GEMDEPS"].empty?
+ Bundler.ui.warn(
+ "The RUBYGEMS_GEMDEPS environment variable is set. This enables RubyGems' " \
+ "experimental Gemfile mode, which may conflict with Bundler and cause unexpected errors. " \
+ "To remove this warning, unset RUBYGEMS_GEMDEPS.", :wrap => true
+ )
+ end
+ end
+
+ def self.deprecated_option(*args, &blk)
+ return if Bundler.feature_flag.forget_cli_options?
+ method_option(*args, &blk)
+ end
+
+ check_unknown_options!(:except => [:config, :exec])
+ stop_on_unknown_option! :exec
+
+ desc "cli_help", "Prints a summary of bundler commands", :hide => true
+ def cli_help
+ version
+ Bundler.ui.info "\n"
+
+ primary_commands = ["install", "update",
+ Bundler.feature_flag.cache_command_is_package? ? "cache" : "package",
+ "exec", "config", "help"]
+
+ list = self.class.printable_commands(true)
+ by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] }
+ utilities = by_name.keys.sort - primary_commands
+ primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first }
+ utilities.map! {|name| by_name[name].first }
+
+ shell.say "Bundler commands:\n\n"
+
+ shell.say " Primary commands:\n"
+ shell.print_table(primary_commands, :indent => 4, :truncate => true)
+ shell.say
+ shell.say " Utilities:\n"
+ shell.print_table(utilities, :indent => 4, :truncate => true)
+ shell.say
+ self.class.send(:class_options_help, shell)
+ end
+ default_task(Bundler.feature_flag.default_cli_command)
+
+ class_option "no-color", :type => :boolean, :desc => "Disable colorization in output"
+ class_option "retry", :type => :numeric, :aliases => "-r", :banner => "NUM",
+ :desc => "Specify the number of times you wish to attempt network commands"
+ class_option "verbose", :type => :boolean, :desc => "Enable verbose output mode", :aliases => "-V"
+
+ def help(cli = nil)
+ case cli
+ when "gemfile" then command = "gemfile"
+ when nil then command = "bundle"
+ else command = "bundle-#{cli}"
+ end
+
+ man_path = File.expand_path("../../../man", __FILE__)
+ man_pages = Hash[Dir.glob(File.join(man_path, "*")).grep(/.*\.\d*\Z/).collect do |f|
+ [File.basename(f, ".*"), f]
+ end]
+
+ if man_pages.include?(command)
+ if Bundler.which("man") && man_path !~ %r{^file:/.+!/META-INF/jruby.home/.+}
+ Kernel.exec "man #{man_pages[command]}"
+ else
+ puts File.read("#{man_path}/#{File.basename(man_pages[command])}.txt")
+ end
+ elsif command_path = Bundler.which("bundler-#{cli}")
+ Kernel.exec(command_path, "--help")
+ else
+ super
+ end
+ end
+
+ def self.handle_no_command_error(command, has_namespace = $thor_runner)
+ if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command)
+ return Bundler::Plugin.exec_command(command, ARGV[1..-1])
+ end
+
+ return super unless command_path = Bundler.which("bundler-#{command}")
+
+ Kernel.exec(command_path, *ARGV[1..-1])
+ end
+
+ desc "init [OPTIONS]", "Generates a Gemfile into the current working directory"
+ long_desc <<-D
+ Init generates a default Gemfile in the current working directory. When adding a
+ Gemfile to a gem with a gemspec, the --gemspec option will automatically add each
+ dependency listed in the gemspec file to the newly created Gemfile.
+ D
+ deprecated_option "gemspec", :type => :string, :banner => "Use the specified .gemspec to create the Gemfile"
+ def init
+ require "bundler/cli/init"
+ Init.new(options.dup).run
+ end
+
+ desc "check [OPTIONS]", "Checks if the dependencies listed in Gemfile are satisfied by currently installed gems"
+ long_desc <<-D
+ Check searches the local machine for each of the gems requested in the Gemfile. If
+ all gems are found, Bundler prints a success message and exits with a status of 0.
+ If not, the first missing gem is listed and Bundler exits status 1.
+ D
+ method_option "dry-run", :type => :boolean, :default => false, :banner =>
+ "Lock the Gemfile"
+ method_option "gemfile", :type => :string, :banner =>
+ "Use the specified gemfile instead of Gemfile"
+ method_option "path", :type => :string, :banner =>
+ "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}"
+ map "c" => "check"
+ def check
+ require "bundler/cli/check"
+ Check.new(options).run
+ end
+
+ desc "remove [GEM [GEM ...]]", "Removes gems from the Gemfile"
+ long_desc <<-D
+ Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If the gem is not found, Bundler prints a error message and if gem could not be removed due to any reason Bundler will display a warning.
+ D
+ method_option "install", :type => :boolean, :banner =>
+ "Runs 'bundle install' after removing the gems from the Gemfile"
+ def remove(*gems)
+ require "bundler/cli/remove"
+ Remove.new(gems, options).run
+ end
+
+ desc "install [OPTIONS]", "Install the current environment to the system"
+ long_desc <<-D
+ Install will install all of the gems in the current bundle, making them available
+ for use. In a freshly checked out repository, this command will give you the same
+ gem versions as the last person who updated the Gemfile and ran `bundle update`.
+
+ Passing [DIR] to install (e.g. vendor) will cause the unpacked gems to be installed
+ into the [DIR] directory rather than into system gems.
+
+ If the bundle has already been installed, bundler will tell you so and then exit.
+ D
+ deprecated_option "binstubs", :type => :string, :lazy_default => "bin", :banner =>
+ "Generate bin stubs for bundled gems to ./bin"
+ deprecated_option "clean", :type => :boolean, :banner =>
+ "Run bundle clean automatically after install"
+ deprecated_option "deployment", :type => :boolean, :banner =>
+ "Install using defaults tuned for deployment environments"
+ deprecated_option "frozen", :type => :boolean, :banner =>
+ "Do not allow the Gemfile.lock to be updated after this install"
+ method_option "full-index", :type => :boolean, :banner =>
+ "Fall back to using the single-file index of all gems"
+ method_option "gemfile", :type => :string, :banner =>
+ "Use the specified gemfile instead of Gemfile"
+ method_option "jobs", :aliases => "-j", :type => :numeric, :banner =>
+ "Specify the number of jobs to run in parallel"
+ method_option "local", :type => :boolean, :banner =>
+ "Do not attempt to fetch gems remotely and use the gem cache instead"
+ deprecated_option "no-cache", :type => :boolean, :banner =>
+ "Don't update the existing gem cache."
+ method_option "redownload", :type => :boolean, :aliases => "--force", :banner =>
+ "Force downloading every gem."
+ deprecated_option "no-prune", :type => :boolean, :banner =>
+ "Don't remove stale gems from the cache."
+ deprecated_option "path", :type => :string, :banner =>
+ "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine"
+ method_option "quiet", :type => :boolean, :banner =>
+ "Only output warnings and errors."
+ deprecated_option "shebang", :type => :string, :banner =>
+ "Specify a different shebang executable name than the default (usually 'ruby')"
+ method_option "standalone", :type => :array, :lazy_default => [], :banner =>
+ "Make a bundle that can work without the Bundler runtime"
+ deprecated_option "system", :type => :boolean, :banner =>
+ "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application"
+ method_option "trust-policy", :alias => "P", :type => :string, :banner =>
+ "Gem trust policy (like gem install -P). Must be one of " +
+ Bundler.rubygems.security_policy_keys.join("|")
+ deprecated_option "without", :type => :array, :banner =>
+ "Exclude gems that are part of the specified named group."
+ deprecated_option "with", :type => :array, :banner =>
+ "Include gems that are part of the specified named group."
+ map "i" => "install"
+ def install
+ SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force")
+ require "bundler/cli/install"
+ Bundler.settings.temporary(:no_install => false) do
+ Install.new(options.dup).run
+ end
+ end
+
+ desc "update [OPTIONS]", "Update the current environment"
+ long_desc <<-D
+ Update will install the newest versions of the gems listed in the Gemfile. Use
+ update when you have changed the Gemfile, or if you want to get the newest
+ possible versions of the gems in the bundle.
+ D
+ method_option "full-index", :type => :boolean, :banner =>
+ "Fall back to using the single-file index of all gems"
+ method_option "gemfile", :type => :string, :banner =>
+ "Use the specified gemfile instead of Gemfile"
+ method_option "group", :aliases => "-g", :type => :array, :banner =>
+ "Update a specific group"
+ method_option "jobs", :aliases => "-j", :type => :numeric, :banner =>
+ "Specify the number of jobs to run in parallel"
+ method_option "local", :type => :boolean, :banner =>
+ "Do not attempt to fetch gems remotely and use the gem cache instead"
+ method_option "quiet", :type => :boolean, :banner =>
+ "Only output warnings and errors."
+ method_option "source", :type => :array, :banner =>
+ "Update a specific source (and all gems associated with it)"
+ method_option "redownload", :type => :boolean, :aliases => "--force", :banner =>
+ "Force downloading every gem."
+ method_option "ruby", :type => :boolean, :banner =>
+ "Update ruby specified in Gemfile.lock"
+ method_option "bundler", :type => :string, :lazy_default => "> 0.a", :banner =>
+ "Update the locked version of bundler"
+ method_option "patch", :type => :boolean, :banner =>
+ "Prefer updating only to next patch version"
+ method_option "minor", :type => :boolean, :banner =>
+ "Prefer updating only to next minor version"
+ method_option "major", :type => :boolean, :banner =>
+ "Prefer updating to next major version (default)"
+ method_option "strict", :type => :boolean, :banner =>
+ "Do not allow any gem to be updated past latest --patch | --minor | --major"
+ method_option "conservative", :type => :boolean, :banner =>
+ "Use bundle install conservative update behavior and do not allow shared dependencies to be updated."
+ method_option "all", :type => :boolean, :banner =>
+ "Update everything."
+ def update(*gems)
+ SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force")
+ require "bundler/cli/update"
+ Update.new(options, gems).run
+ end
+
+ desc "show GEM [OPTIONS]", "Shows all gems that are part of the bundle, or the path to a given gem"
+ long_desc <<-D
+ Show lists the names and versions of all gems that are required by your Gemfile.
+ Calling show with [GEM] will list the exact location of that gem on your machine.
+ D
+ method_option "paths", :type => :boolean,
+ :banner => "List the paths of all gems that are required by your Gemfile."
+ method_option "outdated", :type => :boolean,
+ :banner => "Show verbose output including whether gems are outdated."
+ def show(gem_name = nil)
+ if ARGV[0] == "show"
+ rest = ARGV[1..-1]
+
+ new_command = rest.find {|arg| !arg.start_with?("--") } ? "info" : "list"
+
+ new_arguments = rest.map do |arg|
+ next arg if arg != "--paths"
+ next "--path" if new_command == "info"
+ end
+
+ old_argv = ARGV.join(" ")
+ new_argv = [new_command, *new_arguments.compact].join(" ")
+
+ Bundler::SharedHelpers.major_deprecation(2, "use `bundle #{new_argv}` instead of `bundle #{old_argv}`")
+ end
+ require "bundler/cli/show"
+ Show.new(options, gem_name).run
+ end
+ # TODO: 2.0 remove `bundle show`
+
+ if Bundler.feature_flag.list_command?
+ desc "list", "List all gems in the bundle"
+ method_option "name-only", :type => :boolean, :banner => "print only the gem names"
+ method_option "only-group", :type => :string, :banner => "print gems from a particular group"
+ method_option "without-group", :type => :string, :banner => "print all gems expect from a group"
+ method_option "paths", :type => :boolean, :banner => "print the path to each gem in the bundle"
+ def list
+ require "bundler/cli/list"
+ List.new(options).run
+ end
+
+ map %w[ls] => "list"
+ else
+ map %w[list] => "show"
+ end
+
+ desc "info GEM [OPTIONS]", "Show information for the given gem"
+ method_option "path", :type => :boolean, :banner => "Print full path to gem"
+ def info(gem_name)
+ require "bundler/cli/info"
+ Info.new(options, gem_name).run
+ end
+
+ desc "binstubs GEM [OPTIONS]", "Install the binstubs of the listed gem"
+ long_desc <<-D
+ Generate binstubs for executables in [GEM]. Binstubs are put into bin,
+ or the --binstubs directory if one has been set. Calling binstubs with [GEM [GEM]]
+ will create binstubs for all given gems.
+ D
+ method_option "force", :type => :boolean, :default => false, :banner =>
+ "Overwrite existing binstubs if they exist"
+ method_option "path", :type => :string, :lazy_default => "bin", :banner =>
+ "Binstub destination directory (default bin)"
+ method_option "shebang", :type => :string, :banner =>
+ "Specify a different shebang executable name than the default (usually 'ruby')"
+ method_option "standalone", :type => :boolean, :banner =>
+ "Make binstubs that can work without the Bundler runtime"
+ method_option "all", :type => :boolean, :banner =>
+ "Install binstubs for all gems"
+ def binstubs(*gems)
+ require "bundler/cli/binstubs"
+ Binstubs.new(options, gems).run
+ end
+
+ desc "add GEM VERSION", "Add gem to Gemfile and run bundle install"
+ long_desc <<-D
+ Adds the specified gem to Gemfile (if valid) and run 'bundle install' in one step.
+ D
+ method_option "version", :aliases => "-v", :type => :string
+ method_option "group", :aliases => "-g", :type => :string
+ method_option "source", :aliases => "-s", :type => :string
+ method_option "skip-install", :type => :boolean, :banner =>
+ "Adds gem to the Gemfile but does not install it"
+ method_option "optimistic", :type => :boolean, :banner => "Adds optimistic declaration of version to gem"
+ method_option "strict", :type => :boolean, :banner => "Adds strict declaration of version to gem"
+ def add(*gems)
+ require "bundler/cli/add"
+ Add.new(options.dup, gems).run
+ end
+
+ desc "outdated GEM [OPTIONS]", "List installed gems with newer versions available"
+ long_desc <<-D
+ Outdated lists the names and versions of gems that have a newer version available
+ in the given source. Calling outdated with [GEM [GEM]] will only check for newer
+ versions of the given gems. Prerelease gems are ignored by default. If your gems
+ are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1.
+
+ For more information on patch level options (--major, --minor, --patch,
+ --update-strict) see documentation on the same options on the update command.
+ D
+ method_option "group", :type => :string, :banner => "List gems from a specific group"
+ method_option "groups", :type => :boolean, :banner => "List gems organized by groups"
+ method_option "local", :type => :boolean, :banner =>
+ "Do not attempt to fetch gems remotely and use the gem cache instead"
+ method_option "pre", :type => :boolean, :banner => "Check for newer pre-release gems"
+ method_option "source", :type => :array, :banner => "Check against a specific source"
+ method_option "strict", :type => :boolean, :banner =>
+ "Only list newer versions allowed by your Gemfile requirements"
+ method_option "update-strict", :type => :boolean, :banner =>
+ "Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor | --major"
+ method_option "minor", :type => :boolean, :banner => "Prefer updating only to next minor version"
+ method_option "major", :type => :boolean, :banner => "Prefer updating to next major version (default)"
+ method_option "patch", :type => :boolean, :banner => "Prefer updating only to next patch version"
+ method_option "filter-major", :type => :boolean, :banner => "Only list major newer versions"
+ method_option "filter-minor", :type => :boolean, :banner => "Only list minor newer versions"
+ method_option "filter-patch", :type => :boolean, :banner => "Only list patch newer versions"
+ method_option "parseable", :aliases => "--porcelain", :type => :boolean, :banner =>
+ "Use minimal formatting for more parseable output"
+ method_option "only-explicit", :type => :boolean, :banner =>
+ "Only list gems specified in your Gemfile, not their dependencies"
+ def outdated(*gems)
+ require "bundler/cli/outdated"
+ Outdated.new(options, gems).run
+ end
+
+ if Bundler.feature_flag.cache_command_is_package?
+ map %w[cache] => :package
+ else
+ desc "cache [OPTIONS]", "Cache all the gems to vendor/cache", :hide => true
+ unless Bundler.feature_flag.cache_command_is_package?
+ method_option "all", :type => :boolean,
+ :banner => "Include all sources (including path and git)."
+ end
+ method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms present in the lockfile, not only the current one"
+ method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache."
+ def cache
+ require "bundler/cli/cache"
+ Cache.new(options).run
+ end
+ end
+
+ desc "#{Bundler.feature_flag.cache_command_is_package? ? :cache : :package} [OPTIONS]", "Locks and then caches all of the gems into vendor/cache"
+ unless Bundler.feature_flag.cache_command_is_package?
+ method_option "all", :type => :boolean,
+ :banner => "Include all sources (including path and git)."
+ end
+ method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms present in the lockfile, not only the current one"
+ method_option "cache-path", :type => :string, :banner =>
+ "Specify a different cache path than the default (vendor/cache)."
+ method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile"
+ method_option "no-install", :type => :boolean, :banner => "Don't install the gems, only the package."
+ method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache."
+ method_option "path", :type => :string, :banner =>
+ "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine"
+ method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors."
+ method_option "frozen", :type => :boolean, :banner =>
+ "Do not allow the Gemfile.lock to be updated after this package operation's install"
+ long_desc <<-D
+ The package command will copy the .gem files for every gem in the bundle into the
+ directory ./vendor/cache. If you then check that directory into your source
+ control repository, others who check out your source will be able to install the
+ bundle without having to download any additional gems.
+ D
+ def package
+ require "bundler/cli/package"
+ Package.new(options).run
+ end
+ map %w[pack] => :package
+
+ desc "exec [OPTIONS]", "Run the command in context of the bundle"
+ method_option :keep_file_descriptors, :type => :boolean, :default => false
+ method_option :gemfile, :type => :string, :required => false
+ long_desc <<-D
+ Exec runs a command, providing it access to the gems in the bundle. While using
+ bundle exec you can require and call the bundled gems as if they were installed
+ into the system wide RubyGems repository.
+ D
+ map "e" => "exec"
+ def exec(*args)
+ require "bundler/cli/exec"
+ Exec.new(options, args).run
+ end
+
+ desc "config NAME [VALUE]", "Retrieve or set a configuration value"
+ long_desc <<-D
+ Retrieves or sets a configuration value. If only one parameter is provided, retrieve the value. If two parameters are provided, replace the
+ existing value with the newly provided one.
+
+ By default, setting a configuration value sets it for all projects
+ on the machine.
+
+ If a global setting is superceded by local configuration, this command
+ will show the current value, as well as any superceded values and
+ where they were specified.
+ D
+ method_option "parseable", :type => :boolean, :banner => "Use minimal formatting for more parseable output"
+ def config(*args)
+ require "bundler/cli/config"
+ Config.new(options, args, self).run
+ end
+
+ desc "open GEM", "Opens the source directory of the given bundled gem"
+ def open(name)
+ require "bundler/cli/open"
+ Open.new(options, name).run
+ end
+
+ if Bundler.feature_flag.console_command?
+ desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded"
+ def console(group = nil)
+ require "bundler/cli/console"
+ Console.new(options, group).run
+ end
+ end
+
+ desc "version", "Prints the bundler's version information"
+ def version
+ cli_help = current_command.name == "cli_help"
+ if cli_help || ARGV.include?("version")
+ build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})"
+ end
+
+ if !cli_help && Bundler.feature_flag.print_only_version_number?
+ Bundler.ui.info "#{Bundler::VERSION}#{build_info}"
+ else
+ Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}"
+ end
+ end
+ map %w[-v --version] => :version
+
+ desc "licenses", "Prints the license of all gems in the bundle"
+ def licenses
+ Bundler.load.specs.sort_by {|s| s.license.to_s }.reverse_each do |s|
+ gem_name = s.name
+ license = s.license || s.licenses
+
+ if license.empty?
+ Bundler.ui.warn "#{gem_name}: Unknown"
+ else
+ Bundler.ui.info "#{gem_name}: #{license}"
+ end
+ end
+ end
+
+ if Bundler.feature_flag.viz_command?
+ desc "viz [OPTIONS]", "Generates a visual dependency graph", :hide => true
+ long_desc <<-D
+ Viz generates a PNG file of the current Gemfile as a dependency graph.
+ Viz requires the ruby-graphviz gem (and its dependencies).
+ The associated gems must also be installed via 'bundle install'.
+ D
+ method_option :file, :type => :string, :default => "gem_graph", :aliases => "-f", :desc => "The name to use for the generated file. see format option"
+ method_option :format, :type => :string, :default => "png", :aliases => "-F", :desc => "This is output format option. Supported format is png, jpg, svg, dot ..."
+ method_option :requirements, :type => :boolean, :default => false, :aliases => "-R", :desc => "Set to show the version of each required dependency."
+ method_option :version, :type => :boolean, :default => false, :aliases => "-v", :desc => "Set to show each gem version."
+ method_option :without, :type => :array, :default => [], :aliases => "-W", :banner => "GROUP[ GROUP...]", :desc => "Exclude gems that are part of the specified named group."
+ def viz
+ SharedHelpers.major_deprecation 2, "The `viz` command has been moved to the `bundle-viz` gem, see https://github.com/bundler/bundler-viz"
+ require "bundler/cli/viz"
+ Viz.new(options.dup).run
+ end
+ end
+
+ old_gem = instance_method(:gem)
+
+ desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem"
+ method_option :exe, :type => :boolean, :default => false, :aliases => ["--bin", "-b"], :desc => "Generate a binary executable for your library."
+ method_option :coc, :type => :boolean, :desc => "Generate a code of conduct file. Set a default with `bundle config gem.coc true`."
+ method_option :edit, :type => :string, :aliases => "-e", :required => false, :banner => "EDITOR",
+ :lazy_default => [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? },
+ :desc => "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)"
+ method_option :ext, :type => :boolean, :default => false, :desc => "Generate the boilerplate for C extension code"
+ method_option :mit, :type => :boolean, :desc => "Generate an MIT license file. Set a default with `bundle config gem.mit true`."
+ method_option :test, :type => :string, :lazy_default => "rspec", :aliases => "-t", :banner => "rspec",
+ :desc => "Generate a test directory for your library, either rspec or minitest. Set a default with `bundle config gem.test rspec`."
+ def gem(name)
+ end
+
+ commands["gem"].tap do |gem_command|
+ def gem_command.run(instance, args = [])
+ arity = 1 # name
+
+ require "bundler/cli/gem"
+ cmd_args = args + [instance]
+ cmd_args.unshift(instance.options)
+
+ cmd = begin
+ Gem.new(*cmd_args)
+ rescue ArgumentError => e
+ instance.class.handle_argument_error(self, e, args, arity)
+ end
+
+ cmd.run
+ end
+ end
+
+ undef_method(:gem)
+ define_method(:gem, old_gem)
+ private :gem
+
+ def self.source_root
+ File.expand_path(File.join(File.dirname(__FILE__), "templates"))
+ end
+
+ desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory", :hide => true
+ method_option "dry-run", :type => :boolean, :default => false, :banner =>
+ "Only print out changes, do not clean gems"
+ method_option "force", :type => :boolean, :default => false, :banner =>
+ "Forces clean even if --path is not set"
+ def clean
+ require "bundler/cli/clean"
+ Clean.new(options.dup).run
+ end
+
+ desc "platform [OPTIONS]", "Displays platform compatibility information"
+ method_option "ruby", :type => :boolean, :default => false, :banner =>
+ "only display ruby related platform information"
+ def platform
+ require "bundler/cli/platform"
+ Platform.new(options).run
+ end
+
+ desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", :hide => true
+ method_option "source", :type => :string, :banner =>
+ "Install gem from the given source"
+ method_option "group", :type => :string, :banner =>
+ "Install gem into a bundler group"
+ def inject(name, version)
+ SharedHelpers.major_deprecation 2, "The `inject` command has been replaced by the `add` command"
+ require "bundler/cli/inject"
+ Inject.new(options.dup, name, version).run
+ end
+
+ desc "lock", "Creates a lockfile without installing"
+ method_option "update", :type => :array, :lazy_default => true, :banner =>
+ "ignore the existing lockfile, update all gems by default, or update list of given gems"
+ method_option "local", :type => :boolean, :default => false, :banner =>
+ "do not attempt to fetch remote gemspecs and use the local gem cache only"
+ method_option "print", :type => :boolean, :default => false, :banner =>
+ "print the lockfile to STDOUT instead of writing to the file system"
+ method_option "lockfile", :type => :string, :default => nil, :banner =>
+ "the path the lockfile should be written to"
+ method_option "full-index", :type => :boolean, :default => false, :banner =>
+ "Fall back to using the single-file index of all gems"
+ method_option "add-platform", :type => :array, :default => [], :banner =>
+ "Add a new platform to the lockfile"
+ method_option "remove-platform", :type => :array, :default => [], :banner =>
+ "Remove a platform from the lockfile"
+ method_option "patch", :type => :boolean, :banner =>
+ "If updating, prefer updating only to next patch version"
+ method_option "minor", :type => :boolean, :banner =>
+ "If updating, prefer updating only to next minor version"
+ method_option "major", :type => :boolean, :banner =>
+ "If updating, prefer updating to next major version (default)"
+ method_option "strict", :type => :boolean, :banner =>
+ "If updating, do not allow any gem to be updated past latest --patch | --minor | --major"
+ method_option "conservative", :type => :boolean, :banner =>
+ "If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated"
+ def lock
+ require "bundler/cli/lock"
+ Lock.new(options).run
+ end
+
+ desc "env", "Print information about the environment Bundler is running under"
+ def env
+ Env.write($stdout)
+ end
+
+ desc "doctor [OPTIONS]", "Checks the bundle for common problems"
+ long_desc <<-D
+ Doctor scans the OS dependencies of each of the gems requested in the Gemfile. If
+ missing dependencies are detected, Bundler prints them and exits status 1.
+ Otherwise, Bundler prints a success message and exits with a status of 0.
+ D
+ method_option "gemfile", :type => :string, :banner =>
+ "Use the specified gemfile instead of Gemfile"
+ method_option "quiet", :type => :boolean, :banner =>
+ "Only output warnings and errors."
+ def doctor
+ require "bundler/cli/doctor"
+ Doctor.new(options).run
+ end
+
+ desc "issue", "Learn how to report an issue in Bundler"
+ def issue
+ require "bundler/cli/issue"
+ Issue.new.run
+ end
+
+ desc "pristine [GEMS...]", "Restores installed gems to pristine condition"
+ long_desc <<-D
+ Restores installed gems to pristine condition from files located in the
+ gem cache. Gems installed from a git repository will be issued `git
+ checkout --force`.
+ D
+ def pristine(*gems)
+ require "bundler/cli/pristine"
+ Pristine.new(gems).run
+ end
+
+ if Bundler.feature_flag.plugins?
+ require "bundler/cli/plugin"
+ desc "plugin", "Manage the bundler plugins"
+ subcommand "plugin", Plugin
+ end
+
+ # Reformat the arguments passed to bundle that include a --help flag
+ # into the corresponding `bundle help #{command}` call
+ def self.reformatted_help_args(args)
+ bundler_commands = all_commands.keys
+ help_flags = %w[--help -h]
+ exec_commands = %w[e ex exe exec]
+ help_used = args.index {|a| help_flags.include? a }
+ exec_used = args.index {|a| exec_commands.include? a }
+ command = args.find {|a| bundler_commands.include? a }
+ if exec_used && help_used
+ if exec_used + help_used == 1
+ %w[help exec]
+ else
+ args
+ end
+ elsif help_used
+ args = args.dup
+ args.delete_at(help_used)
+ ["help", command || args].flatten.compact
+ else
+ args
+ end
+ end
+
+ private
+
+ # Automatically invoke `bundle install` and resume if
+ # Bundler.settings[:auto_install] exists. This is set through config cmd
+ # `bundle config auto_install 1`.
+ #
+ # Note that this method `nil`s out the global Definition object, so it
+ # should be called first, before you instantiate anything like an
+ # `Installer` that'll keep a reference to the old one instead.
+ def auto_install
+ return unless Bundler.settings[:auto_install]
+
+ begin
+ Bundler.definition.specs
+ rescue GemNotFound
+ Bundler.ui.info "Automatically installing missing gems."
+ Bundler.reset!
+ invoke :install, []
+ Bundler.reset!
+ end
+ end
+
+ def current_command
+ _, _, config = @_initializer
+ config[:current_command]
+ end
+
+ def print_command
+ return unless Bundler.ui.debug?
+ cmd = current_command
+ command_name = cmd.name
+ return if PARSEABLE_COMMANDS.include?(command_name)
+ command = ["bundle", command_name] + args
+ options_to_print = options.dup
+ options_to_print.delete_if do |k, v|
+ next unless o = cmd.options[k]
+ o.default == v
+ end
+ command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip
+ command.reject!(&:empty?)
+ Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}"
+ end
+
+ def warn_on_outdated_bundler
+ return if Bundler.settings[:disable_version_check]
+
+ command_name = current_command.name
+ return if PARSEABLE_COMMANDS.include?(command_name)
+
+ return unless SharedHelpers.md5_available?
+
+ latest = Fetcher::CompactIndex.
+ new(nil, Source::Rubygems::Remote.new(URI("https://rubygems.org")), nil).
+ send(:compact_index_client).
+ instance_variable_get(:@cache).
+ dependencies("bundler").
+ map {|d| Gem::Version.new(d.first) }.
+ max
+ return unless latest
+
+ current = Gem::Version.new(VERSION)
+ return if current >= latest
+ latest_installed = Bundler.rubygems.find_name("bundler").map(&:version).max
+
+ installation = "To install the latest version, run `gem install bundler#{" --pre" if latest.prerelease?}`"
+ if latest_installed && latest_installed > current
+ suggestion = "To update to the most recent installed version (#{latest_installed}), run `bundle update --bundler`"
+ suggestion = "#{installation}\n#{suggestion}" if latest_installed < latest
+ else
+ suggestion = installation
+ end
+
+ Bundler.ui.warn "The latest bundler is #{latest}, but you are currently running #{current}.\n#{suggestion}"
+ rescue RuntimeError
+ nil
+ end
+ end
+end
diff --git a/lib/bundler/cli/add.rb b/lib/bundler/cli/add.rb
new file mode 100644
index 0000000000..9709e71be0
--- /dev/null
+++ b/lib/bundler/cli/add.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Add
+ def initialize(options, gems)
+ @gems = gems
+ @options = options
+ @options[:group] = @options[:group].split(",").map(&:strip) if !@options[:group].nil? && !@options[:group].empty?
+ end
+
+ def run
+ raise InvalidOption, "You can not specify `--strict` and `--optimistic` at the same time." if @options[:strict] && @options[:optimistic]
+
+ # raise error when no gems are specified
+ raise InvalidOption, "Please specify gems to add." if @gems.empty?
+
+ version = @options[:version].nil? ? nil : @options[:version].split(",").map(&:strip)
+
+ unless version.nil?
+ version.each do |v|
+ raise InvalidOption, "Invalid gem requirement pattern '#{v}'" unless Gem::Requirement::PATTERN =~ v.to_s
+ end
+ end
+
+ dependencies = @gems.map {|g| Bundler::Dependency.new(g, version, @options) }
+
+ Injector.inject(dependencies,
+ :conservative_versioning => @options[:version].nil?, # Perform conservative versioning only when version is not specified
+ :optimistic => @options[:optimistic],
+ :strict => @options[:strict])
+
+ Installer.install(Bundler.root, Bundler.definition) unless @options["skip-install"]
+ end
+ end
+end
diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb
new file mode 100644
index 0000000000..266396eedc
--- /dev/null
+++ b/lib/bundler/cli/binstubs.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Binstubs
+ attr_reader :options, :gems
+ def initialize(options, gems)
+ @options = options
+ @gems = gems
+ end
+
+ def run
+ Bundler.definition.validate_runtime!
+ path_option = options["path"]
+ path_option = nil if path_option && path_option.empty?
+ Bundler.settings.set_command_option :bin, path_option if options["path"]
+ Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
+ installer = Installer.new(Bundler.root, Bundler.definition)
+
+ installer_opts = { :force => options[:force], :binstubs_cmd => true }
+
+ if options[:all]
+ raise InvalidOption, "Cannot specify --all with specific gems" unless gems.empty?
+ @gems = Bundler.definition.specs.map(&:name)
+ installer_opts.delete(:binstubs_cmd)
+ elsif gems.empty?
+ Bundler.ui.error "`bundle binstubs` needs at least one gem to run."
+ exit 1
+ end
+
+ gems.each do |gem_name|
+ spec = Bundler.definition.specs.find {|s| s.name == gem_name }
+ unless spec
+ raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(
+ gem_name, Bundler.definition.specs
+ )
+ end
+
+ if options[:standalone]
+ next Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") if gem_name == "bundler"
+ Bundler.settings.temporary(:path => (Bundler.settings[:path] || Bundler.root)) do
+ installer.generate_standalone_bundler_executable_stubs(spec)
+ end
+ else
+ installer.generate_bundler_executable_stubs(spec, installer_opts)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb
new file mode 100644
index 0000000000..9d2ba87d34
--- /dev/null
+++ b/lib/bundler/cli/cache.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Cache
+ attr_reader :options
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ Bundler.definition.validate_runtime!
+ Bundler.definition.resolve_with_cache!
+ setup_cache_all
+ Bundler.settings.set_command_option_if_given :cache_all_platforms, options["all-platforms"]
+ Bundler.load.cache
+ Bundler.settings.set_command_option_if_given :no_prune, options["no-prune"]
+ Bundler.load.lock
+ rescue GemNotFound => e
+ Bundler.ui.error(e.message)
+ Bundler.ui.warn "Run `bundle install` to install missing gems."
+ exit 1
+ end
+
+ private
+
+ def setup_cache_all
+ Bundler.settings.set_command_option_if_given :cache_all, options[:all]
+
+ if Bundler.definition.has_local_dependencies? && !Bundler.feature_flag.cache_all?
+ Bundler.ui.warn "Your Gemfile contains path and git dependencies. If you want " \
+ "to package them as well, please pass the --all flag. This will be the default " \
+ "on Bundler 2.0."
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/check.rb b/lib/bundler/cli/check.rb
new file mode 100644
index 0000000000..19c0aaea06
--- /dev/null
+++ b/lib/bundler/cli/check.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Check
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ Bundler.settings.set_command_option_if_given :path, options[:path]
+
+ begin
+ definition = Bundler.definition
+ definition.validate_runtime!
+ not_installed = definition.missing_specs
+ rescue GemNotFound, VersionConflict
+ Bundler.ui.error "Bundler can't satisfy your Gemfile's dependencies."
+ Bundler.ui.warn "Install missing gems with `bundle install`."
+ exit 1
+ end
+
+ if not_installed.any?
+ Bundler.ui.error "The following gems are missing"
+ not_installed.each {|s| Bundler.ui.error " * #{s.name} (#{s.version})" }
+ Bundler.ui.warn "Install missing gems with `bundle install`"
+ exit 1
+ elsif !Bundler.default_lockfile.file? && Bundler.frozen_bundle?
+ Bundler.ui.error "This bundle has been frozen, but there is no #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} present"
+ exit 1
+ else
+ Bundler.load.lock(:preserve_unknown_sections => true) unless options[:"dry-run"]
+ Bundler.ui.info "The Gemfile's dependencies are satisfied"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/clean.rb b/lib/bundler/cli/clean.rb
new file mode 100644
index 0000000000..4a407fbae7
--- /dev/null
+++ b/lib/bundler/cli/clean.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Clean
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ require_path_or_force unless options[:"dry-run"]
+ Bundler.load.clean(options[:"dry-run"])
+ end
+
+ protected
+
+ def require_path_or_force
+ return unless Bundler.use_system_gems? && !options[:force]
+ raise InvalidOption, "Cleaning all the gems on your system is dangerous! " \
+ "If you're sure you want to remove every system gem not in this " \
+ "bundle, run `bundle clean --force`."
+ end
+ end
+end
diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb
new file mode 100644
index 0000000000..9d40ee9dfd
--- /dev/null
+++ b/lib/bundler/cli/common.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+module Bundler
+ module CLI::Common
+ def self.output_post_install_messages(messages)
+ return if Bundler.settings["ignore_messages"]
+ messages.to_a.each do |name, msg|
+ print_post_install_message(name, msg) unless Bundler.settings["ignore_messages.#{name}"]
+ end
+ end
+
+ def self.print_post_install_message(name, msg)
+ Bundler.ui.confirm "Post-install message from #{name}:"
+ Bundler.ui.info msg
+ end
+
+ def self.output_without_groups_message
+ return if Bundler.settings[:without].empty?
+ Bundler.ui.confirm without_groups_message
+ end
+
+ def self.without_groups_message
+ groups = Bundler.settings[:without]
+ group_list = [groups[0...-1].join(", "), groups[-1..-1]].
+ reject {|s| s.to_s.empty? }.join(" and ")
+ group_str = (groups.size == 1) ? "group" : "groups"
+ "Gems in the #{group_str} #{group_list} were not installed."
+ end
+
+ def self.select_spec(name, regex_match = nil)
+ specs = []
+ regexp = Regexp.new(name) if regex_match
+
+ Bundler.definition.specs.each do |spec|
+ return spec if spec.name == name
+ specs << spec if regexp && spec.name =~ regexp
+ end
+
+ case specs.count
+ when 0
+ raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
+ when 1
+ specs.first
+ else
+ ask_for_spec_from(specs)
+ end
+ rescue RegexpError
+ raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
+ end
+
+ def self.ask_for_spec_from(specs)
+ if !$stdout.tty? && ENV["BUNDLE_SPEC_RUN"].nil?
+ raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
+ end
+
+ specs.each_with_index do |spec, index|
+ Bundler.ui.info "#{index.succ} : #{spec.name}", true
+ end
+ Bundler.ui.info "0 : - exit -", true
+
+ num = Bundler.ui.ask("> ").to_i
+ num > 0 ? specs[num - 1] : nil
+ end
+
+ def self.gem_not_found_message(missing_gem_name, alternatives)
+ require "bundler/similarity_detector"
+ message = "Could not find gem '#{missing_gem_name}'."
+ alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a }
+ suggestions = SimilarityDetector.new(alternate_names).similar_word_list(missing_gem_name)
+ message += "\nDid you mean #{suggestions}?" if suggestions
+ message
+ end
+
+ def self.ensure_all_gems_in_lockfile!(names, locked_gems = Bundler.locked_gems)
+ locked_names = locked_gems.specs.map(&:name)
+ names.-(locked_names).each do |g|
+ raise GemNotFound, gem_not_found_message(g, locked_names)
+ end
+ end
+
+ def self.configure_gem_version_promoter(definition, options)
+ patch_level = patch_level_options(options)
+ raise InvalidOption, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1
+ definition.gem_version_promoter.tap do |gvp|
+ gvp.level = patch_level.first || :major
+ gvp.strict = options[:strict] || options["update-strict"]
+ end
+ end
+
+ def self.patch_level_options(options)
+ [:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) }
+ end
+
+ def self.clean_after_install?
+ clean = Bundler.settings[:clean]
+ return clean unless clean.nil?
+ clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil?
+ clean &&= !Bundler.use_system_gems?
+ clean
+ end
+ end
+end
diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb
new file mode 100644
index 0000000000..12f71ea8fe
--- /dev/null
+++ b/lib/bundler/cli/config.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Config
+ attr_reader :name, :options, :scope, :thor
+ attr_accessor :args
+
+ def initialize(options, args, thor)
+ @options = options
+ @args = args
+ @thor = thor
+ @name = peek = args.shift
+ @scope = "global"
+ return unless peek && peek.start_with?("--")
+ @name = args.shift
+ @scope = peek[2..-1]
+ end
+
+ def run
+ unless name
+ confirm_all
+ return
+ end
+
+ unless valid_scope?(scope)
+ Bundler.ui.error "Invalid scope --#{scope} given. Please use --local or --global."
+ exit 1
+ end
+
+ if scope == "delete"
+ Bundler.settings.set_local(name, nil)
+ Bundler.settings.set_global(name, nil)
+ return
+ end
+
+ if args.empty?
+ if options[:parseable]
+ if value = Bundler.settings[name]
+ Bundler.ui.info("#{name}=#{value}")
+ end
+ return
+ end
+
+ confirm(name)
+ return
+ end
+
+ Bundler.ui.info(message) if message
+ Bundler.settings.send("set_#{scope}", name, new_value)
+ end
+
+ private
+
+ def confirm_all
+ if @options[:parseable]
+ thor.with_padding do
+ Bundler.settings.all.each do |setting|
+ val = Bundler.settings[setting]
+ Bundler.ui.info "#{setting}=#{val}"
+ end
+ end
+ else
+ Bundler.ui.confirm "Settings are listed in order of priority. The top value will be used.\n"
+ Bundler.settings.all.each do |setting|
+ Bundler.ui.confirm "#{setting}"
+ show_pretty_values_for(setting)
+ Bundler.ui.confirm ""
+ end
+ end
+ end
+
+ def confirm(name)
+ Bundler.ui.confirm "Settings for `#{name}` in order of priority. The top value will be used"
+ show_pretty_values_for(name)
+ end
+
+ def new_value
+ pathname = Pathname.new(args.join(" "))
+ if name.start_with?("local.") && pathname.directory?
+ pathname.expand_path.to_s
+ else
+ args.join(" ")
+ end
+ end
+
+ def message
+ locations = Bundler.settings.locations(name)
+ if @options[:parseable]
+ "#{name}=#{new_value}" if new_value
+ elsif scope == "global"
+ if locations[:local]
+ "Your application has set #{name} to #{locations[:local].inspect}. " \
+ "This will override the global value you are currently setting"
+ elsif locations[:env]
+ "You have a bundler environment variable for #{name} set to " \
+ "#{locations[:env].inspect}. This will take precedence over the global value you are setting"
+ elsif locations[:global] && locations[:global] != args.join(" ")
+ "You are replacing the current global value of #{name}, which is currently " \
+ "#{locations[:global].inspect}"
+ end
+ elsif scope == "local" && locations[:local] != args.join(" ")
+ "You are replacing the current local value of #{name}, which is currently " \
+ "#{locations[:local].inspect}"
+ end
+ end
+
+ def show_pretty_values_for(setting)
+ thor.with_padding do
+ Bundler.settings.pretty_values_for(setting).each do |line|
+ Bundler.ui.info line
+ end
+ end
+ end
+
+ def valid_scope?(scope)
+ %w[delete local global].include?(scope)
+ end
+ end
+end
diff --git a/lib/bundler/cli/console.rb b/lib/bundler/cli/console.rb
new file mode 100644
index 0000000000..853eca8358
--- /dev/null
+++ b/lib/bundler/cli/console.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Console
+ attr_reader :options, :group
+ def initialize(options, group)
+ @options = options
+ @group = group
+ end
+
+ def run
+ Bundler::SharedHelpers.major_deprecation 2, "bundle console will be replaced " \
+ "by `bin/console` generated by `bundle gem <name>`"
+
+ group ? Bundler.require(:default, *(group.split.map!(&:to_sym))) : Bundler.require
+ ARGV.clear
+
+ console = get_console(Bundler.settings[:console] || "irb")
+ console.start
+ end
+
+ def get_console(name)
+ require name
+ get_constant(name)
+ rescue LoadError
+ Bundler.ui.error "Couldn't load console #{name}, falling back to irb"
+ require "irb"
+ get_constant("irb")
+ end
+
+ def get_constant(name)
+ const_name = {
+ "pry" => :Pry,
+ "ripl" => :Ripl,
+ "irb" => :IRB,
+ }[name]
+ Object.const_get(const_name)
+ rescue NameError
+ Bundler.ui.error "Could not find constant #{const_name}"
+ exit 1
+ end
+ end
+end
diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb
new file mode 100644
index 0000000000..3e0898ff8a
--- /dev/null
+++ b/lib/bundler/cli/doctor.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+require "rbconfig"
+
+module Bundler
+ class CLI::Doctor
+ DARWIN_REGEX = /\s+(.+) \(compatibility /
+ LDD_REGEX = /\t\S+ => (\S+) \(\S+\)/
+
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def otool_available?
+ Bundler.which("otool")
+ end
+
+ def ldd_available?
+ Bundler.which("ldd")
+ end
+
+ def dylibs_darwin(path)
+ output = `/usr/bin/otool -L "#{path}"`.chomp
+ dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq
+ # ignore @rpath and friends
+ dylibs.reject {|dylib| dylib.start_with? "@" }
+ end
+
+ def dylibs_ldd(path)
+ output = `/usr/bin/ldd "#{path}"`.chomp
+ output.split("\n").map do |l|
+ match = l.match(LDD_REGEX)
+ next if match.nil?
+ match.captures[0]
+ end.compact
+ end
+
+ def dylibs(path)
+ case RbConfig::CONFIG["host_os"]
+ when /darwin/
+ return [] unless otool_available?
+ dylibs_darwin(path)
+ when /(linux|solaris|bsd)/
+ return [] unless ldd_available?
+ dylibs_ldd(path)
+ else # Windows, etc.
+ Bundler.ui.warn("Dynamic library check not supported on this platform.")
+ []
+ end
+ end
+
+ def bundles_for_gem(spec)
+ Dir.glob("#{spec.full_gem_path}/**/*.bundle")
+ end
+
+ def check!
+ require "bundler/cli/check"
+ Bundler::CLI::Check.new({}).run
+ end
+
+ def run
+ Bundler.ui.level = "error" if options[:quiet]
+ Bundler.settings.validate!
+ check!
+
+ definition = Bundler.definition
+ broken_links = {}
+
+ definition.specs.each do |spec|
+ bundles_for_gem(spec).each do |bundle|
+ bad_paths = dylibs(bundle).select {|f| !File.exist?(f) }
+ if bad_paths.any?
+ broken_links[spec] ||= []
+ broken_links[spec].concat(bad_paths)
+ end
+ end
+ end
+
+ permissions_valid = check_home_permissions
+
+ if broken_links.any?
+ message = "The following gems are missing OS dependencies:"
+ broken_links.map do |spec, paths|
+ paths.uniq.map do |path|
+ "\n * #{spec.name}: #{path}"
+ end
+ end.flatten.sort.each {|m| message += m }
+ raise ProductionError, message
+ elsif !permissions_valid
+ Bundler.ui.info "No issues found with the installed bundle"
+ end
+ end
+
+ private
+
+ def check_home_permissions
+ require "find"
+ files_not_readable_or_writable = []
+ files_not_rw_and_owned_by_different_user = []
+ files_not_owned_by_current_user_but_still_rw = []
+ Find.find(Bundler.home.to_s).each do |f|
+ if !File.writable?(f) || !File.readable?(f)
+ if File.stat(f).uid != Process.uid
+ files_not_rw_and_owned_by_different_user << f
+ else
+ files_not_readable_or_writable << f
+ end
+ elsif File.stat(f).uid != Process.uid
+ files_not_owned_by_current_user_but_still_rw << f
+ end
+ end
+
+ ok = true
+ if files_not_owned_by_current_user_but_still_rw.any?
+ Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \
+ "user, but are still readable/writable. These files are:\n - #{files_not_owned_by_current_user_but_still_rw.join("\n - ")}"
+
+ ok = false
+ end
+
+ if files_not_rw_and_owned_by_different_user.any?
+ Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \
+ "user, and are not readable/writable. These files are:\n - #{files_not_rw_and_owned_by_different_user.join("\n - ")}"
+
+ ok = false
+ end
+
+ if files_not_readable_or_writable.any?
+ Bundler.ui.warn "Files exist in the Bundler home that are not " \
+ "readable/writable by the current user. These files are:\n - #{files_not_readable_or_writable.join("\n - ")}"
+
+ ok = false
+ end
+
+ ok
+ end
+ end
+end
diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb
new file mode 100644
index 0000000000..c29d632307
--- /dev/null
+++ b/lib/bundler/cli/exec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require "bundler/current_ruby"
+
+module Bundler
+ class CLI::Exec
+ attr_reader :options, :args, :cmd
+
+ TRAPPED_SIGNALS = %w[INT].freeze
+
+ def initialize(options, args)
+ @options = options
+ @cmd = args.shift
+ @args = args
+
+ if Bundler.current_ruby.ruby_2? && !Bundler.current_ruby.jruby?
+ @args << { :close_others => !options.keep_file_descriptors? }
+ elsif options.keep_file_descriptors?
+ Bundler.ui.warn "Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec."
+ end
+ end
+
+ def run
+ validate_cmd!
+ SharedHelpers.set_bundle_environment
+ if bin_path = Bundler.which(cmd)
+ if !Bundler.settings[:disable_exec_load] && ruby_shebang?(bin_path)
+ return kernel_load(bin_path, *args)
+ end
+ # First, try to exec directly to something in PATH
+ if Bundler.current_ruby.jruby_18?
+ kernel_exec(bin_path, *args)
+ else
+ kernel_exec([bin_path, cmd], *args)
+ end
+ else
+ # exec using the given command
+ kernel_exec(cmd, *args)
+ end
+ end
+
+ private
+
+ def validate_cmd!
+ return unless cmd.nil?
+ Bundler.ui.error "bundler: exec needs a command to run"
+ exit 128
+ end
+
+ def kernel_exec(*args)
+ ui = Bundler.ui
+ Bundler.ui = nil
+ Kernel.exec(*args)
+ rescue Errno::EACCES, Errno::ENOEXEC
+ Bundler.ui = ui
+ Bundler.ui.error "bundler: not executable: #{cmd}"
+ exit 126
+ rescue Errno::ENOENT
+ Bundler.ui = ui
+ Bundler.ui.error "bundler: command not found: #{cmd}"
+ Bundler.ui.warn "Install missing gem executables with `bundle install`"
+ exit 127
+ end
+
+ def kernel_load(file, *args)
+ args.pop if args.last.is_a?(Hash)
+ ARGV.replace(args)
+ $0 = file
+ Process.setproctitle(process_title(file, args)) if Process.respond_to?(:setproctitle)
+ ui = Bundler.ui
+ Bundler.ui = nil
+ require "bundler/setup"
+ TRAPPED_SIGNALS.each {|s| trap(s, "DEFAULT") }
+ Kernel.load(file)
+ rescue SystemExit, SignalException
+ raise
+ rescue Exception => e # rubocop:disable Lint/RescueException
+ Bundler.ui = ui
+ Bundler.ui.error "bundler: failed to load command: #{cmd} (#{file})"
+ backtrace = e.backtrace ? e.backtrace.take_while {|bt| !bt.start_with?(__FILE__) } : []
+ abort "#{e.class}: #{e.message}\n #{backtrace.join("\n ")}"
+ end
+
+ def process_title(file, args)
+ "#{file} #{args.join(" ")}".strip
+ end
+
+ def ruby_shebang?(file)
+ possibilities = [
+ "#!/usr/bin/env ruby\n",
+ "#!/usr/bin/env jruby\n",
+ "#!/usr/bin/env truffleruby\n",
+ "#!#{Gem.ruby}\n",
+ ]
+
+ if File.zero?(file)
+ Bundler.ui.warn "#{file} is empty"
+ return false
+ end
+
+ first_line = File.open(file, "rb") {|f| f.read(possibilities.map(&:size).max) }
+ possibilities.any? {|shebang| first_line.start_with?(shebang) }
+ end
+ end
+end
diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb
new file mode 100644
index 0000000000..58e2f8a3fd
--- /dev/null
+++ b/lib/bundler/cli/gem.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: true
+
+require "pathname"
+
+module Bundler
+ class CLI
+ Bundler.require_thor_actions
+ include Thor::Actions
+ end
+
+ class CLI::Gem
+ TEST_FRAMEWORK_VERSIONS = {
+ "rspec" => "3.0",
+ "minitest" => "5.0"
+ }.freeze
+
+ attr_reader :options, :gem_name, :thor, :name, :target
+
+ def initialize(options, gem_name, thor)
+ @options = options
+ @gem_name = resolve_name(gem_name)
+
+ @thor = thor
+ thor.behavior = :invoke
+ thor.destination_root = nil
+
+ @name = @gem_name
+ @target = SharedHelpers.pwd.join(gem_name)
+
+ validate_ext_name if options[:ext]
+ end
+
+ def run
+ Bundler.ui.confirm "Creating gem '#{name}'..."
+
+ underscored_name = name.tr("-", "_")
+ namespaced_path = name.tr("-", "/")
+ constant_name = name.gsub(/-[_-]*(?![_-]|$)/) { "::" }.gsub(/([_-]+|(::)|^)(.|$)/) { $2.to_s + $3.upcase }
+ constant_array = constant_name.split("::")
+
+ git_installed = Bundler.git_present?
+
+ git_author_name = git_installed ? `git config user.name`.chomp : ""
+ github_username = git_installed ? `git config github.user`.chomp : ""
+ git_user_email = git_installed ? `git config user.email`.chomp : ""
+
+ config = {
+ :name => name,
+ :underscored_name => underscored_name,
+ :namespaced_path => namespaced_path,
+ :makefile_path => "#{underscored_name}/#{underscored_name}",
+ :constant_name => constant_name,
+ :constant_array => constant_array,
+ :author => git_author_name.empty? ? "TODO: Write your name" : git_author_name,
+ :email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email,
+ :test => options[:test],
+ :ext => options[:ext],
+ :exe => options[:exe],
+ :bundler_version => bundler_dependency_version,
+ :github_username => github_username.empty? ? "[USERNAME]" : github_username
+ }
+ ensure_safe_gem_name(name, constant_array)
+
+ templates = {
+ "Gemfile.tt" => "Gemfile",
+ "lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb",
+ "lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb",
+ "newgem.gemspec.tt" => "#{name}.gemspec",
+ "Rakefile.tt" => "Rakefile",
+ "README.md.tt" => "README.md",
+ "bin/console.tt" => "bin/console",
+ "bin/setup.tt" => "bin/setup"
+ }
+
+ executables = %w[
+ bin/console
+ bin/setup
+ ]
+
+ templates.merge!("gitignore.tt" => ".gitignore") if Bundler.git_present?
+
+ if test_framework = ask_and_set_test_framework
+ config[:test] = test_framework
+ config[:test_framework_version] = TEST_FRAMEWORK_VERSIONS[test_framework]
+
+ templates.merge!("travis.yml.tt" => ".travis.yml")
+
+ case test_framework
+ when "rspec"
+ templates.merge!(
+ "rspec.tt" => ".rspec",
+ "spec/spec_helper.rb.tt" => "spec/spec_helper.rb",
+ "spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb"
+ )
+ when "minitest"
+ templates.merge!(
+ "test/test_helper.rb.tt" => "test/test_helper.rb",
+ "test/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb"
+ )
+ end
+ end
+
+ config[:test_task] = config[:test] == "minitest" ? "test" : "spec"
+
+ if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?",
+ "This means that any other developer or company will be legally allowed to use your code " \
+ "for free as long as they admit you created it. You can read more about the MIT license " \
+ "at https://choosealicense.com/licenses/mit.")
+ config[:mit] = true
+ Bundler.ui.info "MIT License enabled in config"
+ templates.merge!("LICENSE.txt.tt" => "LICENSE.txt")
+ end
+
+ if ask_and_set(:coc, "Do you want to include a code of conduct in gems you generate?",
+ "Codes of conduct can increase contributions to your project by contributors who " \
+ "prefer collaborative, safe spaces. You can read more about the code of conduct at " \
+ "contributor-covenant.org. Having a code of conduct means agreeing to the responsibility " \
+ "of enforcing it, so be sure that you are prepared to do that. Be sure that your email " \
+ "address is specified as a contact in the generated code of conduct so that people know " \
+ "who to contact in case of a violation. For suggestions about " \
+ "how to enforce codes of conduct, see https://bit.ly/coc-enforcement.")
+ config[:coc] = true
+ Bundler.ui.info "Code of conduct enabled in config"
+ templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md")
+ end
+
+ templates.merge!("exe/newgem.tt" => "exe/#{name}") if config[:exe]
+
+ if options[:ext]
+ templates.merge!(
+ "ext/newgem/extconf.rb.tt" => "ext/#{name}/extconf.rb",
+ "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h",
+ "ext/newgem/newgem.c.tt" => "ext/#{name}/#{underscored_name}.c"
+ )
+ end
+
+ templates.each do |src, dst|
+ destination = target.join(dst)
+ SharedHelpers.filesystem_access(destination) do
+ thor.template("newgem/#{src}", destination, config)
+ end
+ end
+
+ executables.each do |file|
+ SharedHelpers.filesystem_access(target.join(file)) do |path|
+ executable = (path.stat.mode | 0o111)
+ path.chmod(executable)
+ end
+ end
+
+ if Bundler.git_present?
+ Bundler.ui.info "Initializing git repo in #{target}"
+ Dir.chdir(target) do
+ `git init`
+ `git add .`
+ end
+ end
+
+ # Open gemspec in editor
+ open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit]
+
+ Bundler.ui.info "Gem '#{name}' was successfully created. " \
+ "For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html"
+ rescue Errno::EEXIST => e
+ raise GenericSystemCallError.new(e, "There was a conflict while creating the new gem.")
+ end
+
+ private
+
+ def resolve_name(name)
+ SharedHelpers.pwd.join(name).basename.to_s
+ end
+
+ def ask_and_set(key, header, message)
+ choice = options[key]
+ choice = Bundler.settings["gem.#{key}"] if choice.nil?
+
+ if choice.nil?
+ Bundler.ui.confirm header
+ choice = Bundler.ui.yes? "#{message} y/(n):"
+ Bundler.settings.set_global("gem.#{key}", choice)
+ end
+
+ choice
+ end
+
+ def validate_ext_name
+ return unless gem_name.index("-")
+
+ Bundler.ui.error "You have specified a gem name which does not conform to the \n" \
+ "naming guidelines for C extensions. For more information, \n" \
+ "see the 'Extension Naming' section at the following URL:\n" \
+ "http://guides.rubygems.org/gems-with-extensions/\n"
+ exit 1
+ end
+
+ def ask_and_set_test_framework
+ test_framework = options[:test] || Bundler.settings["gem.test"]
+
+ if test_framework.nil?
+ Bundler.ui.confirm "Do you want to generate tests with your gem?"
+ result = Bundler.ui.ask "Type 'rspec' or 'minitest' to generate those test files now and " \
+ "in the future. rspec/minitest/(none):"
+ if result =~ /rspec|minitest/
+ test_framework = result
+ else
+ test_framework = false
+ end
+ end
+
+ if Bundler.settings["gem.test"].nil?
+ Bundler.settings.set_global("gem.test", test_framework)
+ end
+
+ test_framework
+ end
+
+ def bundler_dependency_version
+ v = Gem::Version.new(Bundler::VERSION)
+ req = v.segments[0..1]
+ req << "a" if v.prerelease?
+ req.join(".")
+ end
+
+ def ensure_safe_gem_name(name, constant_array)
+ if name =~ /^\d/
+ Bundler.ui.error "Invalid gem name #{name} Please give a name which does not start with numbers."
+ exit 1
+ end
+
+ constant_name = constant_array.join("::")
+
+ existing_constant = constant_array.inject(Object) do |c, s|
+ defined = begin
+ c.const_defined?(s)
+ rescue NameError
+ Bundler.ui.error "Invalid gem name #{name} -- `#{constant_name}` is an invalid constant name"
+ exit 1
+ end
+ (defined && c.const_get(s)) || break
+ end
+
+ return unless existing_constant
+ Bundler.ui.error "Invalid gem name #{name} constant #{constant_name} is already in use. Please choose another gem name."
+ exit 1
+ end
+
+ def open_editor(editor, file)
+ thor.run(%(#{editor} "#{file}"))
+ end
+ end
+end
diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb
new file mode 100644
index 0000000000..958b525067
--- /dev/null
+++ b/lib/bundler/cli/info.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Info
+ attr_reader :gem_name, :options
+ def initialize(options, gem_name)
+ @options = options
+ @gem_name = gem_name
+ end
+
+ def run
+ spec = spec_for_gem(gem_name)
+
+ spec_not_found(gem_name) unless spec
+ return print_gem_path(spec) if @options[:path]
+ print_gem_info(spec)
+ end
+
+ private
+
+ def spec_for_gem(gem_name)
+ spec = Bundler.definition.specs.find {|s| s.name == gem_name }
+ spec || default_gem_spec(gem_name)
+ end
+
+ def default_gem_spec(gem_name)
+ return unless Gem::Specification.respond_to?(:find_all_by_name)
+ gem_spec = Gem::Specification.find_all_by_name(gem_name).last
+ return gem_spec if gem_spec && gem_spec.respond_to?(:default_gem?) && gem_spec.default_gem?
+ end
+
+ def spec_not_found(gem_name)
+ raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(gem_name, Bundler.definition.dependencies)
+ end
+
+ def print_gem_path(spec)
+ Bundler.ui.info spec.full_gem_path
+ end
+
+ def print_gem_info(spec)
+ gem_info = String.new
+ gem_info << " * #{spec.name} (#{spec.version}#{spec.git_version})\n"
+ gem_info << "\tSummary: #{spec.summary}\n" if spec.summary
+ gem_info << "\tHomepage: #{spec.homepage}\n" if spec.homepage
+ gem_info << "\tPath: #{spec.full_gem_path}\n"
+ gem_info << "\tDefault Gem: yes" if spec.respond_to?(:default_gem?) && spec.default_gem?
+ Bundler.ui.info gem_info
+ end
+ end
+end
diff --git a/lib/bundler/cli/init.rb b/lib/bundler/cli/init.rb
new file mode 100644
index 0000000000..40df797269
--- /dev/null
+++ b/lib/bundler/cli/init.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Init
+ attr_reader :options
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ if File.exist?(gemfile)
+ Bundler.ui.error "#{gemfile} already exists at #{File.expand_path(gemfile)}"
+ exit 1
+ end
+
+ unless File.writable?(Dir.pwd)
+ Bundler.ui.error "Can not create #{gemfile} as the current directory is not writable."
+ exit 1
+ end
+
+ if options[:gemspec]
+ gemspec = File.expand_path(options[:gemspec])
+ unless File.exist?(gemspec)
+ Bundler.ui.error "Gem specification #{gemspec} doesn't exist"
+ exit 1
+ end
+
+ spec = Bundler.load_gemspec_uncached(gemspec)
+
+ File.open(gemfile, "wb") do |file|
+ file << "# Generated from #{gemspec}\n"
+ file << spec.to_gemfile
+ end
+ else
+ FileUtils.cp(File.expand_path("../../templates/#{gemfile}", __FILE__), gemfile)
+ end
+
+ puts "Writing new #{gemfile} to #{SharedHelpers.pwd}/#{gemfile}"
+ end
+
+ private
+
+ def gemfile
+ @gemfile ||= Bundler.feature_flag.init_gems_rb? ? "gems.rb" : "Gemfile"
+ end
+ end
+end
diff --git a/lib/bundler/cli/inject.rb b/lib/bundler/cli/inject.rb
new file mode 100644
index 0000000000..b00675d348
--- /dev/null
+++ b/lib/bundler/cli/inject.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Inject
+ attr_reader :options, :name, :version, :group, :source, :gems
+ def initialize(options, name, version)
+ @options = options
+ @name = name
+ @version = version || last_version_number
+ @group = options[:group].split(",") unless options[:group].nil?
+ @source = options[:source]
+ @gems = []
+ end
+
+ def run
+ # The required arguments allow Thor to give useful feedback when the arguments
+ # are incorrect. This adds those first two arguments onto the list as a whole.
+ gems.unshift(source).unshift(group).unshift(version).unshift(name)
+
+ # Build an array of Dependency objects out of the arguments
+ deps = []
+ # when `inject` support addition of more than one gem, then this loop will
+ # help. Currently this loop is running once.
+ gems.each_slice(4) do |gem_name, gem_version, gem_group, gem_source|
+ ops = Gem::Requirement::OPS.map {|key, _val| key }
+ has_op = ops.any? {|op| gem_version.start_with? op }
+ gem_version = "~> #{gem_version}" unless has_op
+ deps << Bundler::Dependency.new(gem_name, gem_version, "group" => gem_group, "source" => gem_source)
+ end
+
+ added = Injector.inject(deps, options)
+
+ if added.any?
+ Bundler.ui.confirm "Added to Gemfile:"
+ Bundler.ui.confirm(added.map do |d|
+ name = "'#{d.name}'"
+ requirement = ", '#{d.requirement}'"
+ group = ", :group => #{d.groups.inspect}" if d.groups != Array(:default)
+ source = ", :source => '#{d.source}'" unless d.source.nil?
+ %(gem #{name}#{requirement}#{group}#{source})
+ end.join("\n"))
+ else
+ Bundler.ui.confirm "All gems were already present in the Gemfile"
+ end
+ end
+
+ private
+
+ def last_version_number
+ definition = Bundler.definition(true)
+ definition.resolve_remotely!
+ specs = definition.index[name].sort_by(&:version)
+ unless options[:pre]
+ specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
+ end
+ spec = specs.last
+ spec.version.to_s
+ end
+ end
+end
diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb
new file mode 100644
index 0000000000..b40e5f0e9e
--- /dev/null
+++ b/lib/bundler/cli/install.rb
@@ -0,0 +1,217 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Install
+ attr_reader :options
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ Bundler.ui.level = "error" if options[:quiet]
+
+ warn_if_root
+
+ normalize_groups
+
+ Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Bundler::FREEBSD
+
+ # Disable color in deployment mode
+ Bundler.ui.shell = Thor::Shell::Basic.new if options[:deployment]
+
+ check_for_options_conflicts
+
+ check_trust_policy
+
+ if options[:deployment] || options[:frozen] || Bundler.frozen_bundle?
+ unless Bundler.default_lockfile.exist?
+ flag = "--deployment flag" if options[:deployment]
+ flag ||= "--frozen flag" if options[:frozen]
+ flag ||= "deployment setting"
+ raise ProductionError, "The #{flag} requires a #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}. Please make " \
+ "sure you have checked your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} into version control " \
+ "before deploying."
+ end
+
+ options[:local] = true if Bundler.app_cache.exist?
+
+ if Bundler.feature_flag.deployment_means_frozen?
+ Bundler.settings.set_command_option :deployment, true
+ else
+ Bundler.settings.set_command_option :frozen, true
+ end
+ end
+
+ # When install is called with --no-deployment, disable deployment mode
+ if options[:deployment] == false
+ Bundler.settings.set_command_option :frozen, nil
+ options[:system] = true
+ end
+
+ normalize_settings
+
+ Bundler::Fetcher.disable_endpoint = options["full-index"]
+
+ if options["binstubs"]
+ Bundler::SharedHelpers.major_deprecation 2,
+ "The --binstubs option will be removed in favor of `bundle binstubs`"
+ end
+
+ Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
+
+ definition = Bundler.definition
+ definition.validate_runtime!
+
+ installer = Installer.install(Bundler.root, definition, options)
+ Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.frozen_bundle?
+
+ Bundler.ui.confirm "Bundle complete! #{dependencies_count_for(definition)}, #{gems_installed_for(definition)}."
+ Bundler::CLI::Common.output_without_groups_message
+
+ if Bundler.use_system_gems?
+ Bundler.ui.confirm "Use `bundle info [gemname]` to see where a bundled gem is installed."
+ else
+ relative_path = Bundler.configured_bundle_path.base_path_relative_to_pwd
+ Bundler.ui.confirm "Bundled gems are installed into `#{relative_path}`"
+ end
+
+ Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
+
+ warn_ambiguous_gems
+
+ if CLI::Common.clean_after_install?
+ require "bundler/cli/clean"
+ Bundler::CLI::Clean.new(options).run
+ end
+ rescue GemNotFound, VersionConflict => e
+ if options[:local] && Bundler.app_cache.exist?
+ Bundler.ui.warn "Some gems seem to be missing from your #{Bundler.settings.app_cache_path} directory."
+ end
+
+ unless Bundler.definition.has_rubygems_remotes?
+ Bundler.ui.warn <<-WARN, :wrap => true
+ Your Gemfile has no gem server sources. If you need gems that are \
+ not already on your machine, add a line like this to your Gemfile:
+ source 'https://rubygems.org'
+ WARN
+ end
+ raise e
+ rescue Gem::InvalidSpecificationException => e
+ Bundler.ui.warn "You have one or more invalid gemspecs that need to be fixed."
+ raise e
+ end
+
+ private
+
+ def warn_if_root
+ return if Bundler.settings[:silence_root_warning] || Bundler::WINDOWS || !Process.uid.zero?
+ Bundler.ui.warn "Don't run Bundler as root. Bundler can ask for sudo " \
+ "if it is needed, and installing your bundle as root will break this " \
+ "application for all non-root users on this machine.", :wrap => true
+ end
+
+ def dependencies_count_for(definition)
+ count = definition.dependencies.count
+ "#{count} Gemfile #{count == 1 ? "dependency" : "dependencies"}"
+ end
+
+ def gems_installed_for(definition)
+ count = definition.specs.count
+ "#{count} #{count == 1 ? "gem" : "gems"} now installed"
+ end
+
+ def check_for_group_conflicts_in_cli_options
+ conflicting_groups = Array(options[:without]) & Array(options[:with])
+ return if conflicting_groups.empty?
+ raise InvalidOption, "You can't list a group in both with and without." \
+ " The offending groups are: #{conflicting_groups.join(", ")}."
+ end
+
+ def check_for_options_conflicts
+ if (options[:path] || options[:deployment]) && options[:system]
+ error_message = String.new
+ error_message << "You have specified both --path as well as --system. Please choose only one option.\n" if options[:path]
+ error_message << "You have specified both --deployment as well as --system. Please choose only one option.\n" if options[:deployment]
+ raise InvalidOption.new(error_message)
+ end
+ end
+
+ def check_trust_policy
+ trust_policy = options["trust-policy"]
+ unless Bundler.rubygems.security_policies.keys.unshift(nil).include?(trust_policy)
+ raise InvalidOption, "RubyGems doesn't know about trust policy '#{trust_policy}'. " \
+ "The known policies are: #{Bundler.rubygems.security_policies.keys.join(", ")}."
+ end
+ Bundler.settings.set_command_option_if_given :"trust-policy", trust_policy
+ end
+
+ def normalize_groups
+ options[:with] &&= options[:with].join(":").tr(" ", ":").split(":")
+ options[:without] &&= options[:without].join(":").tr(" ", ":").split(":")
+
+ check_for_group_conflicts_in_cli_options
+
+ Bundler.settings.set_command_option :with, nil if options[:with] == []
+ Bundler.settings.set_command_option :without, nil if options[:without] == []
+
+ with = options.fetch(:with, [])
+ with |= Bundler.settings[:with].map(&:to_s)
+ with -= options[:without] if options[:without]
+
+ without = options.fetch(:without, [])
+ without |= Bundler.settings[:without].map(&:to_s)
+ without -= options[:with] if options[:with]
+
+ options[:with] = with
+ options[:without] = without
+ end
+
+ def normalize_settings
+ Bundler.settings.set_command_option :path, nil if options[:system]
+ Bundler.settings.temporary(:path_relative_to_cwd => false) do
+ Bundler.settings.set_command_option :path, "vendor/bundle" if options[:deployment]
+ end
+ Bundler.settings.set_command_option_if_given :path, options[:path]
+ Bundler.settings.temporary(:path_relative_to_cwd => false) do
+ Bundler.settings.set_command_option :path, "bundle" if options["standalone"] && Bundler.settings[:path].nil?
+ end
+
+ bin_option = options["binstubs"]
+ bin_option = nil if bin_option && bin_option.empty?
+ Bundler.settings.set_command_option :bin, bin_option if options["binstubs"]
+
+ Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
+
+ Bundler.settings.set_command_option_if_given :jobs, options["jobs"]
+
+ Bundler.settings.set_command_option_if_given :no_prune, options["no-prune"]
+
+ Bundler.settings.set_command_option_if_given :no_install, options["no-install"]
+
+ Bundler.settings.set_command_option_if_given :clean, options["clean"]
+
+ unless Bundler.settings[:without] == options[:without] && Bundler.settings[:with] == options[:with]
+ # need to nil them out first to get around validation for backwards compatibility
+ Bundler.settings.set_command_option :without, nil
+ Bundler.settings.set_command_option :with, nil
+ Bundler.settings.set_command_option :without, options[:without] - options[:with]
+ Bundler.settings.set_command_option :with, options[:with]
+ end
+
+ options[:force] = options[:redownload]
+ end
+
+ def warn_ambiguous_gems
+ Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris|
+ Bundler.ui.error "Warning: the gem '#{name}' was found in multiple sources."
+ Bundler.ui.error "Installed from: #{installed_from_uri}"
+ Bundler.ui.error "Also found in:"
+ also_found_in_uris.each {|uri| Bundler.ui.error " * #{uri}" }
+ Bundler.ui.error "You should add a source requirement to restrict this gem to your preferred source."
+ Bundler.ui.error "For example:"
+ Bundler.ui.error " gem '#{name}', :source => '#{installed_from_uri}'"
+ Bundler.ui.error "Then uninstall the gem '#{name}' (or delete all bundled gems) and then install again."
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb
new file mode 100644
index 0000000000..91f827ea99
--- /dev/null
+++ b/lib/bundler/cli/issue.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require "rbconfig"
+
+module Bundler
+ class CLI::Issue
+ def run
+ Bundler.ui.info <<-EOS.gsub(/^ {8}/, "")
+ Did you find an issue with Bundler? Before filing a new issue,
+ be sure to check out these resources:
+
+ 1. Check out our troubleshooting guide for quick fixes to common issues:
+ https://github.com/bundler/bundler/blob/master/doc/TROUBLESHOOTING.md
+
+ 2. Instructions for common Bundler uses can be found on the documentation
+ site: http://bundler.io/
+
+ 3. Information about each Bundler command can be found in the Bundler
+ man pages: http://bundler.io/man/bundle.1.html
+
+ Hopefully the troubleshooting steps above resolved your problem! If things
+ still aren't working the way you expect them to, please let us know so
+ that we can diagnose and help fix the problem you're having. Please
+ view the Filing Issues guide for more information:
+ https://github.com/bundler/bundler/blob/master/doc/contributing/ISSUES.md
+
+ EOS
+
+ Bundler.ui.info Bundler::Env.report
+
+ Bundler.ui.info "\n## Bundle Doctor"
+ doctor
+ end
+
+ def doctor
+ require "bundler/cli/doctor"
+ Bundler::CLI::Doctor.new({}).run
+ end
+ end
+end
diff --git a/lib/bundler/cli/list.rb b/lib/bundler/cli/list.rb
new file mode 100644
index 0000000000..d1799196e7
--- /dev/null
+++ b/lib/bundler/cli/list.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::List
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ raise InvalidOption, "The `--only-group` and `--without-group` options cannot be used together" if @options["only-group"] && @options["without-group"]
+
+ raise InvalidOption, "The `--name-only` and `--paths` options cannot be used together" if @options["name-only"] && @options[:paths]
+
+ specs = if @options["only-group"] || @options["without-group"]
+ filtered_specs_by_groups
+ else
+ Bundler.load.specs
+ end.reject {|s| s.name == "bundler" }.sort_by(&:name)
+
+ return Bundler.ui.info "No gems in the Gemfile" if specs.empty?
+
+ return specs.each {|s| Bundler.ui.info s.name } if @options["name-only"]
+ return specs.each {|s| Bundler.ui.info s.full_gem_path } if @options["paths"]
+
+ Bundler.ui.info "Gems included by the bundle:"
+
+ specs.each {|s| Bundler.ui.info " * #{s.name} (#{s.version}#{s.git_version})" }
+
+ Bundler.ui.info "Use `bundle info` to print more detailed information about a gem"
+ end
+
+ private
+
+ def verify_group_exists(groups)
+ raise InvalidOption, "`#{@options["without-group"]}` group could not be found." if @options["without-group"] && !groups.include?(@options["without-group"].to_sym)
+
+ raise InvalidOption, "`#{@options["only-group"]}` group could not be found." if @options["only-group"] && !groups.include?(@options["only-group"].to_sym)
+ end
+
+ def filtered_specs_by_groups
+ definition = Bundler.definition
+ groups = definition.groups
+
+ verify_group_exists(groups)
+
+ show_groups =
+ if @options["without-group"]
+ groups.reject {|g| g == @options["without-group"].to_sym }
+ elsif @options["only-group"]
+ groups.select {|g| g == @options["only-group"].to_sym }
+ else
+ groups
+ end.map(&:to_sym)
+
+ definition.specs_for(show_groups)
+ end
+ end
+end
diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb
new file mode 100644
index 0000000000..7dd078b1ef
--- /dev/null
+++ b/lib/bundler/cli/lock.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Lock
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ unless Bundler.default_gemfile
+ Bundler.ui.error "Unable to find a Gemfile to lock"
+ exit 1
+ end
+
+ print = options[:print]
+ ui = Bundler.ui
+ Bundler.ui = UI::Silent.new if print
+
+ Bundler::Fetcher.disable_endpoint = options["full-index"]
+
+ update = options[:update]
+ if update.is_a?(Array) # unlocking specific gems
+ Bundler::CLI::Common.ensure_all_gems_in_lockfile!(update)
+ update = { :gems => update, :lock_shared_dependencies => options[:conservative] }
+ end
+ definition = Bundler.definition(update)
+
+ Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options) if options[:update]
+
+ options["remove-platform"].each do |platform|
+ definition.remove_platform(platform)
+ end
+
+ options["add-platform"].each do |platform_string|
+ platform = Gem::Platform.new(platform_string)
+ if platform.to_s == "unknown"
+ Bundler.ui.warn "The platform `#{platform_string}` is unknown to RubyGems " \
+ "and adding it will likely lead to resolution errors"
+ end
+ definition.add_platform(platform)
+ end
+
+ if definition.platforms.empty?
+ raise InvalidOption, "Removing all platforms from the bundle is not allowed"
+ end
+
+ definition.resolve_remotely! unless options[:local]
+
+ if print
+ puts definition.to_lock
+ else
+ file = options[:lockfile]
+ file = file ? File.expand_path(file) : Bundler.default_lockfile
+ puts "Writing lockfile to #{file}"
+ definition.lock(file)
+ end
+
+ Bundler.ui = ui
+ end
+ end
+end
diff --git a/lib/bundler/cli/open.rb b/lib/bundler/cli/open.rb
new file mode 100644
index 0000000000..552fe6f128
--- /dev/null
+++ b/lib/bundler/cli/open.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require "shellwords"
+
+module Bundler
+ class CLI::Open
+ attr_reader :options, :name
+ def initialize(options, name)
+ @options = options
+ @name = name
+ end
+
+ def run
+ editor = [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }
+ return Bundler.ui.info("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") unless editor
+ return unless spec = Bundler::CLI::Common.select_spec(name, :regex_match)
+ path = spec.full_gem_path
+ Dir.chdir(path) do
+ command = Shellwords.split(editor) + [path]
+ Bundler.with_original_env do
+ system(*command)
+ end || Bundler.ui.info("Could not run '#{command.join(" ")}'")
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb
new file mode 100644
index 0000000000..2ca90293db
--- /dev/null
+++ b/lib/bundler/cli/outdated.rb
@@ -0,0 +1,266 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Outdated
+ attr_reader :options, :gems
+
+ def initialize(options, gems)
+ @options = options
+ @gems = gems
+ end
+
+ def run
+ check_for_deployment_mode
+
+ sources = Array(options[:source])
+
+ gems.each do |gem_name|
+ Bundler::CLI::Common.select_spec(gem_name)
+ end
+
+ Bundler.definition.validate_runtime!
+ current_specs = Bundler.ui.silence { Bundler.definition.resolve }
+ current_dependencies = {}
+ Bundler.ui.silence do
+ Bundler.load.dependencies.each do |dep|
+ current_dependencies[dep.name] = dep
+ end
+ end
+
+ definition = if gems.empty? && sources.empty?
+ # We're doing a full update
+ Bundler.definition(true)
+ else
+ Bundler.definition(:gems => gems, :sources => sources)
+ end
+
+ Bundler::CLI::Common.configure_gem_version_promoter(
+ Bundler.definition,
+ options
+ )
+
+ # the patch level options imply strict is also true. It wouldn't make
+ # sense otherwise.
+ strict = options[:strict] ||
+ Bundler::CLI::Common.patch_level_options(options).any?
+
+ filter_options_patch = options.keys &
+ %w[filter-major filter-minor filter-patch]
+
+ definition_resolution = proc do
+ options[:local] ? definition.resolve_with_cache! : definition.resolve_remotely!
+ end
+
+ if options[:parseable]
+ Bundler.ui.silence(&definition_resolution)
+ else
+ definition_resolution.call
+ end
+
+ Bundler.ui.info ""
+ outdated_gems_by_groups = {}
+ outdated_gems_list = []
+
+ # Loop through the current specs
+ gemfile_specs, dependency_specs = current_specs.partition do |spec|
+ current_dependencies.key? spec.name
+ end
+
+ specs = if options["only-explicit"]
+ gemfile_specs
+ else
+ gemfile_specs + dependency_specs
+ end
+
+ specs.sort_by(&:name).each do |current_spec|
+ next if !gems.empty? && !gems.include?(current_spec.name)
+
+ dependency = current_dependencies[current_spec.name]
+ active_spec = retrieve_active_spec(strict, definition, current_spec)
+
+ next if active_spec.nil?
+ if filter_options_patch.any?
+ update_present = update_present_via_semver_portions(current_spec, active_spec, options)
+ next unless update_present
+ end
+
+ gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version)
+ next unless gem_outdated || (current_spec.git_version != active_spec.git_version)
+ groups = nil
+ if dependency && !options[:parseable]
+ groups = dependency.groups.join(", ")
+ end
+
+ outdated_gems_list << { :active_spec => active_spec,
+ :current_spec => current_spec,
+ :dependency => dependency,
+ :groups => groups }
+
+ outdated_gems_by_groups[groups] ||= []
+ outdated_gems_by_groups[groups] << { :active_spec => active_spec,
+ :current_spec => current_spec,
+ :dependency => dependency,
+ :groups => groups }
+ end
+
+ if outdated_gems_list.empty?
+ display_nothing_outdated_message(filter_options_patch)
+ else
+ unless options[:parseable]
+ if options[:pre]
+ Bundler.ui.info "Outdated gems included in the bundle (including " \
+ "pre-releases):"
+ else
+ Bundler.ui.info "Outdated gems included in the bundle:"
+ end
+ end
+
+ options_include_groups = [:group, :groups].select do |v|
+ options.keys.include?(v.to_s)
+ end
+
+ if options_include_groups.any?
+ ordered_groups = outdated_gems_by_groups.keys.compact.sort
+ [nil, ordered_groups].flatten.each do |groups|
+ gems = outdated_gems_by_groups[groups]
+ contains_group = if groups
+ groups.split(",").include?(options[:group])
+ else
+ options[:group] == "group"
+ end
+
+ next if (!options[:groups] && !contains_group) || gems.nil?
+
+ unless options[:parseable]
+ if groups
+ Bundler.ui.info "===== Group #{groups} ====="
+ else
+ Bundler.ui.info "===== Without group ====="
+ end
+ end
+
+ gems.each do |gem|
+ print_gem(
+ gem[:current_spec],
+ gem[:active_spec],
+ gem[:dependency],
+ groups,
+ options_include_groups.any?
+ )
+ end
+ end
+ else
+ outdated_gems_list.each do |gem|
+ print_gem(
+ gem[:current_spec],
+ gem[:active_spec],
+ gem[:dependency],
+ gem[:groups],
+ options_include_groups.any?
+ )
+ end
+ end
+
+ exit 1
+ end
+ end
+
+ private
+
+ def retrieve_active_spec(strict, definition, current_spec)
+ if strict
+ active_spec = definition.find_resolved_spec(current_spec)
+ else
+ active_specs = definition.find_indexed_specs(current_spec)
+ if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
+ active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
+ end
+ active_spec = active_specs.last
+ end
+
+ active_spec
+ end
+
+ def display_nothing_outdated_message(filter_options_patch)
+ unless options[:parseable]
+ if filter_options_patch.any?
+ display = filter_options_patch.map do |o|
+ o.sub("filter-", "")
+ end.join(" or ")
+
+ Bundler.ui.info "No #{display} updates to display.\n"
+ else
+ Bundler.ui.info "Bundle up to date!\n"
+ end
+ end
+ end
+
+ def print_gem(current_spec, active_spec, dependency, groups, options_include_groups)
+ spec_version = "#{active_spec.version}#{active_spec.git_version}"
+ spec_version += " (from #{active_spec.loaded_from})" if Bundler.ui.debug? && active_spec.loaded_from
+ current_version = "#{current_spec.version}#{current_spec.git_version}"
+
+ if dependency && dependency.specific?
+ dependency_version = %(, requested #{dependency.requirement})
+ end
+
+ spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, " \
+ "installed #{current_version}#{dependency_version})"
+
+ output_message = if options[:parseable]
+ spec_outdated_info.to_s
+ elsif options_include_groups || !groups
+ " * #{spec_outdated_info}"
+ else
+ " * #{spec_outdated_info} in groups \"#{groups}\""
+ end
+
+ Bundler.ui.info output_message.rstrip
+ end
+
+ def check_for_deployment_mode
+ return unless Bundler.frozen_bundle?
+ suggested_command = if Bundler.settings.locations("frozen")[:global]
+ "bundle config --delete frozen"
+ elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
+ "bundle config --delete deployment"
+ else
+ "bundle install --no-deployment"
+ end
+ raise ProductionError, "You are trying to check outdated gems in " \
+ "deployment mode. Run `bundle outdated` elsewhere.\n" \
+ "\nIf this is a development machine, remove the " \
+ "#{Bundler.default_gemfile} freeze" \
+ "\nby running `#{suggested_command}`."
+ end
+
+ def update_present_via_semver_portions(current_spec, active_spec, options)
+ current_major = current_spec.version.segments.first
+ active_major = active_spec.version.segments.first
+
+ update_present = false
+ update_present = active_major > current_major if options["filter-major"]
+
+ if !update_present && (options["filter-minor"] || options["filter-patch"]) && current_major == active_major
+ current_minor = get_version_semver_portion_value(current_spec, 1)
+ active_minor = get_version_semver_portion_value(active_spec, 1)
+
+ update_present = active_minor > current_minor if options["filter-minor"]
+
+ if !update_present && options["filter-patch"] && current_minor == active_minor
+ current_patch = get_version_semver_portion_value(current_spec, 2)
+ active_patch = get_version_semver_portion_value(active_spec, 2)
+
+ update_present = active_patch > current_patch
+ end
+ end
+
+ update_present
+ end
+
+ def get_version_semver_portion_value(spec, version_portion_index)
+ version_section = spec.version.segments[version_portion_index, 1]
+ version_section.nil? ? 0 : (version_section.first || 0)
+ end
+ end
+end
diff --git a/lib/bundler/cli/package.rb b/lib/bundler/cli/package.rb
new file mode 100644
index 0000000000..2dcd0e1e29
--- /dev/null
+++ b/lib/bundler/cli/package.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Package
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ Bundler.ui.level = "error" if options[:quiet]
+ Bundler.settings.set_command_option_if_given :path, options[:path]
+ Bundler.settings.set_command_option_if_given :cache_all_platforms, options["all-platforms"]
+ Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"]
+
+ setup_cache_all
+ install
+
+ # TODO: move cache contents here now that all bundles are locked
+ custom_path = Bundler.settings[:path] if options[:path]
+ Bundler.load.cache(custom_path)
+ end
+
+ private
+
+ def install
+ require "bundler/cli/install"
+ options = self.options.dup
+ if Bundler.settings[:cache_all_platforms]
+ options["local"] = false
+ options["update"] = true
+ end
+ Bundler::CLI::Install.new(options).run
+ end
+
+ def setup_cache_all
+ all = options.fetch(:all, Bundler.feature_flag.cache_command_is_package? || nil)
+
+ Bundler.settings.set_command_option_if_given :cache_all, all
+
+ if Bundler.definition.has_local_dependencies? && !Bundler.feature_flag.cache_all?
+ Bundler.ui.warn "Your Gemfile contains path and git dependencies. If you want " \
+ "to package them as well, please pass the --all flag. This will be the default " \
+ "on Bundler 2.0."
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/platform.rb b/lib/bundler/cli/platform.rb
new file mode 100644
index 0000000000..e97cad49a4
--- /dev/null
+++ b/lib/bundler/cli/platform.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Platform
+ attr_reader :options
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ platforms, ruby_version = Bundler.ui.silence do
+ locked_ruby_version = Bundler.locked_gems && Bundler.locked_gems.ruby_version
+ gemfile_ruby_version = Bundler.definition.ruby_version && Bundler.definition.ruby_version.single_version_string
+ [Bundler.definition.platforms.map {|p| "* #{p}" },
+ locked_ruby_version || gemfile_ruby_version]
+ end
+ output = []
+
+ if options[:ruby]
+ if ruby_version
+ output << ruby_version
+ else
+ output << "No ruby version specified"
+ end
+ else
+ output << "Your platform is: #{RUBY_PLATFORM}"
+ output << "Your app has gems that work on these platforms:\n#{platforms.join("\n")}"
+
+ if ruby_version
+ output << "Your Gemfile specifies a Ruby version requirement:\n* #{ruby_version}"
+
+ begin
+ Bundler.definition.validate_runtime!
+ output << "Your current platform satisfies the Ruby version requirement."
+ rescue RubyVersionMismatch => e
+ output << e.message
+ end
+ else
+ output << "Your Gemfile does not specify a Ruby version requirement."
+ end
+ end
+
+ Bundler.ui.info output.join("\n\n")
+ end
+ end
+end
diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb
new file mode 100644
index 0000000000..5488a9f28d
--- /dev/null
+++ b/lib/bundler/cli/plugin.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require "bundler/vendored_thor"
+module Bundler
+ class CLI::Plugin < Thor
+ desc "install PLUGINS", "Install the plugin from the source"
+ long_desc <<-D
+ Install plugins either from the rubygems source provided (with --source option) or from a git source provided with (--git option). If no sources are provided, it uses Gem.sources
+ D
+ method_option "source", :type => :string, :default => nil, :banner =>
+ "URL of the RubyGems source to fetch the plugin from"
+ method_option "version", :type => :string, :default => nil, :banner =>
+ "The version of the plugin to fetch"
+ method_option "git", :type => :string, :default => nil, :banner =>
+ "URL of the git repo to fetch from"
+ method_option "branch", :type => :string, :default => nil, :banner =>
+ "The git branch to checkout"
+ method_option "ref", :type => :string, :default => nil, :banner =>
+ "The git revision to check out"
+ def install(*plugins)
+ Bundler::Plugin.install(plugins, options)
+ end
+ end
+end
diff --git a/lib/bundler/cli/pristine.rb b/lib/bundler/cli/pristine.rb
new file mode 100644
index 0000000000..4a411a83fc
--- /dev/null
+++ b/lib/bundler/cli/pristine.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Pristine
+ def initialize(gems)
+ @gems = gems
+ end
+
+ def run
+ CLI::Common.ensure_all_gems_in_lockfile!(@gems)
+ definition = Bundler.definition
+ definition.validate_runtime!
+ installer = Bundler::Installer.new(Bundler.root, definition)
+
+ Bundler.load.specs.each do |spec|
+ next if spec.name == "bundler" # Source::Rubygems doesn't install bundler
+ next if !@gems.empty? && !@gems.include?(spec.name)
+
+ gem_name = "#{spec.name} (#{spec.version}#{spec.git_version})"
+ gem_name += " (#{spec.platform})" if !spec.platform.nil? && spec.platform != Gem::Platform::RUBY
+
+ case source = spec.source
+ when Source::Rubygems
+ cached_gem = spec.cache_file
+ unless File.exist?(cached_gem)
+ Bundler.ui.error("Failed to pristine #{gem_name}. Cached gem #{cached_gem} does not exist.")
+ next
+ end
+
+ FileUtils.rm_rf spec.full_gem_path
+ when Source::Git
+ source.remote!
+ if extension_cache_path = source.extension_cache_path(spec)
+ FileUtils.rm_rf extension_cache_path
+ end
+ FileUtils.rm_rf spec.extension_dir if spec.respond_to?(:extension_dir)
+ FileUtils.rm_rf spec.full_gem_path
+ else
+ Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.")
+ next
+ end
+
+ Bundler::GemInstaller.new(spec, installer, false, 0, true).install_from_spec
+ end
+ end
+ end
+end
diff --git a/lib/bundler/cli/remove.rb b/lib/bundler/cli/remove.rb
new file mode 100644
index 0000000000..cd6a2cec28
--- /dev/null
+++ b/lib/bundler/cli/remove.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Remove
+ def initialize(gems, options)
+ @gems = gems
+ @options = options
+ end
+
+ def run
+ raise InvalidOption, "Please specify gems to remove." if @gems.empty?
+
+ Injector.remove(@gems, {})
+
+ Installer.install(Bundler.root, Bundler.definition) if @options["install"]
+ end
+ end
+end
diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb
new file mode 100644
index 0000000000..61756801b2
--- /dev/null
+++ b/lib/bundler/cli/show.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Show
+ attr_reader :options, :gem_name, :latest_specs
+ def initialize(options, gem_name)
+ @options = options
+ @gem_name = gem_name
+ @verbose = options[:verbose] || options[:outdated]
+ @latest_specs = fetch_latest_specs if @verbose
+ end
+
+ def run
+ Bundler.ui.silence do
+ Bundler.definition.validate_runtime!
+ Bundler.load.lock
+ end
+
+ if gem_name
+ if gem_name == "bundler"
+ path = File.expand_path("../../../..", __FILE__)
+ else
+ spec = Bundler::CLI::Common.select_spec(gem_name, :regex_match)
+ return unless spec
+ path = spec.full_gem_path
+ unless File.directory?(path)
+ Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at:"
+ end
+ end
+ return Bundler.ui.info(path)
+ end
+
+ if options[:paths]
+ Bundler.load.specs.sort_by(&:name).map do |s|
+ Bundler.ui.info s.full_gem_path
+ end
+ else
+ Bundler.ui.info "Gems included by the bundle:"
+ Bundler.load.specs.sort_by(&:name).each do |s|
+ desc = " * #{s.name} (#{s.version}#{s.git_version})"
+ if @verbose
+ latest = latest_specs.find {|l| l.name == s.name }
+ Bundler.ui.info <<-END.gsub(/^ +/, "")
+ #{desc}
+ \tSummary: #{s.summary || "No description available."}
+ \tHomepage: #{s.homepage || "No website available."}
+ \tStatus: #{outdated?(s, latest) ? "Outdated - #{s.version} < #{latest.version}" : "Up to date"}
+ END
+ else
+ Bundler.ui.info desc
+ end
+ end
+ end
+ end
+
+ private
+
+ def fetch_latest_specs
+ definition = Bundler.definition(true)
+ if options[:outdated]
+ Bundler.ui.info "Fetching remote specs for outdated check...\n\n"
+ Bundler.ui.silence { definition.resolve_remotely! }
+ else
+ definition.resolve_with_cache!
+ end
+ Bundler.reset!
+ definition.specs
+ end
+
+ def outdated?(current, latest)
+ return false unless latest
+ Gem::Version.new(current.version) < Gem::Version.new(latest.version)
+ end
+ end
+end
diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb
new file mode 100644
index 0000000000..b088853768
--- /dev/null
+++ b/lib/bundler/cli/update.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Update
+ attr_reader :options, :gems
+ def initialize(options, gems)
+ @options = options
+ @gems = gems
+ end
+
+ def run
+ Bundler.ui.level = "error" if options[:quiet]
+
+ Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
+
+ sources = Array(options[:source])
+ groups = Array(options[:group]).map(&:to_sym)
+
+ full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !options[:bundler]
+
+ if full_update && !options[:all]
+ if Bundler.feature_flag.update_requires_all_flag?
+ raise InvalidOption, "To update everything, pass the `--all` flag."
+ end
+ SharedHelpers.major_deprecation 2, "Pass --all to `bundle update` to update everything"
+ elsif !full_update && options[:all]
+ raise InvalidOption, "Cannot specify --all along with specific options."
+ end
+
+ if full_update
+ # We're doing a full update
+ Bundler.definition(true)
+ else
+ unless Bundler.default_lockfile.exist?
+ raise GemfileLockNotFound, "This Bundle hasn't been installed yet. " \
+ "Run `bundle install` to update and install the bundled gems."
+ end
+ Bundler::CLI::Common.ensure_all_gems_in_lockfile!(gems)
+
+ if groups.any?
+ deps = Bundler.definition.dependencies.select {|d| (d.groups & groups).any? }
+ gems.concat(deps.map(&:name))
+ end
+
+ Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby],
+ :lock_shared_dependencies => options[:conservative],
+ :bundler => options[:bundler])
+ end
+
+ Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options)
+
+ Bundler::Fetcher.disable_endpoint = options["full-index"]
+
+ opts = options.dup
+ opts["update"] = true
+ opts["local"] = options[:local]
+
+ Bundler.settings.set_command_option_if_given :jobs, opts["jobs"]
+
+ Bundler.definition.validate_runtime!
+ installer = Installer.install Bundler.root, Bundler.definition, opts
+ Bundler.load.cache if Bundler.app_cache.exist?
+
+ if CLI::Common.clean_after_install?
+ require "bundler/cli/clean"
+ Bundler::CLI::Clean.new(options).run
+ end
+
+ if locked_gems = Bundler.definition.locked_gems
+ gems.each do |name|
+ locked_version = locked_gems.specs.find {|s| s.name == name }
+ locked_version &&= locked_version.version
+ next unless locked_version
+ new_version = Bundler.definition.specs[name].first
+ new_version &&= new_version.version
+ if !new_version
+ Bundler.ui.warn "Bundler attempted to update #{name} but it was removed from the bundle"
+ elsif new_version < locked_version
+ Bundler.ui.warn "Note: #{name} version regressed from #{locked_version} to #{new_version}"
+ elsif new_version == locked_version
+ Bundler.ui.warn "Bundler attempted to update #{name} but its version stayed the same"
+ end
+ end
+ end
+
+ Bundler.ui.confirm "Bundle updated!"
+ Bundler::CLI::Common.output_without_groups_message
+ Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
+ end
+ end
+end
diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb
new file mode 100644
index 0000000000..644f9b25cf
--- /dev/null
+++ b/lib/bundler/cli/viz.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CLI::Viz
+ attr_reader :options, :gem_name
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ # make sure we get the right `graphviz`. There is also a `graphviz`
+ # gem we're not built to support
+ gem "ruby-graphviz"
+ require "graphviz"
+
+ options[:without] = options[:without].join(":").tr(" ", ":").split(":")
+ output_file = File.expand_path(options[:file])
+
+ graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without])
+ graph.viz
+ rescue LoadError => e
+ Bundler.ui.error e.inspect
+ Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:"
+ Bundler.ui.warn "`gem install ruby-graphviz`"
+ rescue StandardError => e
+ raise unless e.message =~ /GraphViz not installed or dot not in PATH/
+ Bundler.ui.error e.message
+ Bundler.ui.warn "Please install GraphViz. On a Mac with Homebrew, you can run `brew install graphviz`."
+ end
+ end
+end
diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb
new file mode 100644
index 0000000000..6c241ca07a
--- /dev/null
+++ b/lib/bundler/compact_index_client.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require "pathname"
+require "set"
+
+module Bundler
+ class CompactIndexClient
+ DEBUG_MUTEX = Mutex.new
+ def self.debug
+ return unless ENV["DEBUG_COMPACT_INDEX"]
+ DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
+ end
+
+ class Error < StandardError; end
+
+ require "bundler/compact_index_client/cache"
+ require "bundler/compact_index_client/updater"
+
+ attr_reader :directory
+
+ # @return [Lambda] A lambda that takes an array of inputs and a block, and
+ # maps the inputs with the block in parallel.
+ #
+ attr_accessor :in_parallel
+
+ def initialize(directory, fetcher)
+ @directory = Pathname.new(directory)
+ @updater = Updater.new(fetcher)
+ @cache = Cache.new(@directory)
+ @endpoints = Set.new
+ @info_checksums_by_name = {}
+ @parsed_checksums = false
+ @mutex = Mutex.new
+ @in_parallel = lambda do |inputs, &blk|
+ inputs.map(&blk)
+ end
+ end
+
+ def names
+ Bundler::CompactIndexClient.debug { "/names" }
+ update(@cache.names_path, "names")
+ @cache.names
+ end
+
+ def versions
+ Bundler::CompactIndexClient.debug { "/versions" }
+ update(@cache.versions_path, "versions")
+ versions, @info_checksums_by_name = @cache.versions
+ versions
+ end
+
+ def dependencies(names)
+ Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
+ in_parallel.call(names) do |name|
+ update_info(name)
+ @cache.dependencies(name).map {|d| d.unshift(name) }
+ end.flatten(1)
+ end
+
+ def spec(name, version, platform = nil)
+ Bundler::CompactIndexClient.debug { "spec(name = #{name}, version = #{version}, platform = #{platform})" }
+ update_info(name)
+ @cache.specific_dependency(name, version, platform)
+ end
+
+ def update_and_parse_checksums!
+ Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" }
+ return @info_checksums_by_name if @parsed_checksums
+ update(@cache.versions_path, "versions")
+ @info_checksums_by_name = @cache.checksums
+ @parsed_checksums = true
+ end
+
+ private
+
+ def update(local_path, remote_path)
+ Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" }
+ unless synchronize { @endpoints.add?(remote_path) }
+ Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
+ return
+ end
+ @updater.update(local_path, url(remote_path))
+ end
+
+ def update_info(name)
+ Bundler::CompactIndexClient.debug { "update_info(#{name})" }
+ path = @cache.info_path(name)
+ checksum = @updater.checksum_for_file(path)
+ unless existing = @info_checksums_by_name[name]
+ Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" }
+ return
+ end
+ if checksum == existing
+ Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" }
+ return
+ end
+ Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" }
+ update(path, "info/#{name}")
+ end
+
+ def url(path)
+ path
+ end
+
+ def synchronize
+ @mutex.synchronize { yield }
+ end
+ end
+end
diff --git a/lib/bundler/compact_index_client/cache.rb b/lib/bundler/compact_index_client/cache.rb
new file mode 100644
index 0000000000..f6105d3bb3
--- /dev/null
+++ b/lib/bundler/compact_index_client/cache.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CompactIndexClient
+ class Cache
+ attr_reader :directory
+
+ def initialize(directory)
+ @directory = Pathname.new(directory).expand_path
+ info_roots.each do |dir|
+ SharedHelpers.filesystem_access(dir) do
+ FileUtils.mkdir_p(dir)
+ end
+ end
+ end
+
+ def names
+ lines(names_path)
+ end
+
+ def names_path
+ directory.join("names")
+ end
+
+ def versions
+ versions_by_name = Hash.new {|hash, key| hash[key] = [] }
+ info_checksums_by_name = {}
+
+ lines(versions_path).each do |line|
+ name, versions_string, info_checksum = line.split(" ", 3)
+ info_checksums_by_name[name] = info_checksum || ""
+ versions_string.split(",").each do |version|
+ if version.start_with?("-")
+ version = version[1..-1].split("-", 2).unshift(name)
+ versions_by_name[name].delete(version)
+ else
+ version = version.split("-", 2).unshift(name)
+ versions_by_name[name] << version
+ end
+ end
+ end
+
+ [versions_by_name, info_checksums_by_name]
+ end
+
+ def versions_path
+ directory.join("versions")
+ end
+
+ def checksums
+ checksums = {}
+
+ lines(versions_path).each do |line|
+ name, _, checksum = line.split(" ", 3)
+ checksums[name] = checksum
+ end
+
+ checksums
+ end
+
+ def dependencies(name)
+ lines(info_path(name)).map do |line|
+ parse_gem(line)
+ end
+ end
+
+ def info_path(name)
+ name = name.to_s
+ if name =~ /[^a-z0-9_-]/
+ name += "-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}"
+ info_roots.last.join(name)
+ else
+ info_roots.first.join(name)
+ end
+ end
+
+ def specific_dependency(name, version, platform)
+ pattern = [version, platform].compact.join("-")
+ return nil if pattern.empty?
+
+ gem_lines = info_path(name).read
+ gem_line = gem_lines[/^#{Regexp.escape(pattern)}\b.*/, 0]
+ gem_line ? parse_gem(gem_line) : nil
+ end
+
+ private
+
+ def lines(path)
+ return [] unless path.file?
+ lines = SharedHelpers.filesystem_access(path, :read, &:read).split("\n")
+ header = lines.index("---")
+ header ? lines[header + 1..-1] : lines
+ end
+
+ def parse_gem(string)
+ version_and_platform, rest = string.split(" ", 2)
+ version, platform = version_and_platform.split("-", 2)
+ dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
+ dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
+ requirements = requirements ? requirements.map {|r| parse_dependency(r) } : []
+ [version, platform, dependencies, requirements]
+ end
+
+ def parse_dependency(string)
+ dependency = string.split(":")
+ dependency[-1] = dependency[-1].split("&") if dependency.size > 1
+ dependency
+ end
+
+ def info_roots
+ [
+ directory.join("info"),
+ directory.join("info-special-characters"),
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb
new file mode 100644
index 0000000000..4d6eb80044
--- /dev/null
+++ b/lib/bundler/compact_index_client/updater.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+require "bundler/vendored_fileutils"
+require "stringio"
+require "zlib"
+
+module Bundler
+ class CompactIndexClient
+ class Updater
+ class MisMatchedChecksumError < Error
+ def initialize(path, server_checksum, local_checksum)
+ @path = path
+ @server_checksum = server_checksum
+ @local_checksum = local_checksum
+ end
+
+ def message
+ "The checksum of /#{@path} does not match the checksum provided by the server! Something is wrong " \
+ "(local checksum is #{@local_checksum.inspect}, was expecting #{@server_checksum.inspect})."
+ end
+ end
+
+ def initialize(fetcher)
+ @fetcher = fetcher
+ require "tmpdir"
+ end
+
+ def update(local_path, remote_path, retrying = nil)
+ headers = {}
+
+ Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir|
+ local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename)
+
+ # first try to fetch any new bytes on the existing file
+ if retrying.nil? && local_path.file?
+ SharedHelpers.filesystem_access(local_temp_path) do
+ FileUtils.cp local_path, local_temp_path
+ end
+ headers["If-None-Match"] = etag_for(local_temp_path)
+ headers["Range"] =
+ if local_temp_path.size.nonzero?
+ # Subtract a byte to ensure the range won't be empty.
+ # Avoids 416 (Range Not Satisfiable) responses.
+ "bytes=#{local_temp_path.size - 1}-"
+ else
+ "bytes=#{local_temp_path.size}-"
+ end
+ else
+ # Fastly ignores Range when Accept-Encoding: gzip is set
+ headers["Accept-Encoding"] = "gzip"
+ end
+
+ response = @fetcher.call(remote_path, headers)
+ return nil if response.is_a?(Net::HTTPNotModified)
+
+ content = response.body
+ if response["Content-Encoding"] == "gzip"
+ content = Zlib::GzipReader.new(StringIO.new(content)).read
+ end
+
+ SharedHelpers.filesystem_access(local_temp_path) do
+ if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero?
+ local_temp_path.open("a") {|f| f << slice_body(content, 1..-1) }
+ else
+ local_temp_path.open("w") {|f| f << content }
+ end
+ end
+
+ response_etag = (response["ETag"] || "").gsub(%r{\AW/}, "")
+ if etag_for(local_temp_path) == response_etag
+ SharedHelpers.filesystem_access(local_path) do
+ FileUtils.mv(local_temp_path, local_path)
+ end
+ return nil
+ end
+
+ if retrying
+ raise MisMatchedChecksumError.new(remote_path, response_etag, etag_for(local_temp_path))
+ end
+
+ update(local_path, remote_path, :retrying)
+ end
+ rescue Errno::EACCES
+ raise Bundler::PermissionError,
+ "Bundler does not have write access to create a temp directory " \
+ "within #{Dir.tmpdir}. Bundler must have write access to your " \
+ "systems temp directory to function properly. "
+ rescue Zlib::GzipFile::Error
+ raise Bundler::HTTPError
+ end
+
+ def etag_for(path)
+ sum = checksum_for_file(path)
+ sum ? %("#{sum}") : nil
+ end
+
+ def slice_body(body, range)
+ if body.respond_to?(:byteslice)
+ body.byteslice(range)
+ else # pre-1.9.3
+ body.unpack("@#{range.first}a#{range.end + 1}").first
+ end
+ end
+
+ def checksum_for_file(path)
+ return nil unless path.file?
+ # This must use IO.read instead of Digest.file().hexdigest
+ # because we need to preserve \n line endings on windows when calculating
+ # the checksum
+ SharedHelpers.filesystem_access(path, :read) do
+ SharedHelpers.digest(:MD5).hexdigest(IO.read(path))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/compatibility_guard.rb b/lib/bundler/compatibility_guard.rb
new file mode 100644
index 0000000000..750a1db04f
--- /dev/null
+++ b/lib/bundler/compatibility_guard.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: false
+
+require "rubygems"
+require "bundler/version"
+
+if Bundler::VERSION.split(".").first.to_i >= 2
+ if Gem::Version.new(Object::RUBY_VERSION.dup) < Gem::Version.new("2.3")
+ abort "Bundler 2 requires Ruby 2.3 or later. Either install bundler 1 or update to a supported Ruby version."
+ end
+
+ if Gem::Version.new(Gem::VERSION.dup) < Gem::Version.new("2.5")
+ abort "Bundler 2 requires RubyGems 2.5 or later. Either install bundler 1 or update to a supported RubyGems version."
+ end
+end
diff --git a/lib/bundler/constants.rb b/lib/bundler/constants.rb
new file mode 100644
index 0000000000..2e4ebb37ee
--- /dev/null
+++ b/lib/bundler/constants.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Bundler
+ WINDOWS = RbConfig::CONFIG["host_os"] =~ /(msdos|mswin|djgpp|mingw)/
+ FREEBSD = RbConfig::CONFIG["host_os"] =~ /bsd/
+ NULL = WINDOWS ? "NUL" : "/dev/null"
+end
diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb
new file mode 100644
index 0000000000..d5efaad6c5
--- /dev/null
+++ b/lib/bundler/current_ruby.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module Bundler
+ # Returns current version of Ruby
+ #
+ # @return [CurrentRuby] Current version of Ruby
+ def self.current_ruby
+ @current_ruby ||= CurrentRuby.new
+ end
+
+ class CurrentRuby
+ KNOWN_MINOR_VERSIONS = %w[
+ 1.8
+ 1.9
+ 2.0
+ 2.1
+ 2.2
+ 2.3
+ 2.4
+ 2.5
+ 2.6
+ ].freeze
+
+ KNOWN_MAJOR_VERSIONS = KNOWN_MINOR_VERSIONS.map {|v| v.split(".", 2).first }.uniq.freeze
+
+ KNOWN_PLATFORMS = %w[
+ jruby
+ maglev
+ mingw
+ mri
+ mswin
+ mswin64
+ rbx
+ ruby
+ truffleruby
+ x64_mingw
+ ].freeze
+
+ def ruby?
+ !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" ||
+ RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev" || RUBY_ENGINE == "truffleruby")
+ end
+
+ def mri?
+ !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby")
+ end
+
+ def rbx?
+ ruby? && defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
+ end
+
+ def jruby?
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
+ end
+
+ def maglev?
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "maglev"
+ end
+
+ def truffleruby?
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "truffleruby"
+ end
+
+ def mswin?
+ Bundler::WINDOWS
+ end
+
+ def mswin64?
+ Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mswin64" && Bundler.local_platform.cpu == "x64"
+ end
+
+ def mingw?
+ Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu != "x64"
+ end
+
+ def x64_mingw?
+ Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu == "x64"
+ end
+
+ (KNOWN_MINOR_VERSIONS + KNOWN_MAJOR_VERSIONS).each do |version|
+ trimmed_version = version.tr(".", "")
+ define_method(:"on_#{trimmed_version}?") do
+ RUBY_VERSION.start_with?("#{version}.")
+ end
+
+ KNOWN_PLATFORMS.each do |platform|
+ define_method(:"#{platform}_#{trimmed_version}?") do
+ send(:"#{platform}?") && send(:"on_#{trimmed_version}?")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
new file mode 100644
index 0000000000..8e56d4a9bc
--- /dev/null
+++ b/lib/bundler/definition.rb
@@ -0,0 +1,993 @@
+# frozen_string_literal: true
+
+require "bundler/lockfile_parser"
+require "set"
+
+module Bundler
+ class Definition
+ include GemHelpers
+
+ attr_reader(
+ :dependencies,
+ :locked_deps,
+ :locked_gems,
+ :platforms,
+ :requires,
+ :ruby_version,
+ :lockfile,
+ :gemfiles
+ )
+
+ # Given a gemfile and lockfile creates a Bundler definition
+ #
+ # @param gemfile [Pathname] Path to Gemfile
+ # @param lockfile [Pathname,nil] Path to Gemfile.lock
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
+ # to be updated or true if all gems should be updated
+ # @return [Bundler::Definition]
+ def self.build(gemfile, lockfile, unlock)
+ unlock ||= {}
+ gemfile = Pathname.new(gemfile).expand_path
+
+ raise GemfileNotFound, "#{gemfile} not found" unless gemfile.file?
+
+ Dsl.evaluate(gemfile, lockfile, unlock)
+ end
+
+ #
+ # How does the new system work?
+ #
+ # * Load information from Gemfile and Lockfile
+ # * Invalidate stale locked specs
+ # * All specs from stale source are stale
+ # * All specs that are reachable only through a stale
+ # dependency are stale.
+ # * If all fresh dependencies are satisfied by the locked
+ # specs, then we can try to resolve locally.
+ #
+ # @param lockfile [Pathname] Path to Gemfile.lock
+ # @param dependencies [Array(Bundler::Dependency)] array of dependencies from Gemfile
+ # @param sources [Bundler::SourceList]
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
+ # to be updated or true if all gems should be updated
+ # @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version
+ # @param optional_groups [Array(String)] A list of optional groups
+ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = [])
+ if [true, false].include?(unlock)
+ @unlocking_bundler = false
+ @unlocking = unlock
+ else
+ unlock = unlock.dup
+ @unlocking_bundler = unlock.delete(:bundler)
+ unlock.delete_if {|_k, v| Array(v).empty? }
+ @unlocking = !unlock.empty?
+ end
+
+ @dependencies = dependencies
+ @sources = sources
+ @unlock = unlock
+ @optional_groups = optional_groups
+ @remote = false
+ @specs = nil
+ @ruby_version = ruby_version
+ @gemfiles = gemfiles
+
+ @lockfile = lockfile
+ @lockfile_contents = String.new
+ @locked_bundler_version = nil
+ @locked_ruby_version = nil
+ @locked_specs_incomplete_for_platform = false
+
+ if lockfile && File.exist?(lockfile)
+ @lockfile_contents = Bundler.read_file(lockfile)
+ @locked_gems = LockfileParser.new(@lockfile_contents)
+ @locked_platforms = @locked_gems.platforms
+ @platforms = @locked_platforms.dup
+ @locked_bundler_version = @locked_gems.bundler_version
+ @locked_ruby_version = @locked_gems.ruby_version
+
+ if unlock != true
+ @locked_deps = @locked_gems.dependencies
+ @locked_specs = SpecSet.new(@locked_gems.specs)
+ @locked_sources = @locked_gems.sources
+ else
+ @unlock = {}
+ @locked_deps = {}
+ @locked_specs = SpecSet.new([])
+ @locked_sources = []
+ end
+ else
+ @unlock = {}
+ @platforms = []
+ @locked_gems = nil
+ @locked_deps = {}
+ @locked_specs = SpecSet.new([])
+ @locked_sources = []
+ @locked_platforms = []
+ end
+
+ @unlock[:gems] ||= []
+ @unlock[:sources] ||= []
+ @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
+ @ruby_version.diff(locked_ruby_version_object)
+ end
+ @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)
+
+ add_current_platform unless Bundler.frozen_bundle?
+
+ converge_path_sources_to_gemspec_sources
+ @path_changes = converge_paths
+ @source_changes = converge_sources
+
+ unless @unlock[:lock_shared_dependencies]
+ eager_unlock = expand_dependencies(@unlock[:gems], true)
+ @unlock[:gems] = @locked_specs.for(eager_unlock, [], false, false, false).map(&:name)
+ end
+
+ @dependency_changes = converge_dependencies
+ @local_changes = converge_locals
+
+ @requires = compute_requires
+ end
+
+ def gem_version_promoter
+ @gem_version_promoter ||= begin
+ locked_specs =
+ if unlocking? && @locked_specs.empty? && !@lockfile_contents.empty?
+ # Definition uses an empty set of locked_specs to indicate all gems
+ # are unlocked, but GemVersionPromoter needs the locked_specs
+ # for conservative comparison.
+ Bundler::SpecSet.new(@locked_gems.specs)
+ else
+ @locked_specs
+ end
+ GemVersionPromoter.new(locked_specs, @unlock[:gems])
+ end
+ end
+
+ def resolve_with_cache!
+ raise "Specs already loaded" if @specs
+ sources.cached!
+ specs
+ end
+
+ def resolve_remotely!
+ raise "Specs already loaded" if @specs
+ @remote = true
+ sources.remote!
+ specs
+ end
+
+ # For given dependency list returns a SpecSet with Gemspec of all the required
+ # dependencies.
+ # 1. The method first resolves the dependencies specified in Gemfile
+ # 2. After that it tries and fetches gemspec of resolved dependencies
+ #
+ # @return [Bundler::SpecSet]
+ def specs
+ @specs ||= begin
+ begin
+ specs = resolve.materialize(Bundler.settings[:cache_all_platforms] ? dependencies : requested_dependencies)
+ rescue GemNotFound => e # Handle yanked gem
+ gem_name, gem_version = extract_gem_info(e)
+ locked_gem = @locked_specs[gem_name].last
+ raise if locked_gem.nil? || locked_gem.version.to_s != gem_version || !@remote
+ raise GemNotFound, "Your bundle is locked to #{locked_gem}, but that version could not " \
+ "be found in any of the sources listed in your Gemfile. If you haven't changed sources, " \
+ "that means the author of #{locked_gem} has removed it. You'll need to update your bundle " \
+ "to a version other than #{locked_gem} that hasn't been removed in order to install."
+ end
+ unless specs["bundler"].any?
+ bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", VERSION)).last
+ specs["bundler"] = bundler
+ end
+
+ specs
+ end
+ end
+
+ def new_specs
+ specs - @locked_specs
+ end
+
+ def removed_specs
+ @locked_specs - specs
+ end
+
+ def new_platform?
+ @new_platform
+ end
+
+ def missing_specs
+ missing = []
+ resolve.materialize(requested_dependencies, missing)
+ missing
+ end
+
+ def missing_specs?
+ missing = missing_specs
+ return false if missing.empty?
+ Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}"
+ true
+ rescue BundlerError => e
+ @index = nil
+ @resolve = nil
+ @specs = nil
+ @gem_version_promoter = nil
+
+ Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
+ true
+ end
+
+ def requested_specs
+ @requested_specs ||= begin
+ groups = requested_groups
+ groups.map!(&:to_sym)
+ specs_for(groups)
+ end
+ end
+
+ def current_dependencies
+ dependencies.select(&:should_include?)
+ end
+
+ def specs_for(groups)
+ deps = dependencies.select {|d| (d.groups & groups).any? }
+ deps.delete_if {|d| !d.should_include? }
+ specs.for(expand_dependencies(deps))
+ end
+
+ # Resolve all the dependencies specified in Gemfile. It ensures that
+ # dependencies that have been already resolved via locked file and are fresh
+ # are reused when resolving dependencies
+ #
+ # @return [SpecSet] resolved dependencies
+ def resolve
+ @resolve ||= begin
+ last_resolve = converge_locked_specs
+ resolve =
+ if Bundler.frozen_bundle?
+ Bundler.ui.debug "Frozen, using resolution from the lockfile"
+ last_resolve
+ elsif !unlocking? && nothing_changed?
+ Bundler.ui.debug("Found no changes, using resolution from the lockfile")
+ last_resolve
+ else
+ # Run a resolve against the locally available gems
+ Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
+ last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
+ end
+
+ # filter out gems that _can_ be installed on multiple platforms, but don't need
+ # to be
+ resolve.for(expand_dependencies(dependencies, true), [], false, false, false)
+ end
+ end
+
+ def index
+ @index ||= Index.build do |idx|
+ dependency_names = @dependencies.map(&:name)
+
+ sources.all_sources.each do |source|
+ source.dependency_names = dependency_names - pinned_spec_names(source)
+ idx.add_source source.specs
+ dependency_names.concat(source.unmet_deps).uniq!
+ end
+
+ double_check_for_index(idx, dependency_names)
+ end
+ end
+
+ # Suppose the gem Foo depends on the gem Bar. Foo exists in Source A. Bar has some versions that exist in both
+ # sources A and B. At this point, the API request will have found all the versions of Bar in source A,
+ # but will not have found any versions of Bar from source B, which is a problem if the requested version
+ # of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for
+ # each spec we found, we add all possible versions from all sources to the index.
+ def double_check_for_index(idx, dependency_names)
+ pinned_names = pinned_spec_names
+ loop do
+ idxcount = idx.size
+
+ names = :names # do this so we only have to traverse to get dependency_names from the index once
+ unmet_dependency_names = lambda do
+ return names unless names == :names
+ new_names = sources.all_sources.map(&:dependency_names_to_double_check)
+ return names = nil if new_names.compact!
+ names = new_names.flatten(1).concat(dependency_names)
+ names.uniq!
+ names -= pinned_names
+ names
+ end
+
+ sources.all_sources.each do |source|
+ source.double_check_for(unmet_dependency_names)
+ end
+
+ break if idxcount == idx.size
+ end
+ end
+ private :double_check_for_index
+
+ def has_rubygems_remotes?
+ sources.rubygems_sources.any? {|s| s.remotes.any? }
+ end
+
+ def has_local_dependencies?
+ !sources.path_sources.empty? || !sources.git_sources.empty?
+ end
+
+ def spec_git_paths
+ sources.git_sources.map {|s| s.path.to_s }
+ end
+
+ def groups
+ dependencies.map(&:groups).flatten.uniq
+ end
+
+ def lock(file, preserve_unknown_sections = false)
+ contents = to_lock
+
+ # Convert to \r\n if the existing lock has them
+ # i.e., Windows with `git config core.autocrlf=true`
+ contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match("\r\n")
+
+ if @locked_bundler_version
+ locked_major = @locked_bundler_version.segments.first
+ current_major = Gem::Version.create(Bundler::VERSION).segments.first
+
+ if updating_major = locked_major < current_major
+ Bundler.ui.warn "Warning: the lockfile is being updated to Bundler #{current_major}, " \
+ "after which you will be unable to return to Bundler #{@locked_bundler_version.segments.first}."
+ end
+ end
+
+ preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler))
+
+ return if file && File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections)
+
+ if Bundler.frozen_bundle?
+ Bundler.ui.error "Cannot write a changed lockfile while frozen."
+ return
+ end
+
+ SharedHelpers.filesystem_access(file) do |p|
+ File.open(p, "wb") {|f| f.puts(contents) }
+ end
+ end
+
+ def locked_bundler_version
+ if @locked_bundler_version && @locked_bundler_version < Gem::Version.new(Bundler::VERSION)
+ new_version = Bundler::VERSION
+ end
+
+ new_version || @locked_bundler_version || Bundler::VERSION
+ end
+
+ def locked_ruby_version
+ return unless ruby_version
+ if @unlock[:ruby] || !@locked_ruby_version
+ Bundler::RubyVersion.system
+ else
+ @locked_ruby_version
+ end
+ end
+
+ def locked_ruby_version_object
+ return unless @locked_ruby_version
+ @locked_ruby_version_object ||= begin
+ unless version = RubyVersion.from_string(@locked_ruby_version)
+ raise LockfileError, "The Ruby version #{@locked_ruby_version} from " \
+ "#{@lockfile} could not be parsed. " \
+ "Try running bundle update --ruby to resolve this."
+ end
+ version
+ end
+ end
+
+ def to_lock
+ require "bundler/lockfile_generator"
+ LockfileGenerator.generate(self)
+ end
+
+ def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
+ msg = String.new
+ msg << "You are trying to install in deployment mode after changing\n" \
+ "your Gemfile. Run `bundle install` elsewhere and add the\n" \
+ "updated #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} to version control."
+
+ unless explicit_flag
+ suggested_command = if Bundler.settings.locations("frozen")[:global]
+ "bundle config --delete frozen"
+ elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any?
+ "bundle config --delete deployment"
+ else
+ "bundle install --no-deployment"
+ end
+ msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} " \
+ "freeze \nby running `#{suggested_command}`."
+ end
+
+ added = []
+ deleted = []
+ changed = []
+
+ new_platforms = @platforms - @locked_platforms
+ deleted_platforms = @locked_platforms - @platforms
+ added.concat new_platforms.map {|p| "* platform: #{p}" }
+ deleted.concat deleted_platforms.map {|p| "* platform: #{p}" }
+
+ gemfile_sources = sources.lock_sources
+
+ new_sources = gemfile_sources - @locked_sources
+ deleted_sources = @locked_sources - gemfile_sources
+
+ new_deps = @dependencies - @locked_deps.values
+ deleted_deps = @locked_deps.values - @dependencies
+
+ # Check if it is possible that the source is only changed thing
+ if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?)
+ new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
+ deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
+ end
+
+ if @locked_sources != gemfile_sources
+ if new_sources.any?
+ added.concat new_sources.map {|source| "* source: #{source}" }
+ end
+
+ if deleted_sources.any?
+ deleted.concat deleted_sources.map {|source| "* source: #{source}" }
+ end
+ end
+
+ added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
+ if deleted_deps.any?
+ deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" }
+ end
+
+ both_sources = Hash.new {|h, k| h[k] = [] }
+ @dependencies.each {|d| both_sources[d.name][0] = d }
+ @locked_deps.each {|name, d| both_sources[name][1] = d.source }
+
+ both_sources.each do |name, (dep, lock_source)|
+ next unless (dep.nil? && !lock_source.nil?) || (!dep.nil? && !lock_source.nil? && !lock_source.can_lock?(dep))
+ gemfile_source_name = (dep && dep.source) || "no specified source"
+ lockfile_source_name = lock_source || "no specified source"
+ changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_source_name}`"
+ end
+
+ reason = change_reason
+ msg << "\n\n#{reason.split(", ").map(&:capitalize).join("\n")}" unless reason.strip.empty?
+ msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
+ msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any?
+ msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any?
+ msg << "\n"
+
+ raise ProductionError, msg if added.any? || deleted.any? || changed.any? || !nothing_changed?
+ end
+
+ def validate_runtime!
+ validate_ruby!
+ validate_platforms!
+ end
+
+ def validate_ruby!
+ return unless ruby_version
+
+ if diff = ruby_version.diff(Bundler::RubyVersion.system)
+ problem, expected, actual = diff
+
+ msg = case problem
+ when :engine
+ "Your Ruby engine is #{actual}, but your Gemfile specified #{expected}"
+ when :version
+ "Your Ruby version is #{actual}, but your Gemfile specified #{expected}"
+ when :engine_version
+ "Your #{Bundler::RubyVersion.system.engine} version is #{actual}, but your Gemfile specified #{ruby_version.engine} #{expected}"
+ when :patchlevel
+ if !expected.is_a?(String)
+ "The Ruby patchlevel in your Gemfile must be a string"
+ else
+ "Your Ruby patchlevel is #{actual}, but your Gemfile specified #{expected}"
+ end
+ end
+
+ raise RubyVersionMismatch, msg
+ end
+ end
+
+ def validate_platforms!
+ return if @platforms.any? do |bundle_platform|
+ Bundler.rubygems.platforms.any? do |local_platform|
+ MatchPlatform.platforms_match?(bundle_platform, local_platform)
+ end
+ end
+
+ raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
+ "but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
+ "there's no compatible match between those two lists."
+ end
+
+ def add_platform(platform)
+ @new_platform ||= !@platforms.include?(platform)
+ @platforms |= [platform]
+ end
+
+ def remove_platform(platform)
+ return if @platforms.delete(Gem::Platform.new(platform))
+ raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
+ end
+
+ def add_current_platform
+ current_platform = Bundler.local_platform
+ add_platform(current_platform) if Bundler.feature_flag.specific_platform?
+ add_platform(generic(current_platform))
+ end
+
+ def find_resolved_spec(current_spec)
+ specs.find_by_name_and_platform(current_spec.name, current_spec.platform)
+ end
+
+ def find_indexed_specs(current_spec)
+ index[current_spec.name].select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version)
+ end
+
+ attr_reader :sources
+ private :sources
+
+ def nothing_changed?
+ !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
+ end
+
+ def unlocking?
+ @unlocking
+ end
+
+ private
+
+ def change_reason
+ if unlocking?
+ unlock_reason = @unlock.reject {|_k, v| Array(v).empty? }.map do |k, v|
+ if v == true
+ k.to_s
+ else
+ v = Array(v)
+ "#{k}: (#{v.join(", ")})"
+ end
+ end.join(", ")
+ return "bundler is unlocking #{unlock_reason}"
+ end
+ [
+ [@source_changes, "the list of sources changed"],
+ [@dependency_changes, "the dependencies in your gemfile changed"],
+ [@new_platform, "you added a new platform to your gemfile"],
+ [@path_changes, "the gemspecs for path gems changed"],
+ [@local_changes, "the gemspecs for git local gems changed"],
+ [@locked_specs_incomplete_for_platform, "the lockfile does not have all gems needed for the current platform"],
+ ].select(&:first).map(&:last).join(", ")
+ end
+
+ def pretty_dep(dep, source = false)
+ SharedHelpers.pretty_dependency(dep, source)
+ end
+
+ # Check if the specs of the given source changed
+ # according to the locked source.
+ def specs_changed?(source)
+ locked = @locked_sources.find {|s| s == source }
+
+ !locked || dependencies_for_source_changed?(source, locked) || specs_for_source_changed?(source)
+ end
+
+ def dependencies_for_source_changed?(source, locked_source = source)
+ deps_for_source = @dependencies.select {|s| s.source == source }
+ locked_deps_for_source = @locked_deps.values.select {|dep| dep.source == locked_source }
+
+ Set.new(deps_for_source) != Set.new(locked_deps_for_source)
+ end
+
+ def specs_for_source_changed?(source)
+ locked_index = Index.new
+ locked_index.use(@locked_specs.select {|s| source.can_lock?(s) })
+
+ # order here matters, since Index#== is checking source.specs.include?(locked_index)
+ locked_index != source.specs
+ rescue PathError, GitError => e
+ Bundler.ui.debug "Assuming that #{source} has not changed since fetching its specs errored (#{e})"
+ false
+ end
+
+ # Get all locals and override their matching sources.
+ # Return true if any of the locals changed (for example,
+ # they point to a new revision) or depend on new specs.
+ def converge_locals
+ locals = []
+
+ Bundler.settings.local_overrides.map do |k, v|
+ spec = @dependencies.find {|s| s.name == k }
+ source = spec && spec.source
+ if source && source.respond_to?(:local_override!)
+ source.unlock! if @unlock[:gems].include?(spec.name)
+ locals << [source, source.local_override!(v)]
+ end
+ end
+
+ sources_with_changes = locals.select do |source, changed|
+ changed || specs_changed?(source)
+ end.map(&:first)
+ !sources_with_changes.each {|source| @unlock[:sources] << source.name }.empty?
+ end
+
+ def converge_paths
+ sources.path_sources.any? do |source|
+ specs_changed?(source)
+ end
+ end
+
+ def converge_path_source_to_gemspec_source(source)
+ return source unless source.instance_of?(Source::Path)
+ gemspec_source = sources.path_sources.find {|s| s.is_a?(Source::Gemspec) && s.as_path_source == source }
+ gemspec_source || source
+ end
+
+ def converge_path_sources_to_gemspec_sources
+ @locked_sources.map! do |source|
+ converge_path_source_to_gemspec_source(source)
+ end
+ @locked_specs.each do |spec|
+ spec.source &&= converge_path_source_to_gemspec_source(spec.source)
+ end
+ @locked_deps.each do |_, dep|
+ dep.source &&= converge_path_source_to_gemspec_source(dep.source)
+ end
+ end
+
+ def converge_rubygems_sources
+ return false if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+
+ changes = false
+
+ # Get the RubyGems sources from the Gemfile.lock
+ locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
+ # Get the RubyGems remotes from the Gemfile
+ actual_remotes = sources.rubygems_remotes
+
+ # If there is a RubyGems source in both
+ if !locked_gem_sources.empty? && !actual_remotes.empty?
+ locked_gem_sources.each do |locked_gem|
+ # Merge the remotes from the Gemfile into the Gemfile.lock
+ changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
+ end
+ end
+
+ changes
+ end
+
+ def converge_sources
+ changes = false
+
+ changes |= converge_rubygems_sources
+
+ # Replace the sources from the Gemfile with the sources from the Gemfile.lock,
+ # if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent
+ # source in the Gemfile.lock, use the one from the Gemfile.
+ changes |= sources.replace_sources!(@locked_sources)
+
+ sources.all_sources.each do |source|
+ # If the source is unlockable and the current command allows an unlock of
+ # the source (for example, you are doing a `bundle update <foo>` of a git-pinned
+ # gem), unlock it. For git sources, this means to unlock the revision, which
+ # will cause the `ref` used to be the most recent for the branch (or master) if
+ # an explicit `ref` is not used.
+ if source.respond_to?(:unlock!) && @unlock[:sources].include?(source.name)
+ source.unlock!
+ changes = true
+ end
+ end
+
+ changes
+ end
+
+ def converge_dependencies
+ frozen = Bundler.frozen_bundle?
+ (@dependencies + @locked_deps.values).each do |dep|
+ locked_source = @locked_deps[dep.name]
+ # This is to make sure that if bundler is installing in deployment mode and
+ # after locked_source and sources don't match, we still use locked_source.
+ if frozen && !locked_source.nil? &&
+ locked_source.respond_to?(:source) && locked_source.source.instance_of?(Source::Path) && locked_source.source.path.exist?
+ dep.source = locked_source.source
+ elsif dep.source
+ dep.source = sources.get(dep.source)
+ end
+ if dep.source.is_a?(Source::Gemspec)
+ dep.platforms.concat(@platforms.map {|p| Dependency::REVERSE_PLATFORM_MAP[p] }.flatten(1)).uniq!
+ end
+ end
+
+ changes = false
+ # We want to know if all match, but don't want to check all entries
+ # This means we need to return false if any dependency doesn't match
+ # the lock or doesn't exist in the lock.
+ @dependencies.each do |dependency|
+ unless locked_dep = @locked_deps[dependency.name]
+ changes = true
+ next
+ end
+
+ # Gem::Dependency#== matches Gem::Dependency#type. As the lockfile
+ # doesn't carry a notion of the dependency type, if you use
+ # add_development_dependency in a gemspec that's loaded with the gemspec
+ # directive, the lockfile dependencies and resolved dependencies end up
+ # with a mismatch on #type. Work around that by setting the type on the
+ # dep from the lockfile.
+ locked_dep.instance_variable_set(:@type, dependency.type)
+
+ # We already know the name matches from the hash lookup
+ # so we only need to check the requirement now
+ changes ||= dependency.requirement != locked_dep.requirement
+ end
+
+ changes
+ end
+
+ # Remove elements from the locked specs that are expired. This will most
+ # commonly happen if the Gemfile has changed since the lockfile was last
+ # generated
+ def converge_locked_specs
+ deps = []
+
+ # Build a list of dependencies that are the same in the Gemfile
+ # and Gemfile.lock. If the Gemfile modified a dependency, but
+ # the gem in the Gemfile.lock still satisfies it, this is fine
+ # too.
+ @dependencies.each do |dep|
+ locked_dep = @locked_deps[dep.name]
+
+ # If the locked_dep doesn't match the dependency we're looking for then we ignore the locked_dep
+ locked_dep = nil unless locked_dep == dep
+
+ if in_locked_deps?(dep, locked_dep) || satisfies_locked_spec?(dep)
+ deps << dep
+ elsif dep.source.is_a?(Source::Path) && dep.current_platform? && (!locked_dep || dep.source != locked_dep.source)
+ @locked_specs.each do |s|
+ @unlock[:gems] << s.name if s.source == dep.source
+ end
+
+ dep.source.unlock! if dep.source.respond_to?(:unlock!)
+ dep.source.specs.each {|s| @unlock[:gems] << s.name }
+ end
+ end
+
+ unlock_source_unlocks_spec = Bundler.feature_flag.unlock_source_unlocks_spec?
+
+ converged = []
+ @locked_specs.each do |s|
+ # Replace the locked dependency's source with the equivalent source from the Gemfile
+ dep = @dependencies.find {|d| s.satisfies?(d) }
+ s.source = (dep && dep.source) || sources.get(s.source)
+
+ # Don't add a spec to the list if its source is expired. For example,
+ # if you change a Git gem to RubyGems.
+ next if s.source.nil?
+ next if @unlock[:sources].include?(s.source.name)
+
+ # XXX This is a backwards-compatibility fix to preserve the ability to
+ # unlock a single gem by passing its name via `--source`. See issue #3759
+ # TODO: delete in Bundler 2
+ next if unlock_source_unlocks_spec && @unlock[:sources].include?(s.name)
+
+ # If the spec is from a path source and it doesn't exist anymore
+ # then we unlock it.
+
+ # Path sources have special logic
+ if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec)
+ other_sources_specs = begin
+ s.source.specs
+ rescue PathError, GitError
+ # if we won't need the source (according to the lockfile),
+ # don't error if the path/git source isn't available
+ next if @locked_specs.
+ for(requested_dependencies, [], false, true, false).
+ none? {|locked_spec| locked_spec.source == s.source }
+
+ raise
+ end
+
+ other = other_sources_specs[s].first
+
+ # If the spec is no longer in the path source, unlock it. This
+ # commonly happens if the version changed in the gemspec
+ next unless other
+
+ deps2 = other.dependencies.select {|d| d.type != :development }
+ runtime_dependencies = s.dependencies.select {|d| d.type != :development }
+ # If the dependencies of the path source have changed, unlock it
+ next unless runtime_dependencies.sort == deps2.sort
+ end
+
+ converged << s
+ end
+
+ resolve = SpecSet.new(converged)
+ expanded_deps = expand_dependencies(deps, true)
+ @locked_specs_incomplete_for_platform = !resolve.for(expanded_deps, @unlock[:gems], true, true)
+ resolve = resolve.for(expanded_deps, @unlock[:gems], false, false, false)
+ diff = nil
+
+ # Now, we unlock any sources that do not have anymore gems pinned to it
+ sources.all_sources.each do |source|
+ next unless source.respond_to?(:unlock!)
+
+ unless resolve.any? {|s| s.source == source }
+ diff ||= @locked_specs.to_a - resolve.to_a
+ source.unlock! if diff.any? {|s| s.source == source }
+ end
+ end
+
+ resolve
+ end
+
+ def in_locked_deps?(dep, locked_dep)
+ # Because the lockfile can't link a dep to a specific remote, we need to
+ # treat sources as equivalent anytime the locked dep has all the remotes
+ # that the Gemfile dep does.
+ locked_dep && locked_dep.source && dep.source && locked_dep.source.include?(dep.source)
+ end
+
+ def satisfies_locked_spec?(dep)
+ @locked_specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
+ end
+
+ # This list of dependencies is only used in #resolve, so it's OK to add
+ # the metadata dependencies here
+ def expanded_dependencies
+ @expanded_dependencies ||= begin
+ expand_dependencies(dependencies + metadata_dependencies, @remote)
+ end
+ end
+
+ def metadata_dependencies
+ @metadata_dependencies ||= begin
+ ruby_versions = concat_ruby_version_requirements(@ruby_version)
+ if ruby_versions.empty? || !@ruby_version.exact?
+ concat_ruby_version_requirements(RubyVersion.system)
+ concat_ruby_version_requirements(locked_ruby_version_object) unless @unlock[:ruby]
+ end
+ [
+ Dependency.new("ruby\0", ruby_versions),
+ Dependency.new("rubygems\0", Gem::VERSION),
+ ]
+ end
+ end
+
+ def concat_ruby_version_requirements(ruby_version, ruby_versions = [])
+ return ruby_versions unless ruby_version
+ if ruby_version.patchlevel
+ ruby_versions << ruby_version.to_gem_version_with_patchlevel
+ else
+ ruby_versions.concat(ruby_version.versions.map do |version|
+ requirement = Gem::Requirement.new(version)
+ if requirement.exact?
+ "~> #{version}.0"
+ else
+ requirement
+ end
+ end)
+ end
+ end
+
+ def expand_dependencies(dependencies, remote = false)
+ sorted_platforms = Resolver.sort_platforms(@platforms)
+ deps = []
+ dependencies.each do |dep|
+ dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
+ next if !remote && !dep.current_platform?
+ platforms = dep.gem_platforms(sorted_platforms)
+ if platforms.empty? && !Bundler.settings[:disable_platform_warnings]
+ mapped_platforms = dep.platforms.map {|p| Dependency::PLATFORM_MAP[p] }
+ Bundler.ui.warn \
+ "The dependency #{dep} will be unused by any of the platforms Bundler is installing for. " \
+ "Bundler is installing for #{@platforms.join ", "} but the dependency " \
+ "is only for #{mapped_platforms.join ", "}. " \
+ "To add those platforms to the bundle, " \
+ "run `bundle lock --add-platform #{mapped_platforms.join " "}`."
+ end
+ platforms.each do |p|
+ deps << DepProxy.new(dep, p) if remote || p == generic_local_platform
+ end
+ end
+ deps
+ end
+
+ def requested_dependencies
+ groups = requested_groups
+ groups.map!(&:to_sym)
+ dependencies.reject {|d| !d.should_include? || (d.groups & groups).empty? }
+ end
+
+ def source_requirements
+ # Load all specs from remote sources
+ index
+
+ # Record the specs available in each gem's source, so that those
+ # specs will be available later when the resolver knows where to
+ # look for that gemspec (or its dependencies)
+ default = sources.default_source
+ source_requirements = { :default => default }
+ default = nil unless Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ dependencies.each do |dep|
+ next unless source = dep.source || default
+ source_requirements[dep.name] = source
+ end
+ metadata_dependencies.each do |dep|
+ source_requirements[dep.name] = sources.metadata_source
+ end
+ source_requirements["bundler"] = sources.metadata_source # needs to come last to override
+ source_requirements
+ end
+
+ def pinned_spec_names(skip = nil)
+ pinned_names = []
+ default = Bundler.feature_flag.lockfile_uses_separate_rubygems_sources? && sources.default_source
+ @dependencies.each do |dep|
+ next unless dep_source = dep.source || default
+ next if dep_source == skip
+ pinned_names << dep.name
+ end
+ pinned_names
+ end
+
+ def requested_groups
+ groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with]
+ end
+
+ def lockfiles_equal?(current, proposed, preserve_unknown_sections)
+ if preserve_unknown_sections
+ sections_to_ignore = LockfileParser.sections_to_ignore(@locked_bundler_version)
+ sections_to_ignore += LockfileParser.unknown_sections_in_lockfile(current)
+ sections_to_ignore += LockfileParser::ENVIRONMENT_VERSION_SECTIONS
+ pattern = /#{Regexp.union(sections_to_ignore)}\n(\s{2,}.*\n)+/
+ whitespace_cleanup = /\n{2,}/
+ current = current.gsub(pattern, "\n").gsub(whitespace_cleanup, "\n\n").strip
+ proposed = proposed.gsub(pattern, "\n").gsub(whitespace_cleanup, "\n\n").strip
+ end
+ current == proposed
+ end
+
+ def extract_gem_info(error)
+ # This method will extract the error message like "Could not find foo-1.2.3 in any of the sources"
+ # to an array. The first element will be the gem name (e.g. foo), the second will be the version number.
+ error.message.scan(/Could not find (\w+)-(\d+(?:\.\d+)+)/).flatten
+ end
+
+ def compute_requires
+ dependencies.reduce({}) do |requires, dep|
+ next requires unless dep.should_include?
+ requires[dep.name] = Array(dep.autorequire || dep.name).map do |file|
+ # Allow `require: true` as an alias for `require: <name>`
+ file == true ? dep.name : file
+ end
+ requires
+ end
+ end
+
+ def additional_base_requirements_for_resolve
+ return [] unless @locked_gems && Bundler.feature_flag.only_update_to_newer_versions?
+ dependencies_by_name = dependencies.inject({}) {|memo, dep| memo.update(dep.name => dep) }
+ @locked_gems.specs.reduce({}) do |requirements, locked_spec|
+ name = locked_spec.name
+ next requirements if @locked_gems.dependencies[name] != dependencies_by_name[name]
+ dep = Gem::Dependency.new(name, ">= #{locked_spec.version}")
+ requirements[name] = DepProxy.new(dep, locked_spec.platform)
+ requirements
+ end.values
+ end
+
+ def equivalent_rubygems_remotes?(source)
+ return false unless source.is_a?(Source::Rubygems)
+
+ Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
+ end
+ end
+end
diff --git a/lib/bundler/dep_proxy.rb b/lib/bundler/dep_proxy.rb
new file mode 100644
index 0000000000..6c32179ac1
--- /dev/null
+++ b/lib/bundler/dep_proxy.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Bundler
+ class DepProxy
+ attr_reader :__platform, :dep
+
+ def initialize(dep, platform)
+ @dep = dep
+ @__platform = platform
+ end
+
+ def hash
+ @hash ||= [dep, __platform].hash
+ end
+
+ def ==(other)
+ return false if other.class != self.class
+ dep == other.dep && __platform == other.__platform
+ end
+
+ alias_method :eql?, :==
+
+ def type
+ @dep.type
+ end
+
+ def name
+ @dep.name
+ end
+
+ def requirement
+ @dep.requirement
+ end
+
+ def to_s
+ s = name.dup
+ s << " (#{requirement})" unless requirement == Gem::Requirement.default
+ s << " #{__platform}" unless __platform == Gem::Platform::RUBY
+ s
+ end
+
+ private
+
+ def method_missing(*args, &blk)
+ @dep.send(*args, &blk)
+ end
+ end
+end
diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb
new file mode 100644
index 0000000000..8840ad6a9c
--- /dev/null
+++ b/lib/bundler/dependency.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+require "rubygems/dependency"
+require "bundler/shared_helpers"
+require "bundler/rubygems_ext"
+
+module Bundler
+ class Dependency < Gem::Dependency
+ attr_reader :autorequire
+ attr_reader :groups, :platforms, :gemfile
+
+ PLATFORM_MAP = {
+ :ruby => Gem::Platform::RUBY,
+ :ruby_18 => Gem::Platform::RUBY,
+ :ruby_19 => Gem::Platform::RUBY,
+ :ruby_20 => Gem::Platform::RUBY,
+ :ruby_21 => Gem::Platform::RUBY,
+ :ruby_22 => Gem::Platform::RUBY,
+ :ruby_23 => Gem::Platform::RUBY,
+ :ruby_24 => Gem::Platform::RUBY,
+ :ruby_25 => Gem::Platform::RUBY,
+ :mri => Gem::Platform::RUBY,
+ :mri_18 => Gem::Platform::RUBY,
+ :mri_19 => Gem::Platform::RUBY,
+ :mri_20 => Gem::Platform::RUBY,
+ :mri_21 => Gem::Platform::RUBY,
+ :mri_22 => Gem::Platform::RUBY,
+ :mri_23 => Gem::Platform::RUBY,
+ :mri_24 => Gem::Platform::RUBY,
+ :mri_25 => Gem::Platform::RUBY,
+ :rbx => Gem::Platform::RUBY,
+ :truffleruby => Gem::Platform::RUBY,
+ :jruby => Gem::Platform::JAVA,
+ :jruby_18 => Gem::Platform::JAVA,
+ :jruby_19 => Gem::Platform::JAVA,
+ :mswin => Gem::Platform::MSWIN,
+ :mswin_18 => Gem::Platform::MSWIN,
+ :mswin_19 => Gem::Platform::MSWIN,
+ :mswin_20 => Gem::Platform::MSWIN,
+ :mswin_21 => Gem::Platform::MSWIN,
+ :mswin_22 => Gem::Platform::MSWIN,
+ :mswin_23 => Gem::Platform::MSWIN,
+ :mswin_24 => Gem::Platform::MSWIN,
+ :mswin_25 => Gem::Platform::MSWIN,
+ :mswin64 => Gem::Platform::MSWIN64,
+ :mswin64_19 => Gem::Platform::MSWIN64,
+ :mswin64_20 => Gem::Platform::MSWIN64,
+ :mswin64_21 => Gem::Platform::MSWIN64,
+ :mswin64_22 => Gem::Platform::MSWIN64,
+ :mswin64_23 => Gem::Platform::MSWIN64,
+ :mswin64_24 => Gem::Platform::MSWIN64,
+ :mswin64_25 => Gem::Platform::MSWIN64,
+ :mingw => Gem::Platform::MINGW,
+ :mingw_18 => Gem::Platform::MINGW,
+ :mingw_19 => Gem::Platform::MINGW,
+ :mingw_20 => Gem::Platform::MINGW,
+ :mingw_21 => Gem::Platform::MINGW,
+ :mingw_22 => Gem::Platform::MINGW,
+ :mingw_23 => Gem::Platform::MINGW,
+ :mingw_24 => Gem::Platform::MINGW,
+ :mingw_25 => Gem::Platform::MINGW,
+ :x64_mingw => Gem::Platform::X64_MINGW,
+ :x64_mingw_20 => Gem::Platform::X64_MINGW,
+ :x64_mingw_21 => Gem::Platform::X64_MINGW,
+ :x64_mingw_22 => Gem::Platform::X64_MINGW,
+ :x64_mingw_23 => Gem::Platform::X64_MINGW,
+ :x64_mingw_24 => Gem::Platform::X64_MINGW,
+ :x64_mingw_25 => Gem::Platform::X64_MINGW,
+ }.freeze
+
+ REVERSE_PLATFORM_MAP = {}.tap do |reverse_platform_map|
+ PLATFORM_MAP.each do |key, value|
+ reverse_platform_map[value] ||= []
+ reverse_platform_map[value] << key
+ end
+
+ reverse_platform_map.each {|_, platforms| platforms.freeze }
+ end.freeze
+
+ def initialize(name, version, options = {}, &blk)
+ type = options["type"] || :runtime
+ super(name, version, type)
+
+ @autorequire = nil
+ @groups = Array(options["group"] || :default).map(&:to_sym)
+ @source = options["source"]
+ @platforms = Array(options["platforms"])
+ @env = options["env"]
+ @should_include = options.fetch("should_include", true)
+ @gemfile = options["gemfile"]
+
+ @autorequire = Array(options["require"] || []) if options.key?("require")
+ end
+
+ # Returns the platforms this dependency is valid for, in the same order as
+ # passed in the `valid_platforms` parameter
+ def gem_platforms(valid_platforms)
+ return valid_platforms if @platforms.empty?
+
+ @gem_platforms ||= @platforms.map {|pl| PLATFORM_MAP[pl] }.compact.uniq
+
+ valid_platforms & @gem_platforms
+ end
+
+ def should_include?
+ @should_include && current_env? && current_platform?
+ end
+
+ def current_env?
+ return true unless @env
+ if @env.is_a?(Hash)
+ @env.all? do |key, val|
+ ENV[key.to_s] && (val.is_a?(String) ? ENV[key.to_s] == val : ENV[key.to_s] =~ val)
+ end
+ else
+ ENV[@env.to_s]
+ end
+ end
+
+ def current_platform?
+ return true if @platforms.empty?
+ @platforms.any? do |p|
+ Bundler.current_ruby.send("#{p}?")
+ end
+ end
+
+ def to_lock
+ out = super
+ out << "!" if source
+ out << "\n"
+ end
+
+ def specific?
+ super
+ rescue NoMethodError
+ requirement != ">= 0"
+ end
+ end
+end
diff --git a/lib/bundler/deployment.rb b/lib/bundler/deployment.rb
new file mode 100644
index 0000000000..291e158ca0
--- /dev/null
+++ b/lib/bundler/deployment.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require "bundler/shared_helpers"
+Bundler::SharedHelpers.major_deprecation 2, "Bundler no longer integrates with " \
+ "Capistrano, but Capistrano provides its own integration with " \
+ "Bundler via the capistrano-bundler gem. Use it instead."
+
+module Bundler
+ class Deployment
+ def self.define_task(context, task_method = :task, opts = {})
+ if defined?(Capistrano) && context.is_a?(Capistrano::Configuration)
+ context_name = "capistrano"
+ role_default = "{:except => {:no_release => true}}"
+ error_type = ::Capistrano::CommandError
+ else
+ context_name = "vlad"
+ role_default = "[:app]"
+ error_type = ::Rake::CommandFailedError
+ end
+
+ roles = context.fetch(:bundle_roles, false)
+ opts[:roles] = roles if roles
+
+ context.send :namespace, :bundle do
+ send :desc, <<-DESC
+ Install the current Bundler environment. By default, gems will be \
+ installed to the shared/bundle path. Gems in the development and \
+ test group will not be installed. The install command is executed \
+ with the --deployment and --quiet flags. If the bundle cmd cannot \
+ be found then you can override the bundle_cmd variable to specify \
+ which one it should use. The base path to the app is fetched from \
+ the :latest_release variable. Set it for custom deploy layouts.
+
+ You can override any of these defaults by setting the variables shown below.
+
+ N.B. bundle_roles must be defined before you require 'bundler/#{context_name}' \
+ in your deploy.rb file.
+
+ set :bundle_gemfile, "Gemfile"
+ set :bundle_dir, File.join(fetch(:shared_path), 'bundle')
+ set :bundle_flags, "--deployment --quiet"
+ set :bundle_without, [:development, :test]
+ set :bundle_with, [:mysql]
+ set :bundle_cmd, "bundle" # e.g. "/opt/ruby/bin/bundle"
+ set :bundle_roles, #{role_default} # e.g. [:app, :batch]
+ DESC
+ send task_method, :install, opts do
+ bundle_cmd = context.fetch(:bundle_cmd, "bundle")
+ bundle_flags = context.fetch(:bundle_flags, "--deployment --quiet")
+ bundle_dir = context.fetch(:bundle_dir, File.join(context.fetch(:shared_path), "bundle"))
+ bundle_gemfile = context.fetch(:bundle_gemfile, "Gemfile")
+ bundle_without = [*context.fetch(:bundle_without, [:development, :test])].compact
+ bundle_with = [*context.fetch(:bundle_with, [])].compact
+ app_path = context.fetch(:latest_release)
+ if app_path.to_s.empty?
+ raise error_type.new("Cannot detect current release path - make sure you have deployed at least once.")
+ end
+ args = ["--gemfile #{File.join(app_path, bundle_gemfile)}"]
+ args << "--path #{bundle_dir}" unless bundle_dir.to_s.empty?
+ args << bundle_flags.to_s
+ args << "--without #{bundle_without.join(" ")}" unless bundle_without.empty?
+ args << "--with #{bundle_with.join(" ")}" unless bundle_with.empty?
+
+ run "cd #{app_path} && #{bundle_cmd} install #{args.join(" ")}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/deprecate.rb b/lib/bundler/deprecate.rb
new file mode 100644
index 0000000000..f59533630e
--- /dev/null
+++ b/lib/bundler/deprecate.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+begin
+ require "rubygems/deprecate"
+rescue LoadError
+ # it's fine if it doesn't exist on the current RubyGems...
+ nil
+end
+
+module Bundler
+ # If Bundler::Deprecate is an autoload constant, we need to define it
+ if defined?(Bundler::Deprecate) && !autoload?(:Deprecate)
+ # nothing to do!
+ elsif defined? ::Deprecate
+ Deprecate = ::Deprecate
+ elsif defined? Gem::Deprecate
+ Deprecate = Gem::Deprecate
+ else
+ class Deprecate
+ end
+ end
+
+ unless Deprecate.respond_to?(:skip_during)
+ def Deprecate.skip_during
+ original = skip
+ self.skip = true
+ yield
+ ensure
+ self.skip = original
+ end
+ end
+
+ unless Deprecate.respond_to?(:skip)
+ def Deprecate.skip
+ @skip ||= false
+ end
+ end
+
+ unless Deprecate.respond_to?(:skip=)
+ def Deprecate.skip=(skip)
+ @skip = skip
+ end
+ end
+end
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
new file mode 100644
index 0000000000..ab59477145
--- /dev/null
+++ b/lib/bundler/dsl.rb
@@ -0,0 +1,615 @@
+# frozen_string_literal: true
+
+require "bundler/dependency"
+require "bundler/ruby_dsl"
+
+module Bundler
+ class Dsl
+ include RubyDsl
+
+ def self.evaluate(gemfile, lockfile, unlock)
+ builder = new
+ builder.eval_gemfile(gemfile)
+ builder.to_definition(lockfile, unlock)
+ end
+
+ VALID_PLATFORMS = Bundler::Dependency::PLATFORM_MAP.keys.freeze
+
+ VALID_KEYS = %w[group groups git path glob name branch ref tag require submodules
+ platform platforms type source install_if gemfile].freeze
+
+ attr_reader :gemspecs
+ attr_accessor :dependencies
+
+ def initialize
+ @source = nil
+ @sources = SourceList.new
+ @git_sources = {}
+ @dependencies = []
+ @groups = []
+ @install_conditionals = []
+ @optional_groups = []
+ @platforms = []
+ @env = nil
+ @ruby_version = nil
+ @gemspecs = []
+ @gemfile = nil
+ @gemfiles = []
+ add_git_sources
+ end
+
+ def eval_gemfile(gemfile, contents = nil)
+ expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile && @gemfile.parent)
+ original_gemfile = @gemfile
+ @gemfile = expanded_gemfile_path
+ @gemfiles << expanded_gemfile_path
+ contents ||= Bundler.read_file(@gemfile.to_s)
+ instance_eval(contents.dup.untaint, gemfile.to_s, 1)
+ rescue Exception => e
+ message = "There was an error " \
+ "#{e.is_a?(GemfileEvalError) ? "evaluating" : "parsing"} " \
+ "`#{File.basename gemfile.to_s}`: #{e.message}"
+
+ raise DSLError.new(message, gemfile, e.backtrace, contents)
+ ensure
+ @gemfile = original_gemfile
+ end
+
+ def gemspec(opts = nil)
+ opts ||= {}
+ path = opts[:path] || "."
+ glob = opts[:glob]
+ name = opts[:name]
+ development_group = opts[:development_group] || :development
+ expanded_path = gemfile_root.join(path)
+
+ gemspecs = Dir[File.join(expanded_path, "{,*}.gemspec")].map {|g| Bundler.load_gemspec(g) }.compact
+ gemspecs.reject! {|s| s.name != name } if name
+ Index.sort_specs(gemspecs)
+ specs_by_name_and_version = gemspecs.group_by {|s| [s.name, s.version] }
+
+ case specs_by_name_and_version.size
+ when 1
+ specs = specs_by_name_and_version.values.first
+ spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first
+
+ @gemspecs << spec
+
+ gem_platforms = Bundler::Dependency::REVERSE_PLATFORM_MAP[Bundler::GemHelpers.generic_local_platform]
+ gem spec.name, :name => spec.name, :path => path, :glob => glob, :platforms => gem_platforms
+
+ group(development_group) do
+ spec.development_dependencies.each do |dep|
+ gem dep.name, *(dep.requirement.as_list + [:type => :development])
+ end
+ end
+ when 0
+ raise InvalidOption, "There are no gemspecs at #{expanded_path}"
+ else
+ raise InvalidOption, "There are multiple gemspecs at #{expanded_path}. " \
+ "Please use the :name option to specify which one should be used"
+ end
+ end
+
+ def gem(name, *args)
+ options = args.last.is_a?(Hash) ? args.pop.dup : {}
+ options["gemfile"] = @gemfile
+ version = args || [">= 0"]
+
+ normalize_options(name, version, options)
+
+ dep = Dependency.new(name, version, options)
+
+ # if there's already a dependency with this name we try to prefer one
+ if current = @dependencies.find {|d| d.name == dep.name }
+ deleted_dep = @dependencies.delete(current) if current.type == :development
+
+ if current.requirement != dep.requirement
+ unless deleted_dep
+ return if dep.type == :development
+
+ update_prompt = ""
+
+ if File.basename(@gemfile) == Injector::INJECTED_GEMS
+ if dep.requirements_list.include?(">= 0") && !current.requirements_list.include?(">= 0")
+ update_prompt = ". Gem already added"
+ else
+ update_prompt = ". If you want to update the gem version, run `bundle update #{current.name}`"
+
+ update_prompt += ". You may also need to change the version requirement specified in the Gemfile if it's too restrictive." unless current.requirements_list.include?(">= 0")
+ end
+ end
+
+ raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \
+ "You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \
+ "#{update_prompt}"
+ end
+
+ else
+ Bundler.ui.warn "Your Gemfile lists the gem #{current.name} (#{current.requirement}) more than once.\n" \
+ "You should probably keep only one of them.\n" \
+ "Remove any duplicate entries and specify the gem only once (per group).\n" \
+ "While it's not a problem now, it could cause errors if you change the version of one of them later."
+ end
+
+ if current.source != dep.source
+ unless deleted_dep
+ return if dep.type == :development
+ raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \
+ "You specified that #{dep.name} (#{dep.requirement}) should come from " \
+ "#{current.source || "an unspecified source"} and #{dep.source}\n"
+ end
+ end
+ end
+
+ @dependencies << dep
+ end
+
+ def source(source, *args, &blk)
+ options = args.last.is_a?(Hash) ? args.pop.dup : {}
+ options = normalize_hash(options)
+ source = normalize_source(source)
+
+ if options.key?("type")
+ options["type"] = options["type"].to_s
+ unless Plugin.source?(options["type"])
+ raise InvalidOption, "No plugin sources available for #{options["type"]}"
+ end
+
+ unless block_given?
+ raise InvalidOption, "You need to pass a block to #source with :type option"
+ end
+
+ source_opts = options.merge("uri" => source)
+ with_source(@sources.add_plugin_source(options["type"], source_opts), &blk)
+ elsif block_given?
+ with_source(@sources.add_rubygems_source("remotes" => source), &blk)
+ else
+ check_primary_source_safety(@sources)
+ @sources.global_rubygems_source = source
+ end
+ end
+
+ def git_source(name, &block)
+ unless block_given?
+ raise InvalidOption, "You need to pass a block to #git_source"
+ end
+
+ if valid_keys.include?(name.to_s)
+ raise InvalidOption, "You cannot use #{name} as a git source. It " \
+ "is a reserved key. Reserved keys are: #{valid_keys.join(", ")}"
+ end
+
+ @git_sources[name.to_s] = block
+ end
+
+ def path(path, options = {}, &blk)
+ unless block_given?
+ msg = "You can no longer specify a path source by itself. Instead, \n" \
+ "either use the :path option on a gem, or specify the gems that \n" \
+ "bundler should find in the path source by passing a block to \n" \
+ "the path method, like: \n\n" \
+ " path 'dir/containing/rails' do\n" \
+ " gem 'rails'\n" \
+ " end\n\n"
+
+ raise DeprecatedError, msg if Bundler.feature_flag.disable_multisource?
+ SharedHelpers.major_deprecation(2, msg.strip)
+ end
+
+ source_options = normalize_hash(options).merge(
+ "path" => Pathname.new(path),
+ "root_path" => gemfile_root,
+ "gemspec" => gemspecs.find {|g| g.name == options["name"] }
+ )
+ source = @sources.add_path_source(source_options)
+ with_source(source, &blk)
+ end
+
+ def git(uri, options = {}, &blk)
+ unless block_given?
+ msg = "You can no longer specify a git source by itself. Instead, \n" \
+ "either use the :git option on a gem, or specify the gems that \n" \
+ "bundler should find in the git source by passing a block to \n" \
+ "the git method, like: \n\n" \
+ " git 'git://github.com/rails/rails.git' do\n" \
+ " gem 'rails'\n" \
+ " end"
+ raise DeprecatedError, msg
+ end
+
+ with_source(@sources.add_git_source(normalize_hash(options).merge("uri" => uri)), &blk)
+ end
+
+ def github(repo, options = {})
+ raise ArgumentError, "GitHub sources require a block" unless block_given?
+ raise DeprecatedError, "The #github method has been removed" if Bundler.feature_flag.skip_default_git_sources?
+ github_uri = @git_sources["github"].call(repo)
+ git_options = normalize_hash(options).merge("uri" => github_uri)
+ git_source = @sources.add_git_source(git_options)
+ with_source(git_source) { yield }
+ end
+
+ def to_definition(lockfile, unlock)
+ Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles)
+ end
+
+ def group(*args, &blk)
+ options = args.last.is_a?(Hash) ? args.pop.dup : {}
+ normalize_group_options(options, args)
+
+ @groups.concat args
+
+ if options["optional"]
+ optional_groups = args - @optional_groups
+ @optional_groups.concat optional_groups
+ end
+
+ yield
+ ensure
+ args.each { @groups.pop }
+ end
+
+ def install_if(*args)
+ @install_conditionals.concat args
+ yield
+ ensure
+ args.each { @install_conditionals.pop }
+ end
+
+ def platforms(*platforms)
+ @platforms.concat platforms
+ yield
+ ensure
+ platforms.each { @platforms.pop }
+ end
+ alias_method :platform, :platforms
+
+ def env(name)
+ old = @env
+ @env = name
+ yield
+ ensure
+ @env = old
+ end
+
+ def plugin(*args)
+ # Pass on
+ end
+
+ def method_missing(name, *args)
+ raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile"
+ end
+
+ private
+
+ def add_git_sources
+ return if Bundler.feature_flag.skip_default_git_sources?
+
+ git_source(:github) do |repo_name|
+ warn_deprecated_git_source(:github, <<-'RUBY'.strip, 'Change any "reponame" :github sources to "username/reponame".')
+"https://github.com/#{repo_name}.git"
+ RUBY
+ # It would be better to use https instead of the git protocol, but this
+ # can break deployment of existing locked bundles when switching between
+ # different versions of Bundler. The change will be made in 2.0, which
+ # does not guarantee compatibility with the 1.x series.
+ #
+ # See https://github.com/bundler/bundler/pull/2569 for discussion
+ #
+ # This can be overridden by adding this code to your Gemfiles:
+ #
+ # git_source(:github) do |repo_name|
+ # repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
+ # "https://github.com/#{repo_name}.git"
+ # end
+ repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
+ # TODO: 2.0 upgrade this setting to the default
+ if Bundler.settings["github.https"]
+ Bundler::SharedHelpers.major_deprecation 2, "The `github.https` setting will be removed"
+ "https://github.com/#{repo_name}.git"
+ else
+ "git://github.com/#{repo_name}.git"
+ end
+ end
+
+ # TODO: 2.0 remove this deprecated git source
+ git_source(:gist) do |repo_name|
+ warn_deprecated_git_source(:gist, '"https://gist.github.com/#{repo_name}.git"')
+
+ "https://gist.github.com/#{repo_name}.git"
+ end
+
+ # TODO: 2.0 remove this deprecated git source
+ git_source(:bitbucket) do |repo_name|
+ warn_deprecated_git_source(:bitbucket, <<-'RUBY'.strip)
+user_name, repo_name = repo_name.split("/")
+repo_name ||= user_name
+"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
+ RUBY
+
+ user_name, repo_name = repo_name.split("/")
+ repo_name ||= user_name
+ "https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
+ end
+ end
+
+ def with_source(source)
+ old_source = @source
+ if block_given?
+ @source = source
+ yield
+ end
+ source
+ ensure
+ @source = old_source
+ end
+
+ def normalize_hash(opts)
+ opts.keys.each do |k|
+ opts[k.to_s] = opts.delete(k) unless k.is_a?(String)
+ end
+ opts
+ end
+
+ def valid_keys
+ @valid_keys ||= VALID_KEYS
+ end
+
+ def normalize_options(name, version, opts)
+ if name.is_a?(Symbol)
+ raise GemfileError, %(You need to specify gem names as Strings. Use 'gem "#{name}"' instead)
+ end
+ if name =~ /\s/
+ raise GemfileError, %('#{name}' is not a valid gem name because it contains whitespace)
+ end
+ if name.empty?
+ raise GemfileError, %(an empty gem name is not valid)
+ end
+
+ normalize_hash(opts)
+
+ git_names = @git_sources.keys.map(&:to_s)
+ validate_keys("gem '#{name}'", opts, valid_keys + git_names)
+
+ groups = @groups.dup
+ opts["group"] = opts.delete("groups") || opts["group"]
+ groups.concat Array(opts.delete("group"))
+ groups = [:default] if groups.empty?
+
+ install_if = @install_conditionals.dup
+ install_if.concat Array(opts.delete("install_if"))
+ install_if = install_if.reduce(true) do |memo, val|
+ memo && (val.respond_to?(:call) ? val.call : val)
+ end
+
+ platforms = @platforms.dup
+ opts["platforms"] = opts["platform"] || opts["platforms"]
+ platforms.concat Array(opts.delete("platforms"))
+ platforms.map!(&:to_sym)
+ platforms.each do |p|
+ next if VALID_PLATFORMS.include?(p)
+ raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
+ end
+
+ # Save sources passed in a key
+ if opts.key?("source")
+ source = normalize_source(opts["source"])
+ opts["source"] = @sources.add_rubygems_source("remotes" => source)
+ end
+
+ git_name = (git_names & opts.keys).last
+ if @git_sources[git_name]
+ opts["git"] = @git_sources[git_name].call(opts[git_name])
+ end
+
+ %w[git path].each do |type|
+ next unless param = opts[type]
+ if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/
+ options = opts.merge("name" => name, "version" => $1)
+ else
+ options = opts.dup
+ end
+ source = send(type, param, options) {}
+ opts["source"] = source
+ end
+
+ opts["source"] ||= @source
+ opts["env"] ||= @env
+ opts["platforms"] = platforms.dup
+ opts["group"] = groups
+ opts["should_include"] = install_if
+ end
+
+ def normalize_group_options(opts, groups)
+ normalize_hash(opts)
+
+ groups = groups.map {|group| ":#{group}" }.join(", ")
+ validate_keys("group #{groups}", opts, %w[optional])
+
+ opts["optional"] ||= false
+ end
+
+ def validate_keys(command, opts, valid_keys)
+ invalid_keys = opts.keys - valid_keys
+
+ git_source = opts.keys & @git_sources.keys.map(&:to_s)
+ if opts["branch"] && !(opts["git"] || opts["github"] || git_source.any?)
+ raise GemfileError, %(The `branch` option for `#{command}` is not allowed. Only gems with a git source can specify a branch)
+ end
+
+ return true unless invalid_keys.any?
+
+ message = String.new
+ message << "You passed #{invalid_keys.map {|k| ":" + k }.join(", ")} "
+ message << if invalid_keys.size > 1
+ "as options for #{command}, but they are invalid."
+ else
+ "as an option for #{command}, but it is invalid."
+ end
+
+ message << " Valid options are: #{valid_keys.join(", ")}."
+ message << " You may be able to resolve this by upgrading Bundler to the newest version."
+ raise InvalidOption, message
+ end
+
+ def normalize_source(source)
+ case source
+ when :gemcutter, :rubygems, :rubyforge
+ Bundler::SharedHelpers.major_deprecation 2, "The source :#{source} is deprecated because HTTP " \
+ "requests are insecure.\nPlease change your source to 'https://" \
+ "rubygems.org' if possible, or 'http://rubygems.org' if not."
+ "http://rubygems.org"
+ when String
+ source
+ else
+ raise GemfileError, "Unknown source '#{source}'"
+ end
+ end
+
+ def check_primary_source_safety(source_list)
+ return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil?
+
+ if Bundler.feature_flag.disable_multisource?
+ msg = "This Gemfile contains multiple primary sources. " \
+ "Each source after the first must include a block to indicate which gems " \
+ "should come from that source"
+ unless Bundler.feature_flag.bundler_2_mode?
+ msg += ". To downgrade this error to a warning, run " \
+ "`bundle config --delete disable_multisource`"
+ end
+ raise GemfileEvalError, msg
+ else
+ Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \
+ "Using `source` more than once without a block is a security risk, and " \
+ "may result in installing unexpected gems. To resolve this warning, use " \
+ "a block to indicate which gems should come from the secondary source. " \
+ "To upgrade this warning to an error, run `bundle config " \
+ "disable_multisource true`."
+ end
+ end
+
+ def warn_deprecated_git_source(name, replacement, additional_message = nil)
+ # TODO: 2.0 remove deprecation
+ additional_message &&= " #{additional_message}"
+ replacement = if replacement.count("\n").zero?
+ "{|repo_name| #{replacement} }"
+ else
+ "do |repo_name|\n#{replacement.to_s.gsub(/^/, " ")}\n end"
+ end
+
+ Bundler::SharedHelpers.major_deprecation 2, <<-EOS
+The :#{name} git source is deprecated, and will be removed in Bundler 2.0.#{additional_message} Add this code to the top of your Gemfile to ensure it continues to work:
+
+ git_source(:#{name}) #{replacement}
+
+ EOS
+ end
+
+ class DSLError < GemfileError
+ # @return [String] the description that should be presented to the user.
+ #
+ attr_reader :description
+
+ # @return [String] the path of the dsl file that raised the exception.
+ #
+ attr_reader :dsl_path
+
+ # @return [Exception] the backtrace of the exception raised by the
+ # evaluation of the dsl file.
+ #
+ attr_reader :backtrace
+
+ # @param [Exception] backtrace @see backtrace
+ # @param [String] dsl_path @see dsl_path
+ #
+ def initialize(description, dsl_path, backtrace, contents = nil)
+ @status_code = $!.respond_to?(:status_code) && $!.status_code
+
+ @description = description
+ @dsl_path = dsl_path
+ @backtrace = backtrace
+ @contents = contents
+ end
+
+ def status_code
+ @status_code || super
+ end
+
+ # @return [String] the contents of the DSL that cause the exception to
+ # be raised.
+ #
+ def contents
+ @contents ||= begin
+ dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
+ end
+ end
+
+ # The message of the exception reports the content of podspec for the
+ # line that generated the original exception.
+ #
+ # @example Output
+ #
+ # Invalid podspec at `RestKit.podspec` - undefined method
+ # `exclude_header_search_paths=' for #<Pod::Specification for
+ # `RestKit/Network (0.9.3)`>
+ #
+ # from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36
+ # -------------------------------------------
+ # # because it would break: #import <CoreData/CoreData.h>
+ # > ns.exclude_header_search_paths = 'Code/RestKit.h'
+ # end
+ # -------------------------------------------
+ #
+ # @return [String] the message of the exception.
+ #
+ def to_s
+ @to_s ||= begin
+ trace_line, description = parse_line_number_from_description
+
+ m = String.new("\n[!] ")
+ m << description
+ m << ". Bundler cannot continue.\n"
+
+ return m unless backtrace && dsl_path && contents
+
+ trace_line = backtrace.find {|l| l.include?(dsl_path.to_s) } || trace_line
+ return m unless trace_line
+ line_numer = trace_line.split(":")[1].to_i - 1
+ return m unless line_numer
+
+ lines = contents.lines.to_a
+ indent = " # "
+ indicator = indent.tr("#", ">")
+ first_line = line_numer.zero?
+ last_line = (line_numer == (lines.count - 1))
+
+ m << "\n"
+ m << "#{indent}from #{trace_line.gsub(/:in.*$/, "")}\n"
+ m << "#{indent}-------------------------------------------\n"
+ m << "#{indent}#{lines[line_numer - 1]}" unless first_line
+ m << "#{indicator}#{lines[line_numer]}"
+ m << "#{indent}#{lines[line_numer + 1]}" unless last_line
+ m << "\n" unless m.end_with?("\n")
+ m << "#{indent}-------------------------------------------\n"
+ end
+ end
+
+ private
+
+ def parse_line_number_from_description
+ description = self.description
+ if dsl_path && description =~ /((#{Regexp.quote File.expand_path(dsl_path)}|#{Regexp.quote dsl_path.to_s}):\d+)/
+ trace_line = Regexp.last_match[1]
+ description = description.sub(/#{Regexp.quote trace_line}:\s*/, "").sub("\n", " - ")
+ end
+ [trace_line, description]
+ end
+ end
+
+ def gemfile_root
+ @gemfile ||= Bundler.default_gemfile
+ @gemfile.dirname
+ end
+ end
+end
diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb
new file mode 100644
index 0000000000..9a00b64e0e
--- /dev/null
+++ b/lib/bundler/endpoint_specification.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+
+module Bundler
+ # used for Creating Specifications from the Gemcutter Endpoint
+ class EndpointSpecification < Gem::Specification
+ ILLFORMED_MESSAGE = 'Ill-formed requirement ["#<YAML::Syck::DefaultKey'.freeze
+ include MatchPlatform
+
+ attr_reader :name, :version, :platform, :required_rubygems_version, :required_ruby_version, :checksum
+ attr_accessor :source, :remote, :dependencies
+
+ def initialize(name, version, platform, dependencies, metadata = nil)
+ super()
+ @name = name
+ @version = Gem::Version.create version
+ @platform = platform
+ @dependencies = dependencies.map {|dep, reqs| build_dependency(dep, reqs) }
+
+ @loaded_from = nil
+ @remote_specification = nil
+
+ parse_metadata(metadata)
+ end
+
+ def fetch_platform
+ @platform
+ end
+
+ # needed for standalone, load required_paths from local gemspec
+ # after the gem is installed
+ def require_paths
+ if @remote_specification
+ @remote_specification.require_paths
+ elsif _local_specification
+ _local_specification.require_paths
+ else
+ super
+ end
+ end
+
+ # needed for inline
+ def load_paths
+ # remote specs aren't installed, and can't have load_paths
+ if _local_specification
+ _local_specification.load_paths
+ else
+ super
+ end
+ end
+
+ # needed for binstubs
+ def executables
+ if @remote_specification
+ @remote_specification.executables
+ elsif _local_specification
+ _local_specification.executables
+ else
+ super
+ end
+ end
+
+ # needed for bundle clean
+ def bindir
+ if @remote_specification
+ @remote_specification.bindir
+ elsif _local_specification
+ _local_specification.bindir
+ else
+ super
+ end
+ end
+
+ # needed for post_install_messages during install
+ def post_install_message
+ if @remote_specification
+ @remote_specification.post_install_message
+ elsif _local_specification
+ _local_specification.post_install_message
+ else
+ super
+ end
+ end
+
+ # needed for "with native extensions" during install
+ def extensions
+ if @remote_specification
+ @remote_specification.extensions
+ elsif _local_specification
+ _local_specification.extensions
+ else
+ super
+ end
+ end
+
+ def _local_specification
+ return unless @loaded_from && File.exist?(local_specification_path)
+ eval(File.read(local_specification_path)).tap do |spec|
+ spec.loaded_from = @loaded_from
+ end
+ end
+
+ def __swap__(spec)
+ SharedHelpers.ensure_same_dependencies(self, dependencies, spec.dependencies)
+ @remote_specification = spec
+ end
+
+ private
+
+ def local_specification_path
+ "#{base_dir}/specifications/#{full_name}.gemspec"
+ end
+
+ def parse_metadata(data)
+ return unless data
+ data.each do |k, v|
+ next unless v
+ case k.to_s
+ when "checksum"
+ @checksum = v.last
+ when "rubygems"
+ @required_rubygems_version = Gem::Requirement.new(v)
+ when "ruby"
+ @required_ruby_version = Gem::Requirement.new(v)
+ end
+ end
+ rescue StandardError => e
+ raise GemspecError, "There was an error parsing the metadata for the gem #{name} (#{version}): #{e.class}\n#{e}\nThe metadata was #{data.inspect}"
+ end
+
+ def build_dependency(name, requirements)
+ Gem::Dependency.new(name, requirements)
+ rescue ArgumentError => e
+ raise unless e.message.include?(ILLFORMED_MESSAGE)
+ puts # we shouldn't print the error message on the "fetching info" status line
+ raise GemspecError,
+ "Unfortunately, the gem #{name} (#{version}) has an invalid " \
+ "gemspec.\nPlease ask the gem author to yank the bad version to fix " \
+ "this issue. For more information, see http://bit.ly/syck-defaultkey."
+ end
+ end
+end
diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb
new file mode 100644
index 0000000000..51738139fa
--- /dev/null
+++ b/lib/bundler/env.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+require "bundler/rubygems_integration"
+require "bundler/source/git/git_proxy"
+
+module Bundler
+ class Env
+ def self.write(io)
+ io.write report
+ end
+
+ def self.report(options = {})
+ print_gemfile = options.delete(:print_gemfile) { true }
+ print_gemspecs = options.delete(:print_gemspecs) { true }
+
+ out = String.new
+ append_formatted_table("Environment", environment, out)
+ append_formatted_table("Bundler Build Metadata", BuildMetadata.to_h, out)
+
+ unless Bundler.settings.all.empty?
+ out << "\n## Bundler settings\n\n```\n"
+ Bundler.settings.all.each do |setting|
+ out << setting << "\n"
+ Bundler.settings.pretty_values_for(setting).each do |line|
+ out << " " << line << "\n"
+ end
+ end
+ out << "```\n"
+ end
+
+ return out unless SharedHelpers.in_bundle?
+
+ if print_gemfile
+ gemfiles = [Bundler.default_gemfile]
+ begin
+ gemfiles = Bundler.definition.gemfiles
+ rescue GemfileNotFound
+ nil
+ end
+
+ out << "\n## Gemfile\n"
+ gemfiles.each do |gemfile|
+ out << "\n### #{Pathname.new(gemfile).relative_path_from(SharedHelpers.pwd)}\n\n"
+ out << "```ruby\n" << read_file(gemfile).chomp << "\n```\n"
+ end
+
+ out << "\n### #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}\n\n"
+ out << "```\n" << read_file(Bundler.default_lockfile).chomp << "\n```\n"
+ end
+
+ if print_gemspecs
+ dsl = Dsl.new.tap {|d| d.eval_gemfile(Bundler.default_gemfile) }
+ out << "\n## Gemspecs\n" unless dsl.gemspecs.empty?
+ dsl.gemspecs.each do |gs|
+ out << "\n### #{File.basename(gs.loaded_from)}"
+ out << "\n\n```ruby\n" << read_file(gs.loaded_from).chomp << "\n```\n"
+ end
+ end
+
+ out
+ end
+
+ def self.read_file(filename)
+ Bundler.read_file(filename.to_s).strip
+ rescue Errno::ENOENT
+ "<No #{filename} found>"
+ rescue RuntimeError => e
+ "#{e.class}: #{e.message}"
+ end
+
+ def self.ruby_version
+ str = String.new("#{RUBY_VERSION}")
+ if RUBY_VERSION < "1.9"
+ str << " (#{RUBY_RELEASE_DATE}"
+ str << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
+ str << ") [#{RUBY_PLATFORM}]"
+ else
+ str << "p#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
+ str << " (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]"
+ end
+ end
+
+ def self.git_version
+ Bundler::Source::Git::GitProxy.new(nil, nil, nil).full_version
+ rescue Bundler::Source::Git::GitNotInstalledError
+ "not installed"
+ end
+
+ def self.version_of(script)
+ return "not installed" unless Bundler.which(script)
+ `#{script} --version`.chomp
+ end
+
+ def self.chruby_version
+ return "not installed" unless Bundler.which("chruby-exec")
+ `chruby-exec -- chruby --version`.
+ sub(/.*^chruby: (#{Gem::Version::VERSION_PATTERN}).*/m, '\1')
+ end
+
+ def self.environment
+ out = []
+
+ out << ["Bundler", Bundler::VERSION]
+ out << [" Platforms", Gem.platforms.join(", ")]
+ out << ["Ruby", ruby_version]
+ out << [" Full Path", Gem.ruby]
+ out << [" Config Dir", Pathname.new(Gem::ConfigFile::SYSTEM_WIDE_CONFIG_FILE).dirname]
+ out << ["RubyGems", Gem::VERSION]
+ out << [" Gem Home", ENV.fetch("GEM_HOME") { Gem.dir }]
+ out << [" Gem Path", ENV.fetch("GEM_PATH") { Gem.path.join(File::PATH_SEPARATOR) }]
+ out << [" User Path", Gem.user_dir]
+ out << [" Bin Dir", Gem.bindir]
+ if defined?(OpenSSL)
+ out << ["OpenSSL"]
+ out << [" Compiled", OpenSSL::OPENSSL_VERSION] if defined?(OpenSSL::OPENSSL_VERSION)
+ out << [" Loaded", OpenSSL::OPENSSL_LIBRARY_VERSION] if defined?(OpenSSL::OPENSSL_LIBRARY_VERSION)
+ out << [" Cert File", OpenSSL::X509::DEFAULT_CERT_FILE] if defined?(OpenSSL::X509::DEFAULT_CERT_FILE)
+ out << [" Cert Dir", OpenSSL::X509::DEFAULT_CERT_DIR] if defined?(OpenSSL::X509::DEFAULT_CERT_DIR)
+ end
+ out << ["Tools"]
+ out << [" Git", git_version]
+ out << [" RVM", ENV.fetch("rvm_version") { version_of("rvm") }]
+ out << [" rbenv", version_of("rbenv")]
+ out << [" chruby", chruby_version]
+
+ %w[rubygems-bundler open_gem].each do |name|
+ specs = Bundler.rubygems.find_name(name)
+ out << [" #{name}", "(#{specs.map(&:version).join(",")})"] unless specs.empty?
+ end
+ if (exe = caller.last.split(":").first) && exe =~ %r{(exe|bin)/bundler?\z}
+ shebang = File.read(exe).lines.first
+ shebang.sub!(/^#!\s*/, "")
+ unless shebang.start_with?(Gem.ruby, "/usr/bin/env ruby")
+ out << ["Gem.ruby", Gem.ruby]
+ out << ["bundle #!", shebang]
+ end
+ end
+
+ out
+ end
+
+ def self.append_formatted_table(title, pairs, out)
+ return if pairs.empty?
+ out << "\n" unless out.empty?
+ out << "## #{title}\n\n```\n"
+ ljust = pairs.map {|k, _v| k.to_s.length }.max
+ pairs.each do |k, v|
+ out << "#{k.to_s.ljust(ljust)} #{v}\n"
+ end
+ out << "```\n"
+ end
+
+ private_class_method :read_file, :ruby_version, :git_version, :append_formatted_table, :version_of, :chruby_version
+ end
+end
diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb
new file mode 100644
index 0000000000..af7c1ef0a4
--- /dev/null
+++ b/lib/bundler/environment_preserver.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Bundler
+ class EnvironmentPreserver
+ INTENTIONALLY_NIL = "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL".freeze
+ BUNDLER_KEYS = %w[
+ BUNDLE_BIN_PATH
+ BUNDLE_GEMFILE
+ BUNDLER_ORIG_MANPATH
+ BUNDLER_VERSION
+ GEM_HOME
+ GEM_PATH
+ MANPATH
+ PATH
+ RB_USER_INSTALL
+ RUBYLIB
+ RUBYOPT
+ ].map(&:freeze).freeze
+ BUNDLER_PREFIX = "BUNDLER_ORIG_".freeze
+
+ # @param env [ENV]
+ # @param keys [Array<String>]
+ def initialize(env, keys)
+ @original = env.to_hash
+ @keys = keys
+ @prefix = BUNDLER_PREFIX
+ end
+
+ # @return [Hash]
+ def backup
+ env = @original.clone
+ @keys.each do |key|
+ value = env[key]
+ if !value.nil? && !value.empty?
+ env[@prefix + key] ||= value
+ elsif value.nil?
+ env[@prefix + key] ||= INTENTIONALLY_NIL
+ end
+ end
+ env
+ end
+
+ # @return [Hash]
+ def restore
+ env = @original.clone
+ @keys.each do |key|
+ value_original = env[@prefix + key]
+ next if value_original.nil? || value_original.empty?
+ if value_original == INTENTIONALLY_NIL
+ env.delete(key)
+ else
+ env[key] = value_original
+ end
+ env.delete(@prefix + key)
+ end
+ env
+ end
+ end
+end
diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb
new file mode 100644
index 0000000000..e471bce0b6
--- /dev/null
+++ b/lib/bundler/errors.rb
@@ -0,0 +1,158 @@
+# frozen_string_literal: true
+
+module Bundler
+ class BundlerError < StandardError
+ def self.status_code(code)
+ define_method(:status_code) { code }
+ if match = BundlerError.all_errors.find {|_k, v| v == code }
+ error, _ = match
+ raise ArgumentError,
+ "Trying to register #{self} for status code #{code} but #{error} is already registered"
+ end
+ BundlerError.all_errors[self] = code
+ end
+
+ def self.all_errors
+ @all_errors ||= {}
+ end
+ end
+
+ class GemfileError < BundlerError; status_code(4); end
+ class InstallError < BundlerError; status_code(5); end
+
+ # Internal error, should be rescued
+ class VersionConflict < BundlerError
+ attr_reader :conflicts
+
+ def initialize(conflicts, msg = nil)
+ super(msg)
+ @conflicts = conflicts
+ end
+
+ status_code(6)
+ end
+
+ class GemNotFound < BundlerError; status_code(7); end
+ class InstallHookError < BundlerError; status_code(8); end
+ class GemfileNotFound < BundlerError; status_code(10); end
+ class GitError < BundlerError; status_code(11); end
+ class DeprecatedError < BundlerError; status_code(12); end
+ class PathError < BundlerError; status_code(13); end
+ class GemspecError < BundlerError; status_code(14); end
+ class InvalidOption < BundlerError; status_code(15); end
+ class ProductionError < BundlerError; status_code(16); end
+ class HTTPError < BundlerError
+ status_code(17)
+ def filter_uri(uri)
+ URICredentialsFilter.credential_filtered_uri(uri)
+ end
+ end
+ class RubyVersionMismatch < BundlerError; status_code(18); end
+ class SecurityError < BundlerError; status_code(19); end
+ class LockfileError < BundlerError; status_code(20); end
+ class CyclicDependencyError < BundlerError; status_code(21); end
+ class GemfileLockNotFound < BundlerError; status_code(22); end
+ class PluginError < BundlerError; status_code(29); end
+ class SudoNotPermittedError < BundlerError; status_code(30); end
+ class ThreadCreationError < BundlerError; status_code(33); end
+ class APIResponseMismatchError < BundlerError; status_code(34); end
+ class GemfileEvalError < GemfileError; end
+ class MarshalError < StandardError; end
+
+ class PermissionError < BundlerError
+ def initialize(path, permission_type = :write)
+ @path = path
+ @permission_type = permission_type
+ end
+
+ def action
+ case @permission_type
+ when :read then "read from"
+ when :write then "write to"
+ when :executable, :exec then "execute"
+ else @permission_type.to_s
+ end
+ end
+
+ def message
+ "There was an error while trying to #{action} `#{@path}`. " \
+ "It is likely that you need to grant #{@permission_type} permissions " \
+ "for that path."
+ end
+
+ status_code(23)
+ end
+
+ class GemRequireError < BundlerError
+ attr_reader :orig_exception
+
+ def initialize(orig_exception, msg)
+ full_message = msg + "\nGem Load Error is: #{orig_exception.message}\n"\
+ "Backtrace for gem load error is:\n"\
+ "#{orig_exception.backtrace.join("\n")}\n"\
+ "Bundler Error Backtrace:\n"
+ super(full_message)
+ @orig_exception = orig_exception
+ end
+
+ status_code(24)
+ end
+
+ class YamlSyntaxError < BundlerError
+ attr_reader :orig_exception
+
+ def initialize(orig_exception, msg)
+ super(msg)
+ @orig_exception = orig_exception
+ end
+
+ status_code(25)
+ end
+
+ class TemporaryResourceError < PermissionError
+ def message
+ "There was an error while trying to #{action} `#{@path}`. " \
+ "Some resource was temporarily unavailable. It's suggested that you try" \
+ "the operation again."
+ end
+
+ status_code(26)
+ end
+
+ class VirtualProtocolError < BundlerError
+ def message
+ "There was an error relating to virtualization and file access." \
+ "It is likely that you need to grant access to or mount some file system correctly."
+ end
+
+ status_code(27)
+ end
+
+ class OperationNotSupportedError < PermissionError
+ def message
+ "Attempting to #{action} `#{@path}` is unsupported by your OS."
+ end
+
+ status_code(28)
+ end
+
+ class NoSpaceOnDeviceError < PermissionError
+ def message
+ "There was an error while trying to #{action} `#{@path}`. " \
+ "There was insufficient space remaining on the device."
+ end
+
+ status_code(31)
+ end
+
+ class GenericSystemCallError < BundlerError
+ attr_reader :underlying_error
+
+ def initialize(underlying_error, message)
+ @underlying_error = underlying_error
+ super("#{message}\nThe underlying system error is #{@underlying_error.class}: #{@underlying_error}")
+ end
+
+ status_code(32)
+ end
+end
diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb
new file mode 100644
index 0000000000..83e7ff0389
--- /dev/null
+++ b/lib/bundler/feature_flag.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module Bundler
+ class FeatureFlag
+ def self.settings_flag(flag, &default)
+ unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s)
+ raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key"
+ end
+
+ settings_method("#{flag}?", flag, &default)
+ end
+ private_class_method :settings_flag
+
+ def self.settings_option(key, &default)
+ settings_method(key, key, &default)
+ end
+ private_class_method :settings_option
+
+ def self.settings_method(name, key, &default)
+ define_method(name) do
+ value = Bundler.settings[key]
+ value = instance_eval(&default) if value.nil? && !default.nil?
+ value
+ end
+ end
+ private_class_method :settings_method
+
+ (1..10).each {|v| define_method("bundler_#{v}_mode?") { major_version >= v } }
+
+ settings_flag(:allow_bundler_dependency_conflicts) { bundler_2_mode? }
+ settings_flag(:allow_offline_install) { bundler_2_mode? }
+ settings_flag(:auto_clean_without_path) { bundler_2_mode? }
+ settings_flag(:auto_config_jobs) { bundler_2_mode? }
+ settings_flag(:cache_all) { bundler_2_mode? }
+ settings_flag(:cache_command_is_package) { bundler_2_mode? }
+ settings_flag(:console_command) { !bundler_2_mode? }
+ settings_flag(:default_install_uses_path) { bundler_2_mode? }
+ settings_flag(:deployment_means_frozen) { bundler_2_mode? }
+ settings_flag(:disable_multisource) { bundler_2_mode? }
+ settings_flag(:error_on_stderr) { bundler_2_mode? }
+ settings_flag(:forget_cli_options) { bundler_2_mode? }
+ settings_flag(:global_path_appends_ruby_scope) { bundler_2_mode? }
+ settings_flag(:global_gem_cache) { bundler_2_mode? }
+ settings_flag(:init_gems_rb) { bundler_2_mode? }
+ settings_flag(:list_command) { bundler_2_mode? }
+ settings_flag(:lockfile_uses_separate_rubygems_sources) { bundler_2_mode? }
+ settings_flag(:only_update_to_newer_versions) { bundler_2_mode? }
+ settings_flag(:path_relative_to_cwd) { bundler_2_mode? }
+ settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") }
+ settings_flag(:prefer_gems_rb) { bundler_2_mode? }
+ settings_flag(:print_only_version_number) { bundler_2_mode? }
+ settings_flag(:setup_makes_kernel_gem_public) { !bundler_2_mode? }
+ settings_flag(:skip_default_git_sources) { bundler_2_mode? }
+ settings_flag(:specific_platform) { bundler_2_mode? }
+ settings_flag(:suppress_install_using_messages) { bundler_2_mode? }
+ settings_flag(:unlock_source_unlocks_spec) { !bundler_2_mode? }
+ settings_flag(:update_requires_all_flag) { bundler_2_mode? }
+ settings_flag(:use_gem_version_promoter_for_major_updates) { bundler_2_mode? }
+ settings_flag(:viz_command) { !bundler_2_mode? }
+
+ settings_option(:default_cli_command) { bundler_2_mode? ? :cli_help : :install }
+
+ def initialize(bundler_version)
+ @bundler_version = Gem::Version.create(bundler_version)
+ end
+
+ def major_version
+ @bundler_version.segments.first
+ end
+ private :major_version
+ end
+end
diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb
new file mode 100644
index 0000000000..4dd42e42ff
--- /dev/null
+++ b/lib/bundler/fetcher.rb
@@ -0,0 +1,312 @@
+# frozen_string_literal: true
+
+require "bundler/vendored_persistent"
+require "cgi"
+require "securerandom"
+require "zlib"
+
+module Bundler
+ # Handles all the fetching with the rubygems server
+ class Fetcher
+ autoload :CompactIndex, "bundler/fetcher/compact_index"
+ autoload :Downloader, "bundler/fetcher/downloader"
+ autoload :Dependency, "bundler/fetcher/dependency"
+ autoload :Index, "bundler/fetcher/index"
+
+ # This error is raised when it looks like the network is down
+ class NetworkDownError < HTTPError; end
+ # This error is raised if the API returns a 413 (only printed in verbose)
+ class FallbackError < HTTPError; end
+ # This is the error raised if OpenSSL fails the cert verification
+ class CertificateFailureError < HTTPError
+ def initialize(remote_uri)
+ remote_uri = filter_uri(remote_uri)
+ super "Could not verify the SSL certificate for #{remote_uri}.\nThere" \
+ " is a chance you are experiencing a man-in-the-middle attack, but" \
+ " most likely your system doesn't have the CA certificates needed" \
+ " for verification. For information about OpenSSL certificates, see" \
+ " http://bit.ly/ruby-ssl. To connect without using SSL, edit your Gemfile" \
+ " sources and change 'https' to 'http'."
+ end
+ end
+ # This is the error raised when a source is HTTPS and OpenSSL didn't load
+ class SSLError < HTTPError
+ def initialize(msg = nil)
+ super msg || "Could not load OpenSSL.\n" \
+ "You must recompile Ruby with OpenSSL support or change the sources in your " \
+ "Gemfile from 'https' to 'http'. Instructions for compiling with OpenSSL " \
+ "using RVM are available at rvm.io/packages/openssl."
+ end
+ end
+ # This error is raised if HTTP authentication is required, but not provided.
+ class AuthenticationRequiredError < HTTPError
+ def initialize(remote_uri)
+ remote_uri = filter_uri(remote_uri)
+ super "Authentication is required for #{remote_uri}.\n" \
+ "Please supply credentials for this source. You can do this by running:\n" \
+ " bundle config #{remote_uri} username:password"
+ end
+ end
+ # This error is raised if HTTP authentication is provided, but incorrect.
+ class BadAuthenticationError < HTTPError
+ def initialize(remote_uri)
+ remote_uri = filter_uri(remote_uri)
+ super "Bad username or password for #{remote_uri}.\n" \
+ "Please double-check your credentials and correct them."
+ end
+ end
+
+ # Exceptions classes that should bypass retry attempts. If your password didn't work the
+ # first time, it's not going to the third time.
+ NET_ERRORS = [:HTTPBadGateway, :HTTPBadRequest, :HTTPFailedDependency,
+ :HTTPForbidden, :HTTPInsufficientStorage, :HTTPMethodNotAllowed,
+ :HTTPMovedPermanently, :HTTPNoContent, :HTTPNotFound,
+ :HTTPNotImplemented, :HTTPPreconditionFailed, :HTTPRequestEntityTooLarge,
+ :HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity,
+ :HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze
+ FAIL_ERRORS = begin
+ fail_errors = [AuthenticationRequiredError, BadAuthenticationError, FallbackError]
+ fail_errors << Gem::Requirement::BadRequirementError if defined?(Gem::Requirement::BadRequirementError)
+ fail_errors.concat(NET_ERRORS.map {|e| SharedHelpers.const_get_safely(e, Net) }.compact)
+ end.freeze
+
+ class << self
+ attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries
+ end
+
+ self.redirect_limit = Bundler.settings[:redirect] # How many redirects to allow in one request
+ self.api_timeout = Bundler.settings[:timeout] # How long to wait for each API call
+ self.max_retries = Bundler.settings[:retry] # How many retries for the API call
+
+ def initialize(remote)
+ @remote = remote
+
+ Socket.do_not_reverse_lookup = true
+ connection # create persistent connection
+ end
+
+ def uri
+ @remote.anonymized_uri
+ end
+
+ # fetch a gem specification
+ def fetch_spec(spec)
+ spec -= [nil, "ruby", ""]
+ spec_file_name = "#{spec.join "-"}.gemspec"
+
+ uri = URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
+ if uri.scheme == "file"
+ Bundler.load_marshal Bundler.rubygems.inflate(Gem.read_binary(uri.path))
+ elsif cached_spec_path = gemspec_cached_path(spec_file_name)
+ Bundler.load_gemspec(cached_spec_path)
+ else
+ Bundler.load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body)
+ end
+ rescue MarshalError
+ raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
+ "Your network or your gem server is probably having issues right now."
+ end
+
+ # return the specs in the bundler format as an index with retries
+ def specs_with_retry(gem_names, source)
+ Bundler::Retry.new("fetcher", FAIL_ERRORS).attempts do
+ specs(gem_names, source)
+ end
+ end
+
+ # return the specs in the bundler format as an index
+ def specs(gem_names, source)
+ old = Bundler.rubygems.sources
+ index = Bundler::Index.new
+
+ if Bundler::Fetcher.disable_endpoint
+ @use_api = false
+ specs = fetchers.last.specs(gem_names)
+ else
+ specs = []
+ fetchers.shift until fetchers.first.available? || fetchers.empty?
+ fetchers.dup.each do |f|
+ break unless f.api_fetcher? && !gem_names || !specs = f.specs(gem_names)
+ fetchers.delete(f)
+ end
+ @use_api = false if fetchers.none?(&:api_fetcher?)
+ end
+
+ specs.each do |name, version, platform, dependencies, metadata|
+ next if name == "bundler"
+ spec = if dependencies
+ EndpointSpecification.new(name, version, platform, dependencies, metadata)
+ else
+ RemoteSpecification.new(name, version, platform, self)
+ end
+ spec.source = source
+ spec.remote = @remote
+ index << spec
+ end
+
+ index
+ rescue CertificateFailureError
+ Bundler.ui.info "" if gem_names && use_api # newline after dots
+ raise
+ ensure
+ Bundler.rubygems.sources = old
+ end
+
+ def use_api
+ return @use_api if defined?(@use_api)
+
+ fetchers.shift until fetchers.first.available?
+
+ @use_api = if remote_uri.scheme == "file" || Bundler::Fetcher.disable_endpoint
+ false
+ else
+ fetchers.first.api_fetcher?
+ end
+ end
+
+ def user_agent
+ @user_agent ||= begin
+ ruby = Bundler::RubyVersion.system
+
+ agent = String.new("bundler/#{Bundler::VERSION}")
+ agent << " rubygems/#{Gem::VERSION}"
+ agent << " ruby/#{ruby.versions_string(ruby.versions)}"
+ agent << " (#{ruby.host})"
+ agent << " command/#{ARGV.first}"
+
+ if ruby.engine != "ruby"
+ # engine_version raises on unknown engines
+ engine_version = begin
+ ruby.engine_versions
+ rescue RuntimeError
+ "???"
+ end
+ agent << " #{ruby.engine}/#{ruby.versions_string(engine_version)}"
+ end
+
+ agent << " options/#{Bundler.settings.all.join(",")}"
+
+ agent << " ci/#{cis.join(",")}" if cis.any?
+
+ # add a random ID so we can consolidate runs server-side
+ agent << " " << SecureRandom.hex(8)
+
+ # add any user agent strings set in the config
+ extra_ua = Bundler.settings[:user_agent]
+ agent << " " << extra_ua if extra_ua
+
+ agent
+ end
+ end
+
+ def fetchers
+ @fetchers ||= FETCHERS.map {|f| f.new(downloader, @remote, uri) }
+ end
+
+ def http_proxy
+ return unless uri = connection.proxy_uri
+ uri.to_s
+ end
+
+ def inspect
+ "#<#{self.class}:0x#{object_id} uri=#{uri}>"
+ end
+
+ private
+
+ FETCHERS = [CompactIndex, Dependency, Index].freeze
+
+ def cis
+ env_cis = {
+ "TRAVIS" => "travis",
+ "CIRCLECI" => "circle",
+ "SEMAPHORE" => "semaphore",
+ "JENKINS_URL" => "jenkins",
+ "BUILDBOX" => "buildbox",
+ "GO_SERVER_URL" => "go",
+ "SNAP_CI" => "snap",
+ "CI_NAME" => ENV["CI_NAME"],
+ "CI" => "ci"
+ }
+ env_cis.find_all {|env, _| ENV[env] }.map {|_, ci| ci }
+ end
+
+ def connection
+ @connection ||= begin
+ needs_ssl = remote_uri.scheme == "https" ||
+ Bundler.settings[:ssl_verify_mode] ||
+ Bundler.settings[:ssl_client_cert]
+ raise SSLError if needs_ssl && !defined?(OpenSSL::SSL)
+
+ con = PersistentHTTP.new "bundler", :ENV
+ if gem_proxy = Bundler.rubygems.configuration[:http_proxy]
+ con.proxy = URI.parse(gem_proxy) if gem_proxy != :no_proxy
+ end
+
+ if remote_uri.scheme == "https"
+ con.verify_mode = (Bundler.settings[:ssl_verify_mode] ||
+ OpenSSL::SSL::VERIFY_PEER)
+ con.cert_store = bundler_cert_store
+ end
+
+ ssl_client_cert = Bundler.settings[:ssl_client_cert] ||
+ (Bundler.rubygems.configuration.ssl_client_cert if
+ Bundler.rubygems.configuration.respond_to?(:ssl_client_cert))
+ if ssl_client_cert
+ pem = File.read(ssl_client_cert)
+ con.cert = OpenSSL::X509::Certificate.new(pem)
+ con.key = OpenSSL::PKey::RSA.new(pem)
+ end
+
+ con.read_timeout = Fetcher.api_timeout
+ con.open_timeout = Fetcher.api_timeout
+ con.override_headers["User-Agent"] = user_agent
+ con.override_headers["X-Gemfile-Source"] = @remote.original_uri.to_s if @remote.original_uri
+ con
+ end
+ end
+
+ # cached gem specification path, if one exists
+ def gemspec_cached_path(spec_file_name)
+ paths = Bundler.rubygems.spec_cache_dirs.map {|dir| File.join(dir, spec_file_name) }
+ paths = paths.select {|path| File.file? path }
+ paths.first
+ end
+
+ HTTP_ERRORS = [
+ Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH,
+ Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
+ PersistentHTTP::Error, Zlib::BufError, Errno::EHOSTUNREACH
+ ].freeze
+
+ def bundler_cert_store
+ store = OpenSSL::X509::Store.new
+ ssl_ca_cert = Bundler.settings[:ssl_ca_cert] ||
+ (Bundler.rubygems.configuration.ssl_ca_cert if
+ Bundler.rubygems.configuration.respond_to?(:ssl_ca_cert))
+ if ssl_ca_cert
+ if File.directory? ssl_ca_cert
+ store.add_path ssl_ca_cert
+ else
+ store.add_file ssl_ca_cert
+ end
+ else
+ store.set_default_paths
+ certs = File.expand_path("../ssl_certs/*/*.pem", __FILE__)
+ Dir.glob(certs).each {|c| store.add_file c }
+ end
+ store
+ end
+
+ private
+
+ def remote_uri
+ @remote.uri
+ end
+
+ def downloader
+ @downloader ||= Downloader.new(connection, self.class.redirect_limit)
+ end
+ end
+end
diff --git a/lib/bundler/fetcher/base.rb b/lib/bundler/fetcher/base.rb
new file mode 100644
index 0000000000..27987f670a
--- /dev/null
+++ b/lib/bundler/fetcher/base.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Fetcher
+ class Base
+ attr_reader :downloader
+ attr_reader :display_uri
+ attr_reader :remote
+
+ def initialize(downloader, remote, display_uri)
+ raise "Abstract class" if self.class == Base
+ @downloader = downloader
+ @remote = remote
+ @display_uri = display_uri
+ end
+
+ def remote_uri
+ @remote.uri
+ end
+
+ def fetch_uri
+ @fetch_uri ||= begin
+ if remote_uri.host == "rubygems.org"
+ uri = remote_uri.dup
+ uri.host = "index.rubygems.org"
+ uri
+ else
+ remote_uri
+ end
+ end
+ end
+
+ def available?
+ true
+ end
+
+ def api_fetcher?
+ false
+ end
+
+ private
+
+ def log_specs(debug_msg)
+ if Bundler.ui.debug?
+ Bundler.ui.debug debug_msg
+ else
+ Bundler.ui.info ".", false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb
new file mode 100644
index 0000000000..cfc74d642c
--- /dev/null
+++ b/lib/bundler/fetcher/compact_index.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require "bundler/fetcher/base"
+require "bundler/worker"
+
+module Bundler
+ autoload :CompactIndexClient, "bundler/compact_index_client"
+
+ class Fetcher
+ class CompactIndex < Base
+ def self.compact_index_request(method_name)
+ method = instance_method(method_name)
+ undef_method(method_name)
+ define_method(method_name) do |*args, &blk|
+ begin
+ method.bind(self).call(*args, &blk)
+ rescue NetworkDownError, CompactIndexClient::Updater::MisMatchedChecksumError => e
+ raise HTTPError, e.message
+ rescue AuthenticationRequiredError
+ # Fail since we got a 401 from the server.
+ raise
+ rescue HTTPError => e
+ Bundler.ui.trace(e)
+ nil
+ end
+ end
+ end
+
+ def specs(gem_names)
+ specs_for_names(gem_names)
+ end
+ compact_index_request :specs
+
+ def specs_for_names(gem_names)
+ gem_info = []
+ complete_gems = []
+ remaining_gems = gem_names.dup
+
+ until remaining_gems.empty?
+ log_specs "Looking up gems #{remaining_gems.inspect}"
+
+ deps = compact_index_client.dependencies(remaining_gems)
+ next_gems = deps.map {|d| d[3].map(&:first).flatten(1) }.flatten(1).uniq
+ deps.each {|dep| gem_info << dep }
+ complete_gems.concat(deps.map(&:first)).uniq!
+ remaining_gems = next_gems - complete_gems
+ end
+ @bundle_worker.stop if @bundle_worker
+ @bundle_worker = nil # reset it. Not sure if necessary
+
+ gem_info
+ end
+
+ def fetch_spec(spec)
+ spec -= [nil, "ruby", ""]
+ contents = compact_index_client.spec(*spec)
+ return nil if contents.nil?
+ contents.unshift(spec.first)
+ contents[3].map! {|d| Gem::Dependency.new(*d) }
+ EndpointSpecification.new(*contents)
+ end
+ compact_index_request :fetch_spec
+
+ def available?
+ return nil unless SharedHelpers.md5_available?
+ user_home = Bundler.user_home
+ return nil unless user_home.directory? && user_home.writable?
+ # Read info file checksums out of /versions, so we can know if gems are up to date
+ fetch_uri.scheme != "file" && compact_index_client.update_and_parse_checksums!
+ rescue CompactIndexClient::Updater::MisMatchedChecksumError => e
+ Bundler.ui.debug(e.message)
+ nil
+ end
+ compact_index_request :available?
+
+ def api_fetcher?
+ true
+ end
+
+ private
+
+ def compact_index_client
+ @compact_index_client ||= begin
+ SharedHelpers.filesystem_access(cache_path) do
+ CompactIndexClient.new(cache_path, client_fetcher)
+ end.tap do |client|
+ client.in_parallel = lambda do |inputs, &blk|
+ func = lambda {|object, _index| blk.call(object) }
+ worker = bundle_worker(func)
+ inputs.each {|input| worker.enq(input) }
+ inputs.map { worker.deq }
+ end
+ end
+ end
+ end
+
+ def bundle_worker(func = nil)
+ @bundle_worker ||= begin
+ worker_name = "Compact Index (#{display_uri.host})"
+ Bundler::Worker.new(Bundler.current_ruby.rbx? ? 1 : 25, worker_name, func)
+ end
+ @bundle_worker.tap do |worker|
+ worker.instance_variable_set(:@func, func) if func
+ end
+ end
+
+ def cache_path
+ Bundler.user_cache.join("compact_index", remote.cache_slug)
+ end
+
+ def client_fetcher
+ ClientFetcher.new(self, Bundler.ui)
+ end
+
+ ClientFetcher = Struct.new(:fetcher, :ui) do
+ def call(path, headers)
+ fetcher.downloader.fetch(fetcher.fetch_uri + path, headers)
+ rescue NetworkDownError => e
+ raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"]
+ ui.warn "Using the cached data for the new index because of a network error: #{e}"
+ Net::HTTPNotModified.new(nil, nil, nil)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb
new file mode 100644
index 0000000000..1430d1ebeb
--- /dev/null
+++ b/lib/bundler/fetcher/dependency.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require "bundler/fetcher/base"
+require "cgi"
+
+module Bundler
+ class Fetcher
+ class Dependency < Base
+ def available?
+ @available ||= fetch_uri.scheme != "file" && downloader.fetch(dependency_api_uri)
+ rescue NetworkDownError => e
+ raise HTTPError, e.message
+ rescue AuthenticationRequiredError
+ # Fail since we got a 401 from the server.
+ raise
+ rescue HTTPError
+ false
+ end
+
+ def api_fetcher?
+ true
+ end
+
+ def specs(gem_names, full_dependency_list = [], last_spec_list = [])
+ query_list = gem_names.uniq - full_dependency_list
+
+ log_specs "Query List: #{query_list.inspect}"
+
+ return last_spec_list if query_list.empty?
+
+ spec_list, deps_list = Bundler::Retry.new("dependency api", FAIL_ERRORS).attempts do
+ dependency_specs(query_list)
+ end
+
+ returned_gems = spec_list.map(&:first).uniq
+ specs(deps_list, full_dependency_list + returned_gems, spec_list + last_spec_list)
+ rescue MarshalError
+ Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
+ Bundler.ui.debug "could not fetch from the dependency API, trying the full index"
+ nil
+ rescue HTTPError, GemspecError
+ Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
+ Bundler.ui.debug "could not fetch from the dependency API\nit's suggested to retry using the full index via `bundle install --full-index`"
+ nil
+ end
+
+ def dependency_specs(gem_names)
+ Bundler.ui.debug "Query Gemcutter Dependency Endpoint API: #{gem_names.join(",")}"
+
+ gem_list = unmarshalled_dep_gems(gem_names)
+ get_formatted_specs_and_deps(gem_list)
+ end
+
+ def unmarshalled_dep_gems(gem_names)
+ gem_list = []
+ gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names|
+ marshalled_deps = downloader.fetch(dependency_api_uri(names)).body
+ gem_list.concat(Bundler.load_marshal(marshalled_deps))
+ end
+ gem_list
+ end
+
+ def get_formatted_specs_and_deps(gem_list)
+ deps_list = []
+ spec_list = []
+
+ gem_list.each do |s|
+ deps_list.concat(s[:dependencies].map(&:first))
+ deps = s[:dependencies].map {|n, d| [n, d.split(", ")] }
+ spec_list.push([s[:name], s[:number], s[:platform], deps])
+ end
+ [spec_list, deps_list]
+ end
+
+ def dependency_api_uri(gem_names = [])
+ uri = fetch_uri + "api/v1/dependencies"
+ uri.query = "gems=#{CGI.escape(gem_names.sort.join(","))}" if gem_names.any?
+ uri
+ end
+ end
+ end
+end
diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb
new file mode 100644
index 0000000000..e0e0cbf1c9
--- /dev/null
+++ b/lib/bundler/fetcher/downloader.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Fetcher
+ class Downloader
+ attr_reader :connection
+ attr_reader :redirect_limit
+
+ def initialize(connection, redirect_limit)
+ @connection = connection
+ @redirect_limit = redirect_limit
+ end
+
+ def fetch(uri, headers = {}, counter = 0)
+ raise HTTPError, "Too many redirects" if counter >= redirect_limit
+
+ response = request(uri, headers)
+ Bundler.ui.debug("HTTP #{response.code} #{response.message} #{uri}")
+
+ case response
+ when Net::HTTPSuccess, Net::HTTPNotModified
+ response
+ when Net::HTTPRedirection
+ new_uri = URI.parse(response["location"])
+ if new_uri.host == uri.host
+ new_uri.user = uri.user
+ new_uri.password = uri.password
+ end
+ fetch(new_uri, headers, counter + 1)
+ when Net::HTTPRequestedRangeNotSatisfiable
+ new_headers = headers.dup
+ new_headers.delete("Range")
+ new_headers["Accept-Encoding"] = "gzip"
+ fetch(uri, new_headers)
+ when Net::HTTPRequestEntityTooLarge
+ raise FallbackError, response.body
+ when Net::HTTPUnauthorized
+ raise AuthenticationRequiredError, uri.host
+ when Net::HTTPNotFound
+ raise FallbackError, "Net::HTTPNotFound"
+ else
+ raise HTTPError, "#{response.class}#{": #{response.body}" unless response.body.empty?}"
+ end
+ end
+
+ def request(uri, headers)
+ validate_uri_scheme!(uri)
+
+ Bundler.ui.debug "HTTP GET #{uri}"
+ req = Net::HTTP::Get.new uri.request_uri, headers
+ if uri.user
+ user = CGI.unescape(uri.user)
+ password = uri.password ? CGI.unescape(uri.password) : nil
+ req.basic_auth(user, password)
+ end
+ connection.request(uri, req)
+ rescue NoMethodError => e
+ raise unless ["undefined method", "use_ssl="].all? {|snippet| e.message.include? snippet }
+ raise LoadError.new("cannot load such file -- openssl")
+ rescue OpenSSL::SSL::SSLError
+ raise CertificateFailureError.new(uri)
+ rescue *HTTP_ERRORS => e
+ Bundler.ui.trace e
+ case e.message
+ when /host down:/, /getaddrinfo: nodename nor servname provided/
+ raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \
+ "connection and try again."
+ else
+ raise HTTPError, "Network error while fetching #{URICredentialsFilter.credential_filtered_uri(uri)}" \
+ " (#{e})"
+ end
+ end
+
+ private
+
+ def validate_uri_scheme!(uri)
+ return if uri.scheme =~ /\Ahttps?\z/
+ raise InvalidOption,
+ "The request uri `#{uri}` has an invalid scheme (`#{uri.scheme}`). " \
+ "Did you mean `http` or `https`?"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/fetcher/index.rb b/lib/bundler/fetcher/index.rb
new file mode 100644
index 0000000000..1a8064624d
--- /dev/null
+++ b/lib/bundler/fetcher/index.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require "bundler/fetcher/base"
+require "rubygems/remote_fetcher"
+
+module Bundler
+ class Fetcher
+ class Index < Base
+ def specs(_gem_names)
+ Bundler.rubygems.fetch_all_remote_specs(remote)
+ rescue Gem::RemoteFetcher::FetchError, OpenSSL::SSL::SSLError, Net::HTTPFatalError => e
+ case e.message
+ when /certificate verify failed/
+ raise CertificateFailureError.new(display_uri)
+ when /401/
+ raise AuthenticationRequiredError, remote_uri
+ when /403/
+ raise BadAuthenticationError, remote_uri if remote_uri.userinfo
+ raise AuthenticationRequiredError, remote_uri
+ else
+ Bundler.ui.trace e
+ raise HTTPError, "Could not fetch specs from #{display_uri}"
+ end
+ end
+
+ def fetch_spec(spec)
+ spec -= [nil, "ruby", ""]
+ spec_file_name = "#{spec.join "-"}.gemspec"
+
+ uri = URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
+ if uri.scheme == "file"
+ Bundler.load_marshal Bundler.rubygems.inflate(Gem.read_binary(uri.path))
+ elsif cached_spec_path = gemspec_cached_path(spec_file_name)
+ Bundler.load_gemspec(cached_spec_path)
+ else
+ Bundler.load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body)
+ end
+ rescue MarshalError
+ raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
+ "Your network or your gem server is probably having issues right now."
+ end
+
+ private
+
+ # cached gem specification path, if one exists
+ def gemspec_cached_path(spec_file_name)
+ paths = Bundler.rubygems.spec_cache_dirs.map {|dir| File.join(dir, spec_file_name) }
+ paths.find {|path| File.file? path }
+ end
+ end
+ end
+end
diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb
new file mode 100644
index 0000000000..ae3299a7c8
--- /dev/null
+++ b/lib/bundler/friendly_errors.rb
@@ -0,0 +1,131 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+require "cgi"
+require "bundler/vendored_thor"
+
+module Bundler
+ module FriendlyErrors
+ module_function
+
+ def log_error(error)
+ case error
+ when YamlSyntaxError
+ Bundler.ui.error error.message
+ Bundler.ui.trace error.orig_exception
+ when Dsl::DSLError, GemspecError
+ Bundler.ui.error error.message
+ when GemRequireError
+ Bundler.ui.error error.message
+ Bundler.ui.trace error.orig_exception, nil, true
+ when BundlerError
+ Bundler.ui.error error.message, :wrap => true
+ Bundler.ui.trace error
+ when Thor::Error
+ Bundler.ui.error error.message
+ when LoadError
+ raise error unless error.message =~ /cannot load such file -- openssl|openssl.so|libcrypto.so/
+ Bundler.ui.error "\nCould not load OpenSSL."
+ Bundler.ui.warn <<-WARN, :wrap => true
+ You must recompile Ruby with OpenSSL support or change the sources in your \
+ Gemfile from 'https' to 'http'. Instructions for compiling with OpenSSL \
+ using RVM are available at http://rvm.io/packages/openssl.
+ WARN
+ Bundler.ui.trace error
+ when Interrupt
+ Bundler.ui.error "\nQuitting..."
+ Bundler.ui.trace error
+ when Gem::InvalidSpecificationException
+ Bundler.ui.error error.message, :wrap => true
+ when SystemExit
+ when *[defined?(Java::JavaLang::OutOfMemoryError) && Java::JavaLang::OutOfMemoryError].compact
+ Bundler.ui.error "\nYour JVM has run out of memory, and Bundler cannot continue. " \
+ "You can decrease the amount of memory Bundler needs by removing gems from your Gemfile, " \
+ "especially large gems. (Gems can be as large as hundreds of megabytes, and Bundler has to read those files!). " \
+ "Alternatively, you can increase the amount of memory the JVM is able to use by running Bundler with jruby -J-Xmx1024m -S bundle (JRuby defaults to 500MB)."
+ else request_issue_report_for(error)
+ end
+ rescue
+ raise error
+ end
+
+ def exit_status(error)
+ case error
+ when BundlerError then error.status_code
+ when Thor::Error then 15
+ when SystemExit then error.status
+ else 1
+ end
+ end
+
+ def request_issue_report_for(e)
+ Bundler.ui.info <<-EOS.gsub(/^ {8}/, "")
+ --- ERROR REPORT TEMPLATE -------------------------------------------------------
+ # Error Report
+
+ ## Questions
+
+ Please fill out answers to these questions, it'll help us figure out
+ why things are going wrong.
+
+ - **What did you do?**
+
+ I ran the command `#{$PROGRAM_NAME} #{ARGV.join(" ")}`
+
+ - **What did you expect to happen?**
+
+ I expected Bundler to...
+
+ - **What happened instead?**
+
+ Instead, what happened was...
+
+ - **Have you tried any solutions posted on similar issues in our issue tracker, stack overflow, or google?**
+
+ I tried...
+
+ - **Have you read our issues document, https://github.com/bundler/bundler/blob/master/doc/contributing/ISSUES.md?**
+
+ ...
+
+ ## Backtrace
+
+ ```
+ #{e.class}: #{e.message}
+ #{e.backtrace && e.backtrace.join("\n ").chomp}
+ ```
+
+ #{Bundler::Env.report}
+ --- TEMPLATE END ----------------------------------------------------------------
+
+ EOS
+
+ Bundler.ui.error "Unfortunately, an unexpected error occurred, and Bundler cannot continue."
+
+ Bundler.ui.warn <<-EOS.gsub(/^ {8}/, "")
+
+ First, try this link to see if there are any existing issue reports for this error:
+ #{issues_url(e)}
+
+ If there aren't any reports for this error yet, please create copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at:
+ https://github.com/bundler/bundler/issues/new
+ EOS
+ end
+
+ def issues_url(exception)
+ message = exception.message.lines.first.tr(":", " ").chomp
+ message = message.split("-").first if exception.is_a?(Errno)
+ "https://github.com/bundler/bundler/search?q=" \
+ "#{CGI.escape(message)}&type=Issues"
+ end
+ end
+
+ def self.with_friendly_errors
+ yield
+ rescue SignalException
+ raise
+ rescue Exception => e
+ FriendlyErrors.log_error(e)
+ exit FriendlyErrors.exit_status(e)
+ end
+end
diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb
new file mode 100644
index 0000000000..e7673cba88
--- /dev/null
+++ b/lib/bundler/gem_helper.rb
@@ -0,0 +1,202 @@
+# frozen_string_literal: true
+
+require "bundler/vendored_thor" unless defined?(Thor)
+require "bundler"
+
+module Bundler
+ class GemHelper
+ include Rake::DSL if defined? Rake::DSL
+
+ class << self
+ # set when install'd.
+ attr_accessor :instance
+
+ def install_tasks(opts = {})
+ new(opts[:dir], opts[:name]).install
+ end
+
+ def gemspec(&block)
+ gemspec = instance.gemspec
+ block.call(gemspec) if block
+ gemspec
+ end
+ end
+
+ attr_reader :spec_path, :base, :gemspec
+
+ def initialize(base = nil, name = nil)
+ Bundler.ui = UI::Shell.new
+ @base = (base ||= SharedHelpers.pwd)
+ gemspecs = name ? [File.join(base, "#{name}.gemspec")] : Dir[File.join(base, "{,*}.gemspec")]
+ raise "Unable to determine name from existing gemspec. Use :name => 'gemname' in #install_tasks to manually set it." unless gemspecs.size == 1
+ @spec_path = gemspecs.first
+ @gemspec = Bundler.load_gemspec(@spec_path)
+ end
+
+ def install
+ built_gem_path = nil
+
+ desc "Build #{name}-#{version}.gem into the pkg directory."
+ task "build" do
+ built_gem_path = build_gem
+ end
+
+ desc "Build and install #{name}-#{version}.gem into system gems."
+ task "install" => "build" do
+ install_gem(built_gem_path)
+ end
+
+ desc "Build and install #{name}-#{version}.gem into system gems without network access."
+ task "install:local" => "build" do
+ install_gem(built_gem_path, :local)
+ end
+
+ desc "Create tag #{version_tag} and build and push #{name}-#{version}.gem to #{gem_push_host}\n" \
+ "To prevent publishing in RubyGems use `gem_push=no rake release`"
+ task "release", [:remote] => ["build", "release:guard_clean",
+ "release:source_control_push", "release:rubygem_push"] do
+ end
+
+ task "release:guard_clean" do
+ guard_clean
+ end
+
+ task "release:source_control_push", [:remote] do |_, args|
+ tag_version { git_push(args[:remote]) } unless already_tagged?
+ end
+
+ task "release:rubygem_push" do
+ rubygem_push(built_gem_path) if gem_push?
+ end
+
+ GemHelper.instance = self
+ end
+
+ def build_gem
+ file_name = nil
+ sh("gem build -V '#{spec_path}'") do
+ file_name = File.basename(built_gem_path)
+ SharedHelpers.filesystem_access(File.join(base, "pkg")) {|p| FileUtils.mkdir_p(p) }
+ FileUtils.mv(built_gem_path, "pkg")
+ Bundler.ui.confirm "#{name} #{version} built to pkg/#{file_name}."
+ end
+ File.join(base, "pkg", file_name)
+ end
+
+ def install_gem(built_gem_path = nil, local = false)
+ built_gem_path ||= build_gem
+ out, _ = sh_with_code("gem install '#{built_gem_path}'#{" --local" if local}")
+ raise "Couldn't install gem, run `gem install #{built_gem_path}' for more detailed output" unless out[/Successfully installed/]
+ Bundler.ui.confirm "#{name} (#{version}) installed."
+ end
+
+ protected
+
+ def rubygem_push(path)
+ gem_command = "gem push '#{path}'"
+ gem_command += " --key #{gem_key}" if gem_key
+ gem_command += " --host #{allowed_push_host}" if allowed_push_host
+ unless allowed_push_host || Bundler.user_home.join(".gem/credentials").file?
+ raise "Your rubygems.org credentials aren't set. Run `gem push` to set them."
+ end
+ sh(gem_command)
+ Bundler.ui.confirm "Pushed #{name} #{version} to #{gem_push_host}"
+ end
+
+ def built_gem_path
+ Dir[File.join(base, "#{name}-*.gem")].sort_by {|f| File.mtime(f) }.last
+ end
+
+ def git_push(remote = "")
+ perform_git_push remote
+ perform_git_push "#{remote} --tags"
+ Bundler.ui.confirm "Pushed git commits and tags."
+ end
+
+ def allowed_push_host
+ @gemspec.metadata["allowed_push_host"] if @gemspec.respond_to?(:metadata)
+ end
+
+ def gem_push_host
+ env_rubygems_host = ENV["RUBYGEMS_HOST"]
+ env_rubygems_host = nil if
+ env_rubygems_host && env_rubygems_host.empty?
+
+ allowed_push_host || env_rubygems_host || "rubygems.org"
+ end
+
+ def perform_git_push(options = "")
+ cmd = "git push #{options}"
+ out, code = sh_with_code(cmd)
+ raise "Couldn't git push. `#{cmd}' failed with the following output:\n\n#{out}\n" unless code == 0
+ end
+
+ def already_tagged?
+ return false unless sh("git tag").split(/\n/).include?(version_tag)
+ Bundler.ui.confirm "Tag #{version_tag} has already been created."
+ true
+ end
+
+ def guard_clean
+ clean? && committed? || raise("There are files that need to be committed first.")
+ end
+
+ def clean?
+ sh_with_code("git diff --exit-code")[1] == 0
+ end
+
+ def committed?
+ sh_with_code("git diff-index --quiet --cached HEAD")[1] == 0
+ end
+
+ def tag_version
+ sh "git tag -m \"Version #{version}\" #{version_tag}"
+ Bundler.ui.confirm "Tagged #{version_tag}."
+ yield if block_given?
+ rescue RuntimeError
+ Bundler.ui.error "Untagging #{version_tag} due to error."
+ sh_with_code "git tag -d #{version_tag}"
+ raise
+ end
+
+ def version
+ gemspec.version
+ end
+
+ def version_tag
+ "v#{version}"
+ end
+
+ def name
+ gemspec.name
+ end
+
+ def sh(cmd, &block)
+ out, code = sh_with_code(cmd, &block)
+ unless code.zero?
+ raise(out.empty? ? "Running `#{cmd}` failed. Run this command directly for more detailed output." : out)
+ end
+ out
+ end
+
+ def sh_with_code(cmd, &block)
+ cmd += " 2>&1"
+ outbuf = String.new
+ Bundler.ui.debug(cmd)
+ SharedHelpers.chdir(base) do
+ outbuf = `#{cmd}`
+ status = $?.exitstatus
+ block.call(outbuf) if status.zero? && block
+ [outbuf, status]
+ end
+ end
+
+ def gem_key
+ Bundler.settings["gem.push_key"].to_s.downcase if Bundler.settings["gem.push_key"]
+ end
+
+ def gem_push?
+ !%w[n no nil false off 0].include?(ENV["gem_push"].to_s.downcase)
+ end
+ end
+end
diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb
new file mode 100644
index 0000000000..019ae10c66
--- /dev/null
+++ b/lib/bundler/gem_helpers.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+module Bundler
+ module GemHelpers
+ GENERIC_CACHE = {} # rubocop:disable MutableConstant
+ GENERICS = [
+ [Gem::Platform.new("java"), Gem::Platform.new("java")],
+ [Gem::Platform.new("mswin32"), Gem::Platform.new("mswin32")],
+ [Gem::Platform.new("mswin64"), Gem::Platform.new("mswin64")],
+ [Gem::Platform.new("universal-mingw32"), Gem::Platform.new("universal-mingw32")],
+ [Gem::Platform.new("x64-mingw32"), Gem::Platform.new("x64-mingw32")],
+ [Gem::Platform.new("x86_64-mingw32"), Gem::Platform.new("x64-mingw32")],
+ [Gem::Platform.new("mingw32"), Gem::Platform.new("x86-mingw32")]
+ ].freeze
+
+ def generic(p)
+ return p if p == Gem::Platform::RUBY
+
+ GENERIC_CACHE[p] ||= begin
+ _, found = GENERICS.find do |match, _generic|
+ p.os == match.os && (!match.cpu || p.cpu == match.cpu)
+ end
+ found || Gem::Platform::RUBY
+ end
+ end
+ module_function :generic
+
+ def generic_local_platform
+ generic(Bundler.local_platform)
+ end
+ module_function :generic_local_platform
+
+ def platform_specificity_match(spec_platform, user_platform)
+ spec_platform = Gem::Platform.new(spec_platform)
+ return PlatformMatch::EXACT_MATCH if spec_platform == user_platform
+ return PlatformMatch::WORST_MATCH if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
+
+ PlatformMatch.new(
+ PlatformMatch.os_match(spec_platform, user_platform),
+ PlatformMatch.cpu_match(spec_platform, user_platform),
+ PlatformMatch.platform_version_match(spec_platform, user_platform)
+ )
+ end
+ module_function :platform_specificity_match
+
+ def select_best_platform_match(specs, platform)
+ specs.select {|spec| spec.match_platform(platform) }.
+ min_by {|spec| platform_specificity_match(spec.platform, platform) }
+ end
+ module_function :select_best_platform_match
+
+ PlatformMatch = Struct.new(:os_match, :cpu_match, :platform_version_match)
+ class PlatformMatch
+ def <=>(other)
+ return nil unless other.is_a?(PlatformMatch)
+
+ m = os_match <=> other.os_match
+ return m unless m.zero?
+
+ m = cpu_match <=> other.cpu_match
+ return m unless m.zero?
+
+ m = platform_version_match <=> other.platform_version_match
+ m
+ end
+
+ EXACT_MATCH = new(-1, -1, -1).freeze
+ WORST_MATCH = new(1_000_000, 1_000_000, 1_000_000).freeze
+
+ def self.os_match(spec_platform, user_platform)
+ if spec_platform.os == user_platform.os
+ 0
+ else
+ 1
+ end
+ end
+
+ def self.cpu_match(spec_platform, user_platform)
+ if spec_platform.cpu == user_platform.cpu
+ 0
+ elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm")
+ 0
+ elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal"
+ 1
+ else
+ 2
+ end
+ end
+
+ def self.platform_version_match(spec_platform, user_platform)
+ if spec_platform.version == user_platform.version
+ 0
+ elsif spec_platform.version.nil?
+ 1
+ else
+ 2
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/gem_remote_fetcher.rb b/lib/bundler/gem_remote_fetcher.rb
new file mode 100644
index 0000000000..9577535d63
--- /dev/null
+++ b/lib/bundler/gem_remote_fetcher.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "rubygems/remote_fetcher"
+
+module Bundler
+ # Adds support for setting custom HTTP headers when fetching gems from the
+ # server.
+ #
+ # TODO: Get rid of this when and if gemstash only supports RubyGems versions
+ # that contain https://github.com/rubygems/rubygems/commit/3db265cc20b2f813.
+ class GemRemoteFetcher < Gem::RemoteFetcher
+ attr_accessor :headers
+
+ # Extracted from RubyGems 2.4.
+ def fetch_http(uri, last_modified = nil, head = false, depth = 0)
+ fetch_type = head ? Net::HTTP::Head : Net::HTTP::Get
+ # beginning of change
+ response = request uri, fetch_type, last_modified do |req|
+ headers.each {|k, v| req.add_field(k, v) } if headers
+ end
+ # end of change
+
+ case response
+ when Net::HTTPOK, Net::HTTPNotModified then
+ response.uri = uri if response.respond_to? :uri
+ head ? response : response.body
+ when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther,
+ Net::HTTPTemporaryRedirect then
+ raise FetchError.new("too many redirects", uri) if depth > 10
+
+ location = URI.parse response["Location"]
+
+ if https?(uri) && !https?(location)
+ raise FetchError.new("redirecting to non-https resource: #{location}", uri)
+ end
+
+ fetch_http(location, last_modified, head, depth + 1)
+ else
+ raise FetchError.new("bad response #{response.message} #{response.code}", uri)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/gem_tasks.rb b/lib/bundler/gem_tasks.rb
new file mode 100644
index 0000000000..f736517bd7
--- /dev/null
+++ b/lib/bundler/gem_tasks.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require "rake/clean"
+CLOBBER.include "pkg"
+
+require "bundler/gem_helper"
+Bundler::GemHelper.install_tasks
diff --git a/lib/bundler/gem_version_promoter.rb b/lib/bundler/gem_version_promoter.rb
new file mode 100644
index 0000000000..adb951a7a0
--- /dev/null
+++ b/lib/bundler/gem_version_promoter.rb
@@ -0,0 +1,190 @@
+# frozen_string_literal: true
+
+module Bundler
+ # This class contains all of the logic for determining the next version of a
+ # Gem to update to based on the requested level (patch, minor, major).
+ # Primarily designed to work with Resolver which will provide it the list of
+ # available dependency versions as found in its index, before returning it to
+ # to the resolution engine to select the best version.
+ class GemVersionPromoter
+ DEBUG = ENV["DEBUG_RESOLVER"]
+
+ attr_reader :level, :locked_specs, :unlock_gems
+
+ # By default, strict is false, meaning every available version of a gem
+ # is returned from sort_versions. The order gives preference to the
+ # requested level (:patch, :minor, :major) but in complicated requirement
+ # cases some gems will by necessity by promoted past the requested level,
+ # or even reverted to older versions.
+ #
+ # If strict is set to true, the results from sort_versions will be
+ # truncated, eliminating any version outside the current level scope.
+ # This can lead to unexpected outcomes or even VersionConflict exceptions
+ # that report a version of a gem not existing for versions that indeed do
+ # existing in the referenced source.
+ attr_accessor :strict
+
+ attr_accessor :prerelease_specified
+
+ # Given a list of locked_specs and a list of gems to unlock creates a
+ # GemVersionPromoter instance.
+ #
+ # @param locked_specs [SpecSet] All current locked specs. Unlike Definition
+ # where this list is empty if all gems are being updated, this should
+ # always be populated for all gems so this class can properly function.
+ # @param unlock_gems [String] List of gem names being unlocked. If empty,
+ # all gems will be considered unlocked.
+ # @return [GemVersionPromoter]
+ def initialize(locked_specs = SpecSet.new([]), unlock_gems = [])
+ @level = :major
+ @strict = false
+ @locked_specs = locked_specs
+ @unlock_gems = unlock_gems
+ @sort_versions = {}
+ @prerelease_specified = {}
+ end
+
+ # @param value [Symbol] One of three Symbols: :major, :minor or :patch.
+ def level=(value)
+ v = case value
+ when String, Symbol
+ value.to_sym
+ end
+
+ raise ArgumentError, "Unexpected level #{v}. Must be :major, :minor or :patch" unless [:major, :minor, :patch].include?(v)
+ @level = v
+ end
+
+ # Given a Dependency and an Array of SpecGroups of available versions for a
+ # gem, this method will return the Array of SpecGroups sorted (and possibly
+ # truncated if strict is true) in an order to give preference to the current
+ # level (:major, :minor or :patch) when resolution is deciding what versions
+ # best resolve all dependencies in the bundle.
+ # @param dep [Dependency] The Dependency of the gem.
+ # @param spec_groups [SpecGroup] An array of SpecGroups for the same gem
+ # named in the @dep param.
+ # @return [SpecGroup] A new instance of the SpecGroup Array sorted and
+ # possibly filtered.
+ def sort_versions(dep, spec_groups)
+ before_result = "before sort_versions: #{debug_format_result(dep, spec_groups).inspect}" if DEBUG
+
+ @sort_versions[dep] ||= begin
+ gem_name = dep.name
+
+ # An Array per version returned, different entries for different platforms.
+ # We only need the version here so it's ok to hard code this to the first instance.
+ locked_spec = locked_specs[gem_name].first
+
+ if strict
+ filter_dep_specs(spec_groups, locked_spec)
+ else
+ sort_dep_specs(spec_groups, locked_spec)
+ end.tap do |specs|
+ if DEBUG
+ STDERR.puts before_result
+ STDERR.puts " after sort_versions: #{debug_format_result(dep, specs).inspect}"
+ end
+ end
+ end
+ end
+
+ # @return [bool] Convenience method for testing value of level variable.
+ def major?
+ level == :major
+ end
+
+ # @return [bool] Convenience method for testing value of level variable.
+ def minor?
+ level == :minor
+ end
+
+ private
+
+ def filter_dep_specs(spec_groups, locked_spec)
+ res = spec_groups.select do |spec_group|
+ if locked_spec && !major?
+ gsv = spec_group.version
+ lsv = locked_spec.version
+
+ must_match = minor? ? [0] : [0, 1]
+
+ matches = must_match.map {|idx| gsv.segments[idx] == lsv.segments[idx] }
+ (matches.uniq == [true]) ? (gsv >= lsv) : false
+ else
+ true
+ end
+ end
+
+ sort_dep_specs(res, locked_spec)
+ end
+
+ def sort_dep_specs(spec_groups, locked_spec)
+ return spec_groups unless locked_spec
+ @gem_name = locked_spec.name
+ @locked_version = locked_spec.version
+
+ result = spec_groups.sort do |a, b|
+ @a_ver = a.version
+ @b_ver = b.version
+
+ unless @prerelease_specified[@gem_name]
+ a_pre = @a_ver.prerelease?
+ b_pre = @b_ver.prerelease?
+
+ next -1 if a_pre && !b_pre
+ next 1 if b_pre && !a_pre
+ end
+
+ if major?
+ @a_ver <=> @b_ver
+ elsif either_version_older_than_locked
+ @a_ver <=> @b_ver
+ elsif segments_do_not_match(:major)
+ @b_ver <=> @a_ver
+ elsif !minor? && segments_do_not_match(:minor)
+ @b_ver <=> @a_ver
+ else
+ @a_ver <=> @b_ver
+ end
+ end
+ post_sort(result)
+ end
+
+ def either_version_older_than_locked
+ @a_ver < @locked_version || @b_ver < @locked_version
+ end
+
+ def segments_do_not_match(level)
+ index = [:major, :minor].index(level)
+ @a_ver.segments[index] != @b_ver.segments[index]
+ end
+
+ def unlocking_gem?
+ unlock_gems.empty? || unlock_gems.include?(@gem_name)
+ end
+
+ # Specific version moves can't always reliably be done during sorting
+ # as not all elements are compared against each other.
+ def post_sort(result)
+ # default :major behavior in Bundler does not do this
+ return result if major?
+ if unlocking_gem?
+ result
+ else
+ move_version_to_end(result, @locked_version)
+ end
+ end
+
+ def move_version_to_end(result, version)
+ move, keep = result.partition {|s| s.version.to_s == version.to_s }
+ keep.concat(move)
+ end
+
+ def debug_format_result(dep, spec_groups)
+ a = [dep.to_s,
+ spec_groups.map {|sg| [sg.version, sg.dependencies_for_activated_platforms.map {|dp| [dp.name, dp.requirement.to_s] }] }]
+ last_map = a.last.map {|sg_data| [sg_data.first.version, sg_data.last.map {|aa| aa.join(" ") }] }
+ [a.first, last_map, level, strict ? :strict : :not_strict]
+ end
+ end
+end
diff --git a/lib/bundler/gemdeps.rb b/lib/bundler/gemdeps.rb
new file mode 100644
index 0000000000..cd4b25d0e6
--- /dev/null
+++ b/lib/bundler/gemdeps.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Gemdeps
+ def initialize(runtime)
+ @runtime = runtime
+ end
+
+ def requested_specs
+ @runtime.requested_specs
+ end
+
+ def specs
+ @runtime.specs
+ end
+
+ def dependencies
+ @runtime.dependencies
+ end
+
+ def current_dependencies
+ @runtime.current_dependencies
+ end
+
+ def requires
+ @runtime.requires
+ end
+ end
+end
diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb
new file mode 100644
index 0000000000..de6bba0214
--- /dev/null
+++ b/lib/bundler/graph.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+require "set"
+module Bundler
+ class Graph
+ GRAPH_NAME = :Gemfile
+
+ def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = [])
+ @env = env
+ @output_file = output_file
+ @show_version = show_version
+ @show_requirements = show_requirements
+ @output_format = output_format
+ @without_groups = without.map(&:to_sym)
+
+ @groups = []
+ @relations = Hash.new {|h, k| h[k] = Set.new }
+ @node_options = {}
+ @edge_options = {}
+
+ _populate_relations
+ end
+
+ attr_reader :groups, :relations, :node_options, :edge_options, :output_file, :output_format
+
+ def viz
+ GraphVizClient.new(self).run
+ end
+
+ private
+
+ def _populate_relations
+ parent_dependencies = _groups.values.to_set.flatten
+ loop do
+ break if parent_dependencies.empty?
+
+ tmp = Set.new
+ parent_dependencies.each do |dependency|
+ child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set
+ @relations[dependency.name] += child_dependencies.map(&:name).to_set
+ tmp += child_dependencies
+
+ @node_options[dependency.name] = _make_label(dependency, :node)
+ child_dependencies.each do |c_dependency|
+ @edge_options["#{dependency.name}_#{c_dependency.name}"] = _make_label(c_dependency, :edge)
+ end
+ end
+ parent_dependencies = tmp
+ end
+ end
+
+ def _groups
+ relations = Hash.new {|h, k| h[k] = Set.new }
+ @env.current_dependencies.each do |dependency|
+ dependency.groups.each do |group|
+ next if @without_groups.include?(group)
+
+ relations[group.to_s].add(dependency)
+ @relations[group.to_s].add(dependency.name)
+
+ @node_options[group.to_s] ||= _make_label(group, :node)
+ @edge_options["#{group}_#{dependency.name}"] = _make_label(dependency, :edge)
+ end
+ end
+ @groups = relations.keys
+ relations
+ end
+
+ def _make_label(symbol_or_string_or_dependency, element_type)
+ case element_type.to_sym
+ when :node
+ if symbol_or_string_or_dependency.is_a?(Gem::Dependency)
+ label = symbol_or_string_or_dependency.name.dup
+ label << "\n#{spec_for_dependency(symbol_or_string_or_dependency).version}" if @show_version
+ else
+ label = symbol_or_string_or_dependency.to_s
+ end
+ when :edge
+ label = nil
+ if symbol_or_string_or_dependency.respond_to?(:requirements_list) && @show_requirements
+ tmp = symbol_or_string_or_dependency.requirements_list.join(", ")
+ label = tmp if tmp != ">= 0"
+ end
+ else
+ raise ArgumentError, "2nd argument is invalid"
+ end
+ label.nil? ? {} : { :label => label }
+ end
+
+ def spec_for_dependency(dependency)
+ @env.requested_specs.find {|s| s.name == dependency.name }
+ end
+
+ class GraphVizClient
+ def initialize(graph_instance)
+ @graph_name = graph_instance.class::GRAPH_NAME
+ @groups = graph_instance.groups
+ @relations = graph_instance.relations
+ @node_options = graph_instance.node_options
+ @edge_options = graph_instance.edge_options
+ @output_file = graph_instance.output_file
+ @output_format = graph_instance.output_format
+ end
+
+ def g
+ @g ||= ::GraphViz.digraph(@graph_name, :concentrate => true, :normalize => true, :nodesep => 0.55) do |g|
+ g.edge[:weight] = 2
+ g.edge[:fontname] = g.node[:fontname] = "Arial, Helvetica, SansSerif"
+ g.edge[:fontsize] = 12
+ end
+ end
+
+ def run
+ @groups.each do |group|
+ g.add_nodes(
+ group, {
+ :style => "filled",
+ :fillcolor => "#B9B9D5",
+ :shape => "box3d",
+ :fontsize => 16
+ }.merge(@node_options[group])
+ )
+ end
+
+ @relations.each do |parent, children|
+ children.each do |child|
+ if @groups.include?(parent)
+ g.add_nodes(child, { :style => "filled", :fillcolor => "#B9B9D5" }.merge(@node_options[child]))
+ g.add_edges(parent, child, { :constraint => false }.merge(@edge_options["#{parent}_#{child}"]))
+ else
+ g.add_nodes(child, @node_options[child])
+ g.add_edges(parent, child, @edge_options["#{parent}_#{child}"])
+ end
+ end
+ end
+
+ if @output_format.to_s == "debug"
+ $stdout.puts g.output :none => String
+ Bundler.ui.info "debugging bundle viz..."
+ else
+ begin
+ g.output @output_format.to_sym => "#{@output_file}.#{@output_format}"
+ Bundler.ui.info "#{@output_file}.#{@output_format}"
+ rescue ArgumentError => e
+ $stderr.puts "Unsupported output format. See Ruby-Graphviz/lib/graphviz/constants.rb"
+ raise e
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb
new file mode 100644
index 0000000000..9166a92738
--- /dev/null
+++ b/lib/bundler/index.rb
@@ -0,0 +1,213 @@
+# frozen_string_literal: true
+
+require "set"
+
+module Bundler
+ class Index
+ include Enumerable
+
+ def self.build
+ i = new
+ yield i
+ i
+ end
+
+ attr_reader :specs, :all_specs, :sources
+ protected :specs, :all_specs
+
+ RUBY = "ruby".freeze
+ NULL = "\0".freeze
+
+ def initialize
+ @sources = []
+ @cache = {}
+ @specs = Hash.new {|h, k| h[k] = {} }
+ @all_specs = Hash.new {|h, k| h[k] = EMPTY_SEARCH }
+ end
+
+ def initialize_copy(o)
+ @sources = o.sources.dup
+ @cache = {}
+ @specs = Hash.new {|h, k| h[k] = {} }
+ @all_specs = Hash.new {|h, k| h[k] = EMPTY_SEARCH }
+
+ o.specs.each do |name, hash|
+ @specs[name] = hash.dup
+ end
+ o.all_specs.each do |name, array|
+ @all_specs[name] = array.dup
+ end
+ end
+
+ def inspect
+ "#<#{self.class}:0x#{object_id} sources=#{sources.map(&:inspect)} specs.size=#{specs.size}>"
+ end
+
+ def empty?
+ each { return false }
+ true
+ end
+
+ def search_all(name)
+ all_matches = local_search(name) + @all_specs[name]
+ @sources.each do |source|
+ all_matches.concat(source.search_all(name))
+ end
+ all_matches
+ end
+
+ # Search this index's specs, and any source indexes that this index knows
+ # about, returning all of the results.
+ def search(query, base = nil)
+ sort_specs(unsorted_search(query, base))
+ end
+
+ def unsorted_search(query, base)
+ results = local_search(query, base)
+
+ seen = results.map(&:full_name).to_set unless @sources.empty?
+
+ @sources.each do |source|
+ source.unsorted_search(query, base).each do |spec|
+ results << spec if seen.add?(spec.full_name)
+ end
+ end
+
+ results
+ end
+ protected :unsorted_search
+
+ def self.sort_specs(specs)
+ specs.sort_by do |s|
+ platform_string = s.platform.to_s
+ [s.version, platform_string == RUBY ? NULL : platform_string]
+ end
+ end
+
+ def sort_specs(specs)
+ self.class.sort_specs(specs)
+ end
+
+ def local_search(query, base = nil)
+ case query
+ when Gem::Specification, RemoteSpecification, LazySpecification, EndpointSpecification then search_by_spec(query)
+ when String then specs_by_name(query)
+ when Gem::Dependency then search_by_dependency(query, base)
+ when DepProxy then search_by_dependency(query.dep, base)
+ else
+ raise "You can't search for a #{query.inspect}."
+ end
+ end
+
+ alias_method :[], :search
+
+ def <<(spec)
+ @specs[spec.name][spec.full_name] = spec
+ spec
+ end
+
+ def each(&blk)
+ return enum_for(:each) unless blk
+ specs.values.each do |spec_sets|
+ spec_sets.values.each(&blk)
+ end
+ sources.each {|s| s.each(&blk) }
+ self
+ end
+
+ def spec_names
+ names = specs.keys + sources.map(&:spec_names)
+ names.uniq!
+ names
+ end
+
+ # returns a list of the dependencies
+ def unmet_dependency_names
+ dependency_names.select do |name|
+ name != "bundler" && search(name).empty?
+ end
+ end
+
+ def dependency_names
+ names = []
+ each do |spec|
+ spec.dependencies.each do |dep|
+ next if dep.type == :development
+ names << dep.name
+ end
+ end
+ names.uniq
+ end
+
+ def use(other, override_dupes = false)
+ return unless other
+ other.each do |s|
+ if (dupes = search_by_spec(s)) && !dupes.empty?
+ # safe to << since it's a new array when it has contents
+ @all_specs[s.name] = dupes << s
+ next unless override_dupes
+ end
+ self << s
+ end
+ self
+ end
+
+ def size
+ @sources.inject(@specs.size) do |size, source|
+ size += source.size
+ end
+ end
+
+ # Whether all the specs in self are in other
+ # TODO: rename to #include?
+ def ==(other)
+ all? do |spec|
+ other_spec = other[spec].first
+ other_spec && dependencies_eql?(spec, other_spec) && spec.source == other_spec.source
+ end
+ end
+
+ def dependencies_eql?(spec, other_spec)
+ deps = spec.dependencies.select {|d| d.type != :development }
+ other_deps = other_spec.dependencies.select {|d| d.type != :development }
+ Set.new(deps) == Set.new(other_deps)
+ end
+
+ def add_source(index)
+ raise ArgumentError, "Source must be an index, not #{index.class}" unless index.is_a?(Index)
+ @sources << index
+ @sources.uniq! # need to use uniq! here instead of checking for the item before adding
+ end
+
+ private
+
+ def specs_by_name(name)
+ @specs[name].values
+ end
+
+ def search_by_dependency(dependency, base = nil)
+ @cache[base || false] ||= {}
+ @cache[base || false][dependency] ||= begin
+ specs = specs_by_name(dependency.name)
+ specs += base if base
+ found = specs.select do |spec|
+ next true if spec.source.is_a?(Source::Gemspec)
+ if base # allow all platforms when searching from a lockfile
+ dependency.matches_spec?(spec)
+ else
+ dependency.matches_spec?(spec) && Gem::Platform.match(spec.platform)
+ end
+ end
+
+ found
+ end
+ end
+
+ EMPTY_SEARCH = [].freeze
+
+ def search_by_spec(spec)
+ spec = @specs[spec.name][spec.full_name]
+ spec ? [spec] : EMPTY_SEARCH
+ end
+ end
+end
diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb
new file mode 100644
index 0000000000..1bb29f0b36
--- /dev/null
+++ b/lib/bundler/injector.rb
@@ -0,0 +1,253 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Injector
+ INJECTED_GEMS = "injected gems".freeze
+
+ def self.inject(new_deps, options = {})
+ injector = new(new_deps, options)
+ injector.inject(Bundler.default_gemfile, Bundler.default_lockfile)
+ end
+
+ def self.remove(gems, options = {})
+ injector = new(gems, options)
+ injector.remove(Bundler.default_gemfile, Bundler.default_lockfile)
+ end
+
+ def initialize(deps, options = {})
+ @deps = deps
+ @options = options
+ end
+
+ # @param [Pathname] gemfile_path The Gemfile in which to inject the new dependency.
+ # @param [Pathname] lockfile_path The lockfile in which to inject the new dependency.
+ # @return [Array]
+ def inject(gemfile_path, lockfile_path)
+ if Bundler.frozen_bundle?
+ # ensure the lock and Gemfile are synced
+ Bundler.definition.ensure_equivalent_gemfile_and_lockfile(true)
+ end
+
+ # temporarily unfreeze
+ Bundler.settings.temporary(:deployment => false, :frozen => false) do
+ # evaluate the Gemfile we have now
+ builder = Dsl.new
+ builder.eval_gemfile(gemfile_path)
+
+ # don't inject any gems that are already in the Gemfile
+ @deps -= builder.dependencies
+
+ # add new deps to the end of the in-memory Gemfile
+ # Set conservative versioning to false because
+ # we want to let the resolver resolve the version first
+ builder.eval_gemfile(INJECTED_GEMS, build_gem_lines(false)) if @deps.any?
+
+ # resolve to see if the new deps broke anything
+ @definition = builder.to_definition(lockfile_path, {})
+ @definition.resolve_remotely!
+
+ # since nothing broke, we can add those gems to the gemfile
+ append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @deps.any?
+
+ # since we resolved successfully, write out the lockfile
+ @definition.lock(Bundler.default_lockfile)
+
+ # invalidate the cached Bundler.definition
+ Bundler.reset_paths!
+
+ # return an array of the deps that we added
+ @deps
+ end
+ end
+
+ # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies.
+ # @param [Pathname] lockfile_path The lockfile from which to remove dependencies.
+ # @return [Array]
+ def remove(gemfile_path, lockfile_path)
+ # remove gems from each gemfiles we have
+ Bundler.definition.gemfiles.each do |path|
+ deps = remove_deps(path)
+
+ show_warning("No gems were removed from the gemfile.") if deps.empty?
+
+ deps.each {|dep| Bundler.ui.confirm "#{SharedHelpers.pretty_dependency(dep, false)} was removed." }
+ end
+ end
+
+ private
+
+ def conservative_version(spec)
+ version = spec.version
+ return ">= 0" if version.nil?
+ segments = version.segments
+ seg_end_index = version >= Gem::Version.new("1.0") ? 1 : 2
+
+ prerelease_suffix = version.to_s.gsub(version.release.to_s, "") if version.prerelease?
+ "#{version_prefix}#{segments[0..seg_end_index].join(".")}#{prerelease_suffix}"
+ end
+
+ def version_prefix
+ if @options[:strict]
+ "= "
+ elsif @options[:optimistic]
+ ">= "
+ else
+ "~> "
+ end
+ end
+
+ def build_gem_lines(conservative_versioning)
+ @deps.map do |d|
+ name = d.name.dump
+
+ requirement = if conservative_versioning
+ ", \"#{conservative_version(@definition.specs[d.name][0])}\""
+ else
+ ", #{d.requirement.as_list.map(&:dump).join(", ")}"
+ end
+
+ if d.groups != Array(:default)
+ group = d.groups.size == 1 ? ", :group => #{d.groups.first.inspect}" : ", :groups => #{d.groups.inspect}"
+ end
+
+ source = ", :source => \"#{d.source}\"" unless d.source.nil?
+
+ %(gem #{name}#{requirement}#{group}#{source})
+ end.join("\n")
+ end
+
+ def append_to(gemfile_path, new_gem_lines)
+ gemfile_path.open("a") do |f|
+ f.puts
+ f.puts new_gem_lines
+ end
+ end
+
+ # evalutes a gemfile to remove the specified gem
+ # from it.
+ def remove_deps(gemfile_path)
+ initial_gemfile = IO.readlines(gemfile_path)
+
+ Bundler.ui.info "Removing gems from #{gemfile_path}"
+
+ # evaluate the Gemfile we have
+ builder = Dsl.new
+ builder.eval_gemfile(gemfile_path)
+
+ removed_deps = remove_gems_from_dependencies(builder, @deps, gemfile_path)
+
+ # abort the opertion if no gems were removed
+ # no need to operate on gemfile furthur
+ return [] if removed_deps.empty?
+
+ cleaned_gemfile = remove_gems_from_gemfile(@deps, gemfile_path)
+
+ SharedHelpers.write_to_gemfile(gemfile_path, cleaned_gemfile)
+
+ # check for errors
+ # including extra gems being removed
+ # or some gems not being removed
+ # and return the actual removed deps
+ cross_check_for_errors(gemfile_path, builder.dependencies, removed_deps, initial_gemfile)
+ end
+
+ # @param [Dsl] builder Dsl object of current Gemfile.
+ # @param [Array] gems Array of names of gems to be removed.
+ # @param [Pathname] path of the Gemfile
+ # @return [Array] removed_deps Array of removed dependencies.
+ def remove_gems_from_dependencies(builder, gems, gemfile_path)
+ removed_deps = []
+
+ gems.each do |gem_name|
+ deleted_dep = builder.dependencies.find {|d| d.name == gem_name }
+
+ if deleted_dep.nil?
+ raise GemfileError, "`#{gem_name}` is not specified in #{gemfile_path} so it could not be removed."
+ end
+
+ builder.dependencies.delete(deleted_dep)
+
+ removed_deps << deleted_dep
+ end
+
+ removed_deps
+ end
+
+ # @param [Array] gems Array of names of gems to be removed.
+ # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies.
+ def remove_gems_from_gemfile(gems, gemfile_path)
+ patterns = /gem\s+(['"])#{Regexp.union(gems)}\1|gem\s*\((['"])#{Regexp.union(gems)}\2\)/
+
+ # remove lines which match the regex
+ new_gemfile = IO.readlines(gemfile_path).reject {|line| line.match(patterns) }
+
+ # remove lone \n and append them with other strings
+ new_gemfile.each_with_index do |_line, index|
+ if new_gemfile[index + 1] == "\n"
+ new_gemfile[index] += new_gemfile[index + 1]
+ new_gemfile.delete_at(index + 1)
+ end
+ end
+
+ %w[group source env install_if].each {|block| remove_nested_blocks(new_gemfile, block) }
+
+ new_gemfile.join.chomp
+ end
+
+ # @param [Array] gemfile Array of gemfile contents.
+ # @param [String] block_name Name of block name to look for.
+ def remove_nested_blocks(gemfile, block_name)
+ nested_blocks = 0
+
+ # count number of nested blocks
+ gemfile.each_with_index {|line, index| nested_blocks += 1 if !gemfile[index + 1].nil? && gemfile[index + 1].include?(block_name) && line.include?(block_name) }
+
+ while nested_blocks >= 0
+ nested_blocks -= 1
+
+ gemfile.each_with_index do |line, index|
+ next unless !line.nil? && line.include?(block_name)
+ if gemfile[index + 1] =~ /^\s*end\s*$/
+ gemfile[index] = nil
+ gemfile[index + 1] = nil
+ end
+ end
+
+ gemfile.compact!
+ end
+ end
+
+ # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies.
+ # @param [Array] original_deps Array of original dependencies.
+ # @param [Array] removed_deps Array of removed dependencies.
+ # @param [Array] initial_gemfile Contents of original Gemfile before any operation.
+ def cross_check_for_errors(gemfile_path, original_deps, removed_deps, initial_gemfile)
+ # evalute the new gemfile to look for any failure cases
+ builder = Dsl.new
+ builder.eval_gemfile(gemfile_path)
+
+ # record gems which were removed but not requested
+ extra_removed_gems = original_deps - builder.dependencies
+
+ # if some extra gems were removed then raise error
+ # and revert Gemfile to original
+ unless extra_removed_gems.empty?
+ SharedHelpers.write_to_gemfile(gemfile_path, initial_gemfile.join)
+
+ raise InvalidOption, "Gems could not be removed. #{extra_removed_gems.join(", ")} would also have been removed. Bundler cannot continue."
+ end
+
+ # record gems which could not be removed due to some reasons
+ errored_deps = builder.dependencies.select {|d| d.gemfile == gemfile_path } & removed_deps.select {|d| d.gemfile == gemfile_path }
+
+ show_warning "#{errored_deps.map(&:name).join(", ")} could not be removed." unless errored_deps.empty?
+
+ # return actual removed dependencies
+ removed_deps - errored_deps
+ end
+
+ def show_warning(message)
+ Bundler.ui.info Bundler.ui.add_color(message, :yellow)
+ end
+ end
+end
diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb
new file mode 100644
index 0000000000..9d25f3261a
--- /dev/null
+++ b/lib/bundler/inline.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require "bundler/compatibility_guard"
+
+# Allows for declaring a Gemfile inline in a ruby script, optionally installing
+# any gems that aren't already installed on the user's system.
+#
+# @note Every gem that is specified in this 'Gemfile' will be `require`d, as if
+# the user had manually called `Bundler.require`. To avoid a requested gem
+# being automatically required, add the `:require => false` option to the
+# `gem` dependency declaration.
+#
+# @param install [Boolean] whether gems that aren't already installed on the
+# user's system should be installed.
+# Defaults to `false`.
+#
+# @param gemfile [Proc] a block that is evaluated as a `Gemfile`.
+#
+# @example Using an inline Gemfile
+#
+# #!/usr/bin/env ruby
+#
+# require 'bundler/inline'
+#
+# gemfile do
+# source 'https://rubygems.org'
+# gem 'json', require: false
+# gem 'nap', require: 'rest'
+# gem 'cocoapods', '~> 0.34.1'
+# end
+#
+# puts Pod::VERSION # => "0.34.4"
+#
+def gemfile(install = false, options = {}, &gemfile)
+ require "bundler"
+
+ opts = options.dup
+ ui = opts.delete(:ui) { Bundler::UI::Shell.new }
+ raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty?
+
+ old_root = Bundler.method(:root)
+ def Bundler.root
+ Bundler::SharedHelpers.pwd.expand_path
+ end
+ Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile"
+
+ Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins?
+ builder = Bundler::Dsl.new
+ builder.instance_eval(&gemfile)
+
+ definition = builder.to_definition(nil, true)
+ def definition.lock(*); end
+ definition.validate_runtime!
+
+ missing_specs = proc do
+ definition.missing_specs?
+ end
+
+ Bundler.ui = ui if install
+ if install || missing_specs.call
+ Bundler.settings.temporary(:inline => true) do
+ installer = Bundler::Installer.install(Bundler.root, definition, :system => true)
+ installer.post_install_messages.each do |name, message|
+ Bundler.ui.info "Post-install message from #{name}:\n#{message}"
+ end
+ end
+ end
+
+ runtime = Bundler::Runtime.new(nil, definition)
+ runtime.setup.require
+ensure
+ bundler_module = class << Bundler; self; end
+ bundler_module.send(:define_method, :root, old_root) if old_root
+end
diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb
new file mode 100644
index 0000000000..b49cfb6703
--- /dev/null
+++ b/lib/bundler/installer.rb
@@ -0,0 +1,318 @@
+# frozen_string_literal: true
+
+require "erb"
+require "rubygems/dependency_installer"
+require "bundler/worker"
+require "bundler/installer/parallel_installer"
+require "bundler/installer/standalone"
+require "bundler/installer/gem_installer"
+
+module Bundler
+ class Installer
+ class << self
+ attr_accessor :ambiguous_gems
+
+ Installer.ambiguous_gems = []
+ end
+
+ attr_reader :post_install_messages
+
+ # Begins the installation process for Bundler.
+ # For more information see the #run method on this class.
+ def self.install(root, definition, options = {})
+ installer = new(root, definition)
+ Plugin.hook(Plugin::Events::GEM_BEFORE_INSTALL_ALL, definition.dependencies)
+ installer.run(options)
+ Plugin.hook(Plugin::Events::GEM_AFTER_INSTALL_ALL, definition.dependencies)
+ installer
+ end
+
+ def initialize(root, definition)
+ @root = root
+ @definition = definition
+ @post_install_messages = {}
+ end
+
+ # Runs the install procedures for a specific Gemfile.
+ #
+ # Firstly, this method will check to see if `Bundler.bundle_path` exists
+ # and if not then Bundler will create the directory. This is usually the same
+ # location as RubyGems which typically is the `~/.gem` directory
+ # unless other specified.
+ #
+ # Secondly, it checks if Bundler has been configured to be "frozen".
+ # Frozen ensures that the Gemfile and the Gemfile.lock file are matching.
+ # This stops a situation where a developer may update the Gemfile but may not run
+ # `bundle install`, which leads to the Gemfile.lock file not being correctly updated.
+ # If this file is not correctly updated then any other developer running
+ # `bundle install` will potentially not install the correct gems.
+ #
+ # Thirdly, Bundler checks if there are any dependencies specified in the Gemfile.
+ # If there are no dependencies specified then Bundler returns a warning message stating
+ # so and this method returns.
+ #
+ # Fourthly, Bundler checks if the Gemfile.lock exists, and if so
+ # then proceeds to set up a definition based on the Gemfile and the Gemfile.lock.
+ # During this step Bundler will also download information about any new gems
+ # that are not in the Gemfile.lock and resolve any dependencies if needed.
+ #
+ # Fifthly, Bundler resolves the dependencies either through a cache of gems or by remote.
+ # This then leads into the gems being installed, along with stubs for their executables,
+ # but only if the --binstubs option has been passed or Bundler.options[:bin] has been set
+ # earlier.
+ #
+ # Sixthly, a new Gemfile.lock is created from the installed gems to ensure that the next time
+ # that a user runs `bundle install` they will receive any updates from this process.
+ #
+ # Finally, if the user has specified the standalone flag, Bundler will generate the needed
+ # require paths and save them in a `setup.rb` file. See `bundle standalone --help` for more
+ # information.
+ def run(options)
+ create_bundle_path
+
+ ProcessLock.lock do
+ if Bundler.frozen_bundle?
+ @definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment])
+ end
+
+ if @definition.dependencies.empty?
+ Bundler.ui.warn "The Gemfile specifies no dependencies"
+ lock
+ return
+ end
+
+ if resolve_if_needed(options)
+ ensure_specs_are_compatible!
+ warn_on_incompatible_bundler_deps
+ load_plugins
+ options.delete(:jobs)
+ else
+ options[:jobs] = 1 # to avoid the overhead of Bundler::Worker
+ end
+ install(options)
+
+ lock unless Bundler.frozen_bundle?
+ Standalone.new(options[:standalone], @definition).generate if options[:standalone]
+ end
+ end
+
+ def generate_bundler_executable_stubs(spec, options = {})
+ if options[:binstubs_cmd] && spec.executables.empty?
+ options = {}
+ spec.runtime_dependencies.each do |dep|
+ bins = @definition.specs[dep].first.executables
+ options[dep.name] = bins unless bins.empty?
+ end
+ if options.any?
+ Bundler.ui.warn "#{spec.name} has no executables, but you may want " \
+ "one from a gem it depends on."
+ options.each {|name, bins| Bundler.ui.warn " #{name} has: #{bins.join(", ")}" }
+ else
+ Bundler.ui.warn "There are no executables for the gem #{spec.name}."
+ end
+ return
+ end
+
+ # double-assignment to avoid warnings about variables that will be used by ERB
+ bin_path = Bundler.bin_path
+ bin_path = bin_path
+ relative_gemfile_path = Bundler.default_gemfile.relative_path_from(bin_path)
+ relative_gemfile_path = relative_gemfile_path
+ ruby_command = Thor::Util.ruby_command
+ ruby_command = ruby_command
+ template_path = File.expand_path("../templates/Executable", __FILE__)
+ if spec.name == "bundler"
+ template_path += ".bundler"
+ spec.executables = %(bundle)
+ end
+ template = File.read(template_path)
+
+ exists = []
+ spec.executables.each do |executable|
+ binstub_path = "#{bin_path}/#{executable}"
+ if File.exist?(binstub_path) && !options[:force]
+ exists << executable
+ next
+ end
+
+ File.open(binstub_path, "w", 0o777 & ~File.umask) do |f|
+ if RUBY_VERSION >= "2.6"
+ f.puts ERB.new(template, :trim_mode => "-").result(binding)
+ else
+ f.puts ERB.new(template, nil, "-").result(binding)
+ end
+ end
+ end
+
+ if options[:binstubs_cmd] && exists.any?
+ case exists.size
+ when 1
+ Bundler.ui.warn "Skipped #{exists[0]} since it already exists."
+ when 2
+ Bundler.ui.warn "Skipped #{exists.join(" and ")} since they already exist."
+ else
+ items = exists[0...-1].empty? ? nil : exists[0...-1].join(", ")
+ skipped = [items, exists[-1]].compact.join(" and ")
+ Bundler.ui.warn "Skipped #{skipped} since they already exist."
+ end
+ Bundler.ui.warn "If you want to overwrite skipped stubs, use --force."
+ end
+ end
+
+ def generate_standalone_bundler_executable_stubs(spec)
+ # double-assignment to avoid warnings about variables that will be used by ERB
+ bin_path = Bundler.bin_path
+ unless path = Bundler.settings[:path]
+ raise "Can't standalone without an explicit path set"
+ end
+ standalone_path = Bundler.root.join(path).relative_path_from(bin_path)
+ standalone_path = standalone_path
+ template = File.read(File.expand_path("../templates/Executable.standalone", __FILE__))
+ ruby_command = Thor::Util.ruby_command
+ ruby_command = ruby_command
+
+ spec.executables.each do |executable|
+ next if executable == "bundle"
+ executable_path = Pathname(spec.full_gem_path).join(spec.bindir, executable).relative_path_from(bin_path)
+ executable_path = executable_path
+ File.open "#{bin_path}/#{executable}", "w", 0o755 do |f|
+ if RUBY_VERSION >= "2.6"
+ f.puts ERB.new(template, :trim_mode => "-").result(binding)
+ else
+ f.puts ERB.new(template, nil, "-").result(binding)
+ end
+ end
+ end
+ end
+
+ private
+
+ # the order that the resolver provides is significant, since
+ # dependencies might affect the installation of a gem.
+ # that said, it's a rare situation (other than rake), and parallel
+ # installation is SO MUCH FASTER. so we let people opt in.
+ def install(options)
+ force = options["force"]
+ jobs = installation_parallelization(options)
+ install_in_parallel jobs, options[:standalone], force
+ end
+
+ def installation_parallelization(options)
+ if jobs = options.delete(:jobs)
+ return jobs
+ end
+
+ return 1 unless can_install_in_parallel?
+
+ auto_config_jobs = Bundler.feature_flag.auto_config_jobs?
+ if jobs = Bundler.settings[:jobs]
+ if auto_config_jobs
+ jobs
+ else
+ [jobs.pred, 1].max
+ end
+ elsif auto_config_jobs
+ processor_count
+ else
+ 1
+ end
+ end
+
+ def processor_count
+ require "etc"
+ Etc.nprocessors
+ rescue
+ 1
+ end
+
+ def load_plugins
+ Bundler.rubygems.load_plugins
+
+ requested_path_gems = @definition.requested_specs.select {|s| s.source.is_a?(Source::Path) }
+ path_plugin_files = requested_path_gems.map do |spec|
+ begin
+ Bundler.rubygems.spec_matches_for_glob(spec, "rubygems_plugin#{Bundler.rubygems.suffix_pattern}")
+ rescue TypeError
+ error_message = "#{spec.name} #{spec.version} has an invalid gemspec"
+ raise Gem::InvalidSpecificationException, error_message
+ end
+ end.flatten
+ Bundler.rubygems.load_plugin_files(path_plugin_files)
+ end
+
+ def ensure_specs_are_compatible!
+ system_ruby = Bundler::RubyVersion.system
+ rubygems_version = Gem::Version.create(Gem::VERSION)
+ @definition.specs.each do |spec|
+ if required_ruby_version = spec.required_ruby_version
+ unless required_ruby_version.satisfied_by?(system_ruby.gem_version)
+ raise InstallError, "#{spec.full_name} requires ruby version #{required_ruby_version}, " \
+ "which is incompatible with the current version, #{system_ruby}"
+ end
+ end
+ next unless required_rubygems_version = spec.required_rubygems_version
+ unless required_rubygems_version.satisfied_by?(rubygems_version)
+ raise InstallError, "#{spec.full_name} requires rubygems version #{required_rubygems_version}, " \
+ "which is incompatible with the current version, #{rubygems_version}"
+ end
+ end
+ end
+
+ def warn_on_incompatible_bundler_deps
+ bundler_version = Gem::Version.create(Bundler::VERSION)
+ @definition.specs.each do |spec|
+ spec.dependencies.each do |dep|
+ next if dep.type == :development
+ next unless dep.name == "bundler".freeze
+ next if dep.requirement.satisfied_by?(bundler_version)
+
+ Bundler.ui.warn "#{spec.name} (#{spec.version}) has dependency" \
+ " #{SharedHelpers.pretty_dependency(dep)}" \
+ ", which is unsatisfied by the current bundler version #{VERSION}" \
+ ", so the dependency is being ignored"
+ end
+ end
+ end
+
+ def can_install_in_parallel?
+ if Bundler.rubygems.provides?(">= 2.1.0")
+ true
+ else
+ Bundler.ui.warn "RubyGems #{Gem::VERSION} is not threadsafe, so your "\
+ "gems will be installed one at a time. Upgrade to RubyGems 2.1.0 " \
+ "or higher to enable parallel gem installation."
+ false
+ end
+ end
+
+ def install_in_parallel(size, standalone, force = false)
+ spec_installations = ParallelInstaller.call(self, @definition.specs, size, standalone, force)
+ spec_installations.each do |installation|
+ post_install_messages[installation.name] = installation.post_install_message if installation.has_post_install_message?
+ end
+ end
+
+ def create_bundle_path
+ SharedHelpers.filesystem_access(Bundler.bundle_path.to_s) do |p|
+ Bundler.mkdir_p(p)
+ end unless Bundler.bundle_path.exist?
+ rescue Errno::EEXIST
+ raise PathError, "Could not install to path `#{Bundler.bundle_path}` " \
+ "because a file already exists at that path. Either remove or rename the file so the directory can be created."
+ end
+
+ # returns whether or not a re-resolve was needed
+ def resolve_if_needed(options)
+ if !@definition.unlocking? && !options["force"] && !Bundler.settings[:inline] && Bundler.default_lockfile.file?
+ return false if @definition.nothing_changed? && !@definition.missing_specs?
+ end
+
+ options["local"] ? @definition.resolve_with_cache! : @definition.resolve_remotely!
+ true
+ end
+
+ def lock(opts = {})
+ @definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections])
+ end
+ end
+end
diff --git a/lib/bundler/installer/gem_installer.rb b/lib/bundler/installer/gem_installer.rb
new file mode 100644
index 0000000000..e5e245f970
--- /dev/null
+++ b/lib/bundler/installer/gem_installer.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Bundler
+ class GemInstaller
+ attr_reader :spec, :standalone, :worker, :force, :installer
+
+ def initialize(spec, installer, standalone = false, worker = 0, force = false)
+ @spec = spec
+ @installer = installer
+ @standalone = standalone
+ @worker = worker
+ @force = force
+ end
+
+ def install_from_spec
+ post_install_message = spec_settings ? install_with_settings : install
+ Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}"
+ generate_executable_stubs
+ return true, post_install_message
+ rescue Bundler::InstallHookError, Bundler::SecurityError, APIResponseMismatchError
+ raise
+ rescue Errno::ENOSPC
+ return false, out_of_space_message
+ rescue StandardError => e
+ return false, specific_failure_message(e)
+ end
+
+ private
+
+ def specific_failure_message(e)
+ message = "#{e.class}: #{e.message}\n"
+ message += " " + e.backtrace.join("\n ") + "\n\n" if Bundler.ui.debug?
+ message = message.lines.first + Bundler.ui.add_color(message.lines.drop(1).join, :clear)
+ message + Bundler.ui.add_color(failure_message, :red)
+ end
+
+ def failure_message
+ return install_error_message if spec.source.options["git"]
+ "#{install_error_message}\n#{gem_install_message}"
+ end
+
+ def install_error_message
+ "An error occurred while installing #{spec.name} (#{spec.version}), and Bundler cannot continue."
+ end
+
+ def gem_install_message
+ source = spec.source
+ return unless source.respond_to?(:remotes)
+
+ if source.remotes.size == 1
+ "Make sure that `gem install #{spec.name} -v '#{spec.version}' --source '#{source.remotes.first}'` succeeds before bundling."
+ else
+ "Make sure that `gem install #{spec.name} -v '#{spec.version}'` succeeds before bundling."
+ end
+ end
+
+ def spec_settings
+ # Fetch the build settings, if there are any
+ Bundler.settings["build.#{spec.name}"]
+ end
+
+ def install
+ spec.source.install(spec, :force => force, :ensure_builtin_gems_cached => standalone, :build_args => Array(spec_settings))
+ end
+
+ def install_with_settings
+ # Build arguments are global, so this is mutexed
+ Bundler.rubygems.install_with_build_args([spec_settings]) { install }
+ end
+
+ def out_of_space_message
+ "#{install_error_message}\nYour disk is out of space. Free some space to be able to install your bundle."
+ end
+
+ def generate_executable_stubs
+ return if Bundler.feature_flag.forget_cli_options?
+ return if Bundler.settings[:inline]
+ if Bundler.settings[:bin] && standalone
+ installer.generate_standalone_bundler_executable_stubs(spec)
+ elsif Bundler.settings[:bin]
+ installer.generate_bundler_executable_stubs(spec, :force => true)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/installer/parallel_installer.rb b/lib/bundler/installer/parallel_installer.rb
new file mode 100644
index 0000000000..f8a849ccfc
--- /dev/null
+++ b/lib/bundler/installer/parallel_installer.rb
@@ -0,0 +1,233 @@
+# frozen_string_literal: true
+
+require "bundler/worker"
+require "bundler/installer/gem_installer"
+
+module Bundler
+ class ParallelInstaller
+ class SpecInstallation
+ attr_accessor :spec, :name, :post_install_message, :state, :error
+ def initialize(spec)
+ @spec = spec
+ @name = spec.name
+ @state = :none
+ @post_install_message = ""
+ @error = nil
+ end
+
+ def installed?
+ state == :installed
+ end
+
+ def enqueued?
+ state == :enqueued
+ end
+
+ def failed?
+ state == :failed
+ end
+
+ def installation_attempted?
+ installed? || failed?
+ end
+
+ # Only true when spec in neither installed nor already enqueued
+ def ready_to_enqueue?
+ !enqueued? && !installation_attempted?
+ end
+
+ def has_post_install_message?
+ !post_install_message.empty?
+ end
+
+ def ignorable_dependency?(dep)
+ dep.type == :development || dep.name == @name
+ end
+
+ # Checks installed dependencies against spec's dependencies to make
+ # sure needed dependencies have been installed.
+ def dependencies_installed?(all_specs)
+ installed_specs = all_specs.select(&:installed?).map(&:name)
+ dependencies.all? {|d| installed_specs.include? d.name }
+ end
+
+ # Represents only the non-development dependencies, the ones that are
+ # itself and are in the total list.
+ def dependencies
+ @dependencies ||= begin
+ all_dependencies.reject {|dep| ignorable_dependency? dep }
+ end
+ end
+
+ def missing_lockfile_dependencies(all_spec_names)
+ deps = all_dependencies.reject {|dep| ignorable_dependency? dep }
+ deps.reject {|dep| all_spec_names.include? dep.name }
+ end
+
+ # Represents all dependencies
+ def all_dependencies
+ @spec.dependencies
+ end
+
+ def to_s
+ "#<#{self.class} #{@spec.full_name} (#{state})>"
+ end
+ end
+
+ def self.call(*args)
+ new(*args).call
+ end
+
+ attr_reader :size
+
+ def initialize(installer, all_specs, size, standalone, force)
+ @installer = installer
+ @size = size
+ @standalone = standalone
+ @force = force
+ @specs = all_specs.map {|s| SpecInstallation.new(s) }
+ @spec_set = all_specs
+ @rake = @specs.find {|s| s.name == "rake" }
+ end
+
+ def call
+ # Since `autoload` has the potential for threading issues on 1.8.7
+ # TODO: remove in bundler 2.0
+ require "bundler/gem_remote_fetcher" if RUBY_VERSION < "1.9"
+
+ check_for_corrupt_lockfile
+
+ if @size > 1
+ install_with_worker
+ else
+ install_serially
+ end
+
+ handle_error if @specs.any?(&:failed?)
+ @specs
+ ensure
+ worker_pool && worker_pool.stop
+ end
+
+ def check_for_corrupt_lockfile
+ missing_dependencies = @specs.map do |s|
+ [
+ s,
+ s.missing_lockfile_dependencies(@specs.map(&:name)),
+ ]
+ end.reject { |a| a.last.empty? }
+ return if missing_dependencies.empty?
+
+ warning = []
+ warning << "Your lockfile was created by an old Bundler that left some things out."
+ if @size != 1
+ warning << "Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing #{@size} at a time."
+ @size = 1
+ end
+ warning << "You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile."
+ warning << "The missing gems are:"
+
+ missing_dependencies.each do |spec, missing|
+ warning << "* #{missing.map(&:name).join(", ")} depended upon by #{spec.name}"
+ end
+
+ Bundler.ui.warn(warning.join("\n"))
+ end
+
+ private
+
+ def install_with_worker
+ enqueue_specs
+ process_specs until finished_installing?
+ end
+
+ def install_serially
+ until finished_installing?
+ raise "failed to find a spec to enqueue while installing serially" unless spec_install = @specs.find(&:ready_to_enqueue?)
+ spec_install.state = :enqueued
+ do_install(spec_install, 0)
+ end
+ end
+
+ def worker_pool
+ @worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda { |spec_install, worker_num|
+ do_install(spec_install, worker_num)
+ }
+ end
+
+ def do_install(spec_install, worker_num)
+ Plugin.hook(Plugin::Events::GEM_BEFORE_INSTALL, spec_install)
+ gem_installer = Bundler::GemInstaller.new(
+ spec_install.spec, @installer, @standalone, worker_num, @force
+ )
+ success, message = begin
+ gem_installer.install_from_spec
+ rescue RuntimeError => e
+ raise e, "#{e}\n\n#{require_tree_for_spec(spec_install.spec)}"
+ end
+ if success
+ spec_install.state = :installed
+ spec_install.post_install_message = message unless message.nil?
+ else
+ spec_install.state = :failed
+ spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}"
+ end
+ Plugin.hook(Plugin::Events::GEM_AFTER_INSTALL, spec_install)
+ spec_install
+ end
+
+ # Dequeue a spec and save its post-install message and then enqueue the
+ # remaining specs.
+ # Some specs might've had to wait til this spec was installed to be
+ # processed so the call to `enqueue_specs` is important after every
+ # dequeue.
+ def process_specs
+ worker_pool.deq
+ enqueue_specs
+ end
+
+ def finished_installing?
+ @specs.all? do |spec|
+ return true if spec.failed?
+ spec.installed?
+ end
+ end
+
+ def handle_error
+ errors = @specs.select(&:failed?).map(&:error)
+ if exception = errors.find {|e| e.is_a?(Bundler::BundlerError) }
+ raise exception
+ end
+ raise Bundler::InstallError, errors.map(&:to_s).join("\n\n")
+ end
+
+ def require_tree_for_spec(spec)
+ tree = @spec_set.what_required(spec)
+ t = String.new("In #{File.basename(SharedHelpers.default_gemfile)}:\n")
+ tree.each_with_index do |s, depth|
+ t << " " * depth.succ << s.name
+ unless tree.last == s
+ t << %( was resolved to #{s.version}, which depends on)
+ end
+ t << %(\n)
+ end
+ t
+ end
+
+ # Keys in the remains hash represent uninstalled gems specs.
+ # We enqueue all gem specs that do not have any dependencies.
+ # Later we call this lambda again to install specs that depended on
+ # previously installed specifications. We continue until all specs
+ # are installed.
+ def enqueue_specs
+ @specs.select(&:ready_to_enqueue?).each do |spec|
+ next if @rake && !@rake.installed? && spec.name != @rake.name
+
+ if spec.dependencies_installed? @specs
+ spec.state = :enqueued
+ worker_pool.enq spec
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/installer/standalone.rb b/lib/bundler/installer/standalone.rb
new file mode 100644
index 0000000000..ce0c9df1eb
--- /dev/null
+++ b/lib/bundler/installer/standalone.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Standalone
+ def initialize(groups, definition)
+ @specs = groups.empty? ? definition.requested_specs : definition.specs_for(groups.map(&:to_sym))
+ end
+
+ def generate
+ SharedHelpers.filesystem_access(bundler_path) do |p|
+ FileUtils.mkdir_p(p)
+ end
+ File.open File.join(bundler_path, "setup.rb"), "w" do |file|
+ file.puts "require 'rbconfig'"
+ file.puts "# ruby 1.8.7 doesn't define RUBY_ENGINE"
+ file.puts "ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'"
+ file.puts "ruby_version = RbConfig::CONFIG[\"ruby_version\"]"
+ file.puts "path = File.expand_path('..', __FILE__)"
+ paths.each do |path|
+ file.puts %($:.unshift "\#{path}/#{path}")
+ end
+ end
+ end
+
+ private
+
+ def paths
+ @specs.map do |spec|
+ next if spec.name == "bundler"
+ Array(spec.require_paths).map do |path|
+ gem_path(path, spec).sub(version_dir, '#{ruby_engine}/#{ruby_version}')
+ # This is a static string intentionally. It's interpolated at a later time.
+ end
+ end.flatten
+ end
+
+ def version_dir
+ "#{Bundler::RubyVersion.system.engine}/#{RbConfig::CONFIG["ruby_version"]}"
+ end
+
+ def bundler_path
+ Bundler.root.join(Bundler.settings[:path], "bundler")
+ end
+
+ def gem_path(path, spec)
+ full_path = Pathname.new(path).absolute? ? path : File.join(spec.full_gem_path, path)
+ Pathname.new(full_path).relative_path_from(Bundler.root.join(bundler_path)).to_s
+ rescue TypeError
+ error_message = "#{spec.name} #{spec.version} has an invalid gemspec"
+ raise Gem::InvalidSpecificationException.new(error_message)
+ end
+ end
+end
diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb
new file mode 100644
index 0000000000..d9cb01f810
--- /dev/null
+++ b/lib/bundler/lazy_specification.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require "uri"
+require "bundler/match_platform"
+
+module Bundler
+ class LazySpecification
+ Identifier = Struct.new(:name, :version, :source, :platform, :dependencies)
+ class Identifier
+ include Comparable
+ def <=>(other)
+ return unless other.is_a?(Identifier)
+ [name, version, platform_string] <=> [other.name, other.version, other.platform_string]
+ end
+
+ protected
+
+ def platform_string
+ platform_string = platform.to_s
+ platform_string == Index::RUBY ? Index::NULL : platform_string
+ end
+ end
+
+ include MatchPlatform
+
+ attr_reader :name, :version, :dependencies, :platform
+ attr_accessor :source, :remote
+
+ def initialize(name, version, platform, source = nil)
+ @name = name
+ @version = version
+ @dependencies = []
+ @platform = platform || Gem::Platform::RUBY
+ @source = source
+ @specification = nil
+ end
+
+ def full_name
+ if platform == Gem::Platform::RUBY || platform.nil?
+ "#{@name}-#{@version}"
+ else
+ "#{@name}-#{@version}-#{platform}"
+ end
+ end
+
+ def ==(other)
+ identifier == other.identifier
+ end
+
+ def satisfies?(dependency)
+ @name == dependency.name && dependency.requirement.satisfied_by?(Gem::Version.new(@version))
+ end
+
+ def to_lock
+ out = String.new
+
+ if platform == Gem::Platform::RUBY || platform.nil?
+ out << " #{name} (#{version})\n"
+ else
+ out << " #{name} (#{version}-#{platform})\n"
+ end
+
+ dependencies.sort_by(&:to_s).uniq.each do |dep|
+ next if dep.type == :development
+ out << " #{dep.to_lock}\n"
+ end
+
+ out
+ end
+
+ def __materialize__
+ search_object = Bundler.feature_flag.specific_platform? || Bundler.settings[:force_ruby_platform] ? self : Dependency.new(name, version)
+ @specification = if source.is_a?(Source::Gemspec) && source.gemspec.name == name
+ source.gemspec.tap {|s| s.source = source }
+ else
+ search = source.specs.search(search_object).last
+ if search && Gem::Platform.new(search.platform) != Gem::Platform.new(platform) && !search.runtime_dependencies.-(dependencies.reject {|d| d.type == :development }).empty?
+ Bundler.ui.warn "Unable to use the platform-specific (#{search.platform}) version of #{name} (#{version}) " \
+ "because it has different dependencies from the #{platform} version. " \
+ "To use the platform-specific version of the gem, run `bundle config specific_platform true` and install again."
+ search = source.specs.search(self).last
+ end
+ search.dependencies = dependencies if search && (search.is_a?(RemoteSpecification) || search.is_a?(EndpointSpecification))
+ search
+ end
+ end
+
+ def respond_to?(*args)
+ super || @specification ? @specification.respond_to?(*args) : nil
+ end
+
+ def to_s
+ @__to_s ||= if platform == Gem::Platform::RUBY || platform.nil?
+ "#{name} (#{version})"
+ else
+ "#{name} (#{version}-#{platform})"
+ end
+ end
+
+ def identifier
+ @__identifier ||= Identifier.new(name, version, source, platform, dependencies)
+ end
+
+ def git_version
+ return unless source.is_a?(Bundler::Source::Git)
+ " #{source.revision[0..6]}"
+ end
+
+ private
+
+ def to_ary
+ nil
+ end
+
+ def method_missing(method, *args, &blk)
+ raise "LazySpecification has not been materialized yet (calling :#{method} #{args.inspect})" unless @specification
+
+ return super unless respond_to?(method)
+
+ @specification.send(method, *args, &blk)
+ end
+ end
+end
diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb
new file mode 100644
index 0000000000..585077d18d
--- /dev/null
+++ b/lib/bundler/lockfile_generator.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module Bundler
+ class LockfileGenerator
+ attr_reader :definition
+ attr_reader :out
+
+ # @private
+ def initialize(definition)
+ @definition = definition
+ @out = String.new
+ end
+
+ def self.generate(definition)
+ new(definition).generate!
+ end
+
+ def generate!
+ add_sources
+ add_platforms
+ add_dependencies
+ add_locked_ruby_version
+ add_bundled_with
+
+ out
+ end
+
+ private
+
+ def add_sources
+ definition.send(:sources).lock_sources.each_with_index do |source, idx|
+ out << "\n" unless idx.zero?
+
+ # Add the source header
+ out << source.to_lock
+
+ # Find all specs for this source
+ specs = definition.resolve.select {|s| source.can_lock?(s) }
+ add_specs(specs)
+ end
+ end
+
+ def add_specs(specs)
+ # This needs to be sorted by full name so that
+ # gems with the same name, but different platform
+ # are ordered consistently
+ specs.sort_by(&:full_name).each do |spec|
+ next if spec.name == "bundler".freeze
+ out << spec.to_lock
+ end
+ end
+
+ def add_platforms
+ add_section("PLATFORMS", definition.platforms)
+ end
+
+ def add_dependencies
+ out << "\nDEPENDENCIES\n"
+
+ handled = []
+ definition.dependencies.sort_by(&:to_s).each do |dep|
+ next if handled.include?(dep.name)
+ out << dep.to_lock
+ handled << dep.name
+ end
+ end
+
+ def add_locked_ruby_version
+ return unless locked_ruby_version = definition.locked_ruby_version
+ add_section("RUBY VERSION", locked_ruby_version.to_s)
+ end
+
+ def add_bundled_with
+ add_section("BUNDLED WITH", definition.locked_bundler_version.to_s)
+ end
+
+ def add_section(name, value)
+ out << "\n#{name}\n"
+ case value
+ when Array
+ value.map(&:to_s).sort.each do |val|
+ out << " #{val}\n"
+ end
+ when Hash
+ value.to_a.sort_by {|k, _| k.to_s }.each do |key, val|
+ out << " #{key}: #{val}\n"
+ end
+ when String
+ out << " #{value}\n"
+ else
+ raise ArgumentError, "#{value.inspect} can't be serialized in a lockfile"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb
new file mode 100644
index 0000000000..ff706fca1d
--- /dev/null
+++ b/lib/bundler/lockfile_parser.rb
@@ -0,0 +1,256 @@
+# frozen_string_literal: true
+
+# Some versions of the Bundler 1.1 RC series introduced corrupted
+# lockfiles. There were two major problems:
+#
+# * multiple copies of the same GIT section appeared in the lockfile
+# * when this happened, those sections got multiple copies of gems
+# in those sections.
+#
+# As a result, Bundler 1.1 contains code that fixes the earlier
+# corruption. We will remove this fix-up code in Bundler 1.2.
+
+module Bundler
+ class LockfileParser
+ attr_reader :sources, :dependencies, :specs, :platforms, :bundler_version, :ruby_version
+
+ BUNDLED = "BUNDLED WITH".freeze
+ DEPENDENCIES = "DEPENDENCIES".freeze
+ PLATFORMS = "PLATFORMS".freeze
+ RUBY = "RUBY VERSION".freeze
+ GIT = "GIT".freeze
+ GEM = "GEM".freeze
+ PATH = "PATH".freeze
+ PLUGIN = "PLUGIN SOURCE".freeze
+ SPECS = " specs:".freeze
+ OPTIONS = /^ ([a-z]+): (.*)$/i
+ SOURCE = [GIT, GEM, PATH, PLUGIN].freeze
+
+ SECTIONS_BY_VERSION_INTRODUCED = {
+ # The strings have to be dup'ed for old RG on Ruby 2.3+
+ # TODO: remove dup in Bundler 2.0
+ Gem::Version.create("1.0".dup) => [DEPENDENCIES, PLATFORMS, GIT, GEM, PATH].freeze,
+ Gem::Version.create("1.10".dup) => [BUNDLED].freeze,
+ Gem::Version.create("1.12".dup) => [RUBY].freeze,
+ Gem::Version.create("1.13".dup) => [PLUGIN].freeze,
+ }.freeze
+
+ KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten.freeze
+
+ ENVIRONMENT_VERSION_SECTIONS = [BUNDLED, RUBY].freeze
+
+ def self.sections_in_lockfile(lockfile_contents)
+ lockfile_contents.scan(/^\w[\w ]*$/).uniq
+ end
+
+ def self.unknown_sections_in_lockfile(lockfile_contents)
+ sections_in_lockfile(lockfile_contents) - KNOWN_SECTIONS
+ end
+
+ def self.sections_to_ignore(base_version = nil)
+ base_version &&= base_version.release
+ base_version ||= Gem::Version.create("1.0".dup)
+ attributes = []
+ SECTIONS_BY_VERSION_INTRODUCED.each do |version, introduced|
+ next if version <= base_version
+ attributes += introduced
+ end
+ attributes
+ end
+
+ def initialize(lockfile)
+ @platforms = []
+ @sources = []
+ @dependencies = {}
+ @state = nil
+ @specs = {}
+
+ @rubygems_aggregate = Source::Rubygems.new
+
+ if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
+ raise LockfileError, "Your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} contains merge conflicts.\n" \
+ "Run `git checkout HEAD -- #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` first to get a clean lock."
+ end
+
+ lockfile.split(/(?:\r?\n)+/).each do |line|
+ if SOURCE.include?(line)
+ @state = :source
+ parse_source(line)
+ elsif line == DEPENDENCIES
+ @state = :dependency
+ elsif line == PLATFORMS
+ @state = :platform
+ elsif line == RUBY
+ @state = :ruby
+ elsif line == BUNDLED
+ @state = :bundled_with
+ elsif line =~ /^[^\s]/
+ @state = nil
+ elsif @state
+ send("parse_#{@state}", line)
+ end
+ end
+ @sources << @rubygems_aggregate unless Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ @specs = @specs.values.sort_by(&:identifier)
+ warn_for_outdated_bundler_version
+ rescue ArgumentError => e
+ Bundler.ui.debug(e)
+ raise LockfileError, "Your lockfile is unreadable. Run `rm #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` " \
+ "and then `bundle install` to generate a new lockfile."
+ end
+
+ def warn_for_outdated_bundler_version
+ return unless bundler_version
+ prerelease_text = bundler_version.prerelease? ? " --pre" : ""
+ current_version = Gem::Version.create(Bundler::VERSION)
+ case current_version.segments.first <=> bundler_version.segments.first
+ when -1
+ raise LockfileError, "You must use Bundler #{bundler_version.segments.first} or greater with this lockfile."
+ when 0
+ if current_version < bundler_version
+ Bundler.ui.warn "Warning: the running version of Bundler (#{current_version}) is older " \
+ "than the version that created the lockfile (#{bundler_version}). We suggest you " \
+ "upgrade to the latest version of Bundler by running `gem " \
+ "install bundler#{prerelease_text}`.\n"
+ end
+ end
+ end
+
+ private
+
+ TYPES = {
+ GIT => Bundler::Source::Git,
+ GEM => Bundler::Source::Rubygems,
+ PATH => Bundler::Source::Path,
+ PLUGIN => Bundler::Plugin,
+ }.freeze
+
+ def parse_source(line)
+ case line
+ when SPECS
+ case @type
+ when PATH
+ @current_source = TYPES[@type].from_lock(@opts)
+ @sources << @current_source
+ when GIT
+ @current_source = TYPES[@type].from_lock(@opts)
+ # Strip out duplicate GIT sections
+ if @sources.include?(@current_source)
+ @current_source = @sources.find {|s| s == @current_source }
+ else
+ @sources << @current_source
+ end
+ when GEM
+ if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ @opts["remotes"] = @opts.delete("remote")
+ @current_source = TYPES[@type].from_lock(@opts)
+ @sources << @current_source
+ else
+ Array(@opts["remote"]).each do |url|
+ @rubygems_aggregate.add_remote(url)
+ end
+ @current_source = @rubygems_aggregate
+ end
+ when PLUGIN
+ @current_source = Plugin.source_from_lock(@opts)
+ @sources << @current_source
+ end
+ when OPTIONS
+ value = $2
+ value = true if value == "true"
+ value = false if value == "false"
+
+ key = $1
+
+ if @opts[key]
+ @opts[key] = Array(@opts[key])
+ @opts[key] << value
+ else
+ @opts[key] = value
+ end
+ when *SOURCE
+ @current_source = nil
+ @opts = {}
+ @type = line
+ else
+ parse_spec(line)
+ end
+ end
+
+ space = / /
+ NAME_VERSION = /
+ ^(#{space}{2}|#{space}{4}|#{space}{6})(?!#{space}) # Exactly 2, 4, or 6 spaces at the start of the line
+ (.*?) # Name
+ (?:#{space}\(([^-]*) # Space, followed by version
+ (?:-(.*))?\))? # Optional platform
+ (!)? # Optional pinned marker
+ $ # Line end
+ /xo
+
+ def parse_dependency(line)
+ return unless line =~ NAME_VERSION
+ spaces = $1
+ return unless spaces.size == 2
+ name = $2
+ version = $3
+ pinned = $5
+
+ version = version.split(",").map(&:strip) if version
+
+ dep = Bundler::Dependency.new(name, version)
+
+ if pinned && dep.name != "bundler"
+ spec = @specs.find {|_, v| v.name == dep.name }
+ dep.source = spec.last.source if spec
+
+ # Path sources need to know what the default name / version
+ # to use in the case that there are no gemspecs present. A fake
+ # gemspec is created based on the version set on the dependency
+ # TODO: Use the version from the spec instead of from the dependency
+ if version && version.size == 1 && version.first =~ /^\s*= (.+)\s*$/ && dep.source.is_a?(Bundler::Source::Path)
+ dep.source.name = name
+ dep.source.version = $1
+ end
+ end
+
+ @dependencies[dep.name] = dep
+ end
+
+ def parse_spec(line)
+ return unless line =~ NAME_VERSION
+ spaces = $1
+ name = $2
+ version = $3
+ platform = $4
+
+ if spaces.size == 4
+ version = Gem::Version.new(version)
+ platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
+ @current_spec = LazySpecification.new(name, version, platform)
+ @current_spec.source = @current_source
+
+ # Avoid introducing multiple copies of the same spec (caused by
+ # duplicate GIT sections)
+ @specs[@current_spec.identifier] ||= @current_spec
+ elsif spaces.size == 6
+ version = version.split(",").map(&:strip) if version
+ dep = Gem::Dependency.new(name, version)
+ @current_spec.dependencies << dep
+ end
+ end
+
+ def parse_platform(line)
+ @platforms << Gem::Platform.new($1) if line =~ /^ (.*)$/
+ end
+
+ def parse_bundled_with(line)
+ line = line.strip
+ return unless Gem::Version.correct?(line)
+ @bundler_version = Gem::Version.create(line)
+ end
+
+ def parse_ruby(line)
+ @ruby_version = line.strip
+ end
+ end
+end
diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb
new file mode 100644
index 0000000000..56cbbfb95d
--- /dev/null
+++ b/lib/bundler/match_platform.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require "bundler/gem_helpers"
+
+module Bundler
+ module MatchPlatform
+ include GemHelpers
+
+ def match_platform(p)
+ MatchPlatform.platforms_match?(platform, p)
+ end
+
+ def self.platforms_match?(gemspec_platform, local_platform)
+ return true if gemspec_platform.nil?
+ return true if Gem::Platform::RUBY == gemspec_platform
+ return true if local_platform == gemspec_platform
+ gemspec_platform = Gem::Platform.new(gemspec_platform)
+ return true if GemHelpers.generic(gemspec_platform) === local_platform
+ return true if gemspec_platform === local_platform
+
+ false
+ end
+ end
+end
diff --git a/lib/bundler/mirror.rb b/lib/bundler/mirror.rb
new file mode 100644
index 0000000000..b15190e7e5
--- /dev/null
+++ b/lib/bundler/mirror.rb
@@ -0,0 +1,223 @@
+# frozen_string_literal: true
+
+require "socket"
+
+module Bundler
+ class Settings
+ # Class used to build the mirror set and then find a mirror for a given URI
+ #
+ # @param prober [Prober object, nil] by default a TCPSocketProbe, this object
+ # will be used to probe the mirror address to validate that the mirror replies.
+ class Mirrors
+ def initialize(prober = nil)
+ @all = Mirror.new
+ @prober = prober || TCPSocketProbe.new
+ @mirrors = {}
+ end
+
+ # Returns a mirror for the given uri.
+ #
+ # Depending on the uri having a valid mirror or not, it may be a
+ # mirror that points to the provided uri
+ def for(uri)
+ if @all.validate!(@prober).valid?
+ @all
+ else
+ fetch_valid_mirror_for(Settings.normalize_uri(uri))
+ end
+ end
+
+ def each
+ @mirrors.each do |k, v|
+ yield k, v.uri.to_s
+ end
+ end
+
+ def parse(key, value)
+ config = MirrorConfig.new(key, value)
+ mirror = if config.all?
+ @all
+ else
+ @mirrors[config.uri] ||= Mirror.new
+ end
+ config.update_mirror(mirror)
+ end
+
+ private
+
+ def fetch_valid_mirror_for(uri)
+ downcased = uri.to_s.downcase
+ mirror = @mirrors[downcased] || @mirrors[URI(downcased).host] || Mirror.new(uri)
+ mirror.validate!(@prober)
+ mirror = Mirror.new(uri) unless mirror.valid?
+ mirror
+ end
+ end
+
+ # A mirror
+ #
+ # Contains both the uri that should be used as a mirror and the
+ # fallback timeout which will be used for probing if the mirror
+ # replies on time or not.
+ class Mirror
+ DEFAULT_FALLBACK_TIMEOUT = 0.1
+
+ attr_reader :uri, :fallback_timeout
+
+ def initialize(uri = nil, fallback_timeout = 0)
+ self.uri = uri
+ self.fallback_timeout = fallback_timeout
+ @valid = nil
+ end
+
+ def uri=(uri)
+ @uri = if uri.nil?
+ nil
+ else
+ URI(uri.to_s)
+ end
+ @valid = nil
+ end
+
+ def fallback_timeout=(timeout)
+ case timeout
+ when true, "true"
+ @fallback_timeout = DEFAULT_FALLBACK_TIMEOUT
+ when false, "false"
+ @fallback_timeout = 0
+ else
+ @fallback_timeout = timeout.to_i
+ end
+ @valid = nil
+ end
+
+ def ==(other)
+ !other.nil? && uri == other.uri && fallback_timeout == other.fallback_timeout
+ end
+
+ def valid?
+ return false if @uri.nil?
+ return @valid unless @valid.nil?
+ false
+ end
+
+ def validate!(probe = nil)
+ @valid = false if uri.nil?
+ if @valid.nil?
+ @valid = fallback_timeout == 0 || (probe || TCPSocketProbe.new).replies?(self)
+ end
+ self
+ end
+ end
+
+ # Class used to parse one configuration line
+ #
+ # Gets the configuration line and the value.
+ # This object provides a `update_mirror` method
+ # used to setup the given mirror value.
+ class MirrorConfig
+ attr_accessor :uri, :value
+
+ def initialize(config_line, value)
+ uri, fallback =
+ config_line.match(%r{\Amirror\.(all|.+?)(\.fallback_timeout)?\/?\z}).captures
+ @fallback = !fallback.nil?
+ @all = false
+ if uri == "all"
+ @all = true
+ else
+ @uri = URI(uri).absolute? ? Settings.normalize_uri(uri) : uri
+ end
+ @value = value
+ end
+
+ def all?
+ @all
+ end
+
+ def update_mirror(mirror)
+ if @fallback
+ mirror.fallback_timeout = @value
+ else
+ mirror.uri = Settings.normalize_uri(@value)
+ end
+ end
+ end
+
+ # Class used for probing TCP availability for a given mirror.
+ class TCPSocketProbe
+ def replies?(mirror)
+ MirrorSockets.new(mirror).any? do |socket, address, timeout|
+ begin
+ socket.connect_nonblock(address)
+ rescue Errno::EINPROGRESS
+ wait_for_writtable_socket(socket, address, timeout)
+ rescue RuntimeError # Connection failed somehow, again
+ false
+ end
+ end
+ end
+
+ private
+
+ def wait_for_writtable_socket(socket, address, timeout)
+ if IO.select(nil, [socket], nil, timeout)
+ probe_writtable_socket(socket, address)
+ else # TCP Handshake timed out, or there is something dropping packets
+ false
+ end
+ end
+
+ def probe_writtable_socket(socket, address)
+ socket.connect_nonblock(address)
+ rescue Errno::EISCONN
+ true
+ rescue StandardError # Connection failed
+ false
+ end
+ end
+ end
+
+ # Class used to build the list of sockets that correspond to
+ # a given mirror.
+ #
+ # One mirror may correspond to many different addresses, both
+ # because of it having many dns entries or because
+ # the network interface is both ipv4 and ipv5
+ class MirrorSockets
+ def initialize(mirror)
+ @timeout = mirror.fallback_timeout
+ @addresses = Socket.getaddrinfo(mirror.uri.host, mirror.uri.port).map do |address|
+ SocketAddress.new(address[0], address[3], address[1])
+ end
+ end
+
+ def any?
+ @addresses.any? do |address|
+ socket = Socket.new(Socket.const_get(address.type), Socket::SOCK_STREAM, 0)
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
+ value = yield socket, address.to_socket_address, @timeout
+ socket.close unless socket.closed?
+ value
+ end
+ end
+ end
+
+ # Socket address builder.
+ #
+ # Given a socket type, a host and a port,
+ # provides a method to build sockaddr string
+ class SocketAddress
+ attr_reader :type, :host, :port
+
+ def initialize(type, host, port)
+ @type = type
+ @host = host
+ @port = port
+ end
+
+ def to_socket_address
+ Socket.pack_sockaddr_in(@port, @host)
+ end
+ end
+end
diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb
new file mode 100644
index 0000000000..53f9806b73
--- /dev/null
+++ b/lib/bundler/plugin.rb
@@ -0,0 +1,292 @@
+# frozen_string_literal: true
+
+require "bundler/plugin/api"
+
+module Bundler
+ module Plugin
+ autoload :DSL, "bundler/plugin/dsl"
+ autoload :Events, "bundler/plugin/events"
+ autoload :Index, "bundler/plugin/index"
+ autoload :Installer, "bundler/plugin/installer"
+ autoload :SourceList, "bundler/plugin/source_list"
+
+ class MalformattedPlugin < PluginError; end
+ class UndefinedCommandError < PluginError; end
+ class UnknownSourceError < PluginError; end
+
+ PLUGIN_FILE_NAME = "plugins.rb".freeze
+
+ module_function
+
+ def reset!
+ instance_variables.each {|i| remove_instance_variable(i) }
+
+ @sources = {}
+ @commands = {}
+ @hooks_by_event = Hash.new {|h, k| h[k] = [] }
+ @loaded_plugin_names = []
+ end
+
+ reset!
+
+ # Installs a new plugin by the given name
+ #
+ # @param [Array<String>] names the name of plugin to be installed
+ # @param [Hash] options various parameters as described in description.
+ # Refer to cli/plugin for available options
+ def install(names, options)
+ specs = Installer.new.install(names, options)
+
+ save_plugins names, specs
+ rescue PluginError => e
+ if specs
+ specs_to_delete = Hash[specs.select {|k, _v| names.include?(k) && !index.commands.values.include?(k) }]
+ specs_to_delete.values.each {|spec| Bundler.rm_rf(spec.full_gem_path) }
+ end
+
+ Bundler.ui.error "Failed to install plugin #{name}: #{e.message}\n #{e.backtrace.join("\n ")}"
+ end
+
+ # Evaluates the Gemfile with a limited DSL and installs the plugins
+ # specified by plugin method
+ #
+ # @param [Pathname] gemfile path
+ # @param [Proc] block that can be evaluated for (inline) Gemfile
+ def gemfile_install(gemfile = nil, &inline)
+ builder = DSL.new
+ if block_given?
+ builder.instance_eval(&inline)
+ else
+ builder.eval_gemfile(gemfile)
+ end
+ definition = builder.to_definition(nil, true)
+
+ return if definition.dependencies.empty?
+
+ plugins = definition.dependencies.map(&:name).reject {|p| index.installed? p }
+ installed_specs = Installer.new.install_definition(definition)
+
+ save_plugins plugins, installed_specs, builder.inferred_plugins
+ rescue RuntimeError => e
+ unless e.is_a?(GemfileError)
+ Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}"
+ end
+ raise
+ end
+
+ # The index object used to store the details about the plugin
+ def index
+ @index ||= Index.new
+ end
+
+ # The directory root for all plugin related data
+ #
+ # If run in an app, points to local root, in app_config_path
+ # Otherwise, points to global root, in Bundler.user_bundle_path("plugin")
+ def root
+ @root ||= if SharedHelpers.in_bundle?
+ local_root
+ else
+ global_root
+ end
+ end
+
+ def local_root
+ Bundler.app_config_path.join("plugin")
+ end
+
+ # The global directory root for all plugin related data
+ def global_root
+ Bundler.user_bundle_path("plugin")
+ end
+
+ # The cache directory for plugin stuffs
+ def cache
+ @cache ||= root.join("cache")
+ end
+
+ # To be called via the API to register to handle a command
+ def add_command(command, cls)
+ @commands[command] = cls
+ end
+
+ # Checks if any plugin handles the command
+ def command?(command)
+ !index.command_plugin(command).nil?
+ end
+
+ # To be called from Cli class to pass the command and argument to
+ # approriate plugin class
+ def exec_command(command, args)
+ raise UndefinedCommandError, "Command `#{command}` not found" unless command? command
+
+ load_plugin index.command_plugin(command) unless @commands.key? command
+
+ @commands[command].new.exec(command, args)
+ end
+
+ # To be called via the API to register to handle a source plugin
+ def add_source(source, cls)
+ @sources[source] = cls
+ end
+
+ # Checks if any plugin declares the source
+ def source?(name)
+ !index.source_plugin(name.to_s).nil?
+ end
+
+ # @return [Class] that handles the source. The calss includes API::Source
+ def source(name)
+ raise UnknownSourceError, "Source #{name} not found" unless source? name
+
+ load_plugin(index.source_plugin(name)) unless @sources.key? name
+
+ @sources[name]
+ end
+
+ # @param [Hash] The options that are present in the lock file
+ # @return [API::Source] the instance of the class that handles the source
+ # type passed in locked_opts
+ def source_from_lock(locked_opts)
+ src = source(locked_opts["type"])
+
+ src.new(locked_opts.merge("uri" => locked_opts["remote"]))
+ end
+
+ # To be called via the API to register a hooks and corresponding block that
+ # will be called to handle the hook
+ def add_hook(event, &block)
+ unless Events.defined_event?(event)
+ raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events"
+ end
+ @hooks_by_event[event.to_s] << block
+ end
+
+ # Runs all the hooks that are registered for the passed event
+ #
+ # It passes the passed arguments and block to the block registered with
+ # the api.
+ #
+ # @param [String] event
+ def hook(event, *args, &arg_blk)
+ return unless Bundler.feature_flag.plugins?
+ unless Events.defined_event?(event)
+ raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events"
+ end
+
+ plugins = index.hook_plugins(event)
+ return unless plugins.any?
+
+ (plugins - @loaded_plugin_names).each {|name| load_plugin(name) }
+
+ @hooks_by_event[event].each {|blk| blk.call(*args, &arg_blk) }
+ end
+
+ # currently only intended for specs
+ #
+ # @return [String, nil] installed path
+ def installed?(plugin)
+ Index.new.installed?(plugin)
+ end
+
+ # Post installation processing and registering with index
+ #
+ # @param [Array<String>] plugins list to be installed
+ # @param [Hash] specs of plugins mapped to installation path (currently they
+ # contain all the installed specs, including plugins)
+ # @param [Array<String>] names of inferred source plugins that can be ignored
+ def save_plugins(plugins, specs, optional_plugins = [])
+ plugins.each do |name|
+ spec = specs[name]
+ validate_plugin! Pathname.new(spec.full_gem_path)
+ installed = register_plugin(name, spec, optional_plugins.include?(name))
+ Bundler.ui.info "Installed plugin #{name}" if installed
+ end
+ end
+
+ # Checks if the gem is good to be a plugin
+ #
+ # At present it only checks whether it contains plugins.rb file
+ #
+ # @param [Pathname] plugin_path the path plugin is installed at
+ # @raise [MalformattedPlugin] if plugins.rb file is not found
+ def validate_plugin!(plugin_path)
+ plugin_file = plugin_path.join(PLUGIN_FILE_NAME)
+ raise MalformattedPlugin, "#{PLUGIN_FILE_NAME} was not found in the plugin." unless plugin_file.file?
+ end
+
+ # Runs the plugins.rb file in an isolated namespace, records the plugin
+ # actions it registers for and then passes the data to index to be stored.
+ #
+ # @param [String] name the name of the plugin
+ # @param [Specification] spec of installed plugin
+ # @param [Boolean] optional_plugin, removed if there is conflict with any
+ # other plugin (used for default source plugins)
+ #
+ # @raise [MalformattedPlugin] if plugins.rb raises any error
+ def register_plugin(name, spec, optional_plugin = false)
+ commands = @commands
+ sources = @sources
+ hooks = @hooks_by_event
+
+ @commands = {}
+ @sources = {}
+ @hooks_by_event = Hash.new {|h, k| h[k] = [] }
+
+ load_paths = spec.load_paths
+ add_to_load_path(load_paths)
+ path = Pathname.new spec.full_gem_path
+
+ begin
+ load path.join(PLUGIN_FILE_NAME), true
+ rescue StandardError => e
+ raise MalformattedPlugin, "#{e.class}: #{e.message}"
+ end
+
+ if optional_plugin && @sources.keys.any? {|s| source? s }
+ Bundler.rm_rf(path)
+ false
+ else
+ index.register_plugin(name, path.to_s, load_paths, @commands.keys,
+ @sources.keys, @hooks_by_event.keys)
+ true
+ end
+ ensure
+ @commands = commands
+ @sources = sources
+ @hooks_by_event = hooks
+ end
+
+ # Executes the plugins.rb file
+ #
+ # @param [String] name of the plugin
+ def load_plugin(name)
+ # Need to ensure before this that plugin root where the rest of gems
+ # are installed to be on load path to support plugin deps. Currently not
+ # done to avoid conflicts
+ path = index.plugin_path(name)
+
+ add_to_load_path(index.load_paths(name))
+
+ load path.join(PLUGIN_FILE_NAME)
+
+ @loaded_plugin_names << name
+ rescue RuntimeError => e
+ Bundler.ui.error "Failed loading plugin #{name}: #{e.message}"
+ raise
+ end
+
+ def add_to_load_path(load_paths)
+ if insert_index = Bundler.rubygems.load_path_insert_index
+ $LOAD_PATH.insert(insert_index, *load_paths)
+ else
+ $LOAD_PATH.unshift(*load_paths)
+ end
+ end
+
+ class << self
+ private :load_plugin, :register_plugin, :save_plugins, :validate_plugin!,
+ :add_to_load_path
+ end
+ end
+end
diff --git a/lib/bundler/plugin/api.rb b/lib/bundler/plugin/api.rb
new file mode 100644
index 0000000000..a2d5cbb4ac
--- /dev/null
+++ b/lib/bundler/plugin/api.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module Bundler
+ # This is the interfacing class represents the API that we intend to provide
+ # the plugins to use.
+ #
+ # For plugins to be independent of the Bundler internals they shall limit their
+ # interactions to methods of this class only. This will save them from breaking
+ # when some internal change.
+ #
+ # Currently we are delegating the methods defined in Bundler class to
+ # itself. So, this class acts as a buffer.
+ #
+ # If there is some change in the Bundler class that is incompatible to its
+ # previous behavior or if otherwise desired, we can reimplement(or implement)
+ # the method to preserve compatibility.
+ #
+ # To use this, either the class can inherit this class or use it directly.
+ # For example of both types of use, refer the file `spec/plugins/command.rb`
+ #
+ # To use it without inheriting, you will have to create an object of this
+ # to use the functions (except for declaration functions like command, source,
+ # and hooks).
+ module Plugin
+ class API
+ autoload :Source, "bundler/plugin/api/source"
+
+ # The plugins should declare that they handle a command through this helper.
+ #
+ # @param [String] command being handled by them
+ # @param [Class] (optional) class that handles the command. If not
+ # provided, the `self` class will be used.
+ def self.command(command, cls = self)
+ Plugin.add_command command, cls
+ end
+
+ # The plugins should declare that they provide a installation source
+ # through this helper.
+ #
+ # @param [String] the source type they provide
+ # @param [Class] (optional) class that handles the source. If not
+ # provided, the `self` class will be used.
+ def self.source(source, cls = self)
+ cls.send :include, Bundler::Plugin::API::Source
+ Plugin.add_source source, cls
+ end
+
+ def self.hook(event, &block)
+ Plugin.add_hook(event, &block)
+ end
+
+ # The cache dir to be used by the plugins for storage
+ #
+ # @return [Pathname] path of the cache dir
+ def cache_dir
+ Plugin.cache.join("plugins")
+ end
+
+ # A tmp dir to be used by plugins
+ # Accepts names that get concatenated as suffix
+ #
+ # @return [Pathname] object for the new directory created
+ def tmp(*names)
+ Bundler.tmp(["plugin", *names].join("-"))
+ end
+
+ def method_missing(name, *args, &blk)
+ return Bundler.send(name, *args, &blk) if Bundler.respond_to?(name)
+
+ return SharedHelpers.send(name, *args, &blk) if SharedHelpers.respond_to?(name)
+
+ super
+ end
+
+ def respond_to_missing?(name, include_private = false)
+ SharedHelpers.respond_to?(name, include_private) ||
+ Bundler.respond_to?(name, include_private) || super
+ end
+ end
+ end
+end
diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb
new file mode 100644
index 0000000000..586477efb5
--- /dev/null
+++ b/lib/bundler/plugin/api/source.rb
@@ -0,0 +1,306 @@
+# frozen_string_literal: true
+
+require "uri"
+
+module Bundler
+ module Plugin
+ class API
+ # This class provides the base to build source plugins
+ # All the method here are required to build a source plugin (except
+ # `uri_hash`, `gem_install_dir`; they are helpers).
+ #
+ # Defaults for methods, where ever possible are provided which is
+ # expected to work. But, all source plugins have to override
+ # `fetch_gemspec_files` and `install`. Defaults are also not provided for
+ # `remote!`, `cache!` and `unlock!`.
+ #
+ # The defaults shall work for most situations but nevertheless they can
+ # be (preferably should be) overridden as per the plugins' needs safely
+ # (as long as they behave as expected).
+ # On overriding `initialize` you should call super first.
+ #
+ # If required plugin should override `hash`, `==` and `eql?` methods to be
+ # able to match objects representing same sources, but may be created in
+ # different situation (like form gemfile and lockfile). The default ones
+ # checks only for class and uri, but elaborate source plugins may need
+ # more comparisons (e.g. git checking on branch or tag).
+ #
+ # @!attribute [r] uri
+ # @return [String] the remote specified with `source` block in Gemfile
+ #
+ # @!attribute [r] options
+ # @return [String] options passed during initialization (either from
+ # lockfile or Gemfile)
+ #
+ # @!attribute [r] name
+ # @return [String] name that can be used to uniquely identify a source
+ #
+ # @!attribute [rw] dependency_names
+ # @return [Array<String>] Names of dependencies that the source should
+ # try to resolve. It is not necessary to use this list intenally. This
+ # is present to be compatible with `Definition` and is used by
+ # rubygems source.
+ module Source
+ attr_reader :uri, :options, :name
+ attr_accessor :dependency_names
+
+ def initialize(opts)
+ @options = opts
+ @dependency_names = []
+ @uri = opts["uri"]
+ @type = opts["type"]
+ @name = opts["name"] || "#{@type} at #{@uri}"
+ end
+
+ # This is used by the default `spec` method to constructs the
+ # Specification objects for the gems and versions that can be installed
+ # by this source plugin.
+ #
+ # Note: If the spec method is overridden, this function is not necessary
+ #
+ # @return [Array<String>] paths of the gemspec files for gems that can
+ # be installed
+ def fetch_gemspec_files
+ []
+ end
+
+ # Options to be saved in the lockfile so that the source plugin is able
+ # to check out same version of gem later.
+ #
+ # There options are passed when the source plugin is created from the
+ # lock file.
+ #
+ # @return [Hash]
+ def options_to_lock
+ {}
+ end
+
+ # Install the gem specified by the spec at appropriate path.
+ # `install_path` provides a sufficient default, if the source can only
+ # satisfy one gem, but is not binding.
+ #
+ # @return [String] post installation message (if any)
+ def install(spec, opts)
+ raise MalformattedPlugin, "Source plugins need to override the install method."
+ end
+
+ # It builds extensions, generates bins and installs them for the spec
+ # provided.
+ #
+ # It depends on `spec.loaded_from` to get full_gem_path. The source
+ # plugins should set that.
+ #
+ # It should be called in `install` after the plugin is done placing the
+ # gem at correct install location.
+ #
+ # It also runs Gem hooks `pre_install`, `post_build` and `post_install`
+ #
+ # Note: Do not override if you don't know what you are doing.
+ def post_install(spec, disable_exts = false)
+ opts = { :env_shebang => false, :disable_extensions => disable_exts }
+ installer = Bundler::Source::Path::Installer.new(spec, opts)
+ installer.post_install
+ end
+
+ # A default installation path to install a single gem. If the source
+ # servers multiple gems, it's not of much use and the source should one
+ # of its own.
+ def install_path
+ @install_path ||=
+ begin
+ base_name = File.basename(URI.parse(uri).normalize.path)
+
+ gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}")
+ end
+ end
+
+ # Parses the gemspec files to find the specs for the gems that can be
+ # satisfied by the source.
+ #
+ # Few important points to keep in mind:
+ # - If the gems are not installed then it shall return specs for all
+ # the gems it can satisfy
+ # - If gem is installed (that is to be detected by the plugin itself)
+ # then it shall return at least the specs that are installed.
+ # - The `loaded_from` for each of the specs shall be correct (it is
+ # used to find the load path)
+ #
+ # @return [Bundler::Index] index containing the specs
+ def specs
+ files = fetch_gemspec_files
+
+ Bundler::Index.build do |index|
+ files.each do |file|
+ next unless spec = Bundler.load_gemspec(file)
+ Bundler.rubygems.set_installed_by_version(spec)
+
+ spec.source = self
+ Bundler.rubygems.validate(spec)
+
+ index << spec
+ end
+ end
+ end
+
+ # Set internal representation to fetch the gems/specs from remote.
+ #
+ # When this is called, the source should try to fetch the specs and
+ # install from remote path.
+ def remote!
+ end
+
+ # Set internal representation to fetch the gems/specs from app cache.
+ #
+ # When this is called, the source should try to fetch the specs and
+ # install from the path provided by `app_cache_path`.
+ def cached!
+ end
+
+ # This is called to update the spec and installation.
+ #
+ # If the source plugin is loaded from lockfile or otherwise, it shall
+ # refresh the cache/specs (e.g. git sources can make a fresh clone).
+ def unlock!
+ end
+
+ # Name of directory where plugin the is expected to cache the gems when
+ # #cache is called.
+ #
+ # Also this name is matched against the directories in cache for pruning
+ #
+ # This is used by `app_cache_path`
+ def app_cache_dirname
+ base_name = File.basename(URI.parse(uri).normalize.path)
+ "#{base_name}-#{uri_hash}"
+ end
+
+ # This method is called while caching to save copy of the gems that the
+ # source can resolve to path provided by `app_cache_app`so that they can
+ # be reinstalled from the cache without querying the remote (i.e. an
+ # alternative to remote)
+ #
+ # This is stored with the app and source plugins should try to provide
+ # specs and install only from this cache when `cached!` is called.
+ #
+ # This cache is different from the internal caching that can be done
+ # at sub paths of `cache_path` (from API). This can be though as caching
+ # by bundler.
+ def cache(spec, custom_path = nil)
+ new_cache_path = app_cache_path(custom_path)
+
+ FileUtils.rm_rf(new_cache_path)
+ FileUtils.cp_r(install_path, new_cache_path)
+ FileUtils.touch(app_cache_path.join(".bundlecache"))
+ end
+
+ # This shall check if two source object represent the same source.
+ #
+ # The comparison shall take place only on the attribute that can be
+ # inferred from the options passed from Gemfile and not on attibutes
+ # that are used to pin down the gem to specific version (e.g. Git
+ # sources should compare on branch and tag but not on commit hash)
+ #
+ # The sources objects are constructed from Gemfile as well as from
+ # lockfile. To converge the sources, it is necessary that they match.
+ #
+ # The same applies for `eql?` and `hash`
+ def ==(other)
+ other.is_a?(self.class) && uri == other.uri
+ end
+
+ # When overriding `eql?` please preserve the behaviour as mentioned in
+ # docstring for `==` method.
+ alias_method :eql?, :==
+
+ # When overriding `hash` please preserve the behaviour as mentioned in
+ # docstring for `==` method, i.e. two methods equal by above comparison
+ # should have same hash.
+ def hash
+ [self.class, uri].hash
+ end
+
+ # A helper method, not necessary if not used internally.
+ def installed?
+ File.directory?(install_path)
+ end
+
+ # The full path where the plugin should cache the gem so that it can be
+ # installed latter.
+ #
+ # Note: Do not override if you don't know what you are doing.
+ def app_cache_path(custom_path = nil)
+ @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
+ end
+
+ # Used by definition.
+ #
+ # Note: Do not override if you don't know what you are doing.
+ def unmet_deps
+ specs.unmet_dependency_names
+ end
+
+ # Note: Do not override if you don't know what you are doing.
+ def can_lock?(spec)
+ spec.source == self
+ end
+
+ # Generates the content to be entered into the lockfile.
+ # Saves type and remote and also calls to `options_to_lock`.
+ #
+ # Plugin should use `options_to_lock` to save information in lockfile
+ # and not override this.
+ #
+ # Note: Do not override if you don't know what you are doing.
+ def to_lock
+ out = String.new("#{LockfileParser::PLUGIN}\n")
+ out << " remote: #{@uri}\n"
+ out << " type: #{@type}\n"
+ options_to_lock.each do |opt, value|
+ out << " #{opt}: #{value}\n"
+ end
+ out << " specs:\n"
+ end
+
+ def to_s
+ "plugin source for #{options[:type]} with uri #{uri}"
+ end
+
+ # Note: Do not override if you don't know what you are doing.
+ def include?(other)
+ other == self
+ end
+
+ def uri_hash
+ SharedHelpers.digest(:SHA1).hexdigest(uri)
+ end
+
+ # Note: Do not override if you don't know what you are doing.
+ def gem_install_dir
+ Bundler.install_path
+ end
+
+ # It is used to obtain the full_gem_path.
+ #
+ # spec's loaded_from path is expanded against this to get full_gem_path
+ #
+ # Note: Do not override if you don't know what you are doing.
+ def root
+ Bundler.root
+ end
+
+ # @private
+ # Returns true
+ def bundler_plugin_api_source?
+ true
+ end
+
+ # @private
+ # This API on source might not be stable, and for now we expect plugins
+ # to download all specs in `#specs`, so we implement the method for
+ # compatibility purposes and leave it undocumented (and don't support)
+ # overriding it)
+ def double_check_for(*); end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/plugin/dsl.rb b/lib/bundler/plugin/dsl.rb
new file mode 100644
index 0000000000..4bfc8437e0
--- /dev/null
+++ b/lib/bundler/plugin/dsl.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Bundler
+ module Plugin
+ # Dsl to parse the Gemfile looking for plugins to install
+ class DSL < Bundler::Dsl
+ class PluginGemfileError < PluginError; end
+ alias_method :_gem, :gem # To use for plugin installation as gem
+
+ # So that we don't have to override all there methods to dummy ones
+ # explicitly.
+ # They will be handled by method_missing
+ [:gemspec, :gem, :path, :install_if, :platforms, :env].each {|m| undef_method m }
+
+ # This lists the plugins that was added automatically and not specified by
+ # the user.
+ #
+ # When we encounter :type attribute with a source block, we add a plugin
+ # by name bundler-source-<type> to list of plugins to be installed.
+ #
+ # These plugins are optional and are not installed when there is conflict
+ # with any other plugin.
+ attr_reader :inferred_plugins
+
+ def initialize
+ super
+ @sources = Plugin::SourceList.new
+ @inferred_plugins = [] # The source plugins inferred from :type
+ end
+
+ def plugin(name, *args)
+ _gem(name, *args)
+ end
+
+ def method_missing(name, *args)
+ raise PluginGemfileError, "Undefined local variable or method `#{name}' for Gemfile" unless Bundler::Dsl.method_defined? name
+ end
+
+ def source(source, *args, &blk)
+ options = args.last.is_a?(Hash) ? args.pop.dup : {}
+ options = normalize_hash(options)
+ return super unless options.key?("type")
+
+ plugin_name = "bundler-source-#{options["type"]}"
+
+ return if @dependencies.any? {|d| d.name == plugin_name }
+
+ plugin(plugin_name)
+ @inferred_plugins << plugin_name
+ end
+ end
+ end
+end
diff --git a/lib/bundler/plugin/events.rb b/lib/bundler/plugin/events.rb
new file mode 100644
index 0000000000..bc037d1af5
--- /dev/null
+++ b/lib/bundler/plugin/events.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module Bundler
+ module Plugin
+ module Events
+ def self.define(const, event)
+ const = const.to_sym.freeze
+ if const_defined?(const) && const_get(const) != event
+ raise ArgumentError, "Attempting to reassign #{const} to a different value"
+ end
+ const_set(const, event) unless const_defined?(const)
+ @events ||= {}
+ @events[event] = const
+ end
+ private_class_method :define
+
+ def self.reset
+ @events.each_value do |const|
+ remove_const(const)
+ end
+ @events = nil
+ end
+ private_class_method :reset
+
+ # Check if an event has been defined
+ # @param event [String] An event to check
+ # @return [Boolean] A boolean indicating if the event has been defined
+ def self.defined_event?(event)
+ @events ||= {}
+ @events.key?(event)
+ end
+
+ # @!parse
+ # A hook called before each individual gem is installed
+ # Includes a Bundler::ParallelInstaller::SpecInstallation.
+ # No state, error, post_install_message will be present as nothing has installed yet
+ # GEM_BEFORE_INSTALL = "before-install"
+ define :GEM_BEFORE_INSTALL, "before-install"
+
+ # @!parse
+ # A hook called after each individual gem is installed
+ # Includes a Bundler::ParallelInstaller::SpecInstallation.
+ # - If state is failed, an error will be present.
+ # - If state is success, a post_install_message may be present.
+ # GEM_AFTER_INSTALL = "after-install"
+ define :GEM_AFTER_INSTALL, "after-install"
+
+ # @!parse
+ # A hook called before any gems install
+ # Includes an Array of Bundler::Dependency objects
+ # GEM_BEFORE_INSTALL_ALL = "before-install-all"
+ define :GEM_BEFORE_INSTALL_ALL, "before-install-all"
+
+ # @!parse
+ # A hook called after any gems install
+ # Includes an Array of Bundler::Dependency objects
+ # GEM_AFTER_INSTALL_ALL = "after-install-all"
+ define :GEM_AFTER_INSTALL_ALL, "after-install-all"
+ end
+ end
+end
diff --git a/lib/bundler/plugin/index.rb b/lib/bundler/plugin/index.rb
new file mode 100644
index 0000000000..f09587dfda
--- /dev/null
+++ b/lib/bundler/plugin/index.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+module Bundler
+ # Manages which plugins are installed and their sources. This also is supposed to map
+ # which plugin does what (currently the features are not implemented so this class is
+ # now a stub class).
+ module Plugin
+ class Index
+ class CommandConflict < PluginError
+ def initialize(plugin, commands)
+ msg = "Command(s) `#{commands.join("`, `")}` declared by #{plugin} are already registered."
+ super msg
+ end
+ end
+
+ class SourceConflict < PluginError
+ def initialize(plugin, sources)
+ msg = "Source(s) `#{sources.join("`, `")}` declared by #{plugin} are already registered."
+ super msg
+ end
+ end
+
+ attr_reader :commands
+
+ def initialize
+ @plugin_paths = {}
+ @commands = {}
+ @sources = {}
+ @hooks = {}
+ @load_paths = {}
+
+ begin
+ load_index(global_index_file, true)
+ rescue GenericSystemCallError
+ # no need to fail when on a read-only FS, for example
+ nil
+ end
+ load_index(local_index_file) if SharedHelpers.in_bundle?
+ end
+
+ # This function is to be called when a new plugin is installed. This
+ # function shall add the functions of the plugin to existing maps and also
+ # the name to source location.
+ #
+ # @param [String] name of the plugin to be registered
+ # @param [String] path where the plugin is installed
+ # @param [Array<String>] load_paths for the plugin
+ # @param [Array<String>] commands that are handled by the plugin
+ # @param [Array<String>] sources that are handled by the plugin
+ def register_plugin(name, path, load_paths, commands, sources, hooks)
+ old_commands = @commands.dup
+
+ common = commands & @commands.keys
+ raise CommandConflict.new(name, common) unless common.empty?
+ commands.each {|c| @commands[c] = name }
+
+ common = sources & @sources.keys
+ raise SourceConflict.new(name, common) unless common.empty?
+ sources.each {|k| @sources[k] = name }
+
+ hooks.each {|e| (@hooks[e] ||= []) << name }
+
+ @plugin_paths[name] = path
+ @load_paths[name] = load_paths
+ save_index
+ rescue StandardError
+ @commands = old_commands
+ raise
+ end
+
+ # Path of default index file
+ def index_file
+ Plugin.root.join("index")
+ end
+
+ # Path where the global index file is stored
+ def global_index_file
+ Plugin.global_root.join("index")
+ end
+
+ # Path where the local index file is stored
+ def local_index_file
+ Plugin.local_root.join("index")
+ end
+
+ def plugin_path(name)
+ Pathname.new @plugin_paths[name]
+ end
+
+ def load_paths(name)
+ @load_paths[name]
+ end
+
+ # Fetch the name of plugin handling the command
+ def command_plugin(command)
+ @commands[command]
+ end
+
+ def installed?(name)
+ @plugin_paths[name]
+ end
+
+ def source?(source)
+ @sources.key? source
+ end
+
+ def source_plugin(name)
+ @sources[name]
+ end
+
+ # Returns the list of plugin names handling the passed event
+ def hook_plugins(event)
+ @hooks[event] || []
+ end
+
+ private
+
+ # Reads the index file from the directory and initializes the instance
+ # variables.
+ #
+ # It skips the sources if the second param is true
+ # @param [Pathname] index file path
+ # @param [Boolean] is the index file global index
+ def load_index(index_file, global = false)
+ SharedHelpers.filesystem_access(index_file, :read) do |index_f|
+ valid_file = index_f && index_f.exist? && !index_f.size.zero?
+ break unless valid_file
+
+ data = index_f.read
+
+ require "bundler/yaml_serializer"
+ index = YAMLSerializer.load(data)
+
+ @commands.merge!(index["commands"])
+ @hooks.merge!(index["hooks"])
+ @load_paths.merge!(index["load_paths"])
+ @plugin_paths.merge!(index["plugin_paths"])
+ @sources.merge!(index["sources"]) unless global
+ end
+ end
+
+ # Should be called when any of the instance variables change. Stores the
+ # instance variables in YAML format. (The instance variables are supposed
+ # to be only String key value pairs)
+ def save_index
+ index = {
+ "commands" => @commands,
+ "hooks" => @hooks,
+ "load_paths" => @load_paths,
+ "plugin_paths" => @plugin_paths,
+ "sources" => @sources,
+ }
+
+ require "bundler/yaml_serializer"
+ SharedHelpers.filesystem_access(index_file) do |index_f|
+ FileUtils.mkdir_p(index_f.dirname)
+ File.open(index_f, "w") {|f| f.puts YAMLSerializer.dump(index) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb
new file mode 100644
index 0000000000..5379c38979
--- /dev/null
+++ b/lib/bundler/plugin/installer.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+module Bundler
+ # Handles the installation of plugin in appropriate directories.
+ #
+ # This class is supposed to be wrapper over the existing gem installation infra
+ # but currently it itself handles everything as the Source's subclasses (e.g. Source::RubyGems)
+ # are heavily dependent on the Gemfile.
+ module Plugin
+ class Installer
+ autoload :Rubygems, "bundler/plugin/installer/rubygems"
+ autoload :Git, "bundler/plugin/installer/git"
+
+ def install(names, options)
+ version = options[:version] || [">= 0"]
+ Bundler.settings.temporary(:lockfile_uses_separate_rubygems_sources => false, :disable_multisource => false) do
+ if options[:git]
+ install_git(names, version, options)
+ else
+ sources = options[:source] || Bundler.rubygems.sources
+ install_rubygems(names, version, sources)
+ end
+ end
+ end
+
+ # Installs the plugin from Definition object created by limited parsing of
+ # Gemfile searching for plugins to be installed
+ #
+ # @param [Definition] definition object
+ # @return [Hash] map of names to their specs they are installed with
+ def install_definition(definition)
+ def definition.lock(*); end
+ definition.resolve_remotely!
+ specs = definition.specs
+
+ install_from_specs specs
+ end
+
+ private
+
+ def install_git(names, version, options)
+ uri = options.delete(:git)
+ options["uri"] = uri
+
+ source_list = SourceList.new
+ source_list.add_git_source(options)
+
+ # To support both sources
+ if options[:source]
+ source_list.add_rubygems_source("remotes" => options[:source])
+ end
+
+ deps = names.map {|name| Dependency.new name, version }
+
+ definition = Definition.new(nil, deps, source_list, true)
+ install_definition(definition)
+ end
+
+ # Installs the plugin from rubygems source and returns the path where the
+ # plugin was installed
+ #
+ # @param [String] name of the plugin gem to search in the source
+ # @param [Array] version of the gem to install
+ # @param [String, Array<String>] source(s) to resolve the gem
+ #
+ # @return [Hash] map of names to the specs of plugins installed
+ def install_rubygems(names, version, sources)
+ deps = names.map {|name| Dependency.new name, version }
+
+ source_list = SourceList.new
+ source_list.add_rubygems_source("remotes" => sources)
+
+ definition = Definition.new(nil, deps, source_list, true)
+ install_definition(definition)
+ end
+
+ # Installs the plugins and deps from the provided specs and returns map of
+ # gems to their paths
+ #
+ # @param specs to install
+ #
+ # @return [Hash] map of names to the specs
+ def install_from_specs(specs)
+ paths = {}
+
+ specs.each do |spec|
+ spec.source.install spec
+
+ paths[spec.name] = spec
+ end
+
+ paths
+ end
+ end
+ end
+end
diff --git a/lib/bundler/plugin/installer/git.rb b/lib/bundler/plugin/installer/git.rb
new file mode 100644
index 0000000000..fbb6c5e40e
--- /dev/null
+++ b/lib/bundler/plugin/installer/git.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Bundler
+ module Plugin
+ class Installer
+ class Git < Bundler::Source::Git
+ def cache_path
+ @cache_path ||= begin
+ git_scope = "#{base_name}-#{uri_hash}"
+
+ Plugin.cache.join("bundler", "git", git_scope)
+ end
+ end
+
+ def install_path
+ @install_path ||= begin
+ git_scope = "#{base_name}-#{shortref_for_path(revision)}"
+
+ Plugin.root.join("bundler", "gems", git_scope)
+ end
+ end
+
+ def version_message(spec)
+ "#{spec.name} #{spec.version}"
+ end
+
+ def root
+ Plugin.root
+ end
+
+ def generate_bin(spec, disable_extensions = false)
+ # Need to find a way without code duplication
+ # For now, we can ignore this
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/plugin/installer/rubygems.rb b/lib/bundler/plugin/installer/rubygems.rb
new file mode 100644
index 0000000000..7ae74fa93b
--- /dev/null
+++ b/lib/bundler/plugin/installer/rubygems.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Bundler
+ module Plugin
+ class Installer
+ class Rubygems < Bundler::Source::Rubygems
+ def version_message(spec)
+ "#{spec.name} #{spec.version}"
+ end
+
+ private
+
+ def requires_sudo?
+ false # Will change on implementation of project level plugins
+ end
+
+ def rubygems_dir
+ Plugin.root
+ end
+
+ def cache_path
+ Plugin.cache
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb
new file mode 100644
index 0000000000..f0e212205f
--- /dev/null
+++ b/lib/bundler/plugin/source_list.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Bundler
+ # SourceList object to be used while parsing the Gemfile, setting the
+ # approptiate options to be used with Source classes for plugin installation
+ module Plugin
+ class SourceList < Bundler::SourceList
+ def add_git_source(options = {})
+ add_source_to_list Plugin::Installer::Git.new(options), git_sources
+ end
+
+ def add_rubygems_source(options = {})
+ add_source_to_list Plugin::Installer::Rubygems.new(options), @rubygems_sources
+ end
+
+ def all_sources
+ path_sources + git_sources + rubygems_sources + [metadata_source]
+ end
+
+ private
+
+ def rubygems_aggregate_class
+ Plugin::Installer::Rubygems
+ end
+ end
+ end
+end
diff --git a/lib/bundler/process_lock.rb b/lib/bundler/process_lock.rb
new file mode 100644
index 0000000000..cba4fcdba5
--- /dev/null
+++ b/lib/bundler/process_lock.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Bundler
+ class ProcessLock
+ def self.lock(bundle_path = Bundler.bundle_path)
+ lock_file_path = File.join(bundle_path, "bundler.lock")
+ has_lock = false
+
+ File.open(lock_file_path, "w") do |f|
+ f.flock(File::LOCK_EX)
+ has_lock = true
+ yield
+ f.flock(File::LOCK_UN)
+ end
+ rescue Errno::EACCES, Errno::ENOLCK, *[SharedHelpers.const_get_safely(:ENOTSUP, Errno)].compact
+ # In the case the user does not have access to
+ # create the lock file or is using NFS where
+ # locks are not available we skip locking.
+ yield
+ ensure
+ FileUtils.rm_f(lock_file_path) if has_lock
+ end
+ end
+end
diff --git a/lib/bundler/psyched_yaml.rb b/lib/bundler/psyched_yaml.rb
new file mode 100644
index 0000000000..e654416a5a
--- /dev/null
+++ b/lib/bundler/psyched_yaml.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# Psych could be a gem, so try to ask for it
+begin
+ gem "psych"
+rescue LoadError
+end if defined?(gem)
+
+# Psych could be in the stdlib
+# but it's too late if Syck is already loaded
+begin
+ require "psych" unless defined?(Syck)
+rescue LoadError
+ # Apparently Psych wasn't available. Oh well.
+end
+
+# At least load the YAML stdlib, whatever that may be
+require "yaml" unless defined?(YAML.dump)
+
+module Bundler
+ # On encountering invalid YAML,
+ # Psych raises Psych::SyntaxError
+ if defined?(::Psych::SyntaxError)
+ YamlLibrarySyntaxError = ::Psych::SyntaxError
+ else # Syck raises ArgumentError
+ YamlLibrarySyntaxError = ::ArgumentError
+ end
+end
+
+require "bundler/deprecate"
+begin
+ Bundler::Deprecate.skip_during do
+ require "rubygems/safe_yaml"
+ end
+rescue LoadError
+ # it's OK if the file isn't there
+end
diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb
new file mode 100644
index 0000000000..23e1234330
--- /dev/null
+++ b/lib/bundler/remote_specification.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require "uri"
+
+module Bundler
+ # Represents a lazily loaded gem specification, where the full specification
+ # is on the source server in rubygems' "quick" index. The proxy object is to
+ # be seeded with what we're given from the source's abbreviated index - the
+ # full specification will only be fetched when necessary.
+ class RemoteSpecification
+ include MatchPlatform
+ include Comparable
+
+ attr_reader :name, :version, :platform
+ attr_writer :dependencies
+ attr_accessor :source, :remote
+
+ def initialize(name, version, platform, spec_fetcher)
+ @name = name
+ @version = Gem::Version.create version
+ @platform = platform
+ @spec_fetcher = spec_fetcher
+ @dependencies = nil
+ end
+
+ # Needed before installs, since the arch matters then and quick
+ # specs don't bother to include the arch in the platform string
+ def fetch_platform
+ @platform = _remote_specification.platform
+ end
+
+ def full_name
+ if platform == Gem::Platform::RUBY || platform.nil?
+ "#{@name}-#{@version}"
+ else
+ "#{@name}-#{@version}-#{platform}"
+ end
+ end
+
+ # Compare this specification against another object. Using sort_obj
+ # is compatible with Gem::Specification and other Bundler or RubyGems
+ # objects. Otherwise, use the default Object comparison.
+ def <=>(other)
+ if other.respond_to?(:sort_obj)
+ sort_obj <=> other.sort_obj
+ else
+ super
+ end
+ end
+
+ # Because Rubyforge cannot be trusted to provide valid specifications
+ # once the remote gem is downloaded, the backend specification will
+ # be swapped out.
+ def __swap__(spec)
+ SharedHelpers.ensure_same_dependencies(self, dependencies, spec.dependencies)
+ @_remote_specification = spec
+ end
+
+ # Create a delegate used for sorting. This strategy is copied from
+ # RubyGems 2.23 and ensures that Bundler's specifications can be
+ # compared and sorted with RubyGems' own specifications.
+ #
+ # @see #<=>
+ # @see Gem::Specification#sort_obj
+ #
+ # @return [Array] an object you can use to compare and sort this
+ # specification against other specifications
+ def sort_obj
+ [@name, @version, @platform == Gem::Platform::RUBY ? -1 : 1]
+ end
+
+ def to_s
+ "#<#{self.class} name=#{name} version=#{version} platform=#{platform}>"
+ end
+
+ def dependencies
+ @dependencies ||= begin
+ deps = method_missing(:dependencies)
+
+ # allow us to handle when the specs dependencies are an array of array of string
+ # see https://github.com/bundler/bundler/issues/5797
+ deps = deps.map {|d| d.is_a?(Gem::Dependency) ? d : Gem::Dependency.new(*d) }
+
+ deps
+ end
+ end
+
+ def git_version
+ return unless loaded_from && source.is_a?(Bundler::Source::Git)
+ " #{source.revision[0..6]}"
+ end
+
+ private
+
+ def to_ary
+ nil
+ end
+
+ def _remote_specification
+ @_remote_specification ||= @spec_fetcher.fetch_spec([@name, @version, @platform])
+ @_remote_specification || raise(GemspecError, "Gemspec data for #{full_name} was" \
+ " missing from the server! Try installing with `--full-index` as a workaround.")
+ end
+
+ def method_missing(method, *args, &blk)
+ _remote_specification.send(method, *args, &blk)
+ end
+
+ def respond_to?(method, include_all = false)
+ super || _remote_specification.respond_to?(method, include_all)
+ end
+ public :respond_to?
+ end
+end
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
new file mode 100644
index 0000000000..545b4cc88a
--- /dev/null
+++ b/lib/bundler/resolver.rb
@@ -0,0 +1,373 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Resolver
+ require "bundler/vendored_molinillo"
+ require "bundler/resolver/spec_group"
+
+ # Figures out the best possible configuration of gems that satisfies
+ # the list of passed dependencies and any child dependencies without
+ # causing any gem activation errors.
+ #
+ # ==== Parameters
+ # *dependencies<Gem::Dependency>:: The list of dependencies to resolve
+ #
+ # ==== Returns
+ # <GemBundle>,nil:: If the list of dependencies can be resolved, a
+ # collection of gemspecs is returned. Otherwise, nil is returned.
+ def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil)
+ platforms = Set.new(platforms) if platforms
+ base = SpecSet.new(base) unless base.is_a?(SpecSet)
+ resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
+ result = resolver.start(requirements)
+ SpecSet.new(result)
+ end
+
+ def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
+ @index = index
+ @source_requirements = source_requirements
+ @base = base
+ @resolver = Molinillo::Resolver.new(self, self)
+ @search_for = {}
+ @base_dg = Molinillo::DependencyGraph.new
+ @base.each do |ls|
+ dep = Dependency.new(ls.name, ls.version)
+ @base_dg.add_vertex(ls.name, DepProxy.new(dep, ls.platform), true)
+ end
+ additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) }
+ @platforms = platforms
+ @gem_version_promoter = gem_version_promoter
+ @allow_bundler_dependency_conflicts = Bundler.feature_flag.allow_bundler_dependency_conflicts?
+ @lockfile_uses_separate_rubygems_sources = Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ @use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major?
+ end
+
+ def start(requirements)
+ @gem_version_promoter.prerelease_specified = @prerelease_specified = {}
+ requirements.each {|dep| @prerelease_specified[dep.name] ||= dep.prerelease? }
+
+ verify_gemfile_dependencies_are_found!(requirements)
+ dg = @resolver.resolve(requirements, @base_dg)
+ dg.map(&:payload).
+ reject {|sg| sg.name.end_with?("\0") }.
+ map(&:to_specs).flatten
+ rescue Molinillo::VersionConflict => e
+ message = version_conflict_message(e)
+ raise VersionConflict.new(e.conflicts.keys.uniq, message)
+ rescue Molinillo::CircularDependencyError => e
+ names = e.dependencies.sort_by(&:name).map {|d| "gem '#{d.name}'" }
+ raise CyclicDependencyError, "Your bundle requires gems that depend" \
+ " on each other, creating an infinite loop. Please remove" \
+ " #{names.count > 1 ? "either " : ""}#{names.join(" or ")}" \
+ " and try again."
+ end
+
+ include Molinillo::UI
+
+ # Conveys debug information to the user.
+ #
+ # @param [Integer] depth the current depth of the resolution process.
+ # @return [void]
+ def debug(depth = 0)
+ return unless debug?
+ debug_info = yield
+ debug_info = debug_info.inspect unless debug_info.is_a?(String)
+ STDERR.puts debug_info.split("\n").map {|s| " " * depth + s }
+ end
+
+ def debug?
+ return @debug_mode if defined?(@debug_mode)
+ @debug_mode = ENV["DEBUG_RESOLVER"] || ENV["DEBUG_RESOLVER_TREE"] || false
+ end
+
+ def before_resolution
+ Bundler.ui.info "Resolving dependencies...", debug?
+ end
+
+ def after_resolution
+ Bundler.ui.info ""
+ end
+
+ def indicate_progress
+ Bundler.ui.info ".", false unless debug?
+ end
+
+ include Molinillo::SpecificationProvider
+
+ def dependencies_for(specification)
+ specification.dependencies_for_activated_platforms
+ end
+
+ def search_for(dependency)
+ platform = dependency.__platform
+ dependency = dependency.dep unless dependency.is_a? Gem::Dependency
+ search = @search_for[dependency] ||= begin
+ index = index_for(dependency)
+ results = index.search(dependency, @base[dependency.name])
+
+ if vertex = @base_dg.vertex_named(dependency.name)
+ locked_requirement = vertex.payload.requirement
+ end
+
+ if !@prerelease_specified[dependency.name] && (!@use_gvp || locked_requirement.nil?)
+ # Move prereleases to the beginning of the list, so they're considered
+ # last during resolution.
+ pre, results = results.partition {|spec| spec.version.prerelease? }
+ results = pre + results
+ end
+
+ spec_groups = if results.any?
+ nested = []
+ results.each do |spec|
+ version, specs = nested.last
+ if version == spec.version
+ specs << spec
+ else
+ nested << [spec.version, [spec]]
+ end
+ end
+ nested.reduce([]) do |groups, (version, specs)|
+ next groups if locked_requirement && !locked_requirement.satisfied_by?(version)
+ spec_group = SpecGroup.new(specs)
+ spec_group.ignores_bundler_dependencies = @allow_bundler_dependency_conflicts
+ groups << spec_group
+ end
+ else
+ []
+ end
+ # GVP handles major itself, but it's still a bit risky to trust it with it
+ # until we get it settled with new behavior. For 2.x it can take over all cases.
+ if !@use_gvp
+ spec_groups
+ else
+ @gem_version_promoter.sort_versions(dependency, spec_groups)
+ end
+ end
+ search.select {|sg| sg.for?(platform) }.each {|sg| sg.activate_platform!(platform) }
+ end
+
+ def index_for(dependency)
+ source = @source_requirements[dependency.name]
+ if source
+ source.specs
+ elsif @lockfile_uses_separate_rubygems_sources
+ Index.build do |idx|
+ if dependency.all_sources
+ dependency.all_sources.each {|s| idx.add_source(s.specs) if s }
+ else
+ idx.add_source @source_requirements[:default].specs
+ end
+ end
+ else
+ @index
+ end
+ end
+
+ def name_for(dependency)
+ dependency.name
+ end
+
+ def name_for_explicit_dependency_source
+ Bundler.default_gemfile.basename.to_s
+ rescue
+ "Gemfile"
+ end
+
+ def name_for_locking_dependency_source
+ Bundler.default_lockfile.basename.to_s
+ rescue
+ "Gemfile.lock"
+ end
+
+ def requirement_satisfied_by?(requirement, activated, spec)
+ return false unless requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec)
+ spec.activate_platform!(requirement.__platform) if !@platforms || @platforms.include?(requirement.__platform)
+ true
+ end
+
+ def relevant_sources_for_vertex(vertex)
+ if vertex.root?
+ [@source_requirements[vertex.name]]
+ elsif @lockfile_uses_separate_rubygems_sources
+ vertex.recursive_predecessors.map do |v|
+ @source_requirements[v.name]
+ end << @source_requirements[:default]
+ end
+ end
+
+ def sort_dependencies(dependencies, activated, conflicts)
+ dependencies.sort_by do |dependency|
+ dependency.all_sources = relevant_sources_for_vertex(activated.vertex_named(dependency.name))
+ name = name_for(dependency)
+ vertex = activated.vertex_named(name)
+ [
+ @base_dg.vertex_named(name) ? 0 : 1,
+ vertex.payload ? 0 : 1,
+ vertex.root? ? 0 : 1,
+ amount_constrained(dependency),
+ conflicts[name] ? 0 : 1,
+ vertex.payload ? 0 : search_for(dependency).count,
+ self.class.platform_sort_key(dependency.__platform),
+ ]
+ end
+ end
+
+ # Sort platforms from most general to most specific
+ def self.sort_platforms(platforms)
+ platforms.sort_by do |platform|
+ platform_sort_key(platform)
+ end
+ end
+
+ def self.platform_sort_key(platform)
+ return ["", "", ""] if Gem::Platform::RUBY == platform
+ platform.to_a.map {|part| part || "" }
+ end
+
+ private
+
+ # returns an integer \in (-\infty, 0]
+ # a number closer to 0 means the dependency is less constraining
+ #
+ # dependencies w/ 0 or 1 possibilities (ignoring version requirements)
+ # are given very negative values, so they _always_ sort first,
+ # before dependencies that are unconstrained
+ def amount_constrained(dependency)
+ @amount_constrained ||= {}
+ @amount_constrained[dependency.name] ||= begin
+ if (base = @base[dependency.name]) && !base.empty?
+ dependency.requirement.satisfied_by?(base.first.version) ? 0 : 1
+ else
+ all = index_for(dependency).search(dependency.name).size
+
+ if all <= 1
+ all - 1_000_000
+ else
+ search = search_for(dependency)
+ search = @prerelease_specified[dependency.name] ? search.count : search.count {|s| !s.version.prerelease? }
+ search - all
+ end
+ end
+ end
+ end
+
+ def verify_gemfile_dependencies_are_found!(requirements)
+ requirements.each do |requirement|
+ name = requirement.name
+ next if name == "bundler"
+ next unless search_for(requirement).empty?
+
+ cache_message = begin
+ " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist?
+ rescue GemfileNotFound
+ nil
+ end
+
+ if (base = @base[name]) && !base.empty?
+ version = base.first.version
+ message = "You have requested:\n" \
+ " #{name} #{requirement.requirement}\n\n" \
+ "The bundle currently has #{name} locked at #{version}.\n" \
+ "Try running `bundle update #{name}`\n\n" \
+ "If you are updating multiple gems in your Gemfile at once,\n" \
+ "try passing them all to `bundle update`"
+ elsif source = @source_requirements[name]
+ specs = source.specs[name]
+ versions_with_platforms = specs.map {|s| [s.version, s.platform] }
+ message = String.new("Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in #{source}#{cache_message}.\n")
+ message << if versions_with_platforms.any?
+ "The source contains '#{name}' at: #{formatted_versions_with_platforms(versions_with_platforms)}"
+ else
+ "The source does not contain any versions of '#{name}'"
+ end
+ else
+ message = "Could not find gem '#{requirement}' in any of the gem sources " \
+ "listed in your Gemfile#{cache_message}."
+ end
+ raise GemNotFound, message
+ end
+ end
+
+ def formatted_versions_with_platforms(versions_with_platforms)
+ version_platform_strs = versions_with_platforms.map do |vwp|
+ version = vwp.first
+ platform = vwp.last
+ version_platform_str = String.new(version.to_s)
+ version_platform_str << " #{platform}" unless platform.nil? || platform == Gem::Platform::RUBY
+ version_platform_str
+ end
+ version_platform_strs.join(", ")
+ end
+
+ def version_conflict_message(e)
+ e.message_with_trees(
+ :solver_name => "Bundler",
+ :possibility_type => "gem",
+ :reduce_trees => lambda do |trees|
+ # called first, because we want to reduce the amount of work required to find maximal empty sets
+ trees = trees.uniq {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } }
+
+ # bail out if tree size is too big for Array#combination to make any sense
+ return trees if trees.size > 15
+ maximal = 1.upto(trees.size).map do |size|
+ trees.map(&:last).flatten(1).combination(size).to_a
+ end.flatten(1).select do |deps|
+ Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement)))
+ end.min_by(&:size)
+ trees.reject! {|t| !maximal.include?(t.last) } if maximal
+
+ trees = trees.sort_by {|t| t.flatten.map(&:to_s) }
+ trees.uniq! {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } }
+
+ trees.sort_by {|t| t.reverse.map(&:name) }
+ end,
+ :printable_requirement => lambda {|req| SharedHelpers.pretty_dependency(req) },
+ :additional_message_for_conflict => lambda do |o, name, conflict|
+ if name == "bundler"
+ o << %(\n Current Bundler version:\n bundler (#{Bundler::VERSION}))
+ other_bundler_required = !conflict.requirement.requirement.satisfied_by?(Gem::Version.new Bundler::VERSION)
+ end
+
+ if name == "bundler" && other_bundler_required
+ o << "\n"
+ o << "This Gemfile requires a different version of Bundler.\n"
+ o << "Perhaps you need to update Bundler by running `gem install bundler`?\n"
+ end
+ if conflict.locked_requirement
+ o << "\n"
+ o << %(Running `bundle update` will rebuild your snapshot from scratch, using only\n)
+ o << %(the gems in your Gemfile, which may resolve the conflict.\n)
+ elsif !conflict.existing
+ o << "\n"
+
+ relevant_sources = if conflict.requirement.source
+ [conflict.requirement.source]
+ elsif conflict.requirement.all_sources
+ conflict.requirement.all_sources
+ elsif @lockfile_uses_separate_rubygems_sources
+ # every conflict should have an explicit group of sources when we
+ # enforce strict pinning
+ raise "no source set for #{conflict}"
+ else
+ []
+ end.compact.map(&:to_s).uniq.sort
+
+ o << "Could not find gem '#{SharedHelpers.pretty_dependency(conflict.requirement)}'"
+ if conflict.requirement_trees.first.size > 1
+ o << ", which is required by "
+ o << "gem '#{SharedHelpers.pretty_dependency(conflict.requirement_trees.first[-2])}',"
+ end
+ o << " "
+
+ o << if relevant_sources.empty?
+ "in any of the sources.\n"
+ else
+ "in any of the relevant sources:\n #{relevant_sources * "\n "}\n"
+ end
+ end
+ end,
+ :version_for_spec => lambda {|spec| spec.version }
+ )
+ end
+ end
+end
diff --git a/lib/bundler/resolver/spec_group.rb b/lib/bundler/resolver/spec_group.rb
new file mode 100644
index 0000000000..34d043aed7
--- /dev/null
+++ b/lib/bundler/resolver/spec_group.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Resolver
+ class SpecGroup
+ include GemHelpers
+
+ attr_accessor :name, :version, :source
+ attr_accessor :ignores_bundler_dependencies
+
+ def initialize(all_specs)
+ raise ArgumentError, "cannot initialize with an empty value" unless exemplary_spec = all_specs.first
+ @name = exemplary_spec.name
+ @version = exemplary_spec.version
+ @source = exemplary_spec.source
+
+ @activated_platforms = []
+ @dependencies = nil
+ @specs = Hash.new do |specs, platform|
+ specs[platform] = select_best_platform_match(all_specs, platform)
+ end
+ @ignores_bundler_dependencies = true
+ end
+
+ def to_specs
+ @activated_platforms.map do |p|
+ next unless s = @specs[p]
+ lazy_spec = LazySpecification.new(name, version, s.platform, source)
+ lazy_spec.dependencies.replace s.dependencies
+ lazy_spec
+ end.compact
+ end
+
+ def activate_platform!(platform)
+ return unless for?(platform)
+ return if @activated_platforms.include?(platform)
+ @activated_platforms << platform
+ end
+
+ def for?(platform)
+ spec = @specs[platform]
+ !spec.nil?
+ end
+
+ def to_s
+ @to_s ||= "#{name} (#{version})"
+ end
+
+ def dependencies_for_activated_platforms
+ dependencies = @activated_platforms.map {|p| __dependencies[p] }
+ metadata_dependencies = @activated_platforms.map do |platform|
+ metadata_dependencies(@specs[platform], platform)
+ end
+ dependencies.concat(metadata_dependencies).flatten
+ end
+
+ def ==(other)
+ return unless other.is_a?(SpecGroup)
+ name == other.name &&
+ version == other.version &&
+ source == other.source
+ end
+
+ def eql?(other)
+ name.eql?(other.name) &&
+ version.eql?(other.version) &&
+ source.eql?(other.source)
+ end
+
+ def hash
+ to_s.hash ^ source.hash
+ end
+
+ private
+
+ def __dependencies
+ @dependencies = Hash.new do |dependencies, platform|
+ dependencies[platform] = []
+ if spec = @specs[platform]
+ spec.dependencies.each do |dep|
+ next if dep.type == :development
+ next if @ignores_bundler_dependencies && dep.name == "bundler".freeze
+ dependencies[platform] << DepProxy.new(dep, platform)
+ end
+ end
+ dependencies[platform]
+ end
+ end
+
+ def metadata_dependencies(spec, platform)
+ return [] unless spec
+ # Only allow endpoint specifications since they won't hit the network to
+ # fetch the full gemspec when calling required_ruby_version
+ return [] if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification)
+ dependencies = []
+ if !spec.required_ruby_version.nil? && !spec.required_ruby_version.none?
+ dependencies << DepProxy.new(Gem::Dependency.new("ruby\0", spec.required_ruby_version), platform)
+ end
+ if !spec.required_rubygems_version.nil? && !spec.required_rubygems_version.none?
+ dependencies << DepProxy.new(Gem::Dependency.new("rubygems\0", spec.required_rubygems_version), platform)
+ end
+ dependencies
+ end
+ end
+ end
+end
diff --git a/lib/bundler/retry.rb b/lib/bundler/retry.rb
new file mode 100644
index 0000000000..244606dcc9
--- /dev/null
+++ b/lib/bundler/retry.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Bundler
+ # General purpose class for retrying code that may fail
+ class Retry
+ attr_accessor :name, :total_runs, :current_run
+
+ class << self
+ def default_attempts
+ default_retries + 1
+ end
+ alias_method :attempts, :default_attempts
+
+ def default_retries
+ Bundler.settings[:retry]
+ end
+ end
+
+ def initialize(name, exceptions = nil, retries = self.class.default_retries)
+ @name = name
+ @retries = retries
+ @exceptions = Array(exceptions) || []
+ @total_runs = @retries + 1 # will run once, then upto attempts.times
+ end
+
+ def attempt(&block)
+ @current_run = 0
+ @failed = false
+ @error = nil
+ run(&block) while keep_trying?
+ @result
+ end
+ alias_method :attempts, :attempt
+
+ private
+
+ def run(&block)
+ @failed = false
+ @current_run += 1
+ @result = block.call
+ rescue => e
+ fail_attempt(e)
+ end
+
+ def fail_attempt(e)
+ @failed = true
+ if last_attempt? || @exceptions.any? {|k| e.is_a?(k) }
+ Bundler.ui.info "" unless Bundler.ui.debug?
+ raise e
+ end
+ return true unless name
+ Bundler.ui.info "" unless Bundler.ui.debug? # Add new line incase dots preceded this
+ Bundler.ui.warn "Retrying #{name} due to error (#{current_run.next}/#{total_runs}): #{e.class} #{e.message}", Bundler.ui.debug?
+ end
+
+ def keep_trying?
+ return true if current_run.zero?
+ return false if last_attempt?
+ return true if @failed
+ end
+
+ def last_attempt?
+ current_run >= total_runs
+ end
+ end
+end
diff --git a/lib/bundler/ruby_dsl.rb b/lib/bundler/ruby_dsl.rb
new file mode 100644
index 0000000000..f6ba220cd5
--- /dev/null
+++ b/lib/bundler/ruby_dsl.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Bundler
+ module RubyDsl
+ def ruby(*ruby_version)
+ options = ruby_version.last.is_a?(Hash) ? ruby_version.pop : {}
+ ruby_version.flatten!
+ raise GemfileError, "Please define :engine_version" if options[:engine] && options[:engine_version].nil?
+ raise GemfileError, "Please define :engine" if options[:engine_version] && options[:engine].nil?
+
+ if options[:engine] == "ruby" && options[:engine_version] &&
+ ruby_version != Array(options[:engine_version])
+ raise GemfileEvalError, "ruby_version must match the :engine_version for MRI"
+ end
+ @ruby_version = RubyVersion.new(ruby_version, options[:patchlevel], options[:engine], options[:engine_version])
+ end
+ end
+end
diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb
new file mode 100644
index 0000000000..e6c31a94c9
--- /dev/null
+++ b/lib/bundler/ruby_version.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+module Bundler
+ class RubyVersion
+ attr_reader :versions,
+ :patchlevel,
+ :engine,
+ :engine_versions,
+ :gem_version,
+ :engine_gem_version
+
+ def initialize(versions, patchlevel, engine, engine_version)
+ # The parameters to this method must satisfy the
+ # following constraints, which are verified in
+ # the DSL:
+ #
+ # * If an engine is specified, an engine version
+ # must also be specified
+ # * If an engine version is specified, an engine
+ # must also be specified
+ # * If the engine is "ruby", the engine version
+ # must not be specified, or the engine version
+ # specified must match the version.
+
+ @versions = Array(versions).map do |v|
+ op, v = Gem::Requirement.parse(v)
+ op == "=" ? v.to_s : "#{op} #{v}"
+ end
+
+ @gem_version = Gem::Requirement.create(@versions.first).requirements.first.last
+ @input_engine = engine && engine.to_s
+ @engine = engine && engine.to_s || "ruby"
+ @engine_versions = (engine_version && Array(engine_version)) || @versions
+ @engine_gem_version = Gem::Requirement.create(@engine_versions.first).requirements.first.last
+ @patchlevel = patchlevel
+ end
+
+ def to_s(versions = self.versions)
+ output = String.new("ruby #{versions_string(versions)}")
+ output << "p#{patchlevel}" if patchlevel
+ output << " (#{engine} #{versions_string(engine_versions)})" unless engine == "ruby"
+
+ output
+ end
+
+ # @private
+ PATTERN = /
+ ruby\s
+ ([\d.]+) # ruby version
+ (?:p(-?\d+))? # optional patchlevel
+ (?:\s\((\S+)\s(.+)\))? # optional engine info
+ /xo
+
+ # Returns a RubyVersion from the given string.
+ # @param [String] the version string to match.
+ # @return [RubyVersion,Nil] The version if the string is a valid RubyVersion
+ # description, and nil otherwise.
+ def self.from_string(string)
+ new($1, $2, $3, $4) if string =~ PATTERN
+ end
+
+ def single_version_string
+ to_s(gem_version)
+ end
+
+ def ==(other)
+ versions == other.versions &&
+ engine == other.engine &&
+ engine_versions == other.engine_versions &&
+ patchlevel == other.patchlevel
+ end
+
+ def host
+ @host ||= [
+ RbConfig::CONFIG["host_cpu"],
+ RbConfig::CONFIG["host_vendor"],
+ RbConfig::CONFIG["host_os"]
+ ].join("-")
+ end
+
+ # Returns a tuple of these things:
+ # [diff, this, other]
+ # The priority of attributes are
+ # 1. engine
+ # 2. ruby_version
+ # 3. engine_version
+ def diff(other)
+ raise ArgumentError, "Can only diff with a RubyVersion, not a #{other.class}" unless other.is_a?(RubyVersion)
+ if engine != other.engine && @input_engine
+ [:engine, engine, other.engine]
+ elsif versions.empty? || !matches?(versions, other.gem_version)
+ [:version, versions_string(versions), versions_string(other.versions)]
+ elsif @input_engine && !matches?(engine_versions, other.engine_gem_version)
+ [:engine_version, versions_string(engine_versions), versions_string(other.engine_versions)]
+ elsif patchlevel && (!patchlevel.is_a?(String) || !other.patchlevel.is_a?(String) || !matches?(patchlevel, other.patchlevel))
+ [:patchlevel, patchlevel, other.patchlevel]
+ end
+ end
+
+ def versions_string(versions)
+ Array(versions).join(", ")
+ end
+
+ def self.system
+ ruby_engine = if defined?(RUBY_ENGINE) && !RUBY_ENGINE.nil?
+ RUBY_ENGINE.dup
+ else
+ # not defined in ruby 1.8.7
+ "ruby"
+ end
+ # :sob: mocking RUBY_VERSION breaks stuff on 1.8.7
+ ruby_version = ENV.fetch("BUNDLER_SPEC_RUBY_VERSION") { RUBY_VERSION }.dup
+ ruby_engine_version = case ruby_engine
+ when "ruby"
+ ruby_version
+ when "rbx"
+ Rubinius::VERSION.dup
+ when "jruby"
+ JRUBY_VERSION.dup
+ else
+ RUBY_ENGINE_VERSION.dup
+ end
+ patchlevel = RUBY_PATCHLEVEL.to_s
+
+ @ruby_version ||= RubyVersion.new(ruby_version, patchlevel, ruby_engine, ruby_engine_version)
+ end
+
+ def to_gem_version_with_patchlevel
+ @gem_version_with_patch ||= begin
+ Gem::Version.create("#{@gem_version}.#{@patchlevel}")
+ rescue ArgumentError
+ @gem_version
+ end
+ end
+
+ def exact?
+ return @exact if defined?(@exact)
+ @exact = versions.all? {|v| Gem::Requirement.create(v).exact? }
+ end
+
+ private
+
+ def matches?(requirements, version)
+ # Handles RUBY_PATCHLEVEL of -1 for instances like ruby-head
+ return requirements == version if requirements.to_s == "-1" || version.to_s == "-1"
+
+ Array(requirements).all? do |requirement|
+ Gem::Requirement.create(requirement).satisfied_by?(Gem::Version.create(version))
+ end
+ end
+ end
+end
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
new file mode 100644
index 0000000000..e9f0eac355
--- /dev/null
+++ b/lib/bundler/rubygems_ext.rb
@@ -0,0 +1,210 @@
+# frozen_string_literal: true
+
+require "pathname"
+
+if defined?(Gem::QuickLoader)
+ # Gem Prelude makes me a sad panda :'(
+ Gem::QuickLoader.load_full_rubygems_library
+end
+
+require "rubygems"
+require "rubygems/specification"
+
+begin
+ # Possible use in Gem::Specification#source below and require
+ # shouldn't be deferred.
+ require "rubygems/source"
+rescue LoadError
+ # Not available before RubyGems 2.0.0, ignore
+ nil
+end
+
+require "bundler/match_platform"
+
+module Gem
+ @loaded_stacks = Hash.new {|h, k| h[k] = [] }
+
+ class Specification
+ attr_accessor :remote, :location, :relative_loaded_from
+
+ if instance_methods(false).map(&:to_sym).include?(:source)
+ remove_method :source
+ attr_writer :source
+ def source
+ (defined?(@source) && @source) || Gem::Source::Installed.new
+ end
+ else
+ attr_accessor :source
+ end
+
+ alias_method :rg_full_gem_path, :full_gem_path
+ alias_method :rg_loaded_from, :loaded_from
+
+ attr_writer :full_gem_path unless instance_methods.include?(:full_gem_path=)
+
+ def full_gem_path
+ # this cannot check source.is_a?(Bundler::Plugin::API::Source)
+ # because that _could_ trip the autoload, and if there are unresolved
+ # gems at that time, this method could be called inside another require,
+ # thus raising with that constant being undefined. Better to check a method
+ if source.respond_to?(:path) || (source.respond_to?(:bundler_plugin_api_source?) && source.bundler_plugin_api_source?)
+ Pathname.new(loaded_from).dirname.expand_path(source.root).to_s.untaint
+ else
+ rg_full_gem_path
+ end
+ end
+
+ def loaded_from
+ if relative_loaded_from
+ source.path.join(relative_loaded_from).to_s
+ else
+ rg_loaded_from
+ end
+ end
+
+ def load_paths
+ return full_require_paths if respond_to?(:full_require_paths)
+
+ require_paths.map do |require_path|
+ if require_path.include?(full_gem_path)
+ require_path
+ else
+ File.join(full_gem_path, require_path)
+ end
+ end
+ end
+
+ if method_defined?(:extension_dir)
+ alias_method :rg_extension_dir, :extension_dir
+ def extension_dir
+ @bundler_extension_dir ||= if source.respond_to?(:extension_dir_name)
+ File.expand_path(File.join(extensions_dir, source.extension_dir_name))
+ else
+ rg_extension_dir
+ end
+ end
+ end
+
+ # RubyGems 1.8+ used only.
+ methods = instance_methods(false)
+ gem_dir = methods.first.is_a?(String) ? "gem_dir" : :gem_dir
+ remove_method :gem_dir if methods.include?(gem_dir)
+ def gem_dir
+ full_gem_path
+ end
+
+ def groups
+ @groups ||= []
+ end
+
+ def git_version
+ return unless loaded_from && source.is_a?(Bundler::Source::Git)
+ " #{source.revision[0..6]}"
+ end
+
+ def to_gemfile(path = nil)
+ gemfile = String.new("source 'https://rubygems.org'\n")
+ gemfile << dependencies_to_gemfile(nondevelopment_dependencies)
+ unless development_dependencies.empty?
+ gemfile << "\n"
+ gemfile << dependencies_to_gemfile(development_dependencies, :development)
+ end
+ gemfile
+ end
+
+ def nondevelopment_dependencies
+ dependencies - development_dependencies
+ end
+
+ private
+
+ def dependencies_to_gemfile(dependencies, group = nil)
+ gemfile = String.new
+ if dependencies.any?
+ gemfile << "group :#{group} do\n" if group
+ dependencies.each do |dependency|
+ gemfile << " " if group
+ gemfile << %(gem "#{dependency.name}")
+ req = dependency.requirements_list.first
+ gemfile << %(, "#{req}") if req
+ gemfile << "\n"
+ end
+ gemfile << "end\n" if group
+ end
+ gemfile
+ end
+ end
+
+ class Dependency
+ attr_accessor :source, :groups, :all_sources
+
+ alias_method :eql?, :==
+
+ def encode_with(coder)
+ to_yaml_properties.each do |ivar|
+ coder[ivar.to_s.sub(/^@/, "")] = instance_variable_get(ivar)
+ end
+ end
+
+ def to_yaml_properties
+ instance_variables.reject {|p| ["@source", "@groups", "@all_sources"].include?(p.to_s) }
+ end
+
+ def to_lock
+ out = String.new(" #{name}")
+ unless requirement.none?
+ reqs = requirement.requirements.map {|o, v| "#{o} #{v}" }.sort.reverse
+ out << " (#{reqs.join(", ")})"
+ end
+ out
+ end
+
+ # Backport of performance enhancement added to RubyGems 1.4
+ def matches_spec?(spec)
+ # name can be a Regexp, so use ===
+ return false unless name === spec.name
+ return true if requirement.none?
+
+ requirement.satisfied_by?(spec.version)
+ end unless allocate.respond_to?(:matches_spec?)
+ end
+
+ class Requirement
+ # Backport of performance enhancement added to RubyGems 1.4
+ def none?
+ # note that it might be tempting to replace with with RubyGems 2.0's
+ # improved implementation. Don't. It requires `DefaultRequirement` to be
+ # defined, and more importantantly, these overrides are not used when the
+ # running RubyGems defines these methods
+ to_s == ">= 0"
+ end unless allocate.respond_to?(:none?)
+
+ # Backport of performance enhancement added to RubyGems 2.2
+ def exact?
+ return false unless @requirements.size == 1
+ @requirements[0][0] == "="
+ end unless allocate.respond_to?(:exact?)
+ end
+
+ class Platform
+ JAVA = Gem::Platform.new("java") unless defined?(JAVA)
+ MSWIN = Gem::Platform.new("mswin32") unless defined?(MSWIN)
+ MSWIN64 = Gem::Platform.new("mswin64") unless defined?(MSWIN64)
+ MINGW = Gem::Platform.new("x86-mingw32") unless defined?(MINGW)
+ X64_MINGW = Gem::Platform.new("x64-mingw32") unless defined?(X64_MINGW)
+
+ undef_method :hash if method_defined? :hash
+ def hash
+ @cpu.hash ^ @os.hash ^ @version.hash
+ end
+
+ undef_method :eql? if method_defined? :eql?
+ alias_method :eql?, :==
+ end
+end
+
+module Gem
+ class Specification
+ include ::Bundler::MatchPlatform
+ end
+end
diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb
new file mode 100644
index 0000000000..2b7fa8e0f6
--- /dev/null
+++ b/lib/bundler/rubygems_gem_installer.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+require "rubygems/installer"
+
+module Bundler
+ class RubyGemsGemInstaller < Gem::Installer
+ unless respond_to?(:at)
+ def self.at(*args)
+ new(*args)
+ end
+ end
+
+ def check_executable_overwrite(filename)
+ # Bundler needs to install gems regardless of binstub overwriting
+ end
+
+ def pre_install_checks
+ super && validate_bundler_checksum(options[:bundler_expected_checksum])
+ end
+
+ def build_extensions
+ extension_cache_path = options[:bundler_extension_cache_path]
+ return super unless extension_cache_path && extension_dir = Bundler.rubygems.spec_extension_dir(spec)
+
+ extension_dir = Pathname.new(extension_dir)
+ build_complete = SharedHelpers.filesystem_access(extension_cache_path.join("gem.build_complete"), :read, &:file?)
+ if build_complete && !options[:force]
+ SharedHelpers.filesystem_access(extension_dir.parent, &:mkpath)
+ SharedHelpers.filesystem_access(extension_cache_path) do
+ FileUtils.cp_r extension_cache_path, spec.extension_dir
+ end
+ else
+ super
+ if extension_dir.directory? # not made for gems without extensions
+ SharedHelpers.filesystem_access(extension_cache_path.parent, &:mkpath)
+ SharedHelpers.filesystem_access(extension_cache_path) do
+ FileUtils.cp_r extension_dir, extension_cache_path
+ end
+ end
+ end
+ end
+
+ private
+
+ def validate_bundler_checksum(checksum)
+ return true if Bundler.settings[:disable_checksum_validation]
+ return true unless checksum
+ return true unless source = @package.instance_variable_get(:@gem)
+ return true unless source.respond_to?(:with_read_io)
+ digest = source.with_read_io do |io|
+ digest = SharedHelpers.digest(:SHA256).new
+ digest << io.read(16_384) until io.eof?
+ io.rewind
+ send(checksum_type(checksum), digest)
+ end
+ unless digest == checksum
+ raise SecurityError, <<-MESSAGE
+ Bundler cannot continue installing #{spec.name} (#{spec.version}).
+ The checksum for the downloaded `#{spec.full_name}.gem` does not match \
+ the checksum given by the server. This means the contents of the downloaded \
+ gem is different from what was uploaded to the server, and could be a potential security issue.
+
+ To resolve this issue:
+ 1. delete the downloaded gem located at: `#{spec.gem_dir}/#{spec.full_name}.gem`
+ 2. run `bundle install`
+
+ If you wish to continue installing the downloaded gem, and are certain it does not pose a \
+ security issue despite the mismatching checksum, do the following:
+ 1. run `bundle config disable_checksum_validation true` to turn off checksum verification
+ 2. run `bundle install`
+
+ (More info: The expected SHA256 checksum was #{checksum.inspect}, but the \
+ checksum for the downloaded gem was #{digest.inspect}.)
+ MESSAGE
+ end
+ true
+ end
+
+ def checksum_type(checksum)
+ case checksum.length
+ when 64 then :hexdigest!
+ when 44 then :base64digest!
+ else raise InstallError, "The given checksum for #{spec.full_name} (#{checksum.inspect}) is not a valid SHA256 hexdigest nor base64digest"
+ end
+ end
+
+ def hexdigest!(digest)
+ digest.hexdigest!
+ end
+
+ def base64digest!(digest)
+ if digest.respond_to?(:base64digest!)
+ digest.base64digest!
+ else
+ [digest.digest!].pack("m0")
+ end
+ end
+ end
+end
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
new file mode 100644
index 0000000000..783d106e7b
--- /dev/null
+++ b/lib/bundler/rubygems_integration.rb
@@ -0,0 +1,898 @@
+# frozen_string_literal: true
+
+require "monitor"
+require "rubygems"
+require "rubygems/config_file"
+
+module Bundler
+ class RubygemsIntegration
+ if defined?(Gem::Ext::Builder::CHDIR_MONITOR)
+ EXT_LOCK = Gem::Ext::Builder::CHDIR_MONITOR
+ else
+ EXT_LOCK = Monitor.new
+ end
+
+ def self.version
+ @version ||= Gem::Version.new(Gem::VERSION)
+ end
+
+ def self.provides?(req_str)
+ Gem::Requirement.new(req_str).satisfied_by?(version)
+ end
+
+ def initialize
+ @replaced_methods = {}
+ end
+
+ def version
+ self.class.version
+ end
+
+ def provides?(req_str)
+ self.class.provides?(req_str)
+ end
+
+ def build_args
+ Gem::Command.build_args
+ end
+
+ def build_args=(args)
+ Gem::Command.build_args = args
+ end
+
+ def load_path_insert_index
+ Gem.load_path_insert_index
+ end
+
+ def loaded_specs(name)
+ Gem.loaded_specs[name]
+ end
+
+ def mark_loaded(spec)
+ if spec.respond_to?(:activated=)
+ current = Gem.loaded_specs[spec.name]
+ current.activated = false if current
+ spec.activated = true
+ end
+ Gem.loaded_specs[spec.name] = spec
+ end
+
+ def validate(spec)
+ Bundler.ui.silence { spec.validate(false) }
+ rescue Gem::InvalidSpecificationException => e
+ error_message = "The gemspec at #{spec.loaded_from} is not valid. Please fix this gemspec.\n" \
+ "The validation error was '#{e.message}'\n"
+ raise Gem::InvalidSpecificationException.new(error_message)
+ rescue Errno::ENOENT
+ nil
+ end
+
+ def set_installed_by_version(spec, installed_by_version = Gem::VERSION)
+ return unless spec.respond_to?(:installed_by_version=)
+ spec.installed_by_version = Gem::Version.create(installed_by_version)
+ end
+
+ def spec_missing_extensions?(spec, default = true)
+ return spec.missing_extensions? if spec.respond_to?(:missing_extensions?)
+
+ return false if spec_default_gem?(spec)
+ return false if spec.extensions.empty?
+
+ default
+ end
+
+ def spec_default_gem?(spec)
+ spec.respond_to?(:default_gem?) && spec.default_gem?
+ end
+
+ def spec_matches_for_glob(spec, glob)
+ return spec.matches_for_glob(glob) if spec.respond_to?(:matches_for_glob)
+
+ spec.load_paths.map do |lp|
+ Dir["#{lp}/#{glob}#{suffix_pattern}"]
+ end.flatten(1)
+ end
+
+ def spec_extension_dir(spec)
+ return unless spec.respond_to?(:extension_dir)
+ spec.extension_dir
+ end
+
+ def stub_set_spec(stub, spec)
+ stub.instance_variable_set(:@spec, spec)
+ end
+
+ def path(obj)
+ obj.to_s
+ end
+
+ def platforms
+ return [Gem::Platform::RUBY] if Bundler.settings[:force_ruby_platform]
+ Gem.platforms
+ end
+
+ def configuration
+ require "bundler/psyched_yaml"
+ Gem.configuration
+ rescue Gem::SystemExitException, LoadError => e
+ Bundler.ui.error "#{e.class}: #{e.message}"
+ Bundler.ui.trace e
+ raise
+ rescue YamlLibrarySyntaxError => e
+ raise YamlSyntaxError.new(e, "Your RubyGems configuration, which is " \
+ "usually located in ~/.gemrc, contains invalid YAML syntax.")
+ end
+
+ def ruby_engine
+ Gem.ruby_engine
+ end
+
+ def read_binary(path)
+ Gem.read_binary(path)
+ end
+
+ def inflate(obj)
+ if defined?(Gem::Util)
+ Gem::Util.inflate(obj)
+ else
+ Gem.inflate(obj)
+ end
+ end
+
+ def sources=(val)
+ # Gem.configuration creates a new Gem::ConfigFile, which by default will read ~/.gemrc
+ # If that file exists, its settings (including sources) will overwrite the values we
+ # are about to set here. In order to avoid that, we force memoizing the config file now.
+ configuration
+
+ Gem.sources = val
+ end
+
+ def sources
+ Gem.sources
+ end
+
+ def gem_dir
+ Gem.dir
+ end
+
+ def gem_bindir
+ Gem.bindir
+ end
+
+ def user_home
+ Gem.user_home
+ end
+
+ def gem_path
+ Gem.path
+ end
+
+ def reset
+ Gem::Specification.reset
+ end
+
+ def post_reset_hooks
+ Gem.post_reset_hooks
+ end
+
+ def suffix_pattern
+ Gem.suffix_pattern
+ end
+
+ def gem_cache
+ gem_path.map {|p| File.expand_path("cache", p) }
+ end
+
+ def spec_cache_dirs
+ @spec_cache_dirs ||= begin
+ dirs = gem_path.map {|dir| File.join(dir, "specifications") }
+ dirs << Gem.spec_cache_dir if Gem.respond_to?(:spec_cache_dir) # Not in RubyGems 2.0.3 or earlier
+ dirs.uniq.select {|dir| File.directory? dir }
+ end
+ end
+
+ def marshal_spec_dir
+ Gem::MARSHAL_SPEC_DIR
+ end
+
+ def config_map
+ Gem::ConfigMap
+ end
+
+ def repository_subdirectories
+ %w[cache doc gems specifications]
+ end
+
+ def clear_paths
+ Gem.clear_paths
+ end
+
+ def bin_path(gem, bin, ver)
+ Gem.bin_path(gem, bin, ver)
+ end
+
+ def path_separator
+ File::PATH_SEPARATOR
+ end
+
+ def preserve_paths
+ # this is a no-op outside of RubyGems 1.8
+ yield
+ end
+
+ def loaded_gem_paths
+ # RubyGems 2.2+ can put binary extension into dedicated folders,
+ # therefore use RubyGems facilities to obtain their load paths.
+ if Gem::Specification.method_defined? :full_require_paths
+ loaded_gem_paths = Gem.loaded_specs.map {|_, s| s.full_require_paths }
+ loaded_gem_paths.flatten
+ else
+ $LOAD_PATH.select do |p|
+ Bundler.rubygems.gem_path.any? {|gp| p =~ /^#{Regexp.escape(gp)}/ }
+ end
+ end
+ end
+
+ def load_plugins
+ Gem.load_plugins if Gem.respond_to?(:load_plugins)
+ end
+
+ def load_plugin_files(files)
+ Gem.load_plugin_files(files) if Gem.respond_to?(:load_plugin_files)
+ end
+
+ def ui=(obj)
+ Gem::DefaultUserInteraction.ui = obj
+ end
+
+ def ext_lock
+ EXT_LOCK
+ end
+
+ def fetch_specs(all, pre, &blk)
+ require "rubygems/spec_fetcher"
+ specs = Gem::SpecFetcher.new.list(all, pre)
+ specs.each { yield } if block_given?
+ specs
+ end
+
+ def fetch_prerelease_specs
+ fetch_specs(false, true)
+ rescue Gem::RemoteFetcher::FetchError
+ {} # if we can't download them, there aren't any
+ end
+
+ # TODO: This is for older versions of RubyGems... should we support the
+ # X-Gemfile-Source header on these old versions?
+ # Maybe the newer implementation will work on older RubyGems?
+ # It seems difficult to keep this implementation and still send the header.
+ def fetch_all_remote_specs(remote)
+ old_sources = Bundler.rubygems.sources
+ Bundler.rubygems.sources = [remote.uri.to_s]
+ # Fetch all specs, minus prerelease specs
+ spec_list = fetch_specs(true, false)
+ # Then fetch the prerelease specs
+ fetch_prerelease_specs.each {|k, v| spec_list[k].concat(v) }
+
+ spec_list.values.first
+ ensure
+ Bundler.rubygems.sources = old_sources
+ end
+
+ def with_build_args(args)
+ ext_lock.synchronize do
+ old_args = build_args
+ begin
+ self.build_args = args
+ yield
+ ensure
+ self.build_args = old_args
+ end
+ end
+ end
+
+ def install_with_build_args(args)
+ with_build_args(args) { yield }
+ end
+
+ def gem_from_path(path, policy = nil)
+ require "rubygems/format"
+ Gem::Format.from_file_by_path(path, policy)
+ end
+
+ def spec_from_gem(path, policy = nil)
+ require "rubygems/security"
+ require "bundler/psyched_yaml"
+ gem_from_path(path, security_policies[policy]).spec
+ rescue Gem::Package::FormatError
+ raise GemspecError, "Could not read gem at #{path}. It may be corrupted."
+ rescue Exception, Gem::Exception, Gem::Security::Exception => e
+ if e.is_a?(Gem::Security::Exception) ||
+ e.message =~ /unknown trust policy|unsigned gem/i ||
+ e.message =~ /couldn't verify (meta)?data signature/i
+ raise SecurityError,
+ "The gem #{File.basename(path, ".gem")} can't be installed because " \
+ "the security policy didn't allow it, with the message: #{e.message}"
+ else
+ raise e
+ end
+ end
+
+ def build(spec, skip_validation = false)
+ require "rubygems/builder"
+ Gem::Builder.new(spec).build
+ end
+
+ def build_gem(gem_dir, spec)
+ build(spec)
+ end
+
+ def download_gem(spec, uri, path)
+ uri = Bundler.settings.mirror_for(uri)
+ fetcher = Gem::RemoteFetcher.new(configuration[:http_proxy])
+ Bundler::Retry.new("download gem from #{uri}").attempts do
+ fetcher.download(spec, uri, path)
+ end
+ end
+
+ def security_policy_keys
+ %w[High Medium Low AlmostNo No].map {|level| "#{level}Security" }
+ end
+
+ def security_policies
+ @security_policies ||= begin
+ require "rubygems/security"
+ Gem::Security::Policies
+ rescue LoadError, NameError
+ {}
+ end
+ end
+
+ def reverse_rubygems_kernel_mixin
+ # Disable rubygems' gem activation system
+ kernel = (class << ::Kernel; self; end)
+ [kernel, ::Kernel].each do |k|
+ if k.private_method_defined?(:gem_original_require)
+ redefine_method(k, :require, k.instance_method(:gem_original_require))
+ end
+ end
+ end
+
+ def binstubs_call_gem?
+ true
+ end
+
+ def stubs_provide_full_functionality?
+ false
+ end
+
+ def replace_gem(specs, specs_by_name)
+ reverse_rubygems_kernel_mixin
+
+ executables = nil
+
+ kernel = (class << ::Kernel; self; end)
+ [kernel, ::Kernel].each do |kernel_class|
+ redefine_method(kernel_class, :gem) do |dep, *reqs|
+ executables ||= specs.map(&:executables).flatten if ::Bundler.rubygems.binstubs_call_gem?
+ if executables && executables.include?(File.basename(caller.first.split(":").first))
+ break
+ end
+
+ reqs.pop if reqs.last.is_a?(Hash)
+
+ unless dep.respond_to?(:name) && dep.respond_to?(:requirement)
+ dep = Gem::Dependency.new(dep, reqs)
+ end
+
+ if spec = specs_by_name[dep.name]
+ return true if dep.matches_spec?(spec)
+ end
+
+ message = if spec.nil?
+ "#{dep.name} is not part of the bundle." \
+ " Add it to your #{Bundler.default_gemfile.basename}."
+ else
+ "can't activate #{dep}, already activated #{spec.full_name}. " \
+ "Make sure all dependencies are added to Gemfile."
+ end
+
+ e = Gem::LoadError.new(message)
+ e.name = dep.name
+ if e.respond_to?(:requirement=)
+ e.requirement = dep.requirement
+ elsif e.respond_to?(:version_requirement=)
+ e.version_requirement = dep.requirement
+ end
+ raise e
+ end
+
+ # backwards compatibility shim, see https://github.com/bundler/bundler/issues/5102
+ kernel_class.send(:public, :gem) if Bundler.feature_flag.setup_makes_kernel_gem_public?
+ end
+ end
+
+ def stub_source_index(specs)
+ Gem::SourceIndex.send(:alias_method, :old_initialize, :initialize)
+ redefine_method(Gem::SourceIndex, :initialize) do |*args|
+ @gems = {}
+ # You're looking at this thinking: Oh! This is how I make those
+ # rubygems deprecations go away!
+ #
+ # You'd be correct BUT using of this method in production code
+ # must be approved by the rubygems team itself!
+ #
+ # This is your warning. If you use this and don't have approval
+ # we can't protect you.
+ #
+ Deprecate.skip_during do
+ self.spec_dirs = *args
+ add_specs(*specs)
+ end
+ end
+ end
+
+ # Used to make bin stubs that are not created by bundler work
+ # under bundler. The new Gem.bin_path only considers gems in
+ # +specs+
+ def replace_bin_path(specs, specs_by_name)
+ gem_class = (class << Gem; self; end)
+
+ redefine_method(gem_class, :find_spec_for_exe) do |gem_name, *args|
+ exec_name = args.first
+
+ spec_with_name = specs_by_name[gem_name]
+ spec = if exec_name
+ if spec_with_name && spec_with_name.executables.include?(exec_name)
+ spec_with_name
+ else
+ specs.find {|s| s.executables.include?(exec_name) }
+ end
+ else
+ spec_with_name
+ end
+
+ unless spec
+ message = "can't find executable #{exec_name} for gem #{gem_name}"
+ if !exec_name || spec_with_name.nil?
+ message += ". #{gem_name} is not currently included in the bundle, " \
+ "perhaps you meant to add it to your #{Bundler.default_gemfile.basename}?"
+ end
+ raise Gem::Exception, message
+ end
+
+ raise Gem::Exception, "no default executable for #{spec.full_name}" unless exec_name ||= spec.default_executable
+
+ unless spec.name == gem_name
+ Bundler::SharedHelpers.major_deprecation 2,
+ "Bundler is using a binstub that was created for a different gem (#{spec.name}).\n" \
+ "You should run `bundle binstub #{gem_name}` " \
+ "to work around a system/bundle conflict."
+ end
+ spec
+ end
+
+ redefine_method(gem_class, :activate_bin_path) do |name, *args|
+ exec_name = args.first
+ return ENV["BUNDLE_BIN_PATH"] if exec_name == "bundle"
+
+ # Copy of Rubygems activate_bin_path impl
+ requirement = args.last
+ spec = find_spec_for_exe name, exec_name, [requirement]
+
+ gem_bin = File.join(spec.full_gem_path, spec.bindir, exec_name)
+ gem_from_path_bin = File.join(File.dirname(spec.loaded_from), spec.bindir, exec_name)
+ File.exist?(gem_bin) ? gem_bin : gem_from_path_bin
+ end
+
+ redefine_method(gem_class, :bin_path) do |name, *args|
+ exec_name = args.first
+ return ENV["BUNDLE_BIN_PATH"] if exec_name == "bundle"
+
+ spec = find_spec_for_exe(name, *args)
+ exec_name ||= spec.default_executable
+
+ gem_bin = File.join(spec.full_gem_path, spec.bindir, exec_name)
+ gem_from_path_bin = File.join(File.dirname(spec.loaded_from), spec.bindir, exec_name)
+ File.exist?(gem_bin) ? gem_bin : gem_from_path_bin
+ end
+ end
+
+ # Because Bundler has a static view of what specs are available,
+ # we don't #refresh, so stub it out.
+ def replace_refresh
+ gem_class = (class << Gem; self; end)
+ redefine_method(gem_class, :refresh) {}
+ end
+
+ # Replace or hook into RubyGems to provide a bundlerized view
+ # of the world.
+ def replace_entrypoints(specs)
+ specs_by_name = specs.reduce({}) do |h, s|
+ h[s.name] = s
+ h
+ end
+
+ replace_gem(specs, specs_by_name)
+ stub_rubygems(specs)
+ replace_bin_path(specs, specs_by_name)
+ replace_refresh
+
+ Gem.clear_paths
+ end
+
+ # This backports the correct segment generation code from RubyGems 1.4+
+ # by monkeypatching it into the method in RubyGems 1.3.6 and 1.3.7.
+ def backport_segment_generation
+ redefine_method(Gem::Version, :segments) do
+ @segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
+ /^\d+$/ =~ s ? s.to_i : s
+ end
+ end
+ end
+
+ # This backport fixes the marshaling of @segments.
+ def backport_yaml_initialize
+ redefine_method(Gem::Version, :yaml_initialize) do |_, map|
+ @version = map["version"]
+ @segments = nil
+ @hash = nil
+ end
+ end
+
+ # This backports base_dir which replaces installation path
+ # RubyGems 1.8+
+ def backport_base_dir
+ redefine_method(Gem::Specification, :base_dir) do
+ return Gem.dir unless loaded_from
+ File.dirname File.dirname loaded_from
+ end
+ end
+
+ def backport_cache_file
+ redefine_method(Gem::Specification, :cache_dir) do
+ @cache_dir ||= File.join base_dir, "cache"
+ end
+
+ redefine_method(Gem::Specification, :cache_file) do
+ @cache_file ||= File.join cache_dir, "#{full_name}.gem"
+ end
+ end
+
+ def backport_spec_file
+ redefine_method(Gem::Specification, :spec_dir) do
+ @spec_dir ||= File.join base_dir, "specifications"
+ end
+
+ redefine_method(Gem::Specification, :spec_file) do
+ @spec_file ||= File.join spec_dir, "#{full_name}.gemspec"
+ end
+ end
+
+ def undo_replacements
+ @replaced_methods.each do |(sym, klass), method|
+ redefine_method(klass, sym, method)
+ end
+ if Binding.public_method_defined?(:source_location)
+ post_reset_hooks.reject! {|proc| proc.binding.source_location[0] == __FILE__ }
+ else
+ post_reset_hooks.reject! {|proc| proc.binding.eval("__FILE__") == __FILE__ }
+ end
+ @replaced_methods.clear
+ end
+
+ def redefine_method(klass, method, unbound_method = nil, &block)
+ visibility = method_visibility(klass, method)
+ begin
+ if (instance_method = klass.instance_method(method)) && method != :initialize
+ # doing this to ensure we also get private methods
+ klass.send(:remove_method, method)
+ end
+ rescue NameError
+ # method isn't defined
+ nil
+ end
+ @replaced_methods[[method, klass]] = instance_method
+ if unbound_method
+ klass.send(:define_method, method, unbound_method)
+ klass.send(visibility, method)
+ elsif block
+ klass.send(:define_method, method, &block)
+ klass.send(visibility, method)
+ end
+ end
+
+ def method_visibility(klass, method)
+ if klass.private_method_defined?(method)
+ :private
+ elsif klass.protected_method_defined?(method)
+ :protected
+ else
+ :public
+ end
+ end
+
+ # RubyGems 1.4 through 1.6
+ class Legacy < RubygemsIntegration
+ def initialize
+ super
+ backport_base_dir
+ backport_cache_file
+ backport_spec_file
+ backport_yaml_initialize
+ end
+
+ def stub_rubygems(specs)
+ # RubyGems versions lower than 1.7 use SourceIndex#from_gems_in
+ source_index_class = (class << Gem::SourceIndex; self; end)
+ redefine_method(source_index_class, :from_gems_in) do |*args|
+ Gem::SourceIndex.new.tap do |source_index|
+ source_index.spec_dirs = *args
+ source_index.add_specs(*specs)
+ end
+ end
+ end
+
+ def all_specs
+ Gem.source_index.gems.values
+ end
+
+ def find_name(name)
+ Gem.source_index.find_name(name)
+ end
+
+ def validate(spec)
+ # These versions of RubyGems always validate in "packaging" mode,
+ # which is too strict for the kinds of checks we care about. As a
+ # result, validation is disabled on versions of RubyGems below 1.7.
+ end
+
+ def post_reset_hooks
+ []
+ end
+
+ def reset
+ end
+ end
+
+ # RubyGems versions 1.3.6 and 1.3.7
+ class Ancient < Legacy
+ def initialize
+ super
+ backport_segment_generation
+ end
+ end
+
+ # RubyGems 1.7
+ class Transitional < Legacy
+ def stub_rubygems(specs)
+ stub_source_index(specs)
+ end
+
+ def validate(spec)
+ # Missing summary is downgraded to a warning in later versions,
+ # so we set it to an empty string to prevent an exception here.
+ spec.summary ||= ""
+ RubygemsIntegration.instance_method(:validate).bind(self).call(spec)
+ end
+ end
+
+ # RubyGems 1.8.5-1.8.19
+ class Modern < RubygemsIntegration
+ def stub_rubygems(specs)
+ Gem::Specification.all = specs
+
+ Gem.post_reset do
+ Gem::Specification.all = specs
+ end
+
+ stub_source_index(specs)
+ end
+
+ def all_specs
+ Gem::Specification.to_a
+ end
+
+ def find_name(name)
+ Gem::Specification.find_all_by_name name
+ end
+ end
+
+ # RubyGems 1.8.0 to 1.8.4
+ class AlmostModern < Modern
+ # RubyGems [>= 1.8.0, < 1.8.5] has a bug that changes Gem.dir whenever
+ # you call Gem::Installer#install with an :install_dir set. We have to
+ # change it back for our sudo mode to work.
+ def preserve_paths
+ old_dir = gem_dir
+ old_path = gem_path
+ yield
+ Gem.use_paths(old_dir, old_path)
+ end
+ end
+
+ # RubyGems 1.8.20+
+ class MoreModern < Modern
+ # RubyGems 1.8.20 and adds the skip_validation parameter, so that's
+ # when we start passing it through.
+ def build(spec, skip_validation = false)
+ require "rubygems/builder"
+ Gem::Builder.new(spec).build(skip_validation)
+ end
+ end
+
+ # RubyGems 2.0
+ class Future < RubygemsIntegration
+ def stub_rubygems(specs)
+ Gem::Specification.all = specs
+
+ Gem.post_reset do
+ Gem::Specification.all = specs
+ end
+
+ redefine_method((class << Gem; self; end), :finish_resolve) do |*|
+ []
+ end
+ end
+
+ def all_specs
+ Gem::Specification.to_a
+ end
+
+ def find_name(name)
+ Gem::Specification.find_all_by_name name
+ end
+
+ def fetch_specs(source, remote, name)
+ path = source + "#{name}.#{Gem.marshal_version}.gz"
+ fetcher = gem_remote_fetcher
+ fetcher.headers = { "X-Gemfile-Source" => remote.original_uri.to_s } if remote.original_uri
+ string = fetcher.fetch_path(path)
+ Bundler.load_marshal(string)
+ rescue Gem::RemoteFetcher::FetchError => e
+ # it's okay for prerelease to fail
+ raise e unless name == "prerelease_specs"
+ end
+
+ def fetch_all_remote_specs(remote)
+ source = remote.uri.is_a?(URI) ? remote.uri : URI.parse(source.to_s)
+
+ specs = fetch_specs(source, remote, "specs")
+ pres = fetch_specs(source, remote, "prerelease_specs") || []
+
+ specs.concat(pres)
+ end
+
+ def download_gem(spec, uri, path)
+ uri = Bundler.settings.mirror_for(uri)
+ fetcher = gem_remote_fetcher
+ fetcher.headers = { "X-Gemfile-Source" => spec.remote.original_uri.to_s } if spec.remote.original_uri
+ Bundler::Retry.new("download gem from #{uri}").attempts do
+ fetcher.download(spec, uri, path)
+ end
+ end
+
+ def gem_remote_fetcher
+ require "resolv"
+ proxy = configuration[:http_proxy]
+ dns = Resolv::DNS.new
+ Bundler::GemRemoteFetcher.new(proxy, dns)
+ end
+
+ def gem_from_path(path, policy = nil)
+ require "rubygems/package"
+ p = Gem::Package.new(path)
+ p.security_policy = policy if policy
+ p
+ end
+
+ def build(spec, skip_validation = false)
+ require "rubygems/package"
+ Gem::Package.build(spec, skip_validation)
+ end
+
+ def repository_subdirectories
+ Gem::REPOSITORY_SUBDIRECTORIES
+ end
+
+ def install_with_build_args(args)
+ yield
+ end
+
+ def path_separator
+ Gem.path_separator
+ end
+ end
+
+ # RubyGems 2.1.0
+ class MoreFuture < Future
+ def initialize
+ super
+ backport_ext_builder_monitor
+ end
+
+ def all_specs
+ require "bundler/remote_specification"
+ Gem::Specification.stubs.map do |stub|
+ StubSpecification.from_stub(stub)
+ end
+ end
+
+ def backport_ext_builder_monitor
+ # So we can avoid requiring "rubygems/ext" in its entirety
+ Gem.module_eval <<-RB, __FILE__, __LINE__ + 1
+ module Ext
+ end
+ RB
+
+ require "rubygems/ext/builder"
+
+ Gem::Ext::Builder.class_eval do
+ unless const_defined?(:CHDIR_MONITOR)
+ const_set(:CHDIR_MONITOR, EXT_LOCK)
+ end
+
+ remove_const(:CHDIR_MUTEX) if const_defined?(:CHDIR_MUTEX)
+ const_set(:CHDIR_MUTEX, const_get(:CHDIR_MONITOR))
+ end
+ end
+
+ if Gem::Specification.respond_to?(:stubs_for)
+ def find_name(name)
+ Gem::Specification.stubs_for(name).map(&:to_spec)
+ end
+ else
+ def find_name(name)
+ Gem::Specification.stubs.find_all do |spec|
+ spec.name == name
+ end.map(&:to_spec)
+ end
+ end
+
+ def use_gemdeps(gemfile)
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path(gemfile)
+ require "bundler/gemdeps"
+ runtime = Bundler.setup
+ Bundler.ui = nil
+ activated_spec_names = runtime.requested_specs.map(&:to_spec).sort_by(&:name)
+ [Gemdeps.new(runtime), activated_spec_names]
+ end
+
+ if provides?(">= 2.5.2")
+ # RubyGems-generated binstubs call Kernel#gem
+ def binstubs_call_gem?
+ false
+ end
+
+ # only 2.5.2+ has all of the stub methods we want to use, and since this
+ # is a performance optimization _only_,
+ # we'll restrict ourselves to the most
+ # recent RG versions instead of all versions that have stubs
+ def stubs_provide_full_functionality?
+ true
+ end
+ end
+ end
+ end
+
+ def self.rubygems
+ @rubygems ||= if RubygemsIntegration.provides?(">= 2.1.0")
+ RubygemsIntegration::MoreFuture.new
+ elsif RubygemsIntegration.provides?(">= 1.99.99")
+ RubygemsIntegration::Future.new
+ elsif RubygemsIntegration.provides?(">= 1.8.20")
+ RubygemsIntegration::MoreModern.new
+ elsif RubygemsIntegration.provides?(">= 1.8.5")
+ RubygemsIntegration::Modern.new
+ elsif RubygemsIntegration.provides?(">= 1.8.0")
+ RubygemsIntegration::AlmostModern.new
+ elsif RubygemsIntegration.provides?(">= 1.7.0")
+ RubygemsIntegration::Transitional.new
+ elsif RubygemsIntegration.provides?(">= 1.4.0")
+ RubygemsIntegration::Legacy.new
+ else # RubyGems 1.3.6 and 1.3.7
+ RubygemsIntegration::Ancient.new
+ end
+ end
+end
diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb
new file mode 100644
index 0000000000..762e7b3ec6
--- /dev/null
+++ b/lib/bundler/runtime.rb
@@ -0,0 +1,322 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Runtime
+ include SharedHelpers
+
+ def initialize(root, definition)
+ @root = root
+ @definition = definition
+ end
+
+ def setup(*groups)
+ @definition.ensure_equivalent_gemfile_and_lockfile if Bundler.frozen_bundle?
+
+ groups.map!(&:to_sym)
+
+ # Has to happen first
+ clean_load_path
+
+ specs = groups.any? ? @definition.specs_for(groups) : requested_specs
+
+ SharedHelpers.set_bundle_environment
+ Bundler.rubygems.replace_entrypoints(specs)
+
+ # Activate the specs
+ load_paths = specs.map do |spec|
+ unless spec.loaded_from
+ raise GemNotFound, "#{spec.full_name} is missing. Run `bundle install` to get it."
+ end
+
+ check_for_activated_spec!(spec)
+
+ Bundler.rubygems.mark_loaded(spec)
+ spec.load_paths.reject {|path| $LOAD_PATH.include?(path) }
+ end.reverse.flatten
+
+ # See Gem::Specification#add_self_to_load_path (since RubyGems 1.8)
+ if insert_index = Bundler.rubygems.load_path_insert_index
+ # Gem directories must come after -I and ENV['RUBYLIB']
+ $LOAD_PATH.insert(insert_index, *load_paths)
+ else
+ # We are probably testing in core, -I and RUBYLIB don't apply
+ $LOAD_PATH.unshift(*load_paths)
+ end
+
+ setup_manpath
+
+ lock(:preserve_unknown_sections => true)
+
+ self
+ end
+
+ REQUIRE_ERRORS = [
+ /^no such file to load -- (.+)$/i,
+ /^Missing \w+ (?:file\s*)?([^\s]+.rb)$/i,
+ /^Missing API definition file in (.+)$/i,
+ /^cannot load such file -- (.+)$/i,
+ /^dlopen\([^)]*\): Library not loaded: (.+)$/i,
+ ].freeze
+
+ def require(*groups)
+ groups.map!(&:to_sym)
+ groups = [:default] if groups.empty?
+
+ @definition.dependencies.each do |dep|
+ # Skip the dependency if it is not in any of the requested groups, or
+ # not for the current platform, or doesn't match the gem constraints.
+ next unless (dep.groups & groups).any? && dep.should_include?
+
+ required_file = nil
+
+ begin
+ # Loop through all the specified autorequires for the
+ # dependency. If there are none, use the dependency's name
+ # as the autorequire.
+ Array(dep.autorequire || dep.name).each do |file|
+ # Allow `require: true` as an alias for `require: <name>`
+ file = dep.name if file == true
+ required_file = file
+ begin
+ Kernel.require file
+ rescue RuntimeError => e
+ raise e if e.is_a?(LoadError) # we handle this a little later
+ raise Bundler::GemRequireError.new e,
+ "There was an error while trying to load the gem '#{file}'."
+ end
+ end
+ rescue LoadError => e
+ REQUIRE_ERRORS.find {|r| r =~ e.message }
+ raise if dep.autorequire || $1 != required_file
+
+ if dep.autorequire.nil? && dep.name.include?("-")
+ begin
+ namespaced_file = dep.name.tr("-", "/")
+ Kernel.require namespaced_file
+ rescue LoadError => e
+ REQUIRE_ERRORS.find {|r| r =~ e.message }
+ raise if $1 != namespaced_file
+ end
+ end
+ end
+ end
+ end
+
+ def self.definition_method(meth)
+ define_method(meth) do
+ raise ArgumentError, "no definition when calling Runtime##{meth}" unless @definition
+ @definition.send(meth)
+ end
+ end
+ private_class_method :definition_method
+
+ definition_method :requested_specs
+ definition_method :specs
+ definition_method :dependencies
+ definition_method :current_dependencies
+ definition_method :requires
+
+ def lock(opts = {})
+ return if @definition.nothing_changed? && !@definition.unlocking?
+ @definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections])
+ end
+
+ alias_method :gems, :specs
+
+ def cache(custom_path = nil)
+ cache_path = Bundler.app_cache(custom_path)
+ SharedHelpers.filesystem_access(cache_path) do |p|
+ FileUtils.mkdir_p(p)
+ end unless File.exist?(cache_path)
+
+ Bundler.ui.info "Updating files in #{Bundler.settings.app_cache_path}"
+
+ specs_to_cache = Bundler.settings[:cache_all_platforms] ? @definition.resolve.materialized_for_all_platforms : specs
+ specs_to_cache.each do |spec|
+ next if spec.name == "bundler"
+ next if spec.source.is_a?(Source::Gemspec)
+ spec.source.send(:fetch_gem, spec) if Bundler.settings[:cache_all_platforms] && spec.source.respond_to?(:fetch_gem, true)
+ spec.source.cache(spec, custom_path) if spec.source.respond_to?(:cache)
+ end
+
+ Dir[cache_path.join("*/.git")].each do |git_dir|
+ FileUtils.rm_rf(git_dir)
+ FileUtils.touch(File.expand_path("../.bundlecache", git_dir))
+ end
+
+ prune_cache(cache_path) unless Bundler.settings[:no_prune]
+ end
+
+ def prune_cache(cache_path)
+ SharedHelpers.filesystem_access(cache_path) do |p|
+ FileUtils.mkdir_p(p)
+ end unless File.exist?(cache_path)
+ resolve = @definition.resolve
+ prune_gem_cache(resolve, cache_path)
+ prune_git_and_path_cache(resolve, cache_path)
+ end
+
+ def clean(dry_run = false)
+ gem_bins = Dir["#{Gem.dir}/bin/*"]
+ git_dirs = Dir["#{Gem.dir}/bundler/gems/*"]
+ git_cache_dirs = Dir["#{Gem.dir}/cache/bundler/git/*"]
+ gem_dirs = Dir["#{Gem.dir}/gems/*"]
+ gem_files = Dir["#{Gem.dir}/cache/*.gem"]
+ gemspec_files = Dir["#{Gem.dir}/specifications/*.gemspec"]
+ extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"]
+ spec_gem_paths = []
+ # need to keep git sources around
+ spec_git_paths = @definition.spec_git_paths
+ spec_git_cache_dirs = []
+ spec_gem_executables = []
+ spec_cache_paths = []
+ spec_gemspec_paths = []
+ spec_extension_paths = []
+ specs.each do |spec|
+ spec_gem_paths << spec.full_gem_path
+ # need to check here in case gems are nested like for the rails git repo
+ md = %r{(.+bundler/gems/.+-[a-f0-9]{7,12})}.match(spec.full_gem_path)
+ spec_git_paths << md[1] if md
+ spec_gem_executables << spec.executables.collect do |executable|
+ e = "#{Bundler.rubygems.gem_bindir}/#{executable}"
+ [e, "#{e}.bat"]
+ end
+ spec_cache_paths << spec.cache_file
+ spec_gemspec_paths << spec.spec_file
+ spec_extension_paths << spec.extension_dir if spec.respond_to?(:extension_dir)
+ spec_git_cache_dirs << spec.source.cache_path.to_s if spec.source.is_a?(Bundler::Source::Git)
+ end
+ spec_gem_paths.uniq!
+ spec_gem_executables.flatten!
+
+ stale_gem_bins = gem_bins - spec_gem_executables
+ stale_git_dirs = git_dirs - spec_git_paths - ["#{Gem.dir}/bundler/gems/extensions"]
+ stale_git_cache_dirs = git_cache_dirs - spec_git_cache_dirs
+ stale_gem_dirs = gem_dirs - spec_gem_paths
+ stale_gem_files = gem_files - spec_cache_paths
+ stale_gemspec_files = gemspec_files - spec_gemspec_paths
+ stale_extension_dirs = extension_dirs - spec_extension_paths
+
+ removed_stale_gem_dirs = stale_gem_dirs.collect {|dir| remove_dir(dir, dry_run) }
+ removed_stale_git_dirs = stale_git_dirs.collect {|dir| remove_dir(dir, dry_run) }
+ output = removed_stale_gem_dirs + removed_stale_git_dirs
+
+ unless dry_run
+ stale_files = stale_gem_bins + stale_gem_files + stale_gemspec_files
+ stale_files.each do |file|
+ SharedHelpers.filesystem_access(File.dirname(file)) do |_p|
+ FileUtils.rm(file) if File.exist?(file)
+ end
+ end
+
+ stale_dirs = stale_git_cache_dirs + stale_extension_dirs
+ stale_dirs.each do |stale_dir|
+ SharedHelpers.filesystem_access(stale_dir) do |dir|
+ FileUtils.rm_rf(dir) if File.exist?(dir)
+ end
+ end
+ end
+
+ output
+ end
+
+ private
+
+ def prune_gem_cache(resolve, cache_path)
+ cached = Dir["#{cache_path}/*.gem"]
+
+ cached = cached.delete_if do |path|
+ spec = Bundler.rubygems.spec_from_gem path
+
+ resolve.any? do |s|
+ s.name == spec.name && s.version == spec.version && !s.source.is_a?(Bundler::Source::Git)
+ end
+ end
+
+ if cached.any?
+ Bundler.ui.info "Removing outdated .gem files from #{Bundler.settings.app_cache_path}"
+
+ cached.each do |path|
+ Bundler.ui.info " * #{File.basename(path)}"
+ File.delete(path)
+ end
+ end
+ end
+
+ def prune_git_and_path_cache(resolve, cache_path)
+ cached = Dir["#{cache_path}/*/.bundlecache"]
+
+ cached = cached.delete_if do |path|
+ name = File.basename(File.dirname(path))
+
+ resolve.any? do |s|
+ source = s.source
+ source.respond_to?(:app_cache_dirname) && source.app_cache_dirname == name
+ end
+ end
+
+ if cached.any?
+ Bundler.ui.info "Removing outdated git and path gems from #{Bundler.settings.app_cache_path}"
+
+ cached.each do |path|
+ path = File.dirname(path)
+ Bundler.ui.info " * #{File.basename(path)}"
+ FileUtils.rm_rf(path)
+ end
+ end
+ end
+
+ def setup_manpath
+ # Add man/ subdirectories from activated bundles to MANPATH for man(1)
+ manuals = $LOAD_PATH.map do |path|
+ man_subdir = path.sub(/lib$/, "man")
+ man_subdir unless Dir[man_subdir + "/man?/"].empty?
+ end.compact
+
+ return if manuals.empty?
+ Bundler::SharedHelpers.set_env "MANPATH", manuals.concat(
+ ENV["MANPATH"].to_s.split(File::PATH_SEPARATOR)
+ ).uniq.join(File::PATH_SEPARATOR)
+ end
+
+ def remove_dir(dir, dry_run)
+ full_name = Pathname.new(dir).basename.to_s
+
+ parts = full_name.split("-")
+ name = parts[0..-2].join("-")
+ version = parts.last
+ output = "#{name} (#{version})"
+
+ if dry_run
+ Bundler.ui.info "Would have removed #{output}"
+ else
+ Bundler.ui.info "Removing #{output}"
+ FileUtils.rm_rf(dir)
+ end
+
+ output
+ end
+
+ def check_for_activated_spec!(spec)
+ return unless activated_spec = Bundler.rubygems.loaded_specs(spec.name)
+ return if activated_spec.version == spec.version
+
+ suggestion = if Bundler.rubygems.spec_default_gem?(activated_spec)
+ "Since #{spec.name} is a default gem, you can either remove your dependency on it" \
+ " or try updating to a newer version of bundler that supports #{spec.name} as a default gem."
+ else
+ "Prepending `bundle exec` to your command may solve this."
+ end
+
+ e = Gem::LoadError.new "You have already activated #{activated_spec.name} #{activated_spec.version}, " \
+ "but your Gemfile requires #{spec.name} #{spec.version}. #{suggestion}"
+ e.name = spec.name
+ if e.respond_to?(:requirement=)
+ e.requirement = Gem::Requirement.new(spec.version.to_s)
+ else
+ e.version_requirement = Gem::Requirement.new(spec.version.to_s)
+ end
+ raise e
+ end
+ end
+end
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
new file mode 100644
index 0000000000..fe68d510ff
--- /dev/null
+++ b/lib/bundler/settings.rb
@@ -0,0 +1,463 @@
+# frozen_string_literal: true
+
+require "uri"
+
+module Bundler
+ class Settings
+ autoload :Mirror, "bundler/mirror"
+ autoload :Mirrors, "bundler/mirror"
+ autoload :Validator, "bundler/settings/validator"
+
+ BOOL_KEYS = %w[
+ allow_bundler_dependency_conflicts
+ allow_deployment_source_credential_changes
+ allow_offline_install
+ auto_clean_without_path
+ auto_install
+ auto_config_jobs
+ cache_all
+ cache_all_platforms
+ cache_command_is_package
+ console_command
+ default_install_uses_path
+ deployment
+ deployment_means_frozen
+ disable_checksum_validation
+ disable_exec_load
+ disable_local_branch_check
+ disable_multisource
+ disable_platform_warnings
+ disable_shared_gems
+ disable_version_check
+ error_on_stderr
+ force_ruby_platform
+ forget_cli_options
+ frozen
+ gem.coc
+ gem.mit
+ global_path_appends_ruby_scope
+ global_gem_cache
+ ignore_messages
+ init_gems_rb
+ list_command
+ lockfile_uses_separate_rubygems_sources
+ major_deprecations
+ no_install
+ no_prune
+ only_update_to_newer_versions
+ path_relative_to_cwd
+ path.system
+ plugins
+ prefer_gems_rb
+ print_only_version_number
+ setup_makes_kernel_gem_public
+ silence_root_warning
+ skip_default_git_sources
+ specific_platform
+ suppress_install_using_messages
+ unlock_source_unlocks_spec
+ update_requires_all_flag
+ use_gem_version_promoter_for_major_updates
+ viz_command
+ ].freeze
+
+ NUMBER_KEYS = %w[
+ jobs
+ redirect
+ retry
+ ssl_verify_mode
+ timeout
+ ].freeze
+
+ ARRAY_KEYS = %w[
+ with
+ without
+ ].freeze
+
+ DEFAULT_CONFIG = {
+ :disable_version_check => true,
+ :redirect => 5,
+ :retry => 3,
+ :timeout => 10,
+ }.freeze
+
+ def initialize(root = nil)
+ @root = root
+ @local_config = load_config(local_config_file)
+ @global_config = load_config(global_config_file)
+ @temporary = {}
+ end
+
+ def [](name)
+ key = key_for(name)
+ value = @temporary.fetch(key) do
+ @local_config.fetch(key) do
+ ENV.fetch(key) do
+ @global_config.fetch(key) do
+ DEFAULT_CONFIG.fetch(name) do
+ nil
+ end end end end end
+
+ converted_value(value, name)
+ end
+
+ def set_command_option(key, value)
+ if Bundler.feature_flag.forget_cli_options?
+ temporary(key => value)
+ value
+ else
+ command = if value.nil?
+ "bundle config --delete #{key}"
+ else
+ "bundle config #{key} #{Array(value).join(":")}"
+ end
+
+ Bundler::SharedHelpers.major_deprecation 2,\
+ "flags passed to commands " \
+ "will no longer be automatically remembered. Instead please set flags " \
+ "you want remembered between commands using `bundle config " \
+ "<setting name> <setting value>`, i.e. `#{command}`"
+
+ set_local(key, value)
+ end
+ end
+
+ def set_command_option_if_given(key, value)
+ return if value.nil?
+ set_command_option(key, value)
+ end
+
+ def set_local(key, value)
+ local_config_file || raise(GemfileNotFound, "Could not locate Gemfile")
+
+ set_key(key, value, @local_config, local_config_file)
+ end
+
+ def temporary(update)
+ existing = Hash[update.map {|k, _| [k, @temporary[key_for(k)]] }]
+ update.each do |k, v|
+ set_key(k, v, @temporary, nil)
+ end
+ return unless block_given?
+ begin
+ yield
+ ensure
+ existing.each {|k, v| set_key(k, v, @temporary, nil) }
+ end
+ end
+
+ def set_global(key, value)
+ set_key(key, value, @global_config, global_config_file)
+ end
+
+ def all
+ env_keys = ENV.keys.grep(/\ABUNDLE_.+/)
+
+ keys = @temporary.keys | @global_config.keys | @local_config.keys | env_keys
+
+ keys.map do |key|
+ key.sub(/^BUNDLE_/, "").gsub(/__/, ".").downcase
+ end
+ end
+
+ def local_overrides
+ repos = {}
+ all.each do |k|
+ repos[$'] = self[k] if k =~ /^local\./
+ end
+ repos
+ end
+
+ def mirror_for(uri)
+ uri = URI(uri.to_s) unless uri.is_a?(URI)
+ gem_mirrors.for(uri.to_s).uri
+ end
+
+ def credentials_for(uri)
+ self[uri.to_s] || self[uri.host]
+ end
+
+ def gem_mirrors
+ all.inject(Mirrors.new) do |mirrors, k|
+ mirrors.parse(k, self[k]) if k.start_with?("mirror.")
+ mirrors
+ end
+ end
+
+ def locations(key)
+ key = key_for(key)
+ locations = {}
+ locations[:temporary] = @temporary[key] if @temporary.key?(key)
+ locations[:local] = @local_config[key] if @local_config.key?(key)
+ locations[:env] = ENV[key] if ENV[key]
+ locations[:global] = @global_config[key] if @global_config.key?(key)
+ locations[:default] = DEFAULT_CONFIG[key] if DEFAULT_CONFIG.key?(key)
+ locations
+ end
+
+ def pretty_values_for(exposed_key)
+ key = key_for(exposed_key)
+
+ locations = []
+
+ if @temporary.key?(key)
+ locations << "Set for the current command: #{converted_value(@temporary[key], exposed_key).inspect}"
+ end
+
+ if @local_config.key?(key)
+ locations << "Set for your local app (#{local_config_file}): #{converted_value(@local_config[key], exposed_key).inspect}"
+ end
+
+ if value = ENV[key]
+ locations << "Set via #{key}: #{converted_value(value, exposed_key).inspect}"
+ end
+
+ if @global_config.key?(key)
+ locations << "Set for the current user (#{global_config_file}): #{converted_value(@global_config[key], exposed_key).inspect}"
+ end
+
+ return ["You have not configured a value for `#{exposed_key}`"] if locations.empty?
+ locations
+ end
+
+ # for legacy reasons, in Bundler 1, the ruby scope isnt appended when the setting comes from ENV or the global config,
+ # nor do we respect :disable_shared_gems
+ def path
+ key = key_for(:path)
+ path = ENV[key] || @global_config[key]
+ if path && !@temporary.key?(key) && !@local_config.key?(key)
+ return Path.new(path, Bundler.feature_flag.global_path_appends_ruby_scope?, false, false)
+ end
+
+ system_path = self["path.system"] || (self[:disable_shared_gems] == false)
+ Path.new(self[:path], true, system_path, Bundler.feature_flag.default_install_uses_path?)
+ end
+
+ Path = Struct.new(:explicit_path, :append_ruby_scope, :system_path, :default_install_uses_path) do
+ def path
+ path = base_path
+ path = File.join(path, Bundler.ruby_scope) if append_ruby_scope && !use_system_gems?
+ path
+ end
+
+ def use_system_gems?
+ return true if system_path
+ return false if explicit_path
+ !default_install_uses_path
+ end
+
+ def base_path
+ path = explicit_path
+ path ||= ".bundle" unless use_system_gems?
+ path ||= Bundler.rubygems.gem_dir
+ path
+ end
+
+ def base_path_relative_to_pwd
+ base_path = Pathname.new(self.base_path)
+ expanded_base_path = base_path.expand_path(Bundler.root)
+ relative_path = expanded_base_path.relative_path_from(Pathname.pwd)
+ if relative_path.to_s.start_with?("..")
+ relative_path = base_path if base_path.absolute?
+ else
+ relative_path = Pathname.new(File.join(".", relative_path))
+ end
+ relative_path
+ rescue ArgumentError
+ expanded_base_path
+ end
+
+ def validate!
+ return unless explicit_path && system_path
+ path = Bundler.settings.pretty_values_for(:path)
+ path.unshift(nil, "path:") unless path.empty?
+ system_path = Bundler.settings.pretty_values_for("path.system")
+ system_path.unshift(nil, "path.system:") unless system_path.empty?
+ disable_shared_gems = Bundler.settings.pretty_values_for(:disable_shared_gems)
+ disable_shared_gems.unshift(nil, "disable_shared_gems:") unless disable_shared_gems.empty?
+ raise InvalidOption,
+ "Using a custom path while using system gems is unsupported.\n#{path.join("\n")}\n#{system_path.join("\n")}\n#{disable_shared_gems.join("\n")}"
+ end
+ end
+
+ def allow_sudo?
+ key = key_for(:path)
+ path_configured = @temporary.key?(key) || @local_config.key?(key)
+ !path_configured
+ end
+
+ def ignore_config?
+ ENV["BUNDLE_IGNORE_CONFIG"]
+ end
+
+ def app_cache_path
+ @app_cache_path ||= self[:cache_path] || "vendor/cache"
+ end
+
+ def validate!
+ all.each do |raw_key|
+ [@local_config, ENV, @global_config].each do |settings|
+ value = converted_value(settings[key_for(raw_key)], raw_key)
+ Validator.validate!(raw_key, value, settings.to_hash.dup)
+ end
+ end
+ end
+
+ def key_for(key)
+ key = Settings.normalize_uri(key).to_s if key.is_a?(String) && /https?:/ =~ key
+ key = key.to_s.gsub(".", "__").upcase
+ "BUNDLE_#{key}"
+ end
+
+ private
+
+ def parent_setting_for(name)
+ split_specific_setting_for(name)[0]
+ end
+
+ def specific_gem_for(name)
+ split_specific_setting_for(name)[1]
+ end
+
+ def split_specific_setting_for(name)
+ name.split(".")
+ end
+
+ def is_bool(name)
+ BOOL_KEYS.include?(name.to_s) || BOOL_KEYS.include?(parent_setting_for(name.to_s))
+ end
+
+ def to_bool(value)
+ case value
+ when nil, /\A(false|f|no|n|0|)\z/i, false
+ false
+ else
+ true
+ end
+ end
+
+ def is_num(key)
+ NUMBER_KEYS.include?(key.to_s)
+ end
+
+ def is_array(key)
+ ARRAY_KEYS.include?(key.to_s)
+ end
+
+ def to_array(value)
+ return [] unless value
+ value.split(":").map(&:to_sym)
+ end
+
+ def array_to_s(array)
+ array = Array(array)
+ return nil if array.empty?
+ array.join(":").tr(" ", ":")
+ end
+
+ def set_key(raw_key, value, hash, file)
+ raw_key = raw_key.to_s
+ value = array_to_s(value) if is_array(raw_key)
+
+ key = key_for(raw_key)
+
+ return if hash[key] == value
+
+ hash[key] = value
+ hash.delete(key) if value.nil?
+
+ Validator.validate!(raw_key, converted_value(value, raw_key), hash)
+
+ return unless file
+ SharedHelpers.filesystem_access(file) do |p|
+ FileUtils.mkdir_p(p.dirname)
+ require "bundler/yaml_serializer"
+ p.open("w") {|f| f.write(YAMLSerializer.dump(hash)) }
+ end
+ end
+
+ def converted_value(value, key)
+ if is_array(key)
+ to_array(value)
+ elsif value.nil?
+ nil
+ elsif is_bool(key) || value == "false"
+ to_bool(value)
+ elsif is_num(key)
+ value.to_i
+ else
+ value.to_s
+ end
+ end
+
+ def global_config_file
+ if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty?
+ Pathname.new(ENV["BUNDLE_CONFIG"])
+ else
+ begin
+ Bundler.user_bundle_path("config")
+ rescue PermissionError, GenericSystemCallError
+ nil
+ end
+ end
+ end
+
+ def local_config_file
+ Pathname.new(@root).join("config") if @root
+ end
+
+ CONFIG_REGEX = %r{ # rubocop:disable Style/RegexpLiteral
+ ^
+ (BUNDLE_.+):\s # the key
+ (?: !\s)? # optional exclamation mark found with ruby 1.9.3
+ (['"]?) # optional opening quote
+ (.* # contents of the value
+ (?: # optionally, up until the next key
+ (\n(?!BUNDLE).+)*
+ )
+ )
+ \2 # matching closing quote
+ $
+ }xo
+
+ def load_config(config_file)
+ return {} if !config_file || ignore_config?
+ SharedHelpers.filesystem_access(config_file, :read) do |file|
+ valid_file = file.exist? && !file.size.zero?
+ return {} unless valid_file
+ require "bundler/yaml_serializer"
+ YAMLSerializer.load file.read
+ end
+ end
+
+ PER_URI_OPTIONS = %w[
+ fallback_timeout
+ ].freeze
+
+ NORMALIZE_URI_OPTIONS_PATTERN =
+ /
+ \A
+ (\w+\.)? # optional prefix key
+ (https?.*?) # URI
+ (\.#{Regexp.union(PER_URI_OPTIONS)})? # optional suffix key
+ \z
+ /ix
+
+ # TODO: duplicates Rubygems#normalize_uri
+ # TODO: is this the correct place to validate mirror URIs?
+ def self.normalize_uri(uri)
+ uri = uri.to_s
+ if uri =~ NORMALIZE_URI_OPTIONS_PATTERN
+ prefix = $1
+ uri = $2
+ suffix = $3
+ end
+ uri = "#{uri}/" unless uri.end_with?("/")
+ uri = URI(uri)
+ unless uri.absolute?
+ raise ArgumentError, format("Gem sources must be absolute. You provided '%s'.", uri)
+ end
+ "#{prefix}#{uri}#{suffix}"
+ end
+ end
+end
diff --git a/lib/bundler/settings/validator.rb b/lib/bundler/settings/validator.rb
new file mode 100644
index 0000000000..0a57ea7f03
--- /dev/null
+++ b/lib/bundler/settings/validator.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Settings
+ class Validator
+ class Rule
+ attr_reader :description
+
+ def initialize(keys, description, &validate)
+ @keys = keys
+ @description = description
+ @validate = validate
+ end
+
+ def validate!(key, value, settings)
+ instance_exec(key, value, settings, &@validate)
+ end
+
+ def fail!(key, value, *reasons)
+ reasons.unshift @description
+ raise InvalidOption, "Setting `#{key}` to #{value.inspect} failed:\n#{reasons.map {|r| " - #{r}" }.join("\n")}"
+ end
+
+ def set(settings, key, value, *reasons)
+ hash_key = k(key)
+ return if settings[hash_key] == value
+ reasons.unshift @description
+ Bundler.ui.info "Setting `#{key}` to #{value.inspect}, since #{reasons.join(", ")}"
+ if value.nil?
+ settings.delete(hash_key)
+ else
+ settings[hash_key] = value
+ end
+ end
+
+ def k(key)
+ Bundler.settings.key_for(key)
+ end
+ end
+
+ def self.rules
+ @rules ||= Hash.new {|h, k| h[k] = [] }
+ end
+ private_class_method :rules
+
+ def self.rule(keys, description, &blk)
+ rule = Rule.new(keys, description, &blk)
+ keys.each {|k| rules[k] << rule }
+ end
+ private_class_method :rule
+
+ def self.validate!(key, value, settings)
+ rules_to_validate = rules[key]
+ rules_to_validate.each {|rule| rule.validate!(key, value, settings) }
+ end
+
+ rule %w[path path.system], "path and path.system are mutually exclusive" do |key, value, settings|
+ if key == "path" && value
+ set(settings, "path.system", nil)
+ elsif key == "path.system" && value
+ set(settings, :path, nil)
+ end
+ end
+
+ rule %w[with without], "a group cannot be in both `with` & `without` simultaneously" do |key, value, settings|
+ with = settings.fetch(k(:with), "").split(":").map(&:to_sym)
+ without = settings.fetch(k(:without), "").split(":").map(&:to_sym)
+
+ other_key = key == "with" ? :without : :with
+ other_setting = key == "with" ? without : with
+
+ conflicting = with & without
+ if conflicting.any?
+ fail!(key, value, "`#{other_key}` is current set to #{other_setting.inspect}", "the `#{conflicting.join("`, `")}` groups conflict")
+ end
+ end
+
+ rule %w[path], "relative paths are expanded relative to the current working directory" do |key, value, settings|
+ next if value.nil?
+
+ path = Pathname.new(value)
+ next if !path.relative? || !Bundler.feature_flag.path_relative_to_cwd?
+
+ path = path.expand_path
+
+ root = begin
+ Bundler.root
+ rescue GemfileNotFound
+ Pathname.pwd.expand_path
+ end
+
+ path = begin
+ path.relative_path_from(root)
+ rescue ArgumentError
+ path
+ end
+
+ set(settings, key, path.to_s)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/setup.rb b/lib/bundler/setup.rb
new file mode 100644
index 0000000000..ac6a5bf861
--- /dev/null
+++ b/lib/bundler/setup.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require "bundler/shared_helpers"
+
+if Bundler::SharedHelpers.in_bundle?
+ require "bundler"
+
+ if STDOUT.tty? || ENV["BUNDLER_FORCE_TTY"]
+ begin
+ Bundler.setup
+ rescue Bundler::BundlerError => e
+ puts "\e[31m#{e.message}\e[0m"
+ puts e.backtrace.join("\n") if ENV["DEBUG"]
+ if e.is_a?(Bundler::GemNotFound)
+ puts "\e[33mRun `bundle install` to install missing gems.\e[0m"
+ end
+ exit e.status_code
+ end
+ else
+ Bundler.setup
+ end
+
+ # Add bundler to the load path after disabling system gems
+ bundler_lib = File.expand_path("../..", __FILE__)
+ $LOAD_PATH.unshift(bundler_lib) unless $LOAD_PATH.include?(bundler_lib)
+
+ Bundler.ui = nil
+end
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb
new file mode 100644
index 0000000000..3e2fe24b7a
--- /dev/null
+++ b/lib/bundler/shared_helpers.rb
@@ -0,0 +1,384 @@
+# frozen_string_literal: true
+
+require "bundler/compatibility_guard"
+
+require "pathname"
+require "rubygems"
+
+require "bundler/version"
+require "bundler/constants"
+require "bundler/rubygems_integration"
+require "bundler/current_ruby"
+
+module Gem
+ class Dependency
+ # This is only needed for RubyGems < 1.4
+ unless method_defined? :requirement
+ def requirement
+ version_requirements
+ end
+ end
+ end
+end
+
+module Bundler
+ module SharedHelpers
+ def root
+ gemfile = find_gemfile
+ raise GemfileNotFound, "Could not locate Gemfile" unless gemfile
+ Pathname.new(gemfile).untaint.expand_path.parent
+ end
+
+ def default_gemfile
+ gemfile = find_gemfile(:order_matters)
+ raise GemfileNotFound, "Could not locate Gemfile" unless gemfile
+ Pathname.new(gemfile).untaint.expand_path
+ end
+
+ def default_lockfile
+ gemfile = default_gemfile
+
+ case gemfile.basename.to_s
+ when "gems.rb" then Pathname.new(gemfile.sub(/.rb$/, ".locked"))
+ else Pathname.new("#{gemfile}.lock")
+ end.untaint
+ end
+
+ def default_bundle_dir
+ bundle_dir = find_directory(".bundle")
+ return nil unless bundle_dir
+
+ bundle_dir = Pathname.new(bundle_dir)
+
+ global_bundle_dir = Bundler.user_home.join(".bundle")
+ return nil if bundle_dir == global_bundle_dir
+
+ bundle_dir
+ end
+
+ def in_bundle?
+ find_gemfile
+ end
+
+ def chdir(dir, &blk)
+ Bundler.rubygems.ext_lock.synchronize do
+ Dir.chdir dir, &blk
+ end
+ end
+
+ def pwd
+ Bundler.rubygems.ext_lock.synchronize do
+ Pathname.pwd
+ end
+ end
+
+ def with_clean_git_env(&block)
+ keys = %w[GIT_DIR GIT_WORK_TREE]
+ old_env = keys.inject({}) do |h, k|
+ h.update(k => ENV[k])
+ end
+
+ keys.each {|key| ENV.delete(key) }
+
+ block.call
+ ensure
+ keys.each {|key| ENV[key] = old_env[key] }
+ end
+
+ def set_bundle_environment
+ set_bundle_variables
+ set_path
+ set_rubyopt
+ set_rubylib
+ end
+
+ # Rescues permissions errors raised by file system operations
+ # (ie. Errno:EACCESS, Errno::EAGAIN) and raises more friendly errors instead.
+ #
+ # @param path [String] the path that the action will be attempted to
+ # @param action [Symbol, #to_s] the type of operation that will be
+ # performed. For example: :write, :read, :exec
+ #
+ # @yield path
+ #
+ # @raise [Bundler::PermissionError] if Errno:EACCES is raised in the
+ # given block
+ # @raise [Bundler::TemporaryResourceError] if Errno:EAGAIN is raised in the
+ # given block
+ #
+ # @example
+ # filesystem_access("vendor/cache", :write) do
+ # FileUtils.mkdir_p("vendor/cache")
+ # end
+ #
+ # @see {Bundler::PermissionError}
+ def filesystem_access(path, action = :write, &block)
+ # Use block.call instead of yield because of a bug in Ruby 2.2.2
+ # See https://github.com/bundler/bundler/issues/5341 for details
+ block.call(path.dup.untaint)
+ rescue Errno::EACCES
+ raise PermissionError.new(path, action)
+ rescue Errno::EAGAIN
+ raise TemporaryResourceError.new(path, action)
+ rescue Errno::EPROTO
+ raise VirtualProtocolError.new
+ rescue Errno::ENOSPC
+ raise NoSpaceOnDeviceError.new(path, action)
+ rescue *[const_get_safely(:ENOTSUP, Errno)].compact
+ raise OperationNotSupportedError.new(path, action)
+ rescue Errno::EEXIST, Errno::ENOENT
+ raise
+ rescue SystemCallError => e
+ raise GenericSystemCallError.new(e, "There was an error accessing `#{path}`.")
+ end
+
+ def const_get_safely(constant_name, namespace)
+ const_in_namespace = namespace.constants.include?(constant_name.to_s) ||
+ namespace.constants.include?(constant_name.to_sym)
+ return nil unless const_in_namespace
+ namespace.const_get(constant_name)
+ end
+
+ def major_deprecation(major_version, message)
+ if Bundler.bundler_major_version >= major_version
+ require "bundler/errors"
+ raise DeprecatedError, "[REMOVED FROM #{major_version}.0] #{message}"
+ end
+
+ return unless prints_major_deprecations?
+ @major_deprecation_ui ||= Bundler::UI::Shell.new("no-color" => true)
+ ui = Bundler.ui.is_a?(@major_deprecation_ui.class) ? Bundler.ui : @major_deprecation_ui
+ ui.warn("[DEPRECATED FOR #{major_version}.0] #{message}")
+ end
+
+ def print_major_deprecations!
+ multiple_gemfiles = search_up(".") do |dir|
+ gemfiles = gemfile_names.select {|gf| File.file? File.expand_path(gf, dir) }
+ next if gemfiles.empty?
+ break false if gemfiles.size == 1
+ end
+ if multiple_gemfiles && Bundler.bundler_major_version == 1
+ Bundler::SharedHelpers.major_deprecation 2, \
+ "gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock."
+ end
+
+ if RUBY_VERSION < "2"
+ major_deprecation(2, "Bundler will only support ruby >= 2.0, you are running #{RUBY_VERSION}")
+ end
+ return if Bundler.rubygems.provides?(">= 2")
+ major_deprecation(2, "Bundler will only support rubygems >= 2.0, you are running #{Bundler.rubygems.version}")
+ end
+
+ def trap(signal, override = false, &block)
+ prior = Signal.trap(signal) do
+ block.call
+ prior.call unless override
+ end
+ end
+
+ def ensure_same_dependencies(spec, old_deps, new_deps)
+ new_deps = new_deps.reject {|d| d.type == :development }
+ old_deps = old_deps.reject {|d| d.type == :development }
+
+ without_type = proc {|d| Gem::Dependency.new(d.name, d.requirements_list.sort) }
+ new_deps.map!(&without_type)
+ old_deps.map!(&without_type)
+
+ extra_deps = new_deps - old_deps
+ return if extra_deps.empty?
+
+ Bundler.ui.debug "#{spec.full_name} from #{spec.remote} has either corrupted API or lockfile dependencies" \
+ " (was expecting #{old_deps.map(&:to_s)}, but the real spec has #{new_deps.map(&:to_s)})"
+ raise APIResponseMismatchError,
+ "Downloading #{spec.full_name} revealed dependencies not in the API or the lockfile (#{extra_deps.join(", ")})." \
+ "\nEither installing with `--full-index` or running `bundle update #{spec.name}` should fix the problem."
+ end
+
+ def pretty_dependency(dep, print_source = false)
+ msg = String.new(dep.name)
+ msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default
+
+ if dep.is_a?(Bundler::Dependency)
+ platform_string = dep.platforms.join(", ")
+ msg << " " << platform_string if !platform_string.empty? && platform_string != Gem::Platform::RUBY
+ end
+
+ msg << " from the `#{dep.source}` source" if print_source && dep.source
+ msg
+ end
+
+ def md5_available?
+ return @md5_available if defined?(@md5_available)
+ @md5_available = begin
+ require "openssl"
+ OpenSSL::Digest::MD5.digest("")
+ true
+ rescue LoadError
+ true
+ rescue OpenSSL::Digest::DigestError
+ false
+ end
+ end
+
+ def digest(name)
+ require "digest"
+ Digest(name)
+ end
+
+ def write_to_gemfile(gemfile_path, contents)
+ filesystem_access(gemfile_path) {|g| File.open(g, "w") {|file| file.puts contents } }
+ end
+
+ private
+
+ def validate_bundle_path
+ path_separator = Bundler.rubygems.path_separator
+ return unless Bundler.bundle_path.to_s.split(path_separator).size > 1
+ message = "Your bundle path contains text matching #{path_separator.inspect}, " \
+ "which is the path separator for your system. Bundler cannot " \
+ "function correctly when the Bundle path contains the " \
+ "system's PATH separator. Please change your " \
+ "bundle path to not match #{path_separator.inspect}." \
+ "\nYour current bundle path is '#{Bundler.bundle_path}'."
+ raise Bundler::PathError, message
+ end
+
+ def find_gemfile(order_matters = false)
+ given = ENV["BUNDLE_GEMFILE"]
+ return given if given && !given.empty?
+ names = gemfile_names
+ names.reverse! if order_matters && Bundler.feature_flag.prefer_gems_rb?
+ find_file(*names)
+ end
+
+ def gemfile_names
+ ["Gemfile", "gems.rb"]
+ end
+
+ def find_file(*names)
+ search_up(*names) do |filename|
+ return filename if File.file?(filename)
+ end
+ end
+
+ def find_directory(*names)
+ search_up(*names) do |dirname|
+ return dirname if File.directory?(dirname)
+ end
+ end
+
+ def search_up(*names)
+ previous = nil
+ current = File.expand_path(SharedHelpers.pwd).untaint
+
+ until !File.directory?(current) || current == previous
+ if ENV["BUNDLE_SPEC_RUN"]
+ # avoid stepping above the tmp directory when testing
+ gemspec = if ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"]
+ # for Ruby Core
+ "lib/bundler.gemspec"
+ else
+ "bundler.gemspec"
+ end
+
+ # avoid stepping above the tmp directory when testing
+ return nil if File.file?(File.join(current, gemspec))
+ end
+
+ names.each do |name|
+ filename = File.join(current, name)
+ yield filename
+ end
+ previous = current
+ current = File.expand_path("..", current)
+ end
+ end
+
+ def set_env(key, value)
+ raise ArgumentError, "new key #{key}" unless EnvironmentPreserver::BUNDLER_KEYS.include?(key)
+ orig_key = "#{EnvironmentPreserver::BUNDLER_PREFIX}#{key}"
+ orig = ENV[key]
+ orig ||= EnvironmentPreserver::INTENTIONALLY_NIL
+ ENV[orig_key] ||= orig
+
+ ENV[key] = value
+ end
+ public :set_env
+
+ def set_bundle_variables
+ begin
+ exe_file = Bundler.rubygems.bin_path("bundler", "bundle", VERSION)
+ unless File.exist?(exe_file)
+ exe_file = File.expand_path("../../../exe/bundle", __FILE__)
+ end
+ Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", exe_file
+ rescue Gem::GemNotFoundException
+ exe_file = File.expand_path("../../../exe/bundle", __FILE__)
+ # for Ruby core repository
+ exe_file = File.expand_path("../../../../bin/bundle", __FILE__) unless File.exist?(exe_file)
+ Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", exe_file
+ end
+
+ # Set BUNDLE_GEMFILE
+ Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile(:order_matters).to_s
+ Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION
+ end
+
+ def set_path
+ validate_bundle_path
+ paths = (ENV["PATH"] || "").split(File::PATH_SEPARATOR)
+ paths.unshift "#{Bundler.bundle_path}/bin"
+ Bundler::SharedHelpers.set_env "PATH", paths.uniq.join(File::PATH_SEPARATOR)
+ end
+
+ def set_rubyopt
+ rubyopt = [ENV["RUBYOPT"]].compact
+ return if !rubyopt.empty? && rubyopt.first =~ %r{-rbundler/setup}
+ rubyopt.unshift %(-rbundler/setup)
+ Bundler::SharedHelpers.set_env "RUBYOPT", rubyopt.join(" ")
+ end
+
+ def set_rubylib
+ rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR)
+ rubylib.unshift bundler_ruby_lib
+ Bundler::SharedHelpers.set_env "RUBYLIB", rubylib.uniq.join(File::PATH_SEPARATOR)
+ end
+
+ def bundler_ruby_lib
+ resolve_path File.expand_path("../..", __FILE__)
+ end
+
+ def clean_load_path
+ # handle 1.9 where system gems are always on the load path
+ return unless defined?(::Gem)
+
+ bundler_lib = bundler_ruby_lib
+
+ loaded_gem_paths = Bundler.rubygems.loaded_gem_paths
+
+ $LOAD_PATH.reject! do |p|
+ next if resolve_path(p).start_with?(bundler_lib)
+ loaded_gem_paths.delete(p)
+ end
+ $LOAD_PATH.uniq!
+ end
+
+ def resolve_path(path)
+ expanded = File.expand_path(path)
+ return expanded unless File.respond_to?(:realpath) && File.exist?(expanded)
+
+ File.realpath(expanded)
+ end
+
+ def prints_major_deprecations?
+ require "bundler"
+ deprecation_release = Bundler::VERSION.split(".").drop(1).include?("99")
+ return false if !deprecation_release && !Bundler.settings[:major_deprecations]
+ require "bundler/deprecate"
+ return false if Bundler::Deprecate.skip
+ true
+ end
+
+ extend self
+ end
+end
diff --git a/lib/bundler/similarity_detector.rb b/lib/bundler/similarity_detector.rb
new file mode 100644
index 0000000000..b7f3ee7afa
--- /dev/null
+++ b/lib/bundler/similarity_detector.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Bundler
+ class SimilarityDetector
+ SimilarityScore = Struct.new(:string, :distance)
+
+ # initialize with an array of words to be matched against
+ def initialize(corpus)
+ @corpus = corpus
+ end
+
+ # return an array of words similar to 'word' from the corpus
+ def similar_words(word, limit = 3)
+ words_by_similarity = @corpus.map {|w| SimilarityScore.new(w, levenshtein_distance(word, w)) }
+ words_by_similarity.select {|s| s.distance <= limit }.sort_by(&:distance).map(&:string)
+ end
+
+ # return the result of 'similar_words', concatenated into a list
+ # (eg "a, b, or c")
+ def similar_word_list(word, limit = 3)
+ words = similar_words(word, limit)
+ if words.length == 1
+ words[0]
+ elsif words.length > 1
+ [words[0..-2].join(", "), words[-1]].join(" or ")
+ end
+ end
+
+ protected
+
+ # http://www.informit.com/articles/article.aspx?p=683059&seqNum=36
+ def levenshtein_distance(this, that, ins = 2, del = 2, sub = 1)
+ # ins, del, sub are weighted costs
+ return nil if this.nil?
+ return nil if that.nil?
+ dm = [] # distance matrix
+
+ # Initialize first row values
+ dm[0] = (0..this.length).collect {|i| i * ins }
+ fill = [0] * (this.length - 1)
+
+ # Initialize first column values
+ (1..that.length).each do |i|
+ dm[i] = [i * del, fill.flatten]
+ end
+
+ # populate matrix
+ (1..that.length).each do |i|
+ (1..this.length).each do |j|
+ # critical comparison
+ dm[i][j] = [
+ dm[i - 1][j - 1] + (this[j - 1] == that[i - 1] ? 0 : sub),
+ dm[i][j - 1] + ins,
+ dm[i - 1][j] + del
+ ].min
+ end
+ end
+
+ # The last value in matrix is the Levenshtein distance between the strings
+ dm[that.length][this.length]
+ end
+ end
+end
diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb
new file mode 100644
index 0000000000..26a3625bb1
--- /dev/null
+++ b/lib/bundler/source.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Source
+ autoload :Gemspec, "bundler/source/gemspec"
+ autoload :Git, "bundler/source/git"
+ autoload :Metadata, "bundler/source/metadata"
+ autoload :Path, "bundler/source/path"
+ autoload :Rubygems, "bundler/source/rubygems"
+
+ attr_accessor :dependency_names
+
+ def unmet_deps
+ specs.unmet_dependency_names
+ end
+
+ def version_message(spec)
+ message = "#{spec.name} #{spec.version}"
+ message += " (#{spec.platform})" if spec.platform != Gem::Platform::RUBY && !spec.platform.nil?
+
+ if Bundler.locked_gems
+ locked_spec = Bundler.locked_gems.specs.find {|s| s.name == spec.name }
+ locked_spec_version = locked_spec.version if locked_spec
+ if locked_spec_version && spec.version != locked_spec_version
+ message += Bundler.ui.add_color(" (was #{locked_spec_version})", version_color(spec.version, locked_spec_version))
+ end
+ end
+
+ message
+ end
+
+ def can_lock?(spec)
+ spec.source == self
+ end
+
+ # it's possible that gems from one source depend on gems from some
+ # other source, so now we download gemspecs and iterate over those
+ # dependencies, looking for gems we don't have info on yet.
+ def double_check_for(*); end
+
+ def dependency_names_to_double_check
+ specs.dependency_names
+ end
+
+ def include?(other)
+ other == self
+ end
+
+ def inspect
+ "#<#{self.class}:0x#{object_id} #{self}>"
+ end
+
+ def path?
+ instance_of?(Bundler::Source::Path)
+ end
+
+ def extension_cache_path(spec)
+ return unless Bundler.feature_flag.global_gem_cache?
+ return unless source_slug = extension_cache_slug(spec)
+ Bundler.user_cache.join(
+ "extensions", Gem::Platform.local.to_s, Bundler.ruby_scope,
+ source_slug, spec.full_name
+ )
+ end
+
+ private
+
+ def version_color(spec_version, locked_spec_version)
+ if Gem::Version.correct?(spec_version) && Gem::Version.correct?(locked_spec_version)
+ # display yellow if there appears to be a regression
+ earlier_version?(spec_version, locked_spec_version) ? :yellow : :green
+ else
+ # default to green if the versions cannot be directly compared
+ :green
+ end
+ end
+
+ def earlier_version?(spec_version, locked_spec_version)
+ Gem::Version.new(spec_version) < Gem::Version.new(locked_spec_version)
+ end
+
+ def print_using_message(message)
+ if !message.include?("(was ") && Bundler.feature_flag.suppress_install_using_messages?
+ Bundler.ui.debug message
+ else
+ Bundler.ui.info message
+ end
+ end
+
+ def extension_cache_slug(_)
+ nil
+ end
+ end
+end
diff --git a/lib/bundler/source/gemspec.rb b/lib/bundler/source/gemspec.rb
new file mode 100644
index 0000000000..7e3447e776
--- /dev/null
+++ b/lib/bundler/source/gemspec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Source
+ class Gemspec < Path
+ attr_reader :gemspec
+
+ def initialize(options)
+ super
+ @gemspec = options["gemspec"]
+ end
+
+ def as_path_source
+ Path.new(options)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
new file mode 100644
index 0000000000..0b00608bdd
--- /dev/null
+++ b/lib/bundler/source/git.rb
@@ -0,0 +1,329 @@
+# frozen_string_literal: true
+
+require "bundler/vendored_fileutils"
+require "uri"
+
+module Bundler
+ class Source
+ class Git < Path
+ autoload :GitProxy, "bundler/source/git/git_proxy"
+
+ attr_reader :uri, :ref, :branch, :options, :submodules
+
+ def initialize(options)
+ @options = options
+ @glob = options["glob"] || DEFAULT_GLOB
+
+ @allow_cached = false
+ @allow_remote = false
+
+ # Stringify options that could be set as symbols
+ %w[ref branch tag revision].each {|k| options[k] = options[k].to_s if options[k] }
+
+ @uri = options["uri"] || ""
+ @safe_uri = URICredentialsFilter.credential_filtered_uri(@uri)
+ @branch = options["branch"]
+ @ref = options["ref"] || options["branch"] || options["tag"] || "master"
+ @submodules = options["submodules"]
+ @name = options["name"]
+ @version = options["version"].to_s.strip.gsub("-", ".pre.")
+
+ @copied = false
+ @local = false
+ end
+
+ def self.from_lock(options)
+ new(options.merge("uri" => options.delete("remote")))
+ end
+
+ def to_lock
+ out = String.new("GIT\n")
+ out << " remote: #{@uri}\n"
+ out << " revision: #{revision}\n"
+ %w[ref branch tag submodules].each do |opt|
+ out << " #{opt}: #{options[opt]}\n" if options[opt]
+ end
+ out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
+ out << " specs:\n"
+ end
+
+ def hash
+ [self.class, uri, ref, branch, name, version, submodules].hash
+ end
+
+ def eql?(other)
+ other.is_a?(Git) && uri == other.uri && ref == other.ref &&
+ branch == other.branch && name == other.name &&
+ version == other.version && submodules == other.submodules
+ end
+
+ alias_method :==, :eql?
+
+ def to_s
+ at = if local?
+ path
+ elsif user_ref = options["ref"]
+ if ref =~ /\A[a-z0-9]{4,}\z/i
+ shortref_for_display(user_ref)
+ else
+ user_ref
+ end
+ else
+ ref
+ end
+
+ rev = begin
+ "@#{shortref_for_display(revision)}"
+ rescue GitError
+ nil
+ end
+
+ "#{@safe_uri} (at #{at}#{rev})"
+ end
+
+ def name
+ File.basename(@uri, ".git")
+ end
+
+ # This is the path which is going to contain a specific
+ # checkout of the git repository. When using local git
+ # repos, this is set to the local repo.
+ def install_path
+ @install_path ||= begin
+ git_scope = "#{base_name}-#{shortref_for_path(revision)}"
+
+ path = Bundler.install_path.join(git_scope)
+
+ if !path.exist? && Bundler.requires_sudo?
+ Bundler.user_bundle_path.join(Bundler.ruby_scope).join(git_scope)
+ else
+ path
+ end
+ end
+ end
+
+ alias_method :path, :install_path
+
+ def extension_dir_name
+ "#{base_name}-#{shortref_for_path(revision)}"
+ end
+
+ def unlock!
+ git_proxy.revision = nil
+ options["revision"] = nil
+
+ @unlocked = true
+ end
+
+ def local_override!(path)
+ return false if local?
+
+ path = Pathname.new(path)
+ path = path.expand_path(Bundler.root) unless path.relative?
+
+ unless options["branch"] || Bundler.settings[:disable_local_branch_check]
+ raise GitError, "Cannot use local override for #{name} at #{path} because " \
+ ":branch is not specified in Gemfile. Specify a branch or use " \
+ "`bundle config --delete` to remove the local override"
+ end
+
+ unless path.exist?
+ raise GitError, "Cannot use local override for #{name} because #{path} " \
+ "does not exist. Check `bundle config --delete` to remove the local override"
+ end
+
+ set_local!(path)
+
+ # Create a new git proxy without the cached revision
+ # so the Gemfile.lock always picks up the new revision.
+ @git_proxy = GitProxy.new(path, uri, ref)
+
+ if git_proxy.branch != options["branch"] && !Bundler.settings[:disable_local_branch_check]
+ raise GitError, "Local override for #{name} at #{path} is using branch " \
+ "#{git_proxy.branch} but Gemfile specifies #{options["branch"]}"
+ end
+
+ changed = cached_revision && cached_revision != git_proxy.revision
+
+ if changed && !@unlocked && !git_proxy.contains?(cached_revision)
+ raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(cached_revision)} " \
+ "but the current branch in your local override for #{name} does not contain such commit. " \
+ "Please make sure your branch is up to date."
+ end
+
+ changed
+ end
+
+ def specs(*)
+ set_local!(app_cache_path) if has_app_cache? && !local?
+
+ if requires_checkout? && !@copied
+ fetch
+ git_proxy.copy_to(install_path, submodules)
+ serialize_gemspecs_in(install_path)
+ @copied = true
+ end
+
+ local_specs
+ end
+
+ def install(spec, options = {})
+ force = options[:force]
+
+ print_using_message "Using #{version_message(spec)} from #{self}"
+
+ if (requires_checkout? && !@copied) || force
+ Bundler.ui.debug " * Checking out revision: #{ref}"
+ git_proxy.copy_to(install_path, submodules)
+ serialize_gemspecs_in(install_path)
+ @copied = true
+ end
+
+ generate_bin_options = { :disable_extensions => !Bundler.rubygems.spec_missing_extensions?(spec), :build_args => options[:build_args] }
+ generate_bin(spec, generate_bin_options)
+
+ requires_checkout? ? spec.post_install_message : nil
+ end
+
+ def cache(spec, custom_path = nil)
+ app_cache_path = app_cache_path(custom_path)
+ return unless Bundler.feature_flag.cache_all?
+ return if path == app_cache_path
+ cached!
+ FileUtils.rm_rf(app_cache_path)
+ git_proxy.checkout if requires_checkout?
+ git_proxy.copy_to(app_cache_path, @submodules)
+ serialize_gemspecs_in(app_cache_path)
+ end
+
+ def load_spec_files
+ super
+ rescue PathError => e
+ Bundler.ui.trace e
+ raise GitError, "#{self} is not yet checked out. Run `bundle install` first."
+ end
+
+ # This is the path which is going to contain a cache
+ # of the git repository. When using the same git repository
+ # across different projects, this cache will be shared.
+ # When using local git repos, this is set to the local repo.
+ def cache_path
+ @cache_path ||= begin
+ if Bundler.requires_sudo? || Bundler.feature_flag.global_gem_cache?
+ Bundler.user_cache
+ else
+ Bundler.bundle_path.join("cache", "bundler")
+ end.join("git", git_scope)
+ end
+ end
+
+ def app_cache_dirname
+ "#{base_name}-#{shortref_for_path(cached_revision || revision)}"
+ end
+
+ def revision
+ git_proxy.revision
+ end
+
+ def allow_git_ops?
+ @allow_remote || @allow_cached
+ end
+
+ private
+
+ def serialize_gemspecs_in(destination)
+ destination = destination.expand_path(Bundler.root) if destination.relative?
+ Dir["#{destination}/#{@glob}"].each do |spec_path|
+ # Evaluate gemspecs and cache the result. Gemspecs
+ # in git might require git or other dependencies.
+ # The gemspecs we cache should already be evaluated.
+ spec = Bundler.load_gemspec(spec_path)
+ next unless spec
+ Bundler.rubygems.set_installed_by_version(spec)
+ Bundler.rubygems.validate(spec)
+ File.open(spec_path, "wb") {|file| file.write(spec.to_ruby) }
+ end
+ end
+
+ def set_local!(path)
+ @local = true
+ @local_specs = @git_proxy = nil
+ @cache_path = @install_path = path
+ end
+
+ def has_app_cache?
+ cached_revision && super
+ end
+
+ def local?
+ @local
+ end
+
+ def requires_checkout?
+ allow_git_ops? && !local?
+ end
+
+ def base_name
+ File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*}, ""), ".git")
+ end
+
+ def shortref_for_display(ref)
+ ref[0..6]
+ end
+
+ def shortref_for_path(ref)
+ ref[0..11]
+ end
+
+ def uri_hash
+ if uri =~ %r{^\w+://(\w+@)?}
+ # Downcase the domain component of the URI
+ # and strip off a trailing slash, if one is present
+ input = URI.parse(uri).normalize.to_s.sub(%r{/$}, "")
+ else
+ # If there is no URI scheme, assume it is an ssh/git URI
+ input = uri
+ end
+ SharedHelpers.digest(:SHA1).hexdigest(input)
+ end
+
+ def cached_revision
+ options["revision"]
+ end
+
+ def cached?
+ cache_path.exist?
+ end
+
+ def git_proxy
+ @git_proxy ||= GitProxy.new(cache_path, uri, ref, cached_revision, self)
+ end
+
+ def fetch
+ git_proxy.checkout
+ rescue GitError => e
+ raise unless Bundler.feature_flag.allow_offline_install?
+ Bundler.ui.warn "Using cached git data because of network errors:\n#{e}"
+ end
+
+ # no-op, since we validate when re-serializing the gemspec
+ def validate_spec(_spec); end
+
+ if Bundler.rubygems.stubs_provide_full_functionality?
+ def load_gemspec(file)
+ stub = Gem::StubSpecification.gemspec_stub(file, install_path.parent, install_path.parent)
+ stub.full_gem_path = Pathname.new(file).dirname.expand_path(root).to_s.untaint
+ StubSpecification.from_stub(stub)
+ end
+ end
+
+ def git_scope
+ "#{base_name}-#{uri_hash}"
+ end
+
+ def extension_cache_slug(_)
+ extension_dir_name
+ end
+ end
+ end
+end
diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb
new file mode 100644
index 0000000000..cd964f7e56
--- /dev/null
+++ b/lib/bundler/source/git/git_proxy.rb
@@ -0,0 +1,262 @@
+# frozen_string_literal: true
+
+require "shellwords"
+require "tempfile"
+module Bundler
+ class Source
+ class Git
+ class GitNotInstalledError < GitError
+ def initialize
+ msg = String.new
+ msg << "You need to install git to be able to use gems from git repositories. "
+ msg << "For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git"
+ super msg
+ end
+ end
+
+ class GitNotAllowedError < GitError
+ def initialize(command)
+ msg = String.new
+ msg << "Bundler is trying to run a `git #{command}` at runtime. You probably need to run `bundle install`. However, "
+ msg << "this error message could probably be more useful. Please submit a ticket at http://github.com/bundler/bundler/issues "
+ msg << "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}"
+ super msg
+ end
+ end
+
+ class GitCommandError < GitError
+ def initialize(command, path = nil, extra_info = nil)
+ msg = String.new
+ msg << "Git error: command `git #{command}` in directory #{SharedHelpers.pwd} has failed."
+ msg << "\n#{extra_info}" if extra_info
+ msg << "\nIf this error persists you could try removing the cache directory '#{path}'" if path && path.exist?
+ super msg
+ end
+ end
+
+ class MissingGitRevisionError < GitError
+ def initialize(ref, repo)
+ msg = "Revision #{ref} does not exist in the repository #{repo}. Maybe you misspelled it?"
+ super msg
+ end
+ end
+
+ # The GitProxy is responsible to interact with git repositories.
+ # All actions required by the Git source is encapsulated in this
+ # object.
+ class GitProxy
+ attr_accessor :path, :uri, :ref
+ attr_writer :revision
+
+ def initialize(path, uri, ref, revision = nil, git = nil)
+ @path = path
+ @uri = uri
+ @ref = ref
+ @revision = revision
+ @git = git
+ raise GitNotInstalledError.new if allow? && !Bundler.git_present?
+ end
+
+ def revision
+ return @revision if @revision
+
+ begin
+ @revision ||= find_local_revision
+ rescue GitCommandError
+ raise MissingGitRevisionError.new(ref, URICredentialsFilter.credential_filtered_uri(uri))
+ end
+
+ @revision
+ end
+
+ def branch
+ @branch ||= allowed_in_path do
+ git("rev-parse --abbrev-ref HEAD").strip
+ end
+ end
+
+ def contains?(commit)
+ allowed_in_path do
+ result = git_null("branch --contains #{commit}")
+ $? == 0 && result =~ /^\* (.*)$/
+ end
+ end
+
+ def version
+ git("--version").match(/(git version\s*)?((\.?\d+)+).*/)[2]
+ end
+
+ def full_version
+ git("--version").sub("git version", "").strip
+ end
+
+ def checkout
+ return if path.exist? && has_revision_cached?
+ extra_ref = "#{Shellwords.shellescape(ref)}:#{Shellwords.shellescape(ref)}" if ref && ref.start_with?("refs/")
+
+ Bundler.ui.info "Fetching #{URICredentialsFilter.credential_filtered_uri(uri)}"
+
+ unless path.exist?
+ SharedHelpers.filesystem_access(path.dirname) do |p|
+ FileUtils.mkdir_p(p)
+ end
+ git_retry %(clone #{uri_escaped_with_configured_credentials} "#{path}" --bare --no-hardlinks --quiet)
+ return unless extra_ref
+ end
+
+ in_path do
+ git_retry %(fetch --force --quiet --tags #{uri_escaped_with_configured_credentials} "refs/heads/*:refs/heads/*" #{extra_ref})
+ end
+ end
+
+ def copy_to(destination, submodules = false)
+ # method 1
+ unless File.exist?(destination.join(".git"))
+ begin
+ SharedHelpers.filesystem_access(destination.dirname) do |p|
+ FileUtils.mkdir_p(p)
+ end
+ SharedHelpers.filesystem_access(destination) do |p|
+ FileUtils.rm_rf(p)
+ end
+ git_retry %(clone --no-checkout --quiet "#{path}" "#{destination}")
+ File.chmod(((File.stat(destination).mode | 0o777) & ~File.umask), destination)
+ rescue Errno::EEXIST => e
+ file_path = e.message[%r{.*?(/.*)}, 1]
+ raise GitError, "Bundler could not install a gem because it needs to " \
+ "create a directory, but a file exists - #{file_path}. Please delete " \
+ "this file and try again."
+ end
+ end
+ # method 2
+ SharedHelpers.chdir(destination) do
+ git_retry %(fetch --force --quiet --tags "#{path}")
+
+ begin
+ git "reset --hard #{@revision}"
+ rescue GitCommandError
+ raise MissingGitRevisionError.new(@revision, URICredentialsFilter.credential_filtered_uri(uri))
+ end
+
+ if submodules
+ git_retry "submodule update --init --recursive"
+ elsif Gem::Version.create(version) >= Gem::Version.create("2.9.0")
+ git_retry "submodule deinit --all --force"
+ end
+ end
+ end
+
+ private
+
+ # TODO: Do not rely on /dev/null.
+ # Given that open3 is not cross platform until Ruby 1.9.3,
+ # the best solution is to pipe to /dev/null if it exists.
+ # If it doesn't, everything will work fine, but the user
+ # will get the $stderr messages as well.
+ def git_null(command)
+ git("#{command} 2>#{Bundler::NULL}", false)
+ end
+
+ def git_retry(command)
+ Bundler::Retry.new("`git #{URICredentialsFilter.credential_filtered_string(command, uri)}`", GitNotAllowedError).attempts do
+ git(command)
+ end
+ end
+
+ def git(command, check_errors = true, error_msg = nil)
+ command_with_no_credentials = URICredentialsFilter.credential_filtered_string(command, uri)
+ raise GitNotAllowedError.new(command_with_no_credentials) unless allow?
+
+ out = SharedHelpers.with_clean_git_env do
+ capture_and_filter_stderr(uri) { `git #{command}` }
+ end
+
+ stdout_with_no_credentials = URICredentialsFilter.credential_filtered_string(out, uri)
+ raise GitCommandError.new(command_with_no_credentials, path, error_msg) if check_errors && !$?.success?
+ stdout_with_no_credentials
+ end
+
+ def has_revision_cached?
+ return unless @revision
+ in_path { git("cat-file -e #{@revision}") }
+ true
+ rescue GitError
+ false
+ end
+
+ def remove_cache
+ FileUtils.rm_rf(path)
+ end
+
+ def find_local_revision
+ allowed_in_path do
+ git("rev-parse --verify #{Shellwords.shellescape(ref)}", true).strip
+ end
+ end
+
+ # Escape the URI for git commands
+ def uri_escaped_with_configured_credentials
+ remote = configured_uri_for(uri)
+ if Bundler::WINDOWS
+ # Windows quoting requires double quotes only, with double quotes
+ # inside the string escaped by being doubled.
+ '"' + remote.gsub('"') { '""' } + '"'
+ else
+ # Bash requires single quoted strings, with the single quotes escaped
+ # by ending the string, escaping the quote, and restarting the string.
+ "'" + remote.gsub("'") { "'\\''" } + "'"
+ end
+ end
+
+ # Adds credentials to the URI as Fetcher#configured_uri_for does
+ def configured_uri_for(uri)
+ if /https?:/ =~ uri
+ remote = URI(uri)
+ config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host]
+ remote.userinfo ||= config_auth
+ remote.to_s
+ else
+ uri
+ end
+ end
+
+ def allow?
+ @git ? @git.allow_git_ops? : true
+ end
+
+ def in_path(&blk)
+ checkout unless path.exist?
+ _ = URICredentialsFilter # load it before we chdir
+ SharedHelpers.chdir(path, &blk)
+ end
+
+ def allowed_in_path
+ return in_path { yield } if allow?
+ raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application"
+ end
+
+ # TODO: Replace this with Open3 when upgrading to bundler 2
+ # Similar to #git_null, as Open3 is not cross-platform,
+ # a temporary way is to use Tempfile to capture the stderr.
+ # When replacing this using Open3, make sure git_null is
+ # also replaced by Open3, so stdout and stderr all got handled properly.
+ def capture_and_filter_stderr(uri)
+ return_value, captured_err = ""
+ backup_stderr = STDERR.dup
+ begin
+ Tempfile.open("captured_stderr") do |f|
+ STDERR.reopen(f)
+ return_value = yield
+ f.rewind
+ captured_err = f.read
+ end
+ ensure
+ STDERR.reopen backup_stderr
+ end
+ $stderr.puts URICredentialsFilter.credential_filtered_string(captured_err, uri) if uri && !captured_err.empty?
+ return_value
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/source/metadata.rb b/lib/bundler/source/metadata.rb
new file mode 100644
index 0000000000..9c5657eef6
--- /dev/null
+++ b/lib/bundler/source/metadata.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Source
+ class Metadata < Source
+ def specs
+ @specs ||= Index.build do |idx|
+ idx << Gem::Specification.new("ruby\0", RubyVersion.system.to_gem_version_with_patchlevel)
+ idx << Gem::Specification.new("rubygems\0", Gem::VERSION)
+
+ idx << Gem::Specification.new do |s|
+ s.name = "bundler"
+ s.version = VERSION
+ s.platform = Gem::Platform::RUBY
+ s.source = self
+ s.authors = ["bundler team"]
+ s.bindir = "exe"
+ s.executables = %w[bundle]
+ # can't point to the actual gemspec or else the require paths will be wrong
+ s.loaded_from = File.expand_path("..", __FILE__)
+ end
+ if loaded_spec = Bundler.rubygems.loaded_specs("bundler")
+ idx << loaded_spec # this has to come after the fake gemspec, to override it
+ elsif local_spec = Bundler.rubygems.find_name("bundler").find {|s| s.version.to_s == VERSION }
+ idx << local_spec
+ end
+
+ idx.each {|s| s.source = self }
+ end
+ end
+
+ def cached!; end
+
+ def remote!; end
+
+ def options
+ {}
+ end
+
+ def install(spec, _opts = {})
+ print_using_message "Using #{version_message(spec)}"
+ nil
+ end
+
+ def to_s
+ "the local ruby installation"
+ end
+
+ def ==(other)
+ self.class == other.class
+ end
+ alias_method :eql?, :==
+
+ def hash
+ self.class.hash
+ end
+
+ def version_message(spec)
+ "#{spec.name} #{spec.version}"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb
new file mode 100644
index 0000000000..ed734bf549
--- /dev/null
+++ b/lib/bundler/source/path.rb
@@ -0,0 +1,249 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Source
+ class Path < Source
+ autoload :Installer, "bundler/source/path/installer"
+
+ attr_reader :path, :options, :root_path, :original_path
+ attr_writer :name
+ attr_accessor :version
+
+ protected :original_path
+
+ DEFAULT_GLOB = "{,*,*/*}.gemspec".freeze
+
+ def initialize(options)
+ @options = options.dup
+ @glob = options["glob"] || DEFAULT_GLOB
+
+ @allow_cached = false
+ @allow_remote = false
+
+ @root_path = options["root_path"] || Bundler.root
+
+ if options["path"]
+ @path = Pathname.new(options["path"])
+ @path = expand(@path) unless @path.relative?
+ end
+
+ @name = options["name"]
+ @version = options["version"]
+
+ # Stores the original path. If at any point we move to the
+ # cached directory, we still have the original path to copy from.
+ @original_path = @path
+ end
+
+ def remote!
+ @local_specs = nil
+ @allow_remote = true
+ end
+
+ def cached!
+ @local_specs = nil
+ @allow_cached = true
+ end
+
+ def self.from_lock(options)
+ new(options.merge("path" => options.delete("remote")))
+ end
+
+ def to_lock
+ out = String.new("PATH\n")
+ out << " remote: #{lockfile_path}\n"
+ out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
+ out << " specs:\n"
+ end
+
+ def to_s
+ "source at `#{@path}`"
+ end
+
+ def hash
+ [self.class, expanded_path, version].hash
+ end
+
+ def eql?(other)
+ return unless other.class == self.class
+ expanded_original_path == other.expanded_original_path &&
+ version == other.version
+ end
+
+ alias_method :==, :eql?
+
+ def name
+ File.basename(expanded_path.to_s)
+ end
+
+ def install(spec, options = {})
+ print_using_message "Using #{version_message(spec)} from #{self}"
+ generate_bin(spec, :disable_extensions => true)
+ nil # no post-install message
+ end
+
+ def cache(spec, custom_path = nil)
+ app_cache_path = app_cache_path(custom_path)
+ return unless Bundler.feature_flag.cache_all?
+ return if expand(@original_path).to_s.index(root_path.to_s + "/") == 0
+
+ unless @original_path.exist?
+ raise GemNotFound, "Can't cache gem #{version_message(spec)} because #{self} is missing!"
+ end
+
+ FileUtils.rm_rf(app_cache_path)
+ FileUtils.cp_r("#{@original_path}/.", app_cache_path)
+ FileUtils.touch(app_cache_path.join(".bundlecache"))
+ end
+
+ def local_specs(*)
+ @local_specs ||= load_spec_files
+ end
+
+ def specs
+ if has_app_cache?
+ @path = app_cache_path
+ @expanded_path = nil # Invalidate
+ end
+ local_specs
+ end
+
+ def app_cache_dirname
+ name
+ end
+
+ def root
+ Bundler.root
+ end
+
+ def expanded_original_path
+ @expanded_original_path ||= expand(original_path)
+ end
+
+ private
+
+ def expanded_path
+ @expanded_path ||= expand(path)
+ end
+
+ def expand(somepath)
+ somepath.expand_path(root_path)
+ rescue ArgumentError => e
+ Bundler.ui.debug(e)
+ raise PathError, "There was an error while trying to use the path " \
+ "`#{somepath}`.\nThe error message was: #{e.message}."
+ end
+
+ def lockfile_path
+ return relative_path(original_path) if original_path.absolute?
+ expand(original_path).relative_path_from(Bundler.root)
+ end
+
+ def app_cache_path(custom_path = nil)
+ @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
+ end
+
+ def has_app_cache?
+ SharedHelpers.in_bundle? && app_cache_path.exist?
+ end
+
+ def load_gemspec(file)
+ return unless spec = Bundler.load_gemspec(file)
+ Bundler.rubygems.set_installed_by_version(spec)
+ spec
+ end
+
+ def validate_spec(spec)
+ Bundler.rubygems.validate(spec)
+ end
+
+ def load_spec_files
+ index = Index.new
+
+ if File.directory?(expanded_path)
+ # We sort depth-first since `<<` will override the earlier-found specs
+ Dir["#{expanded_path}/#{@glob}"].sort_by {|p| -p.split(File::SEPARATOR).size }.each do |file|
+ next unless spec = load_gemspec(file)
+ spec.source = self
+
+ # Validation causes extension_dir to be calculated, which depends
+ # on #source, so we validate here instead of load_gemspec
+ validate_spec(spec)
+ index << spec
+ end
+
+ if index.empty? && @name && @version
+ index << Gem::Specification.new do |s|
+ s.name = @name
+ s.source = self
+ s.version = Gem::Version.new(@version)
+ s.platform = Gem::Platform::RUBY
+ s.summary = "Fake gemspec for #{@name}"
+ s.relative_loaded_from = "#{@name}.gemspec"
+ s.authors = ["no one"]
+ if expanded_path.join("bin").exist?
+ executables = expanded_path.join("bin").children
+ executables.reject! {|p| File.directory?(p) }
+ s.executables = executables.map {|c| c.basename.to_s }
+ end
+ end
+ end
+ else
+ message = String.new("The path `#{expanded_path}` ")
+ message << if File.exist?(expanded_path)
+ "is not a directory."
+ else
+ "does not exist."
+ end
+ raise PathError, message
+ end
+
+ index
+ end
+
+ def relative_path(path = self.path)
+ if path.to_s.start_with?(root_path.to_s)
+ return path.relative_path_from(root_path)
+ end
+ path
+ end
+
+ def generate_bin(spec, options = {})
+ gem_dir = Pathname.new(spec.full_gem_path)
+
+ # Some gem authors put absolute paths in their gemspec
+ # and we have to save them from themselves
+ spec.files = spec.files.map do |p|
+ next p unless p =~ /\A#{Pathname::SEPARATOR_PAT}/
+ next if File.directory?(p)
+ begin
+ Pathname.new(p).relative_path_from(gem_dir).to_s
+ rescue ArgumentError
+ p
+ end
+ end.compact
+
+ installer = Path::Installer.new(
+ spec,
+ :env_shebang => false,
+ :disable_extensions => options[:disable_extensions],
+ :build_args => options[:build_args],
+ :bundler_extension_cache_path => extension_cache_path(spec)
+ )
+ installer.post_install
+ rescue Gem::InvalidSpecificationException => e
+ Bundler.ui.warn "\n#{spec.name} at #{spec.full_gem_path} did not have a valid gemspec.\n" \
+ "This prevents bundler from installing bins or native extensions, but " \
+ "that may not affect its functionality."
+
+ if !spec.extensions.empty? && !spec.email.empty?
+ Bundler.ui.warn "If you need to use this package without installing it from a gem " \
+ "repository, please contact #{spec.email} and ask them " \
+ "to modify their .gemspec so it can work with `gem build`."
+ end
+
+ Bundler.ui.warn "The validation message from RubyGems was:\n #{e.message}"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/source/path/installer.rb b/lib/bundler/source/path/installer.rb
new file mode 100644
index 0000000000..a0357ffa39
--- /dev/null
+++ b/lib/bundler/source/path/installer.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Source
+ class Path
+ class Installer < Bundler::RubyGemsGemInstaller
+ attr_reader :spec
+
+ def initialize(spec, options = {})
+ @options = options
+ @spec = spec
+ @gem_dir = Bundler.rubygems.path(spec.full_gem_path)
+ @wrappers = true
+ @env_shebang = true
+ @format_executable = options[:format_executable] || false
+ @build_args = options[:build_args] || Bundler.rubygems.build_args
+ @gem_bin_dir = "#{Bundler.rubygems.gem_dir}/bin"
+ @disable_extensions = options[:disable_extensions]
+
+ if Bundler.requires_sudo?
+ @tmp_dir = Bundler.tmp(spec.full_name).to_s
+ @bin_dir = "#{@tmp_dir}/bin"
+ else
+ @bin_dir = @gem_bin_dir
+ end
+ end
+
+ def post_install
+ SharedHelpers.chdir(@gem_dir) do
+ run_hooks(:pre_install)
+
+ unless @disable_extensions
+ build_extensions
+ run_hooks(:post_build)
+ end
+
+ generate_bin unless spec.executables.nil? || spec.executables.empty?
+
+ run_hooks(:post_install)
+ end
+ ensure
+ Bundler.rm_rf(@tmp_dir) if Bundler.requires_sudo?
+ end
+
+ private
+
+ def generate_bin
+ super
+
+ if Bundler.requires_sudo?
+ SharedHelpers.filesystem_access(@gem_bin_dir) do |p|
+ Bundler.mkdir_p(p)
+ end
+ spec.executables.each do |exe|
+ Bundler.sudo "cp -R #{@bin_dir}/#{exe} #{@gem_bin_dir}"
+ end
+ end
+ end
+
+ def run_hooks(type)
+ hooks_meth = "#{type}_hooks"
+ return unless Gem.respond_to?(hooks_meth)
+ Gem.send(hooks_meth).each do |hook|
+ result = hook.call(self)
+ next unless result == false
+ location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/
+ message = "#{type} hook#{location} failed for #{spec.full_name}"
+ raise InstallHookError, message
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
new file mode 100644
index 0000000000..485b388a32
--- /dev/null
+++ b/lib/bundler/source/rubygems.rb
@@ -0,0 +1,539 @@
+# frozen_string_literal: true
+
+require "uri"
+require "rubygems/user_interaction"
+
+module Bundler
+ class Source
+ class Rubygems < Source
+ autoload :Remote, "bundler/source/rubygems/remote"
+
+ # Use the API when installing less than X gems
+ API_REQUEST_LIMIT = 500
+ # Ask for X gems per API request
+ API_REQUEST_SIZE = 50
+
+ attr_reader :remotes, :caches
+
+ def initialize(options = {})
+ @options = options
+ @remotes = []
+ @dependency_names = []
+ @allow_remote = false
+ @allow_cached = false
+ @caches = [cache_path, *Bundler.rubygems.gem_cache]
+
+ Array(options["remotes"] || []).reverse_each {|r| add_remote(r) }
+ end
+
+ def remote!
+ @specs = nil
+ @allow_remote = true
+ end
+
+ def cached!
+ @specs = nil
+ @allow_cached = true
+ end
+
+ def hash
+ @remotes.hash
+ end
+
+ def eql?(other)
+ other.is_a?(Rubygems) && other.credless_remotes == credless_remotes
+ end
+
+ alias_method :==, :eql?
+
+ def include?(o)
+ o.is_a?(Rubygems) && (o.credless_remotes - credless_remotes).empty?
+ end
+
+ def can_lock?(spec)
+ return super if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ spec.source.is_a?(Rubygems)
+ end
+
+ def options
+ { "remotes" => @remotes.map(&:to_s) }
+ end
+
+ def self.from_lock(options)
+ new(options)
+ end
+
+ def to_lock
+ out = String.new("GEM\n")
+ remotes.reverse_each do |remote|
+ out << " remote: #{suppress_configured_credentials remote}\n"
+ end
+ out << " specs:\n"
+ end
+
+ def to_s
+ if remotes.empty?
+ "locally installed gems"
+ else
+ remote_names = remotes.map(&:to_s).join(", ")
+ "rubygems repository #{remote_names} or installed locally"
+ end
+ end
+ alias_method :name, :to_s
+
+ def specs
+ @specs ||= begin
+ # remote_specs usually generates a way larger Index than the other
+ # sources, and large_idx.use small_idx is way faster than
+ # small_idx.use large_idx.
+ idx = @allow_remote ? remote_specs.dup : Index.new
+ idx.use(cached_specs, :override_dupes) if @allow_cached || @allow_remote
+ idx.use(installed_specs, :override_dupes)
+ idx
+ end
+ end
+
+ def install(spec, opts = {})
+ force = opts[:force]
+ ensure_builtin_gems_cached = opts[:ensure_builtin_gems_cached]
+
+ if ensure_builtin_gems_cached && builtin_gem?(spec)
+ if !cached_path(spec)
+ cached_built_in_gem(spec) unless spec.remote
+ force = true
+ else
+ spec.loaded_from = loaded_from(spec)
+ end
+ end
+
+ if installed?(spec) && !force
+ print_using_message "Using #{version_message(spec)}"
+ return nil # no post-install message
+ end
+
+ # Download the gem to get the spec, because some specs that are returned
+ # by rubygems.org are broken and wrong.
+ if spec.remote
+ # Check for this spec from other sources
+ uris = [spec.remote.anonymized_uri]
+ uris += remotes_for_spec(spec).map(&:anonymized_uri)
+ uris.uniq!
+ Installer.ambiguous_gems << [spec.name, *uris] if uris.length > 1
+
+ s = Bundler.rubygems.spec_from_gem(fetch_gem(spec), Bundler.settings["trust-policy"])
+ spec.__swap__(s)
+ end
+
+ unless Bundler.settings[:no_install]
+ message = "Installing #{version_message(spec)}"
+ message += " with native extensions" if spec.extensions.any?
+ Bundler.ui.confirm message
+
+ path = cached_gem(spec)
+ if requires_sudo?
+ install_path = Bundler.tmp(spec.full_name)
+ bin_path = install_path.join("bin")
+ else
+ install_path = rubygems_dir
+ bin_path = Bundler.system_bindir
+ end
+
+ Bundler.mkdir_p bin_path, :no_sudo => true unless spec.executables.empty? || Bundler.rubygems.provides?(">= 2.7.5")
+
+ installed_spec = nil
+ Bundler.rubygems.preserve_paths do
+ installed_spec = Bundler::RubyGemsGemInstaller.at(
+ path,
+ :install_dir => install_path.to_s,
+ :bin_dir => bin_path.to_s,
+ :ignore_dependencies => true,
+ :wrappers => true,
+ :env_shebang => true,
+ :build_args => opts[:build_args],
+ :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum,
+ :bundler_extension_cache_path => extension_cache_path(spec)
+ ).install
+ end
+ spec.full_gem_path = installed_spec.full_gem_path
+
+ # SUDO HAX
+ if requires_sudo?
+ Bundler.rubygems.repository_subdirectories.each do |name|
+ src = File.join(install_path, name, "*")
+ dst = File.join(rubygems_dir, name)
+ if name == "extensions" && Dir.glob(src).any?
+ src = File.join(src, "*/*")
+ ext_src = Dir.glob(src).first
+ ext_src.gsub!(src[0..-6], "")
+ dst = File.dirname(File.join(dst, ext_src))
+ end
+ SharedHelpers.filesystem_access(dst) do |p|
+ Bundler.mkdir_p(p)
+ end
+ Bundler.sudo "cp -R #{src} #{dst}" if Dir[src].any?
+ end
+
+ spec.executables.each do |exe|
+ SharedHelpers.filesystem_access(Bundler.system_bindir) do |p|
+ Bundler.mkdir_p(p)
+ end
+ Bundler.sudo "cp -R #{install_path}/bin/#{exe} #{Bundler.system_bindir}/"
+ end
+ end
+ installed_spec.loaded_from = loaded_from(spec)
+ end
+ spec.loaded_from = loaded_from(spec)
+
+ spec.post_install_message
+ ensure
+ Bundler.rm_rf(install_path) if requires_sudo?
+ end
+
+ def cache(spec, custom_path = nil)
+ if builtin_gem?(spec)
+ cached_path = cached_built_in_gem(spec)
+ else
+ cached_path = cached_gem(spec)
+ end
+ raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path
+ return if File.dirname(cached_path) == Bundler.app_cache.to_s
+ Bundler.ui.info " * #{File.basename(cached_path)}"
+ FileUtils.cp(cached_path, Bundler.app_cache(custom_path))
+ rescue Errno::EACCES => e
+ Bundler.ui.debug(e)
+ raise InstallError, e.message
+ end
+
+ def cached_built_in_gem(spec)
+ cached_path = cached_path(spec)
+ if cached_path.nil?
+ remote_spec = remote_specs.search(spec).first
+ if remote_spec
+ cached_path = fetch_gem(remote_spec)
+ else
+ Bundler.ui.warn "#{spec.full_name} is built in to Ruby, and can't be cached because your Gemfile doesn't have any sources that contain it."
+ end
+ end
+ cached_path
+ end
+
+ def add_remote(source)
+ uri = normalize_uri(source)
+ @remotes.unshift(uri) unless @remotes.include?(uri)
+ end
+
+ def equivalent_remotes?(other_remotes)
+ other_remotes.map(&method(:remove_auth)) == @remotes.map(&method(:remove_auth))
+ end
+
+ def replace_remotes(other_remotes, allow_equivalent = false)
+ return false if other_remotes == @remotes
+
+ equivalent = allow_equivalent && equivalent_remotes?(other_remotes)
+
+ @remotes = []
+ other_remotes.reverse_each do |r|
+ add_remote r.to_s
+ end
+
+ !equivalent
+ end
+
+ def unmet_deps
+ if @allow_remote && api_fetchers.any?
+ remote_specs.unmet_dependency_names
+ else
+ []
+ end
+ end
+
+ def fetchers
+ @fetchers ||= remotes.map do |uri|
+ remote = Source::Rubygems::Remote.new(uri)
+ Bundler::Fetcher.new(remote)
+ end
+ end
+
+ def double_check_for(unmet_dependency_names)
+ return unless @allow_remote
+ return unless api_fetchers.any?
+
+ unmet_dependency_names = unmet_dependency_names.call
+ unless unmet_dependency_names.nil?
+ if api_fetchers.size <= 1
+ # can't do this when there are multiple fetchers because then we might not fetch from _all_
+ # of them
+ unmet_dependency_names -= remote_specs.spec_names # avoid re-fetching things we've already gotten
+ end
+ return if unmet_dependency_names.empty?
+ end
+
+ Bundler.ui.debug "Double checking for #{unmet_dependency_names || "all specs (due to the size of the request)"} in #{self}"
+
+ fetch_names(api_fetchers, unmet_dependency_names, specs, false)
+ end
+
+ def dependency_names_to_double_check
+ names = []
+ remote_specs.each do |spec|
+ case spec
+ when EndpointSpecification, Gem::Specification, StubSpecification, LazySpecification
+ names.concat(spec.runtime_dependencies)
+ when RemoteSpecification # from the full index
+ return nil
+ else
+ raise "unhandled spec type (#{spec.inspect})"
+ end
+ end
+ names.map!(&:name) if names
+ names
+ end
+
+ protected
+
+ def credless_remotes
+ remotes.map(&method(:suppress_configured_credentials))
+ end
+
+ def remotes_for_spec(spec)
+ specs.search_all(spec.name).inject([]) do |uris, s|
+ uris << s.remote if s.remote
+ uris
+ end
+ end
+
+ def loaded_from(spec)
+ "#{rubygems_dir}/specifications/#{spec.full_name}.gemspec"
+ end
+
+ def cached_gem(spec)
+ cached_gem = cached_path(spec)
+ unless cached_gem
+ raise Bundler::GemNotFound, "Could not find #{spec.file_name} for installation"
+ end
+ cached_gem
+ end
+
+ def cached_path(spec)
+ possibilities = @caches.map {|p| "#{p}/#{spec.file_name}" }
+ possibilities.find {|p| File.exist?(p) }
+ end
+
+ def normalize_uri(uri)
+ uri = uri.to_s
+ uri = "#{uri}/" unless uri =~ %r{/$}
+ uri = URI(uri)
+ raise ArgumentError, "The source must be an absolute URI. For example:\n" \
+ "source 'https://rubygems.org'" if !uri.absolute? || (uri.is_a?(URI::HTTP) && uri.host.nil?)
+ uri
+ end
+
+ def suppress_configured_credentials(remote)
+ remote_nouser = remove_auth(remote)
+ if remote.userinfo && remote.userinfo == Bundler.settings[remote_nouser]
+ remote_nouser
+ else
+ remote
+ end
+ end
+
+ def remove_auth(remote)
+ if remote.user || remote.password
+ remote.dup.tap {|uri| uri.user = uri.password = nil }.to_s
+ else
+ remote.to_s
+ end
+ end
+
+ def installed_specs
+ @installed_specs ||= Index.build do |idx|
+ Bundler.rubygems.all_specs.reverse_each do |spec|
+ next if spec.name == "bundler"
+ spec.source = self
+ if Bundler.rubygems.spec_missing_extensions?(spec, false)
+ Bundler.ui.debug "Source #{self} is ignoring #{spec} because it is missing extensions"
+ next
+ end
+ idx << spec
+ end
+ end
+ end
+
+ def cached_specs
+ @cached_specs ||= begin
+ idx = installed_specs.dup
+
+ Dir["#{cache_path}/*.gem"].each do |gemfile|
+ next if gemfile =~ /^bundler\-[\d\.]+?\.gem/
+ s ||= Bundler.rubygems.spec_from_gem(gemfile)
+ s.source = self
+ if Bundler.rubygems.spec_missing_extensions?(s, false)
+ Bundler.ui.debug "Source #{self} is ignoring #{s} because it is missing extensions"
+ next
+ end
+ idx << s
+ end
+
+ idx
+ end
+ end
+
+ def api_fetchers
+ fetchers.select {|f| f.use_api && f.fetchers.first.api_fetcher? }
+ end
+
+ def remote_specs
+ @remote_specs ||= Index.build do |idx|
+ index_fetchers = fetchers - api_fetchers
+
+ # gather lists from non-api sites
+ fetch_names(index_fetchers, nil, idx, false)
+
+ # because ensuring we have all the gems we need involves downloading
+ # the gemspecs of those gems, if the non-api sites contain more than
+ # about 500 gems, we treat all sites as non-api for speed.
+ allow_api = idx.size < API_REQUEST_LIMIT && dependency_names.size < API_REQUEST_LIMIT
+ Bundler.ui.debug "Need to query more than #{API_REQUEST_LIMIT} gems." \
+ " Downloading full index instead..." unless allow_api
+
+ fetch_names(api_fetchers, allow_api && dependency_names, idx, false)
+ end
+ end
+
+ def fetch_names(fetchers, dependency_names, index, override_dupes)
+ fetchers.each do |f|
+ if dependency_names
+ Bundler.ui.info "Fetching gem metadata from #{f.uri}", Bundler.ui.debug?
+ index.use f.specs_with_retry(dependency_names, self), override_dupes
+ Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
+ else
+ Bundler.ui.info "Fetching source index from #{f.uri}"
+ index.use f.specs_with_retry(nil, self), override_dupes
+ end
+ end
+ end
+
+ def fetch_gem(spec)
+ return false unless spec.remote
+
+ spec.fetch_platform
+
+ download_path = requires_sudo? ? Bundler.tmp(spec.full_name) : rubygems_dir
+ gem_path = "#{rubygems_dir}/cache/#{spec.full_name}.gem"
+
+ SharedHelpers.filesystem_access("#{download_path}/cache") do |p|
+ FileUtils.mkdir_p(p)
+ end
+ download_gem(spec, download_path)
+
+ if requires_sudo?
+ SharedHelpers.filesystem_access("#{rubygems_dir}/cache") do |p|
+ Bundler.mkdir_p(p)
+ end
+ Bundler.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}"
+ end
+
+ gem_path
+ ensure
+ Bundler.rm_rf(download_path) if requires_sudo?
+ end
+
+ def builtin_gem?(spec)
+ # Ruby 2.1, where all included gems have this summary
+ return true if spec.summary =~ /is bundled with Ruby/
+
+ # Ruby 2.0, where gemspecs are stored in specifications/default/
+ spec.loaded_from && spec.loaded_from.include?("specifications/default/")
+ end
+
+ def installed?(spec)
+ installed_specs[spec].any?
+ end
+
+ def requires_sudo?
+ Bundler.requires_sudo?
+ end
+
+ def rubygems_dir
+ Bundler.rubygems.gem_dir
+ end
+
+ def cache_path
+ Bundler.app_cache
+ end
+
+ private
+
+ # Checks if the requested spec exists in the global cache. If it does,
+ # we copy it to the download path, and if it does not, we download it.
+ #
+ # @param [Specification] spec
+ # the spec we want to download or retrieve from the cache.
+ #
+ # @param [String] download_path
+ # the local directory the .gem will end up in.
+ #
+ def download_gem(spec, download_path)
+ local_path = File.join(download_path, "cache/#{spec.full_name}.gem")
+
+ if (cache_path = download_cache_path(spec)) && cache_path.file?
+ SharedHelpers.filesystem_access(local_path) do
+ FileUtils.cp(cache_path, local_path)
+ end
+ else
+ uri = spec.remote.uri
+ Bundler.ui.confirm("Fetching #{version_message(spec)}")
+ rubygems_local_path = Bundler.rubygems.download_gem(spec, uri, download_path)
+ if rubygems_local_path != local_path
+ FileUtils.mv(rubygems_local_path, local_path)
+ end
+ cache_globally(spec, local_path)
+ end
+ end
+
+ # Checks if the requested spec exists in the global cache. If it does
+ # not, we create the relevant global cache subdirectory if it does not
+ # exist and copy the spec from the local cache to the global cache.
+ #
+ # @param [Specification] spec
+ # the spec we want to copy to the global cache.
+ #
+ # @param [String] local_cache_path
+ # the local directory from which we want to copy the .gem.
+ #
+ def cache_globally(spec, local_cache_path)
+ return unless cache_path = download_cache_path(spec)
+ return if cache_path.exist?
+
+ SharedHelpers.filesystem_access(cache_path.dirname, &:mkpath)
+ SharedHelpers.filesystem_access(cache_path) do
+ FileUtils.cp(local_cache_path, cache_path)
+ end
+ end
+
+ # Returns the global cache path of the calling Rubygems::Source object.
+ #
+ # Note that the Source determines the path's subdirectory. We use this
+ # subdirectory in the global cache path so that gems with the same name
+ # -- and possibly different versions -- from different sources are saved
+ # to their respective subdirectories and do not override one another.
+ #
+ # @param [Gem::Specification] specification
+ #
+ # @return [Pathname] The global cache path.
+ #
+ def download_cache_path(spec)
+ return unless Bundler.feature_flag.global_gem_cache?
+ return unless remote = spec.remote
+ return unless cache_slug = remote.cache_slug
+
+ Bundler.user_cache.join("gems", cache_slug, spec.file_name)
+ end
+
+ def extension_cache_slug(spec)
+ return unless remote = spec.remote
+ remote.cache_slug
+ end
+ end
+ end
+end
diff --git a/lib/bundler/source/rubygems/remote.rb b/lib/bundler/source/rubygems/remote.rb
new file mode 100644
index 0000000000..b45f33770a
--- /dev/null
+++ b/lib/bundler/source/rubygems/remote.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Source
+ class Rubygems
+ class Remote
+ attr_reader :uri, :anonymized_uri, :original_uri
+
+ def initialize(uri)
+ orig_uri = uri
+ uri = Bundler.settings.mirror_for(uri)
+ @original_uri = orig_uri if orig_uri != uri
+ fallback_auth = Bundler.settings.credentials_for(uri)
+
+ @uri = apply_auth(uri, fallback_auth).freeze
+ @anonymized_uri = remove_auth(@uri).freeze
+ end
+
+ # @return [String] A slug suitable for use as a cache key for this
+ # remote.
+ #
+ def cache_slug
+ @cache_slug ||= begin
+ return nil unless SharedHelpers.md5_available?
+
+ cache_uri = original_uri || uri
+
+ # URI::File of Ruby 2.6 returns empty string when given "file://".
+ host = defined?(URI::File) && cache_uri.is_a?(URI::File) ? nil : cache_uri.host
+
+ uri_parts = [host, cache_uri.user, cache_uri.port, cache_uri.path]
+ uri_digest = SharedHelpers.digest(:MD5).hexdigest(uri_parts.compact.join("."))
+
+ uri_parts[-1] = uri_digest
+ uri_parts.compact.join(".")
+ end
+ end
+
+ def to_s
+ "rubygems remote at #{anonymized_uri}"
+ end
+
+ private
+
+ def apply_auth(uri, auth)
+ if auth && uri.userinfo.nil?
+ uri = uri.dup
+ uri.userinfo = auth
+ end
+
+ uri
+ rescue URI::InvalidComponentError
+ error_message = "Please CGI escape your usernames and passwords before " \
+ "setting them for authentication."
+ raise HTTPError.new(error_message)
+ end
+
+ def remove_auth(uri)
+ if uri.userinfo
+ uri = uri.dup
+ uri.user = uri.password = nil
+ end
+
+ uri
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb
new file mode 100644
index 0000000000..ac2adacb3d
--- /dev/null
+++ b/lib/bundler/source_list.rb
@@ -0,0 +1,186 @@
+# frozen_string_literal: true
+
+module Bundler
+ class SourceList
+ attr_reader :path_sources,
+ :git_sources,
+ :plugin_sources,
+ :global_rubygems_source,
+ :metadata_source
+
+ def initialize
+ @path_sources = []
+ @git_sources = []
+ @plugin_sources = []
+ @global_rubygems_source = nil
+ @rubygems_aggregate = rubygems_aggregate_class.new
+ @rubygems_sources = []
+ @metadata_source = Source::Metadata.new
+ end
+
+ def add_path_source(options = {})
+ if options["gemspec"]
+ add_source_to_list Source::Gemspec.new(options), path_sources
+ else
+ add_source_to_list Source::Path.new(options), path_sources
+ end
+ end
+
+ def add_git_source(options = {})
+ add_source_to_list(Source::Git.new(options), git_sources).tap do |source|
+ warn_on_git_protocol(source)
+ end
+ end
+
+ def add_rubygems_source(options = {})
+ add_source_to_list Source::Rubygems.new(options), @rubygems_sources
+ end
+
+ def add_plugin_source(source, options = {})
+ add_source_to_list Plugin.source(source).new(options), @plugin_sources
+ end
+
+ def global_rubygems_source=(uri)
+ if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ @global_rubygems_source ||= rubygems_aggregate_class.new("remotes" => uri)
+ end
+ add_rubygems_remote(uri)
+ end
+
+ def add_rubygems_remote(uri)
+ if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ return if Bundler.feature_flag.disable_multisource?
+ raise InvalidOption, "`lockfile_uses_separate_rubygems_sources` cannot be set without `disable_multisource` being set"
+ end
+ @rubygems_aggregate.add_remote(uri)
+ @rubygems_aggregate
+ end
+
+ def default_source
+ global_rubygems_source || @rubygems_aggregate
+ end
+
+ def rubygems_sources
+ @rubygems_sources + [default_source]
+ end
+
+ def rubygems_remotes
+ rubygems_sources.map(&:remotes).flatten.uniq
+ end
+
+ def all_sources
+ path_sources + git_sources + plugin_sources + rubygems_sources + [metadata_source]
+ end
+
+ def get(source)
+ source_list_for(source).find {|s| equal_source?(source, s) || equivalent_source?(source, s) }
+ end
+
+ def lock_sources
+ if Bundler.feature_flag.lockfile_uses_separate_rubygems_sources?
+ [[default_source], @rubygems_sources, git_sources, path_sources, plugin_sources].map do |sources|
+ sources.sort_by(&:to_s)
+ end.flatten(1)
+ else
+ lock_sources = (path_sources + git_sources + plugin_sources).sort_by(&:to_s)
+ lock_sources << combine_rubygems_sources
+ end
+ end
+
+ # Returns true if there are changes
+ def replace_sources!(replacement_sources)
+ return true if replacement_sources.empty?
+
+ [path_sources, git_sources, plugin_sources].each do |source_list|
+ source_list.map! do |source|
+ replacement_sources.find {|s| s == source } || source
+ end
+ end
+
+ replacement_rubygems = !Bundler.feature_flag.lockfile_uses_separate_rubygems_sources? &&
+ replacement_sources.detect {|s| s.is_a?(Source::Rubygems) }
+ @rubygems_aggregate = replacement_rubygems if replacement_rubygems
+
+ return true if !equal_sources?(lock_sources, replacement_sources) && !equivalent_sources?(lock_sources, replacement_sources)
+ return true if replacement_rubygems && rubygems_remotes.to_set != replacement_rubygems.remotes.to_set
+
+ false
+ end
+
+ def cached!
+ all_sources.each(&:cached!)
+ end
+
+ def remote!
+ all_sources.each(&:remote!)
+ end
+
+ def rubygems_primary_remotes
+ @rubygems_aggregate.remotes
+ end
+
+ private
+
+ def rubygems_aggregate_class
+ Source::Rubygems
+ end
+
+ def add_source_to_list(source, list)
+ list.unshift(source).uniq!
+ source
+ end
+
+ def source_list_for(source)
+ case source
+ when Source::Git then git_sources
+ when Source::Path then path_sources
+ when Source::Rubygems then rubygems_sources
+ when Plugin::API::Source then plugin_sources
+ else raise ArgumentError, "Invalid source: #{source.inspect}"
+ end
+ end
+
+ def combine_rubygems_sources
+ Source::Rubygems.new("remotes" => rubygems_remotes)
+ end
+
+ def warn_on_git_protocol(source)
+ return if Bundler.settings["git.allow_insecure"]
+
+ if source.uri =~ /^git\:/
+ Bundler.ui.warn "The git source `#{source.uri}` uses the `git` protocol, " \
+ "which transmits data without encryption. Disable this warning with " \
+ "`bundle config git.allow_insecure true`, or switch to the `https` " \
+ "protocol to keep your data secure."
+ end
+ end
+
+ def equal_sources?(lock_sources, replacement_sources)
+ lock_sources.to_set == replacement_sources.to_set
+ end
+
+ def equal_source?(source, other_source)
+ source == other_source
+ end
+
+ def equivalent_source?(source, other_source)
+ return false unless Bundler.settings[:allow_deployment_source_credential_changes] && source.is_a?(Source::Rubygems)
+
+ equivalent_rubygems_sources?([source], [other_source])
+ end
+
+ def equivalent_sources?(lock_sources, replacement_sources)
+ return false unless Bundler.settings[:allow_deployment_source_credential_changes]
+
+ lock_rubygems_sources, lock_other_sources = lock_sources.partition {|s| s.is_a?(Source::Rubygems) }
+ replacement_rubygems_sources, replacement_other_sources = replacement_sources.partition {|s| s.is_a?(Source::Rubygems) }
+
+ equivalent_rubygems_sources?(lock_rubygems_sources, replacement_rubygems_sources) && equal_sources?(lock_other_sources, replacement_other_sources)
+ end
+
+ def equivalent_rubygems_sources?(lock_sources, replacement_sources)
+ actual_remotes = replacement_sources.map(&:remotes).flatten.uniq
+ lock_sources.all? {|s| s.equivalent_remotes?(actual_remotes) }
+ end
+ end
+end
diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb
new file mode 100644
index 0000000000..5003b2cbec
--- /dev/null
+++ b/lib/bundler/spec_set.rb
@@ -0,0 +1,192 @@
+# frozen_string_literal: true
+
+require "tsort"
+require "forwardable"
+require "set"
+
+module Bundler
+ class SpecSet
+ extend Forwardable
+ include TSort, Enumerable
+
+ def_delegators :@specs, :<<, :length, :add, :remove, :size, :empty?
+ def_delegators :sorted, :each
+
+ def initialize(specs)
+ @specs = specs
+ end
+
+ def for(dependencies, skip = [], check = false, match_current_platform = false, raise_on_missing = true)
+ handled = Set.new
+ deps = dependencies.dup
+ specs = []
+ skip += ["bundler"]
+
+ loop do
+ break unless dep = deps.shift
+ next if !handled.add?(dep) || skip.include?(dep.name)
+
+ if spec = spec_for_dependency(dep, match_current_platform)
+ specs << spec
+
+ spec.dependencies.each do |d|
+ next if d.type == :development
+ d = DepProxy.new(d, dep.__platform) unless match_current_platform
+ deps << d
+ end
+ elsif check
+ return false
+ elsif raise_on_missing
+ others = lookup[dep.name] if match_current_platform
+ message = "Unable to find a spec satisfying #{dep} in the set. Perhaps the lockfile is corrupted?"
+ message += " Found #{others.join(", ")} that did not match the current platform." if others && !others.empty?
+ raise GemNotFound, message
+ end
+ end
+
+ if spec = lookup["bundler"].first
+ specs << spec
+ end
+
+ check ? true : SpecSet.new(specs)
+ end
+
+ def valid_for?(deps)
+ self.for(deps, [], true)
+ end
+
+ def [](key)
+ key = key.name if key.respond_to?(:name)
+ lookup[key].reverse
+ end
+
+ def []=(key, value)
+ @specs << value
+ @lookup = nil
+ @sorted = nil
+ value
+ end
+
+ def sort!
+ self
+ end
+
+ def to_a
+ sorted.dup
+ end
+
+ def to_hash
+ lookup.dup
+ end
+
+ def materialize(deps, missing_specs = nil)
+ materialized = self.for(deps, [], false, true, !missing_specs).to_a
+ deps = materialized.map(&:name).uniq
+ materialized.map! do |s|
+ next s unless s.is_a?(LazySpecification)
+ s.source.dependency_names = deps if s.source.respond_to?(:dependency_names=)
+ spec = s.__materialize__
+ unless spec
+ unless missing_specs
+ raise GemNotFound, "Could not find #{s.full_name} in any of the sources"
+ end
+ missing_specs << s
+ end
+ spec
+ end
+ SpecSet.new(missing_specs ? materialized.compact : materialized)
+ end
+
+ # Materialize for all the specs in the spec set, regardless of what platform they're for
+ # This is in contrast to how for does platform filtering (and specifically different from how `materialize` calls `for` only for the current platform)
+ # @return [Array<Gem::Specification>]
+ def materialized_for_all_platforms
+ names = @specs.map(&:name).uniq
+ @specs.map do |s|
+ next s unless s.is_a?(LazySpecification)
+ s.source.dependency_names = names if s.source.respond_to?(:dependency_names=)
+ spec = s.__materialize__
+ raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec
+ spec
+ end
+ end
+
+ def merge(set)
+ arr = sorted.dup
+ set.each do |set_spec|
+ full_name = set_spec.full_name
+ next if arr.any? {|spec| spec.full_name == full_name }
+ arr << set_spec
+ end
+ SpecSet.new(arr)
+ end
+
+ def find_by_name_and_platform(name, platform)
+ @specs.detect {|spec| spec.name == name && spec.match_platform(platform) }
+ end
+
+ def what_required(spec)
+ unless req = find {|s| s.dependencies.any? {|d| d.type == :runtime && d.name == spec.name } }
+ return [spec]
+ end
+ what_required(req) << spec
+ end
+
+ private
+
+ def sorted
+ rake = @specs.find {|s| s.name == "rake" }
+ begin
+ @sorted ||= ([rake] + tsort).compact.uniq
+ rescue TSort::Cyclic => error
+ cgems = extract_circular_gems(error)
+ raise CyclicDependencyError, "Your bundle requires gems that depend" \
+ " on each other, creating an infinite loop. Please remove either" \
+ " gem '#{cgems[1]}' or gem '#{cgems[0]}' and try again."
+ end
+ end
+
+ def extract_circular_gems(error)
+ if Bundler.current_ruby.mri? && Bundler.current_ruby.on_19?
+ error.message.scan(/(\w+) \([^)]/).flatten
+ else
+ error.message.scan(/@name="(.*?)"/).flatten
+ end
+ end
+
+ def lookup
+ @lookup ||= begin
+ lookup = Hash.new {|h, k| h[k] = [] }
+ Index.sort_specs(@specs).reverse_each do |s|
+ lookup[s.name] << s
+ end
+ lookup
+ end
+ end
+
+ def tsort_each_node
+ # MUST sort by name for backwards compatibility
+ @specs.sort_by(&:name).each {|s| yield s }
+ end
+
+ def spec_for_dependency(dep, match_current_platform)
+ specs_for_platforms = lookup[dep.name]
+ if match_current_platform
+ Bundler.rubygems.platforms.reverse_each do |pl|
+ match = GemHelpers.select_best_platform_match(specs_for_platforms, pl)
+ return match if match
+ end
+ nil
+ else
+ GemHelpers.select_best_platform_match(specs_for_platforms, dep.__platform)
+ end
+ end
+
+ def tsort_each_child(s)
+ s.dependencies.sort_by(&:name).each do |d|
+ next if d.type == :development
+ lookup[d.name].each {|s2| yield s2 }
+ end
+ end
+ end
+end
diff --git a/lib/bundler/ssl_certs/.document b/lib/bundler/ssl_certs/.document
new file mode 100644
index 0000000000..fb66f13c33
--- /dev/null
+++ b/lib/bundler/ssl_certs/.document
@@ -0,0 +1 @@
+# Ignore all files in this directory
diff --git a/lib/bundler/ssl_certs/certificate_manager.rb b/lib/bundler/ssl_certs/certificate_manager.rb
new file mode 100644
index 0000000000..26fc38ec18
--- /dev/null
+++ b/lib/bundler/ssl_certs/certificate_manager.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require "bundler/vendored_fileutils"
+require "net/https"
+require "openssl"
+
+module Bundler
+ module SSLCerts
+ class CertificateManager
+ attr_reader :bundler_cert_path, :bundler_certs, :rubygems_certs
+
+ def self.update_from!(rubygems_path)
+ new(rubygems_path).update!
+ end
+
+ def initialize(rubygems_path = nil)
+ if rubygems_path
+ rubygems_cert_path = File.join(rubygems_path, "lib/rubygems/ssl_certs")
+ @rubygems_certs = certificates_in(rubygems_cert_path)
+ end
+
+ @bundler_cert_path = File.expand_path("..", __FILE__)
+ @bundler_certs = certificates_in(bundler_cert_path)
+ end
+
+ def up_to_date?
+ rubygems_certs.all? do |rc|
+ bundler_certs.find do |bc|
+ File.basename(bc) == File.basename(rc) && FileUtils.compare_file(bc, rc)
+ end
+ end
+ end
+
+ def update!
+ return if up_to_date?
+
+ FileUtils.rm bundler_certs
+ FileUtils.cp rubygems_certs, bundler_cert_path
+ end
+
+ def connect_to(host)
+ http = Net::HTTP.new(host, 443)
+ http.use_ssl = true
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ http.cert_store = store
+ http.head("/")
+ end
+
+ private
+
+ def certificates_in(path)
+ Dir[File.join(path, "**/*.pem")].sort
+ end
+
+ def store
+ @store ||= begin
+ store = OpenSSL::X509::Store.new
+ bundler_certs.each do |cert|
+ store.add_file cert
+ end
+ store
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem b/lib/bundler/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem
new file mode 100644
index 0000000000..f4ce4ca43d
--- /dev/null
+++ b/lib/bundler/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
+MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
+aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
+jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
+xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
+1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
+snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
+U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
+9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
+AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
+yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
+38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
+AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
+DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
+HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
+-----END CERTIFICATE-----
diff --git a/lib/bundler/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem b/lib/bundler/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem
new file mode 100644
index 0000000000..9e6810ab70
--- /dev/null
+++ b/lib/bundler/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
diff --git a/lib/bundler/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem b/lib/bundler/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem
new file mode 100644
index 0000000000..20585f1c01
--- /dev/null
+++ b/lib/bundler/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb
new file mode 100644
index 0000000000..0dd024024a
--- /dev/null
+++ b/lib/bundler/stub_specification.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+require "bundler/remote_specification"
+
+module Bundler
+ class StubSpecification < RemoteSpecification
+ def self.from_stub(stub)
+ return stub if stub.is_a?(Bundler::StubSpecification)
+ spec = new(stub.name, stub.version, stub.platform, nil)
+ spec.stub = stub
+ spec
+ end
+
+ attr_accessor :stub, :ignored
+
+ # Pre 2.2.0 did not include extension_dir
+ # https://github.com/rubygems/rubygems/commit/9485ca2d101b82a946d6f327f4bdcdea6d4946ea
+ if Bundler.rubygems.provides?(">= 2.2.0")
+ def source=(source)
+ super
+ # Stub has no concept of source, which means that extension_dir may be wrong
+ # This is the case for git-based gems. So, instead manually assign the extension dir
+ return unless source.respond_to?(:extension_dir_name)
+ path = File.join(stub.extensions_dir, source.extension_dir_name)
+ stub.extension_dir = File.expand_path(path)
+ end
+ end
+
+ def to_yaml
+ _remote_specification.to_yaml
+ end
+
+ # @!group Stub Delegates
+
+ if Bundler.rubygems.provides?(">= 2.3")
+ # This is defined directly to avoid having to load every installed spec
+ def missing_extensions?
+ stub.missing_extensions?
+ end
+ end
+
+ def activated
+ stub.activated
+ end
+
+ def activated=(activated)
+ stub.instance_variable_set(:@activated, activated)
+ end
+
+ def default_gem
+ stub.default_gem
+ end
+
+ def full_gem_path
+ # deleted gems can have their stubs return nil, so in that case grab the
+ # expired path from the full spec
+ stub.full_gem_path || method_missing(:full_gem_path)
+ end
+
+ if Bundler.rubygems.provides?(">= 2.2.0")
+ def full_require_paths
+ stub.full_require_paths
+ end
+
+ # This is what we do in bundler/rubygems_ext
+ # full_require_paths is always implemented in >= 2.2.0
+ def load_paths
+ full_require_paths
+ end
+ end
+
+ def loaded_from
+ stub.loaded_from
+ end
+
+ if Bundler.rubygems.stubs_provide_full_functionality?
+ def matches_for_glob(glob)
+ stub.matches_for_glob(glob)
+ end
+ end
+
+ def raw_require_paths
+ stub.raw_require_paths
+ end
+
+ private
+
+ def _remote_specification
+ @_remote_specification ||= begin
+ rs = stub.to_spec
+ if rs.equal?(self) # happens when to_spec gets the spec from Gem.loaded_specs
+ rs = Gem::Specification.load(loaded_from)
+ Bundler.rubygems.stub_set_spec(stub, rs)
+ end
+
+ unless rs
+ raise GemspecError, "The gemspec for #{full_name} at #{loaded_from}" \
+ " was missing or broken. Try running `gem pristine #{name} -v #{version}`" \
+ " to fix the cached spec."
+ end
+
+ rs.source = source
+
+ rs
+ end
+ end
+ end
+end
diff --git a/lib/bundler/templates/.document b/lib/bundler/templates/.document
new file mode 100644
index 0000000000..fb66f13c33
--- /dev/null
+++ b/lib/bundler/templates/.document
@@ -0,0 +1 @@
+# Ignore all files in this directory
diff --git a/lib/bundler/templates/Executable b/lib/bundler/templates/Executable
new file mode 100644
index 0000000000..3e8d5b317a
--- /dev/null
+++ b/lib/bundler/templates/Executable
@@ -0,0 +1,29 @@
+#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %>
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application '<%= executable %>' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../<%= relative_gemfile_path %>",
+ Pathname.new(__FILE__).realpath)
+
+bundle_binstub = File.expand_path("../bundle", __FILE__)
+
+if File.file?(bundle_binstub)
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
+ load(bundle_binstub)
+ else
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
+Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
+ end
+end
+
+require "rubygems"
+require "bundler/setup"
+
+load Gem.bin_path("<%= spec.name %>", "<%= executable %>")
diff --git a/lib/bundler/templates/Executable.bundler b/lib/bundler/templates/Executable.bundler
new file mode 100644
index 0000000000..eeda90b584
--- /dev/null
+++ b/lib/bundler/templates/Executable.bundler
@@ -0,0 +1,105 @@
+#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %>
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application '<%= executable %>' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require "rubygems"
+
+m = Module.new do
+ module_function
+
+ def invoked_as_script?
+ File.expand_path($0) == File.expand_path(__FILE__)
+ end
+
+ def env_var_version
+ ENV["BUNDLER_VERSION"]
+ end
+
+ def cli_arg_version
+ return unless invoked_as_script? # don't want to hijack other binstubs
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
+ bundler_version = nil
+ update_index = nil
+ ARGV.each_with_index do |a, i|
+ if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
+ bundler_version = a
+ end
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
+ bundler_version = $1 || ">= 0.a"
+ update_index = i
+ end
+ bundler_version
+ end
+
+ def gemfile
+ gemfile = ENV["BUNDLE_GEMFILE"]
+ return gemfile if gemfile && !gemfile.empty?
+
+ File.expand_path("../<%= relative_gemfile_path %>", __FILE__)
+ end
+
+ def lockfile
+ lockfile =
+ case File.basename(gemfile)
+ when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
+ else "#{gemfile}.lock"
+ end
+ File.expand_path(lockfile)
+ end
+
+ def lockfile_version
+ return unless File.file?(lockfile)
+ lockfile_contents = File.read(lockfile)
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
+ Regexp.last_match(1)
+ end
+
+ def bundler_version
+ @bundler_version ||= begin
+ env_var_version || cli_arg_version ||
+ lockfile_version || "#{Gem::Requirement.default}.a"
+ end
+ end
+
+ def load_bundler!
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
+
+ # must dup string for RG < 1.8 compatibility
+ activate_bundler(bundler_version.dup)
+ end
+
+ def activate_bundler(bundler_version)
+ if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0")
+ bundler_version = "< 2"
+ end
+ gem_error = activation_error_handling do
+ gem "bundler", bundler_version
+ end
+ return if gem_error.nil?
+ require_error = activation_error_handling do
+ require "bundler/version"
+ end
+ return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
+ warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
+ exit 42
+ end
+
+ def activation_error_handling
+ yield
+ nil
+ rescue StandardError, LoadError => e
+ e
+ end
+end
+
+m.load_bundler!
+
+if m.invoked_as_script?
+ load Gem.bin_path("<%= spec.name %>", "<%= executable %>")
+end
diff --git a/lib/bundler/templates/Executable.standalone b/lib/bundler/templates/Executable.standalone
new file mode 100644
index 0000000000..4bf0753f44
--- /dev/null
+++ b/lib/bundler/templates/Executable.standalone
@@ -0,0 +1,14 @@
+#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %>
+#
+# This file was generated by Bundler.
+#
+# The application '<%= executable %>' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require "pathname"
+path = Pathname.new(__FILE__)
+$:.unshift File.expand_path "../<%= standalone_path %>", path.realpath
+
+require "bundler/setup"
+load File.expand_path "../<%= executable_path %>", path.realpath
diff --git a/lib/bundler/templates/Gemfile b/lib/bundler/templates/Gemfile
new file mode 100644
index 0000000000..1afd2cce67
--- /dev/null
+++ b/lib/bundler/templates/Gemfile
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
+
+# gem "rails"
diff --git a/lib/bundler/templates/gems.rb b/lib/bundler/templates/gems.rb
new file mode 100644
index 0000000000..547cd6e8d9
--- /dev/null
+++ b/lib/bundler/templates/gems.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# A sample gems.rb
+source "https://rubygems.org"
+
+git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
+
+# gem "rails"
diff --git a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt
new file mode 100644
index 0000000000..a3833d29d7
--- /dev/null
+++ b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt
@@ -0,0 +1,74 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at <%= config[:email] %>. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/lib/bundler/templates/newgem/Gemfile.tt b/lib/bundler/templates/newgem/Gemfile.tt
new file mode 100644
index 0000000000..c114bd6665
--- /dev/null
+++ b/lib/bundler/templates/newgem/Gemfile.tt
@@ -0,0 +1,6 @@
+source "https://rubygems.org"
+
+git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
+
+# Specify your gem's dependencies in <%= config[:name] %>.gemspec
+gemspec
diff --git a/lib/bundler/templates/newgem/LICENSE.txt.tt b/lib/bundler/templates/newgem/LICENSE.txt.tt
new file mode 100644
index 0000000000..76ef4b0191
--- /dev/null
+++ b/lib/bundler/templates/newgem/LICENSE.txt.tt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) <%= Time.now.year %> <%= config[:author] %>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt
new file mode 100644
index 0000000000..868a0afe67
--- /dev/null
+++ b/lib/bundler/templates/newgem/README.md.tt
@@ -0,0 +1,47 @@
+# <%= config[:constant_name] %>
+
+Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/<%= config[:namespaced_path] %>`. To experiment with that code, run `bin/console` for an interactive prompt.
+
+TODO: Delete this and the text above, and describe your gem
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+```ruby
+gem '<%= config[:name] %>'
+```
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install <%= config[:name] %>
+
+## Usage
+
+TODO: Write usage instructions here
+
+## Development
+
+After checking out the repo, run `bin/setup` to install dependencies.<% if config[:test] %> Then, run `rake <%= config[:test].sub('mini', '').sub('rspec', 'spec') %>` to run the tests.<% end %> You can also run `bin/console` for an interactive prompt that will allow you to experiment.<% if config[:bin] %> Run `bundle exec <%= config[:name] %>` to use the gem in this directory, ignoring other installed copies of this gem.<% end %>
+
+To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
+
+## Contributing
+
+Bug reports and pull requests are welcome on GitHub at https://github.com/<%= config[:github_username] %>/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.<% end %>
+<% if config[:mit] -%>
+
+## License
+
+The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
+<% end -%>
+<% if config[:coc] -%>
+
+## Code of Conduct
+
+Everyone interacting in the <%= config[:constant_name] %> project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/<%= config[:github_username] %>/<%= config[:name] %>/blob/master/CODE_OF_CONDUCT.md).
+<% end -%>
diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt
new file mode 100644
index 0000000000..099da6f3ec
--- /dev/null
+++ b/lib/bundler/templates/newgem/Rakefile.tt
@@ -0,0 +1,29 @@
+require "bundler/gem_tasks"
+<% if config[:test] == "minitest" -%>
+require "rake/testtask"
+
+Rake::TestTask.new(:test) do |t|
+ t.libs << "test"
+ t.libs << "lib"
+ t.test_files = FileList["test/**/*_test.rb"]
+end
+
+<% elsif config[:test] == "rspec" -%>
+require "rspec/core/rake_task"
+
+RSpec::Core::RakeTask.new(:spec)
+
+<% end -%>
+<% if config[:ext] -%>
+require "rake/extensiontask"
+
+task :build => :compile
+
+Rake::ExtensionTask.new("<%= config[:underscored_name] %>") do |ext|
+ ext.lib_dir = "lib/<%= config[:namespaced_path] %>"
+end
+
+task :default => [:clobber, :compile, :<%= config[:test_task] %>]
+<% else -%>
+task :default => :<%= config[:test_task] %>
+<% end -%>
diff --git a/lib/bundler/templates/newgem/bin/console.tt b/lib/bundler/templates/newgem/bin/console.tt
new file mode 100644
index 0000000000..a27f82430f
--- /dev/null
+++ b/lib/bundler/templates/newgem/bin/console.tt
@@ -0,0 +1,14 @@
+#!/usr/bin/env ruby
+
+require "bundler/setup"
+require "<%= config[:namespaced_path] %>"
+
+# You can add fixtures and/or initialization code here to make experimenting
+# with your gem easier. You can also use a different console, if you like.
+
+# (If you use this, don't forget to add pry to your Gemfile!)
+# require "pry"
+# Pry.start
+
+require "irb"
+IRB.start(__FILE__)
diff --git a/lib/bundler/templates/newgem/bin/setup.tt b/lib/bundler/templates/newgem/bin/setup.tt
new file mode 100644
index 0000000000..dce67d860a
--- /dev/null
+++ b/lib/bundler/templates/newgem/bin/setup.tt
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+IFS=$'\n\t'
+set -vx
+
+bundle install
+
+# Do any other automated setup that you need to do here
diff --git a/lib/bundler/templates/newgem/exe/newgem.tt b/lib/bundler/templates/newgem/exe/newgem.tt
new file mode 100644
index 0000000000..a8339bb79f
--- /dev/null
+++ b/lib/bundler/templates/newgem/exe/newgem.tt
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+
+require "<%= config[:namespaced_path] %>"
diff --git a/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt b/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt
new file mode 100644
index 0000000000..8cfc828f94
--- /dev/null
+++ b/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt
@@ -0,0 +1,3 @@
+require "mkmf"
+
+create_makefile(<%= config[:makefile_path].inspect %>)
diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt b/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt
new file mode 100644
index 0000000000..8177c4d202
--- /dev/null
+++ b/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt
@@ -0,0 +1,9 @@
+#include "<%= config[:underscored_name] %>.h"
+
+VALUE rb_m<%= config[:constant_array].join %>;
+
+void
+Init_<%= config[:underscored_name] %>(void)
+{
+ rb_m<%= config[:constant_array].join %> = rb_define_module(<%= config[:constant_name].inspect %>);
+}
diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt b/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt
new file mode 100644
index 0000000000..c6e420b66e
--- /dev/null
+++ b/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt
@@ -0,0 +1,6 @@
+#ifndef <%= config[:underscored_name].upcase %>_H
+#define <%= config[:underscored_name].upcase %>_H 1
+
+#include "ruby.h"
+
+#endif /* <%= config[:underscored_name].upcase %>_H */
diff --git a/lib/bundler/templates/newgem/gitignore.tt b/lib/bundler/templates/newgem/gitignore.tt
new file mode 100644
index 0000000000..b1c9f9986c
--- /dev/null
+++ b/lib/bundler/templates/newgem/gitignore.tt
@@ -0,0 +1,20 @@
+/.bundle/
+/.yardoc
+/_yardoc/
+/coverage/
+/doc/
+/pkg/
+/spec/reports/
+/tmp/
+<%- if config[:ext] -%>
+*.bundle
+*.so
+*.o
+*.a
+mkmf.log
+<%- end -%>
+<%- if config[:test] == "rspec" -%>
+
+# rspec failure tracking
+.rspec_status
+<%- end -%>
diff --git a/lib/bundler/templates/newgem/lib/newgem.rb.tt b/lib/bundler/templates/newgem/lib/newgem.rb.tt
new file mode 100644
index 0000000000..fae6337c3e
--- /dev/null
+++ b/lib/bundler/templates/newgem/lib/newgem.rb.tt
@@ -0,0 +1,13 @@
+require "<%= config[:namespaced_path] %>/version"
+<%- if config[:ext] -%>
+require "<%= config[:namespaced_path] %>/<%= config[:underscored_name] %>"
+<%- end -%>
+
+<%- config[:constant_array].each_with_index do |c, i| -%>
+<%= " " * i %>module <%= c %>
+<%- end -%>
+<%= " " * config[:constant_array].size %>class Error < StandardError; end
+<%= " " * config[:constant_array].size %># Your code goes here...
+<%- (config[:constant_array].size-1).downto(0) do |i| -%>
+<%= " " * i %>end
+<%- end -%>
diff --git a/lib/bundler/templates/newgem/lib/newgem/version.rb.tt b/lib/bundler/templates/newgem/lib/newgem/version.rb.tt
new file mode 100644
index 0000000000..389daf5048
--- /dev/null
+++ b/lib/bundler/templates/newgem/lib/newgem/version.rb.tt
@@ -0,0 +1,7 @@
+<%- config[:constant_array].each_with_index do |c, i| -%>
+<%= " " * i %>module <%= c %>
+<%- end -%>
+<%= " " * config[:constant_array].size %>VERSION = "0.1.0"
+<%- (config[:constant_array].size-1).downto(0) do |i| -%>
+<%= " " * i %>end
+<%- end -%>
diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt
new file mode 100644
index 0000000000..faf6f7bbc5
--- /dev/null
+++ b/lib/bundler/templates/newgem/newgem.gemspec.tt
@@ -0,0 +1,55 @@
+<%- if RUBY_VERSION < "2.0.0" -%>
+# coding: utf-8
+<%- end -%>
+
+lib = File.expand_path("../lib", __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require "<%= config[:namespaced_path] %>/version"
+
+Gem::Specification.new do |spec|
+ spec.name = <%= config[:name].inspect %>
+ spec.version = <%= config[:constant_name] %>::VERSION
+ spec.authors = [<%= config[:author].inspect %>]
+ spec.email = [<%= config[:email].inspect %>]
+
+ spec.summary = %q{TODO: Write a short summary, because RubyGems requires one.}
+ spec.description = %q{TODO: Write a longer description or delete this line.}
+ spec.homepage = "TODO: Put your gem's website or public repo URL here."
+<%- if config[:mit] -%>
+ spec.license = "MIT"
+<%- end -%>
+
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
+ if spec.respond_to?(:metadata)
+ spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
+
+ spec.metadata["homepage_uri"] = spec.homepage
+ spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
+ spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
+ else
+ raise "RubyGems 2.0 or newer is required to protect against " \
+ "public gem pushes."
+ end
+
+ # Specify which files should be added to the gem when it is released.
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
+ end
+ spec.bindir = "exe"
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
+ spec.require_paths = ["lib"]
+<%- if config[:ext] -%>
+ spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"]
+<%- end -%>
+
+ spec.add_development_dependency "bundler", "~> <%= config[:bundler_version] %>"
+ spec.add_development_dependency "rake", "~> 10.0"
+<%- if config[:ext] -%>
+ spec.add_development_dependency "rake-compiler"
+<%- end -%>
+<%- if config[:test] -%>
+ spec.add_development_dependency "<%= config[:test] %>", "~> <%= config[:test_framework_version] %>"
+<%- end -%>
+end
diff --git a/lib/bundler/templates/newgem/rspec.tt b/lib/bundler/templates/newgem/rspec.tt
new file mode 100644
index 0000000000..34c5164d9b
--- /dev/null
+++ b/lib/bundler/templates/newgem/rspec.tt
@@ -0,0 +1,3 @@
+--format documentation
+--color
+--require spec_helper
diff --git a/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt b/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt
new file mode 100644
index 0000000000..c63b487830
--- /dev/null
+++ b/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt
@@ -0,0 +1,9 @@
+RSpec.describe <%= config[:constant_name] %> do
+ it "has a version number" do
+ expect(<%= config[:constant_name] %>::VERSION).not_to be nil
+ end
+
+ it "does something useful" do
+ expect(false).to eq(true)
+ end
+end
diff --git a/lib/bundler/templates/newgem/spec/spec_helper.rb.tt b/lib/bundler/templates/newgem/spec/spec_helper.rb.tt
new file mode 100644
index 0000000000..805cf57e01
--- /dev/null
+++ b/lib/bundler/templates/newgem/spec/spec_helper.rb.tt
@@ -0,0 +1,14 @@
+require "bundler/setup"
+require "<%= config[:namespaced_path] %>"
+
+RSpec.configure do |config|
+ # Enable flags like --only-failures and --next-failure
+ config.example_status_persistence_file_path = ".rspec_status"
+
+ # Disable RSpec exposing methods globally on `Module` and `main`
+ config.disable_monkey_patching!
+
+ config.expect_with :rspec do |c|
+ c.syntax = :expect
+ end
+end
diff --git a/lib/bundler/templates/newgem/test/newgem_test.rb.tt b/lib/bundler/templates/newgem/test/newgem_test.rb.tt
new file mode 100644
index 0000000000..f2af9f90e0
--- /dev/null
+++ b/lib/bundler/templates/newgem/test/newgem_test.rb.tt
@@ -0,0 +1,11 @@
+require "test_helper"
+
+class <%= config[:constant_name] %>Test < Minitest::Test
+ def test_that_it_has_a_version_number
+ refute_nil ::<%= config[:constant_name] %>::VERSION
+ end
+
+ def test_it_does_something_useful
+ assert false
+ end
+end
diff --git a/lib/bundler/templates/newgem/test/test_helper.rb.tt b/lib/bundler/templates/newgem/test/test_helper.rb.tt
new file mode 100644
index 0000000000..725e3e4647
--- /dev/null
+++ b/lib/bundler/templates/newgem/test/test_helper.rb.tt
@@ -0,0 +1,4 @@
+$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
+require "<%= config[:namespaced_path] %>"
+
+require "minitest/autorun"
diff --git a/lib/bundler/templates/newgem/travis.yml.tt b/lib/bundler/templates/newgem/travis.yml.tt
new file mode 100644
index 0000000000..7a3381a889
--- /dev/null
+++ b/lib/bundler/templates/newgem/travis.yml.tt
@@ -0,0 +1,7 @@
+---
+sudo: false
+language: ruby
+cache: bundler
+rvm:
+ - <%= RUBY_VERSION %>
+before_install: gem install bundler -v <%= Bundler::VERSION %>
diff --git a/lib/bundler/ui.rb b/lib/bundler/ui.rb
new file mode 100644
index 0000000000..8138b30d38
--- /dev/null
+++ b/lib/bundler/ui.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Bundler
+ module UI
+ autoload :RGProxy, "bundler/ui/rg_proxy"
+ autoload :Shell, "bundler/ui/shell"
+ autoload :Silent, "bundler/ui/silent"
+ end
+end
diff --git a/lib/bundler/ui/rg_proxy.rb b/lib/bundler/ui/rg_proxy.rb
new file mode 100644
index 0000000000..e2f98481db
--- /dev/null
+++ b/lib/bundler/ui/rg_proxy.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "bundler/ui"
+require "rubygems/user_interaction"
+
+module Bundler
+ module UI
+ class RGProxy < ::Gem::SilentUI
+ def initialize(ui)
+ @ui = ui
+ super()
+ end
+
+ def say(message)
+ @ui && @ui.debug(message)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb
new file mode 100644
index 0000000000..16e3d15713
--- /dev/null
+++ b/lib/bundler/ui/shell.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+require "bundler/vendored_thor"
+
+module Bundler
+ module UI
+ class Shell
+ LEVELS = %w[silent error warn confirm info debug].freeze
+
+ attr_writer :shell
+
+ def initialize(options = {})
+ if options["no-color"] || !$stdout.tty?
+ Thor::Base.shell = Thor::Shell::Basic
+ end
+ @shell = Thor::Base.shell.new
+ @level = ENV["DEBUG"] ? "debug" : "info"
+ @warning_history = []
+ end
+
+ def add_color(string, *color)
+ @shell.set_color(string, *color)
+ end
+
+ def info(msg, newline = nil)
+ tell_me(msg, nil, newline) if level("info")
+ end
+
+ def confirm(msg, newline = nil)
+ tell_me(msg, :green, newline) if level("confirm")
+ end
+
+ def warn(msg, newline = nil)
+ return unless level("warn")
+ return if @warning_history.include? msg
+ @warning_history << msg
+
+ return tell_err(msg, :yellow, newline) if Bundler.feature_flag.error_on_stderr?
+ tell_me(msg, :yellow, newline)
+ end
+
+ def error(msg, newline = nil)
+ return unless level("error")
+ return tell_err(msg, :red, newline) if Bundler.feature_flag.error_on_stderr?
+ tell_me(msg, :red, newline)
+ end
+
+ def debug(msg, newline = nil)
+ tell_me(msg, nil, newline) if debug?
+ end
+
+ def debug?
+ level("debug")
+ end
+
+ def quiet?
+ level("quiet")
+ end
+
+ def ask(msg)
+ @shell.ask(msg)
+ end
+
+ def yes?(msg)
+ @shell.yes?(msg)
+ end
+
+ def no?
+ @shell.no?(msg)
+ end
+
+ def level=(level)
+ raise ArgumentError unless LEVELS.include?(level.to_s)
+ @level = level.to_s
+ end
+
+ def level(name = nil)
+ return @level unless name
+ unless index = LEVELS.index(name)
+ raise "#{name.inspect} is not a valid level"
+ end
+ index <= LEVELS.index(@level)
+ end
+
+ def trace(e, newline = nil, force = false)
+ return unless debug? || force
+ msg = "#{e.class}: #{e.message}\n#{e.backtrace.join("\n ")}"
+ tell_me(msg, nil, newline)
+ end
+
+ def silence(&blk)
+ with_level("silent", &blk)
+ end
+
+ def unprinted_warnings
+ []
+ end
+
+ private
+
+ # valimism
+ def tell_me(msg, color = nil, newline = nil)
+ msg = word_wrap(msg) if newline.is_a?(Hash) && newline[:wrap]
+ if newline.nil?
+ @shell.say(msg, color)
+ else
+ @shell.say(msg, color, newline)
+ end
+ end
+
+ def tell_err(message, color = nil, newline = nil)
+ return if @shell.send(:stderr).closed?
+
+ newline ||= message.to_s !~ /( |\t)\Z/
+ message = word_wrap(message) if newline.is_a?(Hash) && newline[:wrap]
+
+ color = nil if color && !$stderr.tty?
+
+ buffer = @shell.send(:prepare_message, message, *color)
+ buffer << "\n" if newline && !message.to_s.end_with?("\n")
+
+ @shell.send(:stderr).print(buffer)
+ @shell.send(:stderr).flush
+ end
+
+ def strip_leading_spaces(text)
+ spaces = text[/\A\s+/, 0]
+ spaces ? text.gsub(/#{spaces}/, "") : text
+ end
+
+ def word_wrap(text, line_width = @shell.terminal_width)
+ strip_leading_spaces(text).split("\n").collect do |line|
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
+ end * "\n"
+ end
+
+ def with_level(level)
+ original = @level
+ @level = level
+ yield
+ ensure
+ @level = original
+ end
+ end
+ end
+end
diff --git a/lib/bundler/ui/silent.rb b/lib/bundler/ui/silent.rb
new file mode 100644
index 0000000000..dca1b2ac86
--- /dev/null
+++ b/lib/bundler/ui/silent.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Bundler
+ module UI
+ class Silent
+ attr_writer :shell
+
+ def initialize
+ @warnings = []
+ end
+
+ def add_color(string, color)
+ string
+ end
+
+ def info(message, newline = nil)
+ end
+
+ def confirm(message, newline = nil)
+ end
+
+ def warn(message, newline = nil)
+ @warnings |= [message]
+ end
+
+ def error(message, newline = nil)
+ end
+
+ def debug(message, newline = nil)
+ end
+
+ def debug?
+ false
+ end
+
+ def quiet?
+ false
+ end
+
+ def ask(message)
+ end
+
+ def yes?(msg)
+ raise "Cannot ask yes? with a silent shell"
+ end
+
+ def no?
+ raise "Cannot ask no? with a silent shell"
+ end
+
+ def level=(name)
+ end
+
+ def level(name = nil)
+ end
+
+ def trace(message, newline = nil, force = false)
+ end
+
+ def silence
+ yield
+ end
+
+ def unprinted_warnings
+ @warnings
+ end
+ end
+ end
+end
diff --git a/lib/bundler/uri_credentials_filter.rb b/lib/bundler/uri_credentials_filter.rb
new file mode 100644
index 0000000000..ee3692268c
--- /dev/null
+++ b/lib/bundler/uri_credentials_filter.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Bundler
+ module URICredentialsFilter
+ module_function
+
+ def credential_filtered_uri(uri_to_anonymize)
+ return uri_to_anonymize if uri_to_anonymize.nil?
+ uri = uri_to_anonymize.dup
+ uri = URI(uri.to_s) unless uri.is_a?(URI)
+ if uri.userinfo
+ # oauth authentication
+ if uri.password == "x-oauth-basic" || uri.password == "x"
+ # URI as string does not display with password if no user is set
+ oauth_designation = uri.password
+ uri.user = oauth_designation
+ end
+ uri.password = nil
+ end
+ return uri if uri_to_anonymize.is_a?(URI)
+ return uri.to_s if uri_to_anonymize.is_a?(String)
+ rescue URI::InvalidURIError # uri is not canonical uri scheme
+ uri
+ end
+
+ def credential_filtered_string(str_to_filter, uri)
+ return str_to_filter if uri.nil? || str_to_filter.nil?
+ str_with_no_credentials = str_to_filter.dup
+ anonymous_uri_str = credential_filtered_uri(uri).to_s
+ uri_str = uri.to_s
+ if anonymous_uri_str != uri_str
+ str_with_no_credentials = str_with_no_credentials.gsub(uri_str, anonymous_uri_str)
+ end
+ str_with_no_credentials
+ end
+ end
+end
diff --git a/lib/bundler/vendor/fileutils/lib/fileutils.rb b/lib/bundler/vendor/fileutils/lib/fileutils.rb
new file mode 100644
index 0000000000..cc69740845
--- /dev/null
+++ b/lib/bundler/vendor/fileutils/lib/fileutils.rb
@@ -0,0 +1,1638 @@
+# frozen_string_literal: true
+#
+# = fileutils.rb
+#
+# Copyright (c) 2000-2007 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the same terms of ruby.
+#
+# == module Bundler::FileUtils
+#
+# Namespace for several file utility methods for copying, moving, removing, etc.
+#
+# === Module Functions
+#
+# require 'bundler/vendor/fileutils/lib/fileutils'
+#
+# Bundler::FileUtils.cd(dir, options)
+# Bundler::FileUtils.cd(dir, options) {|dir| block }
+# Bundler::FileUtils.pwd()
+# Bundler::FileUtils.mkdir(dir, options)
+# Bundler::FileUtils.mkdir(list, options)
+# Bundler::FileUtils.mkdir_p(dir, options)
+# Bundler::FileUtils.mkdir_p(list, options)
+# Bundler::FileUtils.rmdir(dir, options)
+# Bundler::FileUtils.rmdir(list, options)
+# Bundler::FileUtils.ln(target, link, options)
+# Bundler::FileUtils.ln(targets, dir, options)
+# Bundler::FileUtils.ln_s(target, link, options)
+# Bundler::FileUtils.ln_s(targets, dir, options)
+# Bundler::FileUtils.ln_sf(target, link, options)
+# Bundler::FileUtils.cp(src, dest, options)
+# Bundler::FileUtils.cp(list, dir, options)
+# Bundler::FileUtils.cp_r(src, dest, options)
+# Bundler::FileUtils.cp_r(list, dir, options)
+# Bundler::FileUtils.mv(src, dest, options)
+# Bundler::FileUtils.mv(list, dir, options)
+# Bundler::FileUtils.rm(list, options)
+# Bundler::FileUtils.rm_r(list, options)
+# Bundler::FileUtils.rm_rf(list, options)
+# Bundler::FileUtils.install(src, dest, options)
+# Bundler::FileUtils.chmod(mode, list, options)
+# Bundler::FileUtils.chmod_R(mode, list, options)
+# Bundler::FileUtils.chown(user, group, list, options)
+# Bundler::FileUtils.chown_R(user, group, list, options)
+# Bundler::FileUtils.touch(list, options)
+#
+# The <tt>options</tt> parameter is a hash of options, taken from the list
+# <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
+# <tt>:noop</tt> means that no changes are made. The other three are obvious.
+# Each method documents the options that it honours.
+#
+# All methods that have the concept of a "source" file or directory can take
+# either one file or a list of files in that argument. See the method
+# documentation for examples.
+#
+# There are some `low level' methods, which do not accept any option:
+#
+# Bundler::FileUtils.copy_entry(src, dest, preserve = false, dereference = false)
+# Bundler::FileUtils.copy_file(src, dest, preserve = false, dereference = true)
+# Bundler::FileUtils.copy_stream(srcstream, deststream)
+# Bundler::FileUtils.remove_entry(path, force = false)
+# Bundler::FileUtils.remove_entry_secure(path, force = false)
+# Bundler::FileUtils.remove_file(path, force = false)
+# Bundler::FileUtils.compare_file(path_a, path_b)
+# Bundler::FileUtils.compare_stream(stream_a, stream_b)
+# Bundler::FileUtils.uptodate?(file, cmp_list)
+#
+# == module Bundler::FileUtils::Verbose
+#
+# This module has all methods of Bundler::FileUtils module, but it outputs messages
+# before acting. This equates to passing the <tt>:verbose</tt> flag to methods
+# in Bundler::FileUtils.
+#
+# == module Bundler::FileUtils::NoWrite
+#
+# This module has all methods of Bundler::FileUtils module, but never changes
+# files/directories. This equates to passing the <tt>:noop</tt> flag to methods
+# in Bundler::FileUtils.
+#
+# == module Bundler::FileUtils::DryRun
+#
+# This module has all methods of Bundler::FileUtils module, but never changes
+# files/directories. This equates to passing the <tt>:noop</tt> and
+# <tt>:verbose</tt> flags to methods in Bundler::FileUtils.
+#
+
+module Bundler::FileUtils
+
+ def self.private_module_function(name) #:nodoc:
+ module_function name
+ private_class_method name
+ end
+
+ #
+ # Returns the name of the current directory.
+ #
+ def pwd
+ Dir.pwd
+ end
+ module_function :pwd
+
+ alias getwd pwd
+ module_function :getwd
+
+ #
+ # Changes the current directory to the directory +dir+.
+ #
+ # If this method is called with block, resumes to the old
+ # working directory after the block execution finished.
+ #
+ # Bundler::FileUtils.cd('/', :verbose => true) # chdir and report it
+ #
+ # Bundler::FileUtils.cd('/') do # chdir
+ # # ... # do something
+ # end # return to original directory
+ #
+ def cd(dir, verbose: nil, &block) # :yield: dir
+ fu_output_message "cd #{dir}" if verbose
+ Dir.chdir(dir, &block)
+ fu_output_message 'cd -' if verbose and block
+ end
+ module_function :cd
+
+ alias chdir cd
+ module_function :chdir
+
+ #
+ # Returns true if +new+ is newer than all +old_list+.
+ # Non-existent files are older than any file.
+ #
+ # Bundler::FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
+ # system 'make hello.o'
+ #
+ def uptodate?(new, old_list)
+ return false unless File.exist?(new)
+ new_time = File.mtime(new)
+ old_list.each do |old|
+ if File.exist?(old)
+ return false unless new_time > File.mtime(old)
+ end
+ end
+ true
+ end
+ module_function :uptodate?
+
+ def remove_trailing_slash(dir) #:nodoc:
+ dir == '/' ? dir : dir.chomp(?/)
+ end
+ private_module_function :remove_trailing_slash
+
+ #
+ # Creates one or more directories.
+ #
+ # Bundler::FileUtils.mkdir 'test'
+ # Bundler::FileUtils.mkdir %w( tmp data )
+ # Bundler::FileUtils.mkdir 'notexist', :noop => true # Does not really create.
+ # Bundler::FileUtils.mkdir 'tmp', :mode => 0700
+ #
+ def mkdir(list, mode: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
+ return if noop
+
+ list.each do |dir|
+ fu_mkdir dir, mode
+ end
+ end
+ module_function :mkdir
+
+ #
+ # Creates a directory and all its parent directories.
+ # For example,
+ #
+ # Bundler::FileUtils.mkdir_p '/usr/local/lib/ruby'
+ #
+ # causes to make following directories, if it does not exist.
+ #
+ # * /usr
+ # * /usr/local
+ # * /usr/local/lib
+ # * /usr/local/lib/ruby
+ #
+ # You can pass several directories at a time in a list.
+ #
+ def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
+ return *list if noop
+
+ list.map {|path| remove_trailing_slash(path)}.each do |path|
+ # optimize for the most common case
+ begin
+ fu_mkdir path, mode
+ next
+ rescue SystemCallError
+ next if File.directory?(path)
+ end
+
+ stack = []
+ until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
+ stack.push path
+ path = File.dirname(path)
+ end
+ stack.pop # root directory should exist
+ stack.reverse_each do |dir|
+ begin
+ fu_mkdir dir, mode
+ rescue SystemCallError
+ raise unless File.directory?(dir)
+ end
+ end
+ end
+
+ return *list
+ end
+ module_function :mkdir_p
+
+ alias mkpath mkdir_p
+ alias makedirs mkdir_p
+ module_function :mkpath
+ module_function :makedirs
+
+ def fu_mkdir(path, mode) #:nodoc:
+ path = remove_trailing_slash(path)
+ if mode
+ Dir.mkdir path, mode
+ File.chmod mode, path
+ else
+ Dir.mkdir path
+ end
+ end
+ private_module_function :fu_mkdir
+
+ #
+ # Removes one or more directories.
+ #
+ # Bundler::FileUtils.rmdir 'somedir'
+ # Bundler::FileUtils.rmdir %w(somedir anydir otherdir)
+ # # Does not really remove directory; outputs message.
+ # Bundler::FileUtils.rmdir 'somedir', :verbose => true, :noop => true
+ #
+ def rmdir(list, parents: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
+ return if noop
+ list.each do |dir|
+ begin
+ Dir.rmdir(dir = remove_trailing_slash(dir))
+ if parents
+ until (parent = File.dirname(dir)) == '.' or parent == dir
+ dir = parent
+ Dir.rmdir(dir)
+ end
+ end
+ rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT
+ end
+ end
+ end
+ module_function :rmdir
+
+ #
+ # :call-seq:
+ # Bundler::FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil)
+ #
+ # In the first form, creates a hard link +link+ which points to +target+.
+ # If +link+ already exists, raises Errno::EEXIST.
+ # But if the :force option is set, overwrites +link+.
+ #
+ # Bundler::FileUtils.ln 'gcc', 'cc', verbose: true
+ # Bundler::FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
+ #
+ # In the second form, creates a link +dir/target+ pointing to +target+.
+ # In the third form, creates several hard links in the directory +dir+,
+ # pointing to each item in +targets+.
+ # If +dir+ is not a directory, raises Errno::ENOTDIR.
+ #
+ # Bundler::FileUtils.cd '/sbin'
+ # Bundler::FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
+ #
+ def ln(src, dest, force: nil, noop: nil, verbose: nil)
+ fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest0(src, dest) do |s,d|
+ remove_file d, true if force
+ File.link s, d
+ end
+ end
+ module_function :ln
+
+ alias link ln
+ module_function :link
+
+ #
+ # :call-seq:
+ # Bundler::FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil)
+ # Bundler::FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil)
+ #
+ # In the first form, creates a symbolic link +link+ which points to +target+.
+ # If +link+ already exists, raises Errno::EEXIST.
+ # But if the :force option is set, overwrites +link+.
+ #
+ # Bundler::FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
+ # Bundler::FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
+ #
+ # In the second form, creates a link +dir/target+ pointing to +target+.
+ # In the third form, creates several symbolic links in the directory +dir+,
+ # pointing to each item in +targets+.
+ # If +dir+ is not a directory, raises Errno::ENOTDIR.
+ #
+ # Bundler::FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'
+ #
+ def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
+ fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest0(src, dest) do |s,d|
+ remove_file d, true if force
+ File.symlink s, d
+ end
+ end
+ module_function :ln_s
+
+ alias symlink ln_s
+ module_function :symlink
+
+ #
+ # :call-seq:
+ # Bundler::FileUtils.ln_sf(*args)
+ #
+ # Same as
+ #
+ # Bundler::FileUtils.ln_s(*args, force: true)
+ #
+ def ln_sf(src, dest, noop: nil, verbose: nil)
+ ln_s src, dest, force: true, noop: noop, verbose: verbose
+ end
+ module_function :ln_sf
+
+ #
+ # Copies a file content +src+ to +dest+. If +dest+ is a directory,
+ # copies +src+ to +dest/src+.
+ #
+ # If +src+ is a list of files, then +dest+ must be a directory.
+ #
+ # Bundler::FileUtils.cp 'eval.c', 'eval.c.org'
+ # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
+ # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
+ # Bundler::FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
+ #
+ def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
+ fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ copy_file s, d, preserve
+ end
+ end
+ module_function :cp
+
+ alias copy cp
+ module_function :copy
+
+ #
+ # Copies +src+ to +dest+. If +src+ is a directory, this method copies
+ # all its contents recursively. If +dest+ is a directory, copies
+ # +src+ to +dest/src+.
+ #
+ # +src+ can be a list of files.
+ #
+ # # Installing Ruby library "mylib" under the site_ruby
+ # Bundler::FileUtils.rm_r site_ruby + '/mylib', :force
+ # Bundler::FileUtils.cp_r 'lib/', site_ruby + '/mylib'
+ #
+ # # Examples of copying several files to target directory.
+ # Bundler::FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
+ # Bundler::FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', :noop => true, :verbose => true
+ #
+ # # If you want to copy all contents of a directory instead of the
+ # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
+ # # use following code.
+ # Bundler::FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src,
+ # # but this doesn't.
+ #
+ def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
+ dereference_root: true, remove_destination: nil)
+ fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ copy_entry s, d, preserve, dereference_root, remove_destination
+ end
+ end
+ module_function :cp_r
+
+ #
+ # Copies a file system entry +src+ to +dest+.
+ # If +src+ is a directory, this method copies its contents recursively.
+ # This method preserves file types, c.f. symlink, directory...
+ # (FIFO, device files and etc. are not supported yet)
+ #
+ # Both of +src+ and +dest+ must be a path name.
+ # +src+ must exist, +dest+ must not exist.
+ #
+ # If +preserve+ is true, this method preserves owner, group, and
+ # modified time. Permissions are copied regardless +preserve+.
+ #
+ # If +dereference_root+ is true, this method dereference tree root.
+ #
+ # If +remove_destination+ is true, this method removes each destination file before copy.
+ #
+ def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
+ Entry_.new(src, nil, dereference_root).wrap_traverse(proc do |ent|
+ destent = Entry_.new(dest, ent.rel, false)
+ File.unlink destent.path if remove_destination && File.file?(destent.path)
+ ent.copy destent.path
+ end, proc do |ent|
+ destent = Entry_.new(dest, ent.rel, false)
+ ent.copy_metadata destent.path if preserve
+ end)
+ end
+ module_function :copy_entry
+
+ #
+ # Copies file contents of +src+ to +dest+.
+ # Both of +src+ and +dest+ must be a path name.
+ #
+ def copy_file(src, dest, preserve = false, dereference = true)
+ ent = Entry_.new(src, nil, dereference)
+ ent.copy_file dest
+ ent.copy_metadata dest if preserve
+ end
+ module_function :copy_file
+
+ #
+ # Copies stream +src+ to +dest+.
+ # +src+ must respond to #read(n) and
+ # +dest+ must respond to #write(str).
+ #
+ def copy_stream(src, dest)
+ IO.copy_stream(src, dest)
+ end
+ module_function :copy_stream
+
+ #
+ # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
+ # disk partition, the file is copied then the original file is removed.
+ #
+ # Bundler::FileUtils.mv 'badname.rb', 'goodname.rb'
+ # Bundler::FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error
+ #
+ # Bundler::FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'
+ # Bundler::FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
+ #
+ def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
+ fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ destent = Entry_.new(d, nil, true)
+ begin
+ if destent.exist?
+ if destent.directory?
+ raise Errno::EEXIST, d
+ else
+ destent.remove_file if rename_cannot_overwrite_file?
+ end
+ end
+ begin
+ File.rename s, d
+ rescue Errno::EXDEV
+ copy_entry s, d, true
+ if secure
+ remove_entry_secure s, force
+ else
+ remove_entry s, force
+ end
+ end
+ rescue SystemCallError
+ raise unless force
+ end
+ end
+ end
+ module_function :mv
+
+ alias move mv
+ module_function :move
+
+ def rename_cannot_overwrite_file? #:nodoc:
+ /emx/ =~ RUBY_PLATFORM
+ end
+ private_module_function :rename_cannot_overwrite_file?
+
+ #
+ # Remove file(s) specified in +list+. This method cannot remove directories.
+ # All StandardErrors are ignored when the :force option is set.
+ #
+ # Bundler::FileUtils.rm %w( junk.txt dust.txt )
+ # Bundler::FileUtils.rm Dir.glob('*.so')
+ # Bundler::FileUtils.rm 'NotExistFile', :force => true # never raises exception
+ #
+ def rm(list, force: nil, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
+ return if noop
+
+ list.each do |path|
+ remove_file path, force
+ end
+ end
+ module_function :rm
+
+ alias remove rm
+ module_function :remove
+
+ #
+ # Equivalent to
+ #
+ # Bundler::FileUtils.rm(list, :force => true)
+ #
+ def rm_f(list, noop: nil, verbose: nil)
+ rm list, force: true, noop: noop, verbose: verbose
+ end
+ module_function :rm_f
+
+ alias safe_unlink rm_f
+ module_function :safe_unlink
+
+ #
+ # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
+ # removes its all contents recursively. This method ignores
+ # StandardError when :force option is set.
+ #
+ # Bundler::FileUtils.rm_r Dir.glob('/tmp/*')
+ # Bundler::FileUtils.rm_r 'some_dir', :force => true
+ #
+ # WARNING: This method causes local vulnerability
+ # if one of parent directories or removing directory tree are world
+ # writable (including /tmp, whose permission is 1777), and the current
+ # process has strong privilege such as Unix super user (root), and the
+ # system has symbolic link. For secure removing, read the documentation
+ # of #remove_entry_secure carefully, and set :secure option to true.
+ # Default is :secure=>false.
+ #
+ # NOTE: This method calls #remove_entry_secure if :secure option is set.
+ # See also #remove_entry_secure.
+ #
+ def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
+ list = fu_list(list)
+ fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
+ return if noop
+ list.each do |path|
+ if secure
+ remove_entry_secure path, force
+ else
+ remove_entry path, force
+ end
+ end
+ end
+ module_function :rm_r
+
+ #
+ # Equivalent to
+ #
+ # Bundler::FileUtils.rm_r(list, :force => true)
+ #
+ # WARNING: This method causes local vulnerability.
+ # Read the documentation of #rm_r first.
+ #
+ def rm_rf(list, noop: nil, verbose: nil, secure: nil)
+ rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
+ end
+ module_function :rm_rf
+
+ alias rmtree rm_rf
+ module_function :rmtree
+
+ #
+ # This method removes a file system entry +path+. +path+ shall be a
+ # regular file, a directory, or something. If +path+ is a directory,
+ # remove it recursively. This method is required to avoid TOCTTOU
+ # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
+ # #rm_r causes security hole when:
+ #
+ # * Parent directory is world writable (including /tmp).
+ # * Removing directory tree includes world writable directory.
+ # * The system has symbolic link.
+ #
+ # To avoid this security hole, this method applies special preprocess.
+ # If +path+ is a directory, this method chown(2) and chmod(2) all
+ # removing directories. This requires the current process is the
+ # owner of the removing whole directory tree, or is the super user (root).
+ #
+ # WARNING: You must ensure that *ALL* parent directories cannot be
+ # moved by other untrusted users. For example, parent directories
+ # should not be owned by untrusted users, and should not be world
+ # writable except when the sticky bit set.
+ #
+ # WARNING: Only the owner of the removing directory tree, or Unix super
+ # user (root) should invoke this method. Otherwise this method does not
+ # work.
+ #
+ # For details of this security vulnerability, see Perl's case:
+ #
+ # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
+ # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
+ #
+ # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
+ #
+ def remove_entry_secure(path, force = false)
+ unless fu_have_symlink?
+ remove_entry path, force
+ return
+ end
+ fullpath = File.expand_path(path)
+ st = File.lstat(fullpath)
+ unless st.directory?
+ File.unlink fullpath
+ return
+ end
+ # is a directory.
+ parent_st = File.stat(File.dirname(fullpath))
+ unless parent_st.world_writable?
+ remove_entry path, force
+ return
+ end
+ unless parent_st.sticky?
+ raise ArgumentError, "parent directory is world writable, Bundler::FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
+ end
+ # freeze tree root
+ euid = Process.euid
+ File.open(fullpath + '/.') {|f|
+ unless fu_stat_identical_entry?(st, f.stat)
+ # symlink (TOC-to-TOU attack?)
+ File.unlink fullpath
+ return
+ end
+ f.chown euid, -1
+ f.chmod 0700
+ unless fu_stat_identical_entry?(st, File.lstat(fullpath))
+ # TOC-to-TOU attack?
+ File.unlink fullpath
+ return
+ end
+ }
+ # ---- tree root is frozen ----
+ root = Entry_.new(path)
+ root.preorder_traverse do |ent|
+ if ent.directory?
+ ent.chown euid, -1
+ ent.chmod 0700
+ end
+ end
+ root.postorder_traverse do |ent|
+ begin
+ ent.remove
+ rescue
+ raise unless force
+ end
+ end
+ rescue
+ raise unless force
+ end
+ module_function :remove_entry_secure
+
+ def fu_have_symlink? #:nodoc:
+ File.symlink nil, nil
+ rescue NotImplementedError
+ return false
+ rescue TypeError
+ return true
+ end
+ private_module_function :fu_have_symlink?
+
+ def fu_stat_identical_entry?(a, b) #:nodoc:
+ a.dev == b.dev and a.ino == b.ino
+ end
+ private_module_function :fu_stat_identical_entry?
+
+ #
+ # This method removes a file system entry +path+.
+ # +path+ might be a regular file, a directory, or something.
+ # If +path+ is a directory, remove it recursively.
+ #
+ # See also #remove_entry_secure.
+ #
+ def remove_entry(path, force = false)
+ Entry_.new(path).postorder_traverse do |ent|
+ begin
+ ent.remove
+ rescue
+ raise unless force
+ end
+ end
+ rescue
+ raise unless force
+ end
+ module_function :remove_entry
+
+ #
+ # Removes a file +path+.
+ # This method ignores StandardError if +force+ is true.
+ #
+ def remove_file(path, force = false)
+ Entry_.new(path).remove_file
+ rescue
+ raise unless force
+ end
+ module_function :remove_file
+
+ #
+ # Removes a directory +dir+ and its contents recursively.
+ # This method ignores StandardError if +force+ is true.
+ #
+ def remove_dir(path, force = false)
+ remove_entry path, force # FIXME?? check if it is a directory
+ end
+ module_function :remove_dir
+
+ #
+ # Returns true if the contents of a file +a+ and a file +b+ are identical.
+ #
+ # Bundler::FileUtils.compare_file('somefile', 'somefile') #=> true
+ # Bundler::FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false
+ #
+ def compare_file(a, b)
+ return false unless File.size(a) == File.size(b)
+ File.open(a, 'rb') {|fa|
+ File.open(b, 'rb') {|fb|
+ return compare_stream(fa, fb)
+ }
+ }
+ end
+ module_function :compare_file
+
+ alias identical? compare_file
+ alias cmp compare_file
+ module_function :identical?
+ module_function :cmp
+
+ #
+ # Returns true if the contents of a stream +a+ and +b+ are identical.
+ #
+ def compare_stream(a, b)
+ bsize = fu_stream_blksize(a, b)
+ sa = String.new(capacity: bsize)
+ sb = String.new(capacity: bsize)
+ begin
+ a.read(bsize, sa)
+ b.read(bsize, sb)
+ return true if sa.empty? && sb.empty?
+ end while sa == sb
+ false
+ end
+ module_function :compare_stream
+
+ #
+ # If +src+ is not same as +dest+, copies it and changes the permission
+ # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
+ # This method removes destination before copy.
+ #
+ # Bundler::FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
+ # Bundler::FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
+ #
+ def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
+ noop: nil, verbose: nil)
+ if verbose
+ msg = +"install -c"
+ msg << ' -p' if preserve
+ msg << ' -m ' << mode_to_s(mode) if mode
+ msg << " -o #{owner}" if owner
+ msg << " -g #{group}" if group
+ msg << ' ' << [src,dest].flatten.join(' ')
+ fu_output_message msg
+ end
+ return if noop
+ uid = fu_get_uid(owner)
+ gid = fu_get_gid(group)
+ fu_each_src_dest(src, dest) do |s, d|
+ st = File.stat(s)
+ unless File.exist?(d) and compare_file(s, d)
+ remove_file d, true
+ copy_file s, d
+ File.utime st.atime, st.mtime, d if preserve
+ File.chmod fu_mode(mode, st), d if mode
+ File.chown uid, gid, d if uid or gid
+ end
+ end
+ end
+ module_function :install
+
+ def user_mask(target) #:nodoc:
+ target.each_char.inject(0) do |mask, chr|
+ case chr
+ when "u"
+ mask | 04700
+ when "g"
+ mask | 02070
+ when "o"
+ mask | 01007
+ when "a"
+ mask | 07777
+ else
+ raise ArgumentError, "invalid `who' symbol in file mode: #{chr}"
+ end
+ end
+ end
+ private_module_function :user_mask
+
+ def apply_mask(mode, user_mask, op, mode_mask) #:nodoc:
+ case op
+ when '='
+ (mode & ~user_mask) | (user_mask & mode_mask)
+ when '+'
+ mode | (user_mask & mode_mask)
+ when '-'
+ mode & ~(user_mask & mode_mask)
+ end
+ end
+ private_module_function :apply_mask
+
+ def symbolic_modes_to_i(mode_sym, path) #:nodoc:
+ mode = if File::Stat === path
+ path.mode
+ else
+ File.stat(path).mode
+ end
+ mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause|
+ target, *actions = clause.split(/([=+-])/)
+ raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty?
+ target = 'a' if target.empty?
+ user_mask = user_mask(target)
+ actions.each_slice(2) do |op, perm|
+ need_apply = op == '='
+ mode_mask = (perm || '').each_char.inject(0) do |mask, chr|
+ case chr
+ when "r"
+ mask | 0444
+ when "w"
+ mask | 0222
+ when "x"
+ mask | 0111
+ when "X"
+ if FileTest.directory? path
+ mask | 0111
+ else
+ mask
+ end
+ when "s"
+ mask | 06000
+ when "t"
+ mask | 01000
+ when "u", "g", "o"
+ if mask.nonzero?
+ current_mode = apply_mask(current_mode, user_mask, op, mask)
+ end
+ need_apply = false
+ copy_mask = user_mask(chr)
+ (current_mode & copy_mask) / (copy_mask & 0111) * (user_mask & 0111)
+ else
+ raise ArgumentError, "invalid `perm' symbol in file mode: #{chr}"
+ end
+ end
+
+ if mode_mask.nonzero? || need_apply
+ current_mode = apply_mask(current_mode, user_mask, op, mode_mask)
+ end
+ end
+ current_mode
+ end
+ end
+ private_module_function :symbolic_modes_to_i
+
+ def fu_mode(mode, path) #:nodoc:
+ mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode
+ end
+ private_module_function :fu_mode
+
+ def mode_to_s(mode) #:nodoc:
+ mode.is_a?(String) ? mode : "%o" % mode
+ end
+ private_module_function :mode_to_s
+
+ #
+ # Changes permission bits on the named files (in +list+) to the bit pattern
+ # represented by +mode+.
+ #
+ # +mode+ is the symbolic and absolute mode can be used.
+ #
+ # Absolute mode is
+ # Bundler::FileUtils.chmod 0755, 'somecommand'
+ # Bundler::FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
+ # Bundler::FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
+ #
+ # Symbolic mode is
+ # Bundler::FileUtils.chmod "u=wrx,go=rx", 'somecommand'
+ # Bundler::FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb)
+ # Bundler::FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', :verbose => true
+ #
+ # "a" :: is user, group, other mask.
+ # "u" :: is user's mask.
+ # "g" :: is group's mask.
+ # "o" :: is other's mask.
+ # "w" :: is write permission.
+ # "r" :: is read permission.
+ # "x" :: is execute permission.
+ # "X" ::
+ # is execute permission for directories only, must be used in conjunction with "+"
+ # "s" :: is uid, gid.
+ # "t" :: is sticky bit.
+ # "+" :: is added to a class given the specified mode.
+ # "-" :: Is removed from a given class given mode.
+ # "=" :: Is the exact nature of the class will be given a specified mode.
+
+ def chmod(mode, list, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
+ return if noop
+ list.each do |path|
+ Entry_.new(path).chmod(fu_mode(mode, path))
+ end
+ end
+ module_function :chmod
+
+ #
+ # Changes permission bits on the named files (in +list+)
+ # to the bit pattern represented by +mode+.
+ #
+ # Bundler::FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
+ # Bundler::FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
+ #
+ def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chmod -R%s %s %s',
+ (force ? 'f' : ''),
+ mode_to_s(mode), list.join(' ')) if verbose
+ return if noop
+ list.each do |root|
+ Entry_.new(root).traverse do |ent|
+ begin
+ ent.chmod(fu_mode(mode, ent.path))
+ rescue
+ raise unless force
+ end
+ end
+ end
+ end
+ module_function :chmod_R
+
+ #
+ # Changes owner and group on the named files (in +list+)
+ # to the user +user+ and the group +group+. +user+ and +group+
+ # may be an ID (Integer/String) or a name (String).
+ # If +user+ or +group+ is nil, this method does not change
+ # the attribute.
+ #
+ # Bundler::FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
+ # Bundler::FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
+ #
+ def chown(user, group, list, noop: nil, verbose: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chown %s %s',
+ (group ? "#{user}:#{group}" : user || ':'),
+ list.join(' ')) if verbose
+ return if noop
+ uid = fu_get_uid(user)
+ gid = fu_get_gid(group)
+ list.each do |path|
+ Entry_.new(path).chown uid, gid
+ end
+ end
+ module_function :chown
+
+ #
+ # Changes owner and group on the named files (in +list+)
+ # to the user +user+ and the group +group+ recursively.
+ # +user+ and +group+ may be an ID (Integer/String) or
+ # a name (String). If +user+ or +group+ is nil, this
+ # method does not change the attribute.
+ #
+ # Bundler::FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
+ # Bundler::FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
+ #
+ def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
+ list = fu_list(list)
+ fu_output_message sprintf('chown -R%s %s %s',
+ (force ? 'f' : ''),
+ (group ? "#{user}:#{group}" : user || ':'),
+ list.join(' ')) if verbose
+ return if noop
+ uid = fu_get_uid(user)
+ gid = fu_get_gid(group)
+ list.each do |root|
+ Entry_.new(root).traverse do |ent|
+ begin
+ ent.chown uid, gid
+ rescue
+ raise unless force
+ end
+ end
+ end
+ end
+ module_function :chown_R
+
+ begin
+ require 'etc'
+ rescue LoadError # rescue LoadError for miniruby
+ end
+
+ def fu_get_uid(user) #:nodoc:
+ return nil unless user
+ case user
+ when Integer
+ user
+ when /\A\d+\z/
+ user.to_i
+ else
+ Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil
+ end
+ end
+ private_module_function :fu_get_uid
+
+ def fu_get_gid(group) #:nodoc:
+ return nil unless group
+ case group
+ when Integer
+ group
+ when /\A\d+\z/
+ group.to_i
+ else
+ Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil
+ end
+ end
+ private_module_function :fu_get_gid
+
+ #
+ # Updates modification time (mtime) and access time (atime) of file(s) in
+ # +list+. Files are created if they don't exist.
+ #
+ # Bundler::FileUtils.touch 'timestamp'
+ # Bundler::FileUtils.touch Dir.glob('*.c'); system 'make'
+ #
+ def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
+ list = fu_list(list)
+ t = mtime
+ if verbose
+ fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}"
+ end
+ return if noop
+ list.each do |path|
+ created = nocreate
+ begin
+ File.utime(t, t, path)
+ rescue Errno::ENOENT
+ raise if created
+ File.open(path, 'a') {
+ ;
+ }
+ created = true
+ retry if t
+ end
+ end
+ end
+ module_function :touch
+
+ private
+
+ module StreamUtils_
+ private
+
+ def fu_windows?
+ /mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM
+ end
+
+ def fu_copy_stream0(src, dest, blksize = nil) #:nodoc:
+ IO.copy_stream(src, dest)
+ end
+
+ def fu_stream_blksize(*streams)
+ streams.each do |s|
+ next unless s.respond_to?(:stat)
+ size = fu_blksize(s.stat)
+ return size if size
+ end
+ fu_default_blksize()
+ end
+
+ def fu_blksize(st)
+ s = st.blksize
+ return nil unless s
+ return nil if s == 0
+ s
+ end
+
+ def fu_default_blksize
+ 1024
+ end
+ end
+
+ include StreamUtils_
+ extend StreamUtils_
+
+ class Entry_ #:nodoc: internal use only
+ include StreamUtils_
+
+ def initialize(a, b = nil, deref = false)
+ @prefix = @rel = @path = nil
+ if b
+ @prefix = a
+ @rel = b
+ else
+ @path = a
+ end
+ @deref = deref
+ @stat = nil
+ @lstat = nil
+ end
+
+ def inspect
+ "\#<#{self.class} #{path()}>"
+ end
+
+ def path
+ if @path
+ File.path(@path)
+ else
+ join(@prefix, @rel)
+ end
+ end
+
+ def prefix
+ @prefix || @path
+ end
+
+ def rel
+ @rel
+ end
+
+ def dereference?
+ @deref
+ end
+
+ def exist?
+ begin
+ lstat
+ true
+ rescue Errno::ENOENT
+ false
+ end
+ end
+
+ def file?
+ s = lstat!
+ s and s.file?
+ end
+
+ def directory?
+ s = lstat!
+ s and s.directory?
+ end
+
+ def symlink?
+ s = lstat!
+ s and s.symlink?
+ end
+
+ def chardev?
+ s = lstat!
+ s and s.chardev?
+ end
+
+ def blockdev?
+ s = lstat!
+ s and s.blockdev?
+ end
+
+ def socket?
+ s = lstat!
+ s and s.socket?
+ end
+
+ def pipe?
+ s = lstat!
+ s and s.pipe?
+ end
+
+ S_IF_DOOR = 0xD000
+
+ def door?
+ s = lstat!
+ s and (s.mode & 0xF000 == S_IF_DOOR)
+ end
+
+ def entries
+ opts = {}
+ opts[:encoding] = ::Encoding::UTF_8 if fu_windows?
+ Dir.entries(path(), opts)\
+ .reject {|n| n == '.' or n == '..' }\
+ .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
+ end
+
+ def stat
+ return @stat if @stat
+ if lstat() and lstat().symlink?
+ @stat = File.stat(path())
+ else
+ @stat = lstat()
+ end
+ @stat
+ end
+
+ def stat!
+ return @stat if @stat
+ if lstat! and lstat!.symlink?
+ @stat = File.stat(path())
+ else
+ @stat = lstat!
+ end
+ @stat
+ rescue SystemCallError
+ nil
+ end
+
+ def lstat
+ if dereference?
+ @lstat ||= File.stat(path())
+ else
+ @lstat ||= File.lstat(path())
+ end
+ end
+
+ def lstat!
+ lstat()
+ rescue SystemCallError
+ nil
+ end
+
+ def chmod(mode)
+ if symlink?
+ File.lchmod mode, path() if have_lchmod?
+ else
+ File.chmod mode, path()
+ end
+ end
+
+ def chown(uid, gid)
+ if symlink?
+ File.lchown uid, gid, path() if have_lchown?
+ else
+ File.chown uid, gid, path()
+ end
+ end
+
+ def copy(dest)
+ lstat
+ case
+ when file?
+ copy_file dest
+ when directory?
+ if !File.exist?(dest) and descendant_directory?(dest, path)
+ raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest]
+ end
+ begin
+ Dir.mkdir dest
+ rescue
+ raise unless File.directory?(dest)
+ end
+ when symlink?
+ File.symlink File.readlink(path()), dest
+ when chardev?
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
+ mknod dest, ?c, 0666, lstat().rdev
+ when blockdev?
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
+ mknod dest, ?b, 0666, lstat().rdev
+ when socket?
+ raise "cannot handle socket" unless File.respond_to?(:mknod)
+ mknod dest, nil, lstat().mode, 0
+ when pipe?
+ raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
+ mkfifo dest, 0666
+ when door?
+ raise "cannot handle door: #{path()}"
+ else
+ raise "unknown file type: #{path()}"
+ end
+ end
+
+ def copy_file(dest)
+ File.open(path()) do |s|
+ File.open(dest, 'wb', s.stat.mode) do |f|
+ IO.copy_stream(s, f)
+ end
+ end
+ end
+
+ def copy_metadata(path)
+ st = lstat()
+ if !st.symlink?
+ File.utime st.atime, st.mtime, path
+ end
+ mode = st.mode
+ begin
+ if st.symlink?
+ begin
+ File.lchown st.uid, st.gid, path
+ rescue NotImplementedError
+ end
+ else
+ File.chown st.uid, st.gid, path
+ end
+ rescue Errno::EPERM, Errno::EACCES
+ # clear setuid/setgid
+ mode &= 01777
+ end
+ if st.symlink?
+ begin
+ File.lchmod mode, path
+ rescue NotImplementedError
+ end
+ else
+ File.chmod mode, path
+ end
+ end
+
+ def remove
+ if directory?
+ remove_dir1
+ else
+ remove_file
+ end
+ end
+
+ def remove_dir1
+ platform_support {
+ Dir.rmdir path().chomp(?/)
+ }
+ end
+
+ def remove_file
+ platform_support {
+ File.unlink path
+ }
+ end
+
+ def platform_support
+ return yield unless fu_windows?
+ first_time_p = true
+ begin
+ yield
+ rescue Errno::ENOENT
+ raise
+ rescue => err
+ if first_time_p
+ first_time_p = false
+ begin
+ File.chmod 0700, path() # Windows does not have symlink
+ retry
+ rescue SystemCallError
+ end
+ end
+ raise err
+ end
+ end
+
+ def preorder_traverse
+ stack = [self]
+ while ent = stack.pop
+ yield ent
+ stack.concat ent.entries.reverse if ent.directory?
+ end
+ end
+
+ alias traverse preorder_traverse
+
+ def postorder_traverse
+ if directory?
+ entries().each do |ent|
+ ent.postorder_traverse do |e|
+ yield e
+ end
+ end
+ end
+ ensure
+ yield self
+ end
+
+ def wrap_traverse(pre, post)
+ pre.call self
+ if directory?
+ entries.each do |ent|
+ ent.wrap_traverse pre, post
+ end
+ end
+ post.call self
+ end
+
+ private
+
+ $fileutils_rb_have_lchmod = nil
+
+ def have_lchmod?
+ # This is not MT-safe, but it does not matter.
+ if $fileutils_rb_have_lchmod == nil
+ $fileutils_rb_have_lchmod = check_have_lchmod?
+ end
+ $fileutils_rb_have_lchmod
+ end
+
+ def check_have_lchmod?
+ return false unless File.respond_to?(:lchmod)
+ File.lchmod 0
+ return true
+ rescue NotImplementedError
+ return false
+ end
+
+ $fileutils_rb_have_lchown = nil
+
+ def have_lchown?
+ # This is not MT-safe, but it does not matter.
+ if $fileutils_rb_have_lchown == nil
+ $fileutils_rb_have_lchown = check_have_lchown?
+ end
+ $fileutils_rb_have_lchown
+ end
+
+ def check_have_lchown?
+ return false unless File.respond_to?(:lchown)
+ File.lchown nil, nil
+ return true
+ rescue NotImplementedError
+ return false
+ end
+
+ def join(dir, base)
+ return File.path(dir) if not base or base == '.'
+ return File.path(base) if not dir or dir == '.'
+ File.join(dir, base)
+ end
+
+ if File::ALT_SEPARATOR
+ DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)"
+ else
+ DIRECTORY_TERM = "(?=/|\\z)"
+ end
+ SYSCASE = File::FNM_SYSCASE.nonzero? ? "-i" : ""
+
+ def descendant_directory?(descendant, ascendant)
+ /\A(?#{SYSCASE}:#{Regexp.quote(ascendant)})#{DIRECTORY_TERM}/ =~ File.dirname(descendant)
+ end
+ end # class Entry_
+
+ def fu_list(arg) #:nodoc:
+ [arg].flatten.map {|path| File.path(path) }
+ end
+ private_module_function :fu_list
+
+ def fu_each_src_dest(src, dest) #:nodoc:
+ fu_each_src_dest0(src, dest) do |s, d|
+ raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
+ yield s, d
+ end
+ end
+ private_module_function :fu_each_src_dest
+
+ def fu_each_src_dest0(src, dest) #:nodoc:
+ if tmp = Array.try_convert(src)
+ tmp.each do |s|
+ s = File.path(s)
+ yield s, File.join(dest, File.basename(s))
+ end
+ else
+ src = File.path(src)
+ if File.directory?(dest)
+ yield src, File.join(dest, File.basename(src))
+ else
+ yield src, File.path(dest)
+ end
+ end
+ end
+ private_module_function :fu_each_src_dest0
+
+ def fu_same?(a, b) #:nodoc:
+ File.identical?(a, b)
+ end
+ private_module_function :fu_same?
+
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+
+ def fu_output_message(msg) #:nodoc:
+ @fileutils_output ||= $stderr
+ @fileutils_label ||= ''
+ @fileutils_output.puts @fileutils_label + msg
+ end
+ private_module_function :fu_output_message
+
+ # This hash table holds command options.
+ OPT_TABLE = {} #:nodoc: internal use only
+ (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name|
+ (tbl[name.to_s] = instance_method(name).parameters).map! {|t, n| n if t == :key}.compact!
+ tbl
+ }
+
+ #
+ # Returns an Array of method names which have any options.
+ #
+ # p Bundler::FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
+ #
+ def self.commands
+ OPT_TABLE.keys
+ end
+
+ #
+ # Returns an Array of option names.
+ #
+ # p Bundler::FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
+ #
+ def self.options
+ OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
+ end
+
+ #
+ # Returns true if the method +mid+ have an option +opt+.
+ #
+ # p Bundler::FileUtils.have_option?(:cp, :noop) #=> true
+ # p Bundler::FileUtils.have_option?(:rm, :force) #=> true
+ # p Bundler::FileUtils.have_option?(:rm, :preserve) #=> false
+ #
+ def self.have_option?(mid, opt)
+ li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
+ li.include?(opt)
+ end
+
+ #
+ # Returns an Array of option names of the method +mid+.
+ #
+ # p Bundler::FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"]
+ #
+ def self.options_of(mid)
+ OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
+ end
+
+ #
+ # Returns an Array of method names which have the option +opt+.
+ #
+ # p Bundler::FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
+ #
+ def self.collect_method(opt)
+ OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
+ end
+
+ LOW_METHODS = singleton_methods(false) - collect_method(:noop).map(&:intern)
+ module LowMethods
+ private
+ def _do_nothing(*)end
+ ::Bundler::FileUtils::LOW_METHODS.map {|name| alias_method name, :_do_nothing}
+ end
+
+ METHODS = singleton_methods() - [:private_module_function,
+ :commands, :options, :have_option?, :options_of, :collect_method]
+
+ #
+ # This module has all methods of Bundler::FileUtils module, but it outputs messages
+ # before acting. This equates to passing the <tt>:verbose</tt> flag to
+ # methods in Bundler::FileUtils.
+ #
+ module Verbose
+ include Bundler::FileUtils
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+ names = ::Bundler::FileUtils.collect_method(:verbose)
+ names.each do |name|
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{name}(*args, **options)
+ super(*args, **options, verbose: true)
+ end
+ EOS
+ end
+ private(*names)
+ extend self
+ class << self
+ public(*::Bundler::FileUtils::METHODS)
+ end
+ end
+
+ #
+ # This module has all methods of Bundler::FileUtils module, but never changes
+ # files/directories. This equates to passing the <tt>:noop</tt> flag
+ # to methods in Bundler::FileUtils.
+ #
+ module NoWrite
+ include Bundler::FileUtils
+ include LowMethods
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+ names = ::Bundler::FileUtils.collect_method(:noop)
+ names.each do |name|
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{name}(*args, **options)
+ super(*args, **options, noop: true)
+ end
+ EOS
+ end
+ private(*names)
+ extend self
+ class << self
+ public(*::Bundler::FileUtils::METHODS)
+ end
+ end
+
+ #
+ # This module has all methods of Bundler::FileUtils module, but never changes
+ # files/directories, with printing message before acting.
+ # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
+ # to methods in Bundler::FileUtils.
+ #
+ module DryRun
+ include Bundler::FileUtils
+ include LowMethods
+ @fileutils_output = $stderr
+ @fileutils_label = ''
+ names = ::Bundler::FileUtils.collect_method(:noop)
+ names.each do |name|
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{name}(*args, **options)
+ super(*args, **options, noop: true, verbose: true)
+ end
+ EOS
+ end
+ private(*names)
+ extend self
+ class << self
+ public(*::Bundler::FileUtils::METHODS)
+ end
+ end
+
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo.rb b/lib/bundler/vendor/molinillo/lib/molinillo.rb
new file mode 100644
index 0000000000..9e2867144f
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/compatibility'
+require 'bundler/vendor/molinillo/lib/molinillo/gem_metadata'
+require 'bundler/vendor/molinillo/lib/molinillo/errors'
+require 'bundler/vendor/molinillo/lib/molinillo/resolver'
+require 'bundler/vendor/molinillo/lib/molinillo/modules/ui'
+require 'bundler/vendor/molinillo/lib/molinillo/modules/specification_provider'
+
+# Bundler::Molinillo is a generic dependency resolution algorithm.
+module Bundler::Molinillo
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb b/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb
new file mode 100644
index 0000000000..3eba8e4083
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ # Hacks needed for old Ruby versions.
+ module Compatibility
+ module_function
+
+ if [].respond_to?(:flat_map)
+ # Flat map
+ # @param [Enumerable] enum an enumerable object
+ # @block the block to flat-map with
+ # @return The enum, flat-mapped
+ def flat_map(enum, &blk)
+ enum.flat_map(&blk)
+ end
+ else
+ # Flat map
+ # @param [Enumerable] enum an enumerable object
+ # @block the block to flat-map with
+ # @return The enum, flat-mapped
+ def flat_map(enum, &blk)
+ enum.map(&blk).flatten(1)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
new file mode 100644
index 0000000000..bcacf35243
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ # @!visibility private
+ module Delegates
+ # Delegates all {Bundler::Molinillo::ResolutionState} methods to a `#state` property.
+ module ResolutionState
+ # (see Bundler::Molinillo::ResolutionState#name)
+ def name
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.name
+ end
+
+ # (see Bundler::Molinillo::ResolutionState#requirements)
+ def requirements
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.requirements
+ end
+
+ # (see Bundler::Molinillo::ResolutionState#activated)
+ def activated
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.activated
+ end
+
+ # (see Bundler::Molinillo::ResolutionState#requirement)
+ def requirement
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.requirement
+ end
+
+ # (see Bundler::Molinillo::ResolutionState#possibilities)
+ def possibilities
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.possibilities
+ end
+
+ # (see Bundler::Molinillo::ResolutionState#depth)
+ def depth
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.depth
+ end
+
+ # (see Bundler::Molinillo::ResolutionState#conflicts)
+ def conflicts
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.conflicts
+ end
+
+ # (see Bundler::Molinillo::ResolutionState#unused_unwind_options)
+ def unused_unwind_options
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
+ current_state.unused_unwind_options
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
new file mode 100644
index 0000000000..ec9c770a28
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ module Delegates
+ # Delegates all {Bundler::Molinillo::SpecificationProvider} methods to a
+ # `#specification_provider` property.
+ module SpecificationProvider
+ # (see Bundler::Molinillo::SpecificationProvider#search_for)
+ def search_for(dependency)
+ with_no_such_dependency_error_handling do
+ specification_provider.search_for(dependency)
+ end
+ end
+
+ # (see Bundler::Molinillo::SpecificationProvider#dependencies_for)
+ def dependencies_for(specification)
+ with_no_such_dependency_error_handling do
+ specification_provider.dependencies_for(specification)
+ end
+ end
+
+ # (see Bundler::Molinillo::SpecificationProvider#requirement_satisfied_by?)
+ def requirement_satisfied_by?(requirement, activated, spec)
+ with_no_such_dependency_error_handling do
+ specification_provider.requirement_satisfied_by?(requirement, activated, spec)
+ end
+ end
+
+ # (see Bundler::Molinillo::SpecificationProvider#name_for)
+ def name_for(dependency)
+ with_no_such_dependency_error_handling do
+ specification_provider.name_for(dependency)
+ end
+ end
+
+ # (see Bundler::Molinillo::SpecificationProvider#name_for_explicit_dependency_source)
+ def name_for_explicit_dependency_source
+ with_no_such_dependency_error_handling do
+ specification_provider.name_for_explicit_dependency_source
+ end
+ end
+
+ # (see Bundler::Molinillo::SpecificationProvider#name_for_locking_dependency_source)
+ def name_for_locking_dependency_source
+ with_no_such_dependency_error_handling do
+ specification_provider.name_for_locking_dependency_source
+ end
+ end
+
+ # (see Bundler::Molinillo::SpecificationProvider#sort_dependencies)
+ def sort_dependencies(dependencies, activated, conflicts)
+ with_no_such_dependency_error_handling do
+ specification_provider.sort_dependencies(dependencies, activated, conflicts)
+ end
+ end
+
+ # (see Bundler::Molinillo::SpecificationProvider#allow_missing?)
+ def allow_missing?(dependency)
+ with_no_such_dependency_error_handling do
+ specification_provider.allow_missing?(dependency)
+ end
+ end
+
+ private
+
+ # Ensures any raised {NoSuchDependencyError} has its
+ # {NoSuchDependencyError#required_by} set.
+ # @yield
+ def with_no_such_dependency_error_handling
+ yield
+ rescue NoSuchDependencyError => error
+ if state
+ vertex = activated.vertex_named(name_for(error.dependency))
+ error.required_by += vertex.incoming_edges.map { |e| e.origin.name }
+ error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty?
+ end
+ raise
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
new file mode 100644
index 0000000000..677a8bd916
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
@@ -0,0 +1,223 @@
+# frozen_string_literal: true
+
+require 'set'
+require 'tsort'
+
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/log'
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex'
+
+module Bundler::Molinillo
+ # A directed acyclic graph that is tuned to hold named dependencies
+ class DependencyGraph
+ include Enumerable
+
+ # Enumerates through the vertices of the graph.
+ # @return [Array<Vertex>] The graph's vertices.
+ def each
+ return vertices.values.each unless block_given?
+ vertices.values.each { |v| yield v }
+ end
+
+ include TSort
+
+ # @!visibility private
+ alias tsort_each_node each
+
+ # @!visibility private
+ def tsort_each_child(vertex, &block)
+ vertex.successors.each(&block)
+ end
+
+ # Topologically sorts the given vertices.
+ # @param [Enumerable<Vertex>] vertices the vertices to be sorted, which must
+ # all belong to the same graph.
+ # @return [Array<Vertex>] The sorted vertices.
+ def self.tsort(vertices)
+ TSort.tsort(
+ lambda { |b| vertices.each(&b) },
+ lambda { |v, &b| (v.successors & vertices).each(&b) }
+ )
+ end
+
+ # A directed edge of a {DependencyGraph}
+ # @attr [Vertex] origin The origin of the directed edge
+ # @attr [Vertex] destination The destination of the directed edge
+ # @attr [Object] requirement The requirement the directed edge represents
+ Edge = Struct.new(:origin, :destination, :requirement)
+
+ # @return [{String => Vertex}] the vertices of the dependency graph, keyed
+ # by {Vertex#name}
+ attr_reader :vertices
+
+ # @return [Log] the op log for this graph
+ attr_reader :log
+
+ # Initializes an empty dependency graph
+ def initialize
+ @vertices = {}
+ @log = Log.new
+ end
+
+ # Tags the current state of the dependency as the given tag
+ # @param [Object] tag an opaque tag for the current state of the graph
+ # @return [Void]
+ def tag(tag)
+ log.tag(self, tag)
+ end
+
+ # Rewinds the graph to the state tagged as `tag`
+ # @param [Object] tag the tag to rewind to
+ # @return [Void]
+ def rewind_to(tag)
+ log.rewind_to(self, tag)
+ end
+
+ # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices}
+ # are properly copied.
+ # @param [DependencyGraph] other the graph to copy.
+ def initialize_copy(other)
+ super
+ @vertices = {}
+ @log = other.log.dup
+ traverse = lambda do |new_v, old_v|
+ return if new_v.outgoing_edges.size == old_v.outgoing_edges.size
+ old_v.outgoing_edges.each do |edge|
+ destination = add_vertex(edge.destination.name, edge.destination.payload)
+ add_edge_no_circular(new_v, destination, edge.requirement)
+ traverse.call(destination, edge.destination)
+ end
+ end
+ other.vertices.each do |name, vertex|
+ new_vertex = add_vertex(name, vertex.payload, vertex.root?)
+ new_vertex.explicit_requirements.replace(vertex.explicit_requirements)
+ traverse.call(new_vertex, vertex)
+ end
+ end
+
+ # @return [String] a string suitable for debugging
+ def inspect
+ "#{self.class}:#{vertices.values.inspect}"
+ end
+
+ # @param [Hash] options options for dot output.
+ # @return [String] Returns a dot format representation of the graph
+ def to_dot(options = {})
+ edge_label = options.delete(:edge_label)
+ raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty?
+
+ dot_vertices = []
+ dot_edges = []
+ vertices.each do |n, v|
+ dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]"
+ v.outgoing_edges.each do |e|
+ label = edge_label ? edge_label.call(e) : e.requirement
+ dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]"
+ end
+ end
+
+ dot_vertices.uniq!
+ dot_vertices.sort!
+ dot_edges.uniq!
+ dot_edges.sort!
+
+ dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}')
+ dot.join("\n")
+ end
+
+ # @return [Boolean] whether the two dependency graphs are equal, determined
+ # by a recursive traversal of each {#root_vertices} and its
+ # {Vertex#successors}
+ def ==(other)
+ return false unless other
+ return true if equal?(other)
+ vertices.each do |name, vertex|
+ other_vertex = other.vertex_named(name)
+ return false unless other_vertex
+ return false unless vertex.payload == other_vertex.payload
+ return false unless other_vertex.successors.to_set == vertex.successors.to_set
+ end
+ end
+
+ # @param [String] name
+ # @param [Object] payload
+ # @param [Array<String>] parent_names
+ # @param [Object] requirement the requirement that is requiring the child
+ # @return [void]
+ def add_child_vertex(name, payload, parent_names, requirement)
+ root = !parent_names.delete(nil) { true }
+ vertex = add_vertex(name, payload, root)
+ vertex.explicit_requirements << requirement if root
+ parent_names.each do |parent_name|
+ parent_vertex = vertex_named(parent_name)
+ add_edge(parent_vertex, vertex, requirement)
+ end
+ vertex
+ end
+
+ # Adds a vertex with the given name, or updates the existing one.
+ # @param [String] name
+ # @param [Object] payload
+ # @return [Vertex] the vertex that was added to `self`
+ def add_vertex(name, payload, root = false)
+ log.add_vertex(self, name, payload, root)
+ end
+
+ # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively
+ # removing any non-root vertices that were orphaned in the process
+ # @param [String] name
+ # @return [Array<Vertex>] the vertices which have been detached
+ def detach_vertex_named(name)
+ log.detach_vertex_named(self, name)
+ end
+
+ # @param [String] name
+ # @return [Vertex,nil] the vertex with the given name
+ def vertex_named(name)
+ vertices[name]
+ end
+
+ # @param [String] name
+ # @return [Vertex,nil] the root vertex with the given name
+ def root_vertex_named(name)
+ vertex = vertex_named(name)
+ vertex if vertex && vertex.root?
+ end
+
+ # Adds a new {Edge} to the dependency graph
+ # @param [Vertex] origin
+ # @param [Vertex] destination
+ # @param [Object] requirement the requirement that this edge represents
+ # @return [Edge] the added edge
+ def add_edge(origin, destination, requirement)
+ if destination.path_to?(origin)
+ raise CircularDependencyError.new([origin, destination])
+ end
+ add_edge_no_circular(origin, destination, requirement)
+ end
+
+ # Deletes an {Edge} from the dependency graph
+ # @param [Edge] edge
+ # @return [Void]
+ def delete_edge(edge)
+ log.delete_edge(self, edge.origin.name, edge.destination.name, edge.requirement)
+ end
+
+ # Sets the payload of the vertex with the given name
+ # @param [String] name the name of the vertex
+ # @param [Object] payload the payload
+ # @return [Void]
+ def set_payload(name, payload)
+ log.set_payload(self, name, payload)
+ end
+
+ private
+
+ # Adds a new {Edge} to the dependency graph without checking for
+ # circularity.
+ # @param (see #add_edge)
+ # @return (see #add_edge)
+ def add_edge_no_circular(origin, destination, requirement)
+ log.add_edge_no_circular(self, origin.name, destination.name, requirement)
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
new file mode 100644
index 0000000000..c04c7eec9c
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ class DependencyGraph
+ # An action that modifies a {DependencyGraph} that is reversible.
+ # @abstract
+ class Action
+ # rubocop:disable Lint/UnusedMethodArgument
+
+ # @return [Symbol] The name of the action.
+ def self.action_name
+ raise 'Abstract'
+ end
+
+ # Performs the action on the given graph.
+ # @param [DependencyGraph] graph the graph to perform the action on.
+ # @return [Void]
+ def up(graph)
+ raise 'Abstract'
+ end
+
+ # Reverses the action on the given graph.
+ # @param [DependencyGraph] graph the graph to reverse the action on.
+ # @return [Void]
+ def down(graph)
+ raise 'Abstract'
+ end
+
+ # @return [Action,Nil] The previous action
+ attr_accessor :previous
+
+ # @return [Action,Nil] The next action
+ attr_accessor :next
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
new file mode 100644
index 0000000000..9849aea2fe
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
+module Bundler::Molinillo
+ class DependencyGraph
+ # @!visibility private
+ # (see DependencyGraph#add_edge_no_circular)
+ class AddEdgeNoCircular < Action
+ # @!group Action
+
+ # (see Action.action_name)
+ def self.action_name
+ :add_vertex
+ end
+
+ # (see Action#up)
+ def up(graph)
+ edge = make_edge(graph)
+ edge.origin.outgoing_edges << edge
+ edge.destination.incoming_edges << edge
+ edge
+ end
+
+ # (see Action#down)
+ def down(graph)
+ edge = make_edge(graph)
+ delete_first(edge.origin.outgoing_edges, edge)
+ delete_first(edge.destination.incoming_edges, edge)
+ end
+
+ # @!group AddEdgeNoCircular
+
+ # @return [String] the name of the origin of the edge
+ attr_reader :origin
+
+ # @return [String] the name of the destination of the edge
+ attr_reader :destination
+
+ # @return [Object] the requirement that the edge represents
+ attr_reader :requirement
+
+ # @param [DependencyGraph] graph the graph to find vertices from
+ # @return [Edge] The edge this action adds
+ def make_edge(graph)
+ Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement)
+ end
+
+ # Initialize an action to add an edge to a dependency graph
+ # @param [String] origin the name of the origin of the edge
+ # @param [String] destination the name of the destination of the edge
+ # @param [Object] requirement the requirement that the edge represents
+ def initialize(origin, destination, requirement)
+ @origin = origin
+ @destination = destination
+ @requirement = requirement
+ end
+
+ private
+
+ def delete_first(array, item)
+ return unless index = array.index(item)
+ array.delete_at(index)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
new file mode 100644
index 0000000000..0a1e08255b
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
+module Bundler::Molinillo
+ class DependencyGraph
+ # @!visibility private
+ # (see DependencyGraph#add_vertex)
+ class AddVertex < Action # :nodoc:
+ # @!group Action
+
+ # (see Action.action_name)
+ def self.action_name
+ :add_vertex
+ end
+
+ # (see Action#up)
+ def up(graph)
+ if existing = graph.vertices[name]
+ @existing_payload = existing.payload
+ @existing_root = existing.root
+ end
+ vertex = existing || Vertex.new(name, payload)
+ graph.vertices[vertex.name] = vertex
+ vertex.payload ||= payload
+ vertex.root ||= root
+ vertex
+ end
+
+ # (see Action#down)
+ def down(graph)
+ if defined?(@existing_payload)
+ vertex = graph.vertices[name]
+ vertex.payload = @existing_payload
+ vertex.root = @existing_root
+ else
+ graph.vertices.delete(name)
+ end
+ end
+
+ # @!group AddVertex
+
+ # @return [String] the name of the vertex
+ attr_reader :name
+
+ # @return [Object] the payload for the vertex
+ attr_reader :payload
+
+ # @return [Boolean] whether the vertex is root or not
+ attr_reader :root
+
+ # Initialize an action to add a vertex to a dependency graph
+ # @param [String] name the name of the vertex
+ # @param [Object] payload the payload for the vertex
+ # @param [Boolean] root whether the vertex is root or not
+ def initialize(name, payload, root)
+ @name = name
+ @payload = payload
+ @root = root
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
new file mode 100644
index 0000000000..1d9f4b327d
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
+module Bundler::Molinillo
+ class DependencyGraph
+ # @!visibility private
+ # (see DependencyGraph#delete_edge)
+ class DeleteEdge < Action
+ # @!group Action
+
+ # (see Action.action_name)
+ def self.action_name
+ :delete_edge
+ end
+
+ # (see Action#up)
+ def up(graph)
+ edge = make_edge(graph)
+ edge.origin.outgoing_edges.delete(edge)
+ edge.destination.incoming_edges.delete(edge)
+ end
+
+ # (see Action#down)
+ def down(graph)
+ edge = make_edge(graph)
+ edge.origin.outgoing_edges << edge
+ edge.destination.incoming_edges << edge
+ edge
+ end
+
+ # @!group DeleteEdge
+
+ # @return [String] the name of the origin of the edge
+ attr_reader :origin_name
+
+ # @return [String] the name of the destination of the edge
+ attr_reader :destination_name
+
+ # @return [Object] the requirement that the edge represents
+ attr_reader :requirement
+
+ # @param [DependencyGraph] graph the graph to find vertices from
+ # @return [Edge] The edge this action adds
+ def make_edge(graph)
+ Edge.new(
+ graph.vertex_named(origin_name),
+ graph.vertex_named(destination_name),
+ requirement
+ )
+ end
+
+ # Initialize an action to add an edge to a dependency graph
+ # @param [String] origin_name the name of the origin of the edge
+ # @param [String] destination_name the name of the destination of the edge
+ # @param [Object] requirement the requirement that the edge represents
+ def initialize(origin_name, destination_name, requirement)
+ @origin_name = origin_name
+ @destination_name = destination_name
+ @requirement = requirement
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
new file mode 100644
index 0000000000..385dcbdd06
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
+module Bundler::Molinillo
+ class DependencyGraph
+ # @!visibility private
+ # @see DependencyGraph#detach_vertex_named
+ class DetachVertexNamed < Action
+ # @!group Action
+
+ # (see Action#name)
+ def self.action_name
+ :add_vertex
+ end
+
+ # (see Action#up)
+ def up(graph)
+ return [] unless @vertex = graph.vertices.delete(name)
+
+ removed_vertices = [@vertex]
+ @vertex.outgoing_edges.each do |e|
+ v = e.destination
+ v.incoming_edges.delete(e)
+ if !v.root? && v.incoming_edges.empty?
+ removed_vertices.concat graph.detach_vertex_named(v.name)
+ end
+ end
+
+ @vertex.incoming_edges.each do |e|
+ v = e.origin
+ v.outgoing_edges.delete(e)
+ end
+
+ removed_vertices
+ end
+
+ # (see Action#down)
+ def down(graph)
+ return unless @vertex
+ graph.vertices[@vertex.name] = @vertex
+ @vertex.outgoing_edges.each do |e|
+ e.destination.incoming_edges << e
+ end
+ @vertex.incoming_edges.each do |e|
+ e.origin.outgoing_edges << e
+ end
+ end
+
+ # @!group DetachVertexNamed
+
+ # @return [String] the name of the vertex to detach
+ attr_reader :name
+
+ # Initialize an action to detach a vertex from a dependency graph
+ # @param [String] name the name of the vertex to detach
+ def initialize(name)
+ @name = name
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
new file mode 100644
index 0000000000..8582dd19c1
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular'
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex'
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge'
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named'
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload'
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag'
+
+module Bundler::Molinillo
+ class DependencyGraph
+ # A log for dependency graph actions
+ class Log
+ # Initializes an empty log
+ def initialize
+ @current_action = @first_action = nil
+ end
+
+ # @!macro [new] action
+ # {include:DependencyGraph#$0}
+ # @param [Graph] graph the graph to perform the action on
+ # @param (see DependencyGraph#$0)
+ # @return (see DependencyGraph#$0)
+
+ # @macro action
+ def tag(graph, tag)
+ push_action(graph, Tag.new(tag))
+ end
+
+ # @macro action
+ def add_vertex(graph, name, payload, root)
+ push_action(graph, AddVertex.new(name, payload, root))
+ end
+
+ # @macro action
+ def detach_vertex_named(graph, name)
+ push_action(graph, DetachVertexNamed.new(name))
+ end
+
+ # @macro action
+ def add_edge_no_circular(graph, origin, destination, requirement)
+ push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement))
+ end
+
+ # {include:DependencyGraph#delete_edge}
+ # @param [Graph] graph the graph to perform the action on
+ # @param [String] origin_name
+ # @param [String] destination_name
+ # @param [Object] requirement
+ # @return (see DependencyGraph#delete_edge)
+ def delete_edge(graph, origin_name, destination_name, requirement)
+ push_action(graph, DeleteEdge.new(origin_name, destination_name, requirement))
+ end
+
+ # @macro action
+ def set_payload(graph, name, payload)
+ push_action(graph, SetPayload.new(name, payload))
+ end
+
+ # Pops the most recent action from the log and undoes the action
+ # @param [DependencyGraph] graph
+ # @return [Action] the action that was popped off the log
+ def pop!(graph)
+ return unless action = @current_action
+ unless @current_action = action.previous
+ @first_action = nil
+ end
+ action.down(graph)
+ action
+ end
+
+ extend Enumerable
+
+ # @!visibility private
+ # Enumerates each action in the log
+ # @yield [Action]
+ def each
+ return enum_for unless block_given?
+ action = @first_action
+ loop do
+ break unless action
+ yield action
+ action = action.next
+ end
+ self
+ end
+
+ # @!visibility private
+ # Enumerates each action in the log in reverse order
+ # @yield [Action]
+ def reverse_each
+ return enum_for(:reverse_each) unless block_given?
+ action = @current_action
+ loop do
+ break unless action
+ yield action
+ action = action.previous
+ end
+ self
+ end
+
+ # @macro action
+ def rewind_to(graph, tag)
+ loop do
+ action = pop!(graph)
+ raise "No tag #{tag.inspect} found" unless action
+ break if action.class.action_name == :tag && action.tag == tag
+ end
+ end
+
+ private
+
+ # Adds the given action to the log, running the action
+ # @param [DependencyGraph] graph
+ # @param [Action] action
+ # @return The value returned by `action.up`
+ def push_action(graph, action)
+ action.previous = @current_action
+ @current_action.next = action if @current_action
+ @current_action = action
+ @first_action ||= action
+ action.up(graph)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
new file mode 100644
index 0000000000..37286d104a
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
+module Bundler::Molinillo
+ class DependencyGraph
+ # @!visibility private
+ # @see DependencyGraph#set_payload
+ class SetPayload < Action # :nodoc:
+ # @!group Action
+
+ # (see Action.action_name)
+ def self.action_name
+ :set_payload
+ end
+
+ # (see Action#up)
+ def up(graph)
+ vertex = graph.vertex_named(name)
+ @old_payload = vertex.payload
+ vertex.payload = payload
+ end
+
+ # (see Action#down)
+ def down(graph)
+ graph.vertex_named(name).payload = @old_payload
+ end
+
+ # @!group SetPayload
+
+ # @return [String] the name of the vertex
+ attr_reader :name
+
+ # @return [Object] the payload for the vertex
+ attr_reader :payload
+
+ # Initialize an action to add set the payload for a vertex in a dependency
+ # graph
+ # @param [String] name the name of the vertex
+ # @param [Object] payload the payload for the vertex
+ def initialize(name, payload)
+ @name = name
+ @payload = payload
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
new file mode 100644
index 0000000000..d6ad16e07a
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
+module Bundler::Molinillo
+ class DependencyGraph
+ # @!visibility private
+ # @see DependencyGraph#tag
+ class Tag < Action
+ # @!group Action
+
+ # (see Action.action_name)
+ def self.action_name
+ :tag
+ end
+
+ # (see Action#up)
+ def up(_graph)
+ end
+
+ # (see Action#down)
+ def down(_graph)
+ end
+
+ # @!group Tag
+
+ # @return [Object] An opaque tag
+ attr_reader :tag
+
+ # Initialize an action to tag a state of a dependency graph
+ # @param [Object] tag an opaque tag
+ def initialize(tag)
+ @tag = tag
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
new file mode 100644
index 0000000000..7ecdc4b65a
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ class DependencyGraph
+ # A vertex in a {DependencyGraph} that encapsulates a {#name} and a
+ # {#payload}
+ class Vertex
+ # @return [String] the name of the vertex
+ attr_accessor :name
+
+ # @return [Object] the payload the vertex holds
+ attr_accessor :payload
+
+ # @return [Array<Object>] the explicit requirements that required
+ # this vertex
+ attr_reader :explicit_requirements
+
+ # @return [Boolean] whether the vertex is considered a root vertex
+ attr_accessor :root
+ alias root? root
+
+ # Initializes a vertex with the given name and payload.
+ # @param [String] name see {#name}
+ # @param [Object] payload see {#payload}
+ def initialize(name, payload)
+ @name = name.frozen? ? name : name.dup.freeze
+ @payload = payload
+ @explicit_requirements = []
+ @outgoing_edges = []
+ @incoming_edges = []
+ end
+
+ # @return [Array<Object>] all of the requirements that required
+ # this vertex
+ def requirements
+ (incoming_edges.map(&:requirement) + explicit_requirements).uniq
+ end
+
+ # @return [Array<Edge>] the edges of {#graph} that have `self` as their
+ # {Edge#origin}
+ attr_accessor :outgoing_edges
+
+ # @return [Array<Edge>] the edges of {#graph} that have `self` as their
+ # {Edge#destination}
+ attr_accessor :incoming_edges
+
+ # @return [Array<Vertex>] the vertices of {#graph} that have an edge with
+ # `self` as their {Edge#destination}
+ def predecessors
+ incoming_edges.map(&:origin)
+ end
+
+ # @return [Array<Vertex>] the vertices of {#graph} where `self` is a
+ # {#descendent?}
+ def recursive_predecessors
+ vertices = predecessors
+ vertices += Compatibility.flat_map(vertices, &:recursive_predecessors)
+ vertices.uniq!
+ vertices
+ end
+
+ # @return [Array<Vertex>] the vertices of {#graph} that have an edge with
+ # `self` as their {Edge#origin}
+ def successors
+ outgoing_edges.map(&:destination)
+ end
+
+ # @return [Array<Vertex>] the vertices of {#graph} where `self` is an
+ # {#ancestor?}
+ def recursive_successors
+ vertices = successors
+ vertices += Compatibility.flat_map(vertices, &:recursive_successors)
+ vertices.uniq!
+ vertices
+ end
+
+ # @return [String] a string suitable for debugging
+ def inspect
+ "#{self.class}:#{name}(#{payload.inspect})"
+ end
+
+ # @return [Boolean] whether the two vertices are equal, determined
+ # by a recursive traversal of each {Vertex#successors}
+ def ==(other)
+ return true if equal?(other)
+ shallow_eql?(other) &&
+ successors.to_set == other.successors.to_set
+ end
+
+ # @param [Vertex] other the other vertex to compare to
+ # @return [Boolean] whether the two vertices are equal, determined
+ # solely by {#name} and {#payload} equality
+ def shallow_eql?(other)
+ return true if equal?(other)
+ other &&
+ name == other.name &&
+ payload == other.payload
+ end
+
+ alias eql? ==
+
+ # @return [Fixnum] a hash for the vertex based upon its {#name}
+ def hash
+ name.hash
+ end
+
+ # Is there a path from `self` to `other` following edges in the
+ # dependency graph?
+ # @return true iff there is a path following edges within this {#graph}
+ def path_to?(other)
+ _path_to?(other)
+ end
+
+ alias descendent? path_to?
+
+ # @param [Vertex] other the vertex to check if there's a path to
+ # @param [Set<Vertex>] visited the vertices of {#graph} that have been visited
+ # @return [Boolean] whether there is a path to `other` from `self`
+ def _path_to?(other, visited = Set.new)
+ return false unless visited.add?(self)
+ return true if equal?(other)
+ successors.any? { |v| v._path_to?(other, visited) }
+ end
+ protected :_path_to?
+
+ # Is there a path from `other` to `self` following edges in the
+ # dependency graph?
+ # @return true iff there is a path following edges within this {#graph}
+ def ancestor?(other)
+ other.path_to?(self)
+ end
+
+ alias is_reachable_from? ancestor?
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb
new file mode 100644
index 0000000000..ce0931f103
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ # An error that occurred during the resolution process
+ class ResolverError < StandardError; end
+
+ # An error caused by searching for a dependency that is completely unknown,
+ # i.e. has no versions available whatsoever.
+ class NoSuchDependencyError < ResolverError
+ # @return [Object] the dependency that could not be found
+ attr_accessor :dependency
+
+ # @return [Array<Object>] the specifications that depended upon {#dependency}
+ attr_accessor :required_by
+
+ # Initializes a new error with the given missing dependency.
+ # @param [Object] dependency @see {#dependency}
+ # @param [Array<Object>] required_by @see {#required_by}
+ def initialize(dependency, required_by = [])
+ @dependency = dependency
+ @required_by = required_by.uniq
+ super()
+ end
+
+ # The error message for the missing dependency, including the specifications
+ # that had this dependency.
+ def message
+ sources = required_by.map { |r| "`#{r}`" }.join(' and ')
+ message = "Unable to find a specification for `#{dependency}`"
+ message += " depended upon by #{sources}" unless sources.empty?
+ message
+ end
+ end
+
+ # An error caused by attempting to fulfil a dependency that was circular
+ #
+ # @note This exception will be thrown iff a {Vertex} is added to a
+ # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an
+ # existing {DependencyGraph::Vertex}
+ class CircularDependencyError < ResolverError
+ # [Set<Object>] the dependencies responsible for causing the error
+ attr_reader :dependencies
+
+ # Initializes a new error with the given circular vertices.
+ # @param [Array<DependencyGraph::Vertex>] vertices the vertices in the dependency
+ # that caused the error
+ def initialize(vertices)
+ super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}"
+ @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set
+ end
+ end
+
+ # An error caused by conflicts in version
+ class VersionConflict < ResolverError
+ # @return [{String => Resolution::Conflict}] the conflicts that caused
+ # resolution to fail
+ attr_reader :conflicts
+
+ # @return [SpecificationProvider] the specification provider used during
+ # resolution
+ attr_reader :specification_provider
+
+ # Initializes a new error with the given version conflicts.
+ # @param [{String => Resolution::Conflict}] conflicts see {#conflicts}
+ # @param [SpecificationProvider] specification_provider see {#specification_provider}
+ def initialize(conflicts, specification_provider)
+ pairs = []
+ Compatibility.flat_map(conflicts.values.flatten, &:requirements).each do |conflicting|
+ conflicting.each do |source, conflict_requirements|
+ conflict_requirements.each do |c|
+ pairs << [c, source]
+ end
+ end
+ end
+
+ super "Unable to satisfy the following requirements:\n\n" \
+ "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}"
+
+ @conflicts = conflicts
+ @specification_provider = specification_provider
+ end
+
+ require 'bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider'
+ include Delegates::SpecificationProvider
+
+ # @return [String] An error message that includes requirement trees,
+ # which is much more detailed & customizable than the default message
+ # @param [Hash] opts the options to create a message with.
+ # @option opts [String] :solver_name The user-facing name of the solver
+ # @option opts [String] :possibility_type The generic name of a possibility
+ # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees
+ # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements
+ # @option opts [Proc] :additional_message_for_conflict A proc that appends additional
+ # messages for each conflict
+ # @option opts [Proc] :version_for_spec A proc that returns the version number for a
+ # possibility
+ def message_with_trees(opts = {})
+ solver_name = opts.delete(:solver_name) { self.class.name.split('::').first }
+ possibility_type = opts.delete(:possibility_type) { 'possibility named' }
+ reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } }
+ printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } }
+ additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} }
+ version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) }
+ incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do
+ proc do |name, _conflict|
+ %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":)
+ end
+ end
+
+ conflicts.sort.reduce(''.dup) do |o, (name, conflict)|
+ o << "\n" << incompatible_version_message_for_conflict.call(name, conflict) << "\n"
+ if conflict.locked_requirement
+ o << %( In snapshot (#{name_for_locking_dependency_source}):\n)
+ o << %( #{printable_requirement.call(conflict.locked_requirement)}\n)
+ o << %(\n)
+ end
+ o << %( In #{name_for_explicit_dependency_source}:\n)
+ trees = reduce_trees.call(conflict.requirement_trees)
+
+ o << trees.map do |tree|
+ t = ''.dup
+ depth = 2
+ tree.each do |req|
+ t << ' ' * depth << req.to_s
+ unless tree.last == req
+ if spec = conflict.activated_by_name[name_for(req)]
+ t << %( was resolved to #{version_for_spec.call(spec)}, which)
+ end
+ t << %( depends on)
+ end
+ t << %(\n)
+ depth += 1
+ end
+ t
+ end.join("\n")
+
+ additional_message_for_conflict.call(o, name, conflict)
+
+ o
+ end.strip
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb
new file mode 100644
index 0000000000..73f8fbf2ac
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ # The version of Bundler::Molinillo.
+ VERSION = '0.6.6'.freeze
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
new file mode 100644
index 0000000000..fa094c1981
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ # Provides information about specifcations and dependencies to the resolver,
+ # allowing the {Resolver} class to remain generic while still providing power
+ # and flexibility.
+ #
+ # This module contains the methods that users of Bundler::Molinillo must to implement,
+ # using knowledge of their own model classes.
+ module SpecificationProvider
+ # Search for the specifications that match the given dependency.
+ # The specifications in the returned array will be considered in reverse
+ # order, so the latest version ought to be last.
+ # @note This method should be 'pure', i.e. the return value should depend
+ # only on the `dependency` parameter.
+ #
+ # @param [Object] dependency
+ # @return [Array<Object>] the specifications that satisfy the given
+ # `dependency`.
+ def search_for(dependency)
+ []
+ end
+
+ # Returns the dependencies of `specification`.
+ # @note This method should be 'pure', i.e. the return value should depend
+ # only on the `specification` parameter.
+ #
+ # @param [Object] specification
+ # @return [Array<Object>] the dependencies that are required by the given
+ # `specification`.
+ def dependencies_for(specification)
+ []
+ end
+
+ # Determines whether the given `requirement` is satisfied by the given
+ # `spec`, in the context of the current `activated` dependency graph.
+ #
+ # @param [Object] requirement
+ # @param [DependencyGraph] activated the current dependency graph in the
+ # resolution process.
+ # @param [Object] spec
+ # @return [Boolean] whether `requirement` is satisfied by `spec` in the
+ # context of the current `activated` dependency graph.
+ def requirement_satisfied_by?(requirement, activated, spec)
+ true
+ end
+
+ # Returns the name for the given `dependency`.
+ # @note This method should be 'pure', i.e. the return value should depend
+ # only on the `dependency` parameter.
+ #
+ # @param [Object] dependency
+ # @return [String] the name for the given `dependency`.
+ def name_for(dependency)
+ dependency.to_s
+ end
+
+ # @return [String] the name of the source of explicit dependencies, i.e.
+ # those passed to {Resolver#resolve} directly.
+ def name_for_explicit_dependency_source
+ 'user-specified dependency'
+ end
+
+ # @return [String] the name of the source of 'locked' dependencies, i.e.
+ # those passed to {Resolver#resolve} directly as the `base`
+ def name_for_locking_dependency_source
+ 'Lockfile'
+ end
+
+ # Sort dependencies so that the ones that are easiest to resolve are first.
+ # Easiest to resolve is (usually) defined by:
+ # 1) Is this dependency already activated?
+ # 2) How relaxed are the requirements?
+ # 3) Are there any conflicts for this dependency?
+ # 4) How many possibilities are there to satisfy this dependency?
+ #
+ # @param [Array<Object>] dependencies
+ # @param [DependencyGraph] activated the current dependency graph in the
+ # resolution process.
+ # @param [{String => Array<Conflict>}] conflicts
+ # @return [Array<Object>] a sorted copy of `dependencies`.
+ def sort_dependencies(dependencies, activated, conflicts)
+ dependencies.sort_by do |dependency|
+ name = name_for(dependency)
+ [
+ activated.vertex_named(name).payload ? 0 : 1,
+ conflicts[name] ? 0 : 1,
+ ]
+ end
+ end
+
+ # Returns whether this dependency, which has no possible matching
+ # specifications, can safely be ignored.
+ #
+ # @param [Object] dependency
+ # @return [Boolean] whether this dependency can safely be skipped.
+ def allow_missing?(dependency)
+ false
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb
new file mode 100644
index 0000000000..a166bc6991
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ # Conveys information about the resolution process to a user.
+ module UI
+ # The {IO} object that should be used to print output. `STDOUT`, by default.
+ #
+ # @return [IO]
+ def output
+ STDOUT
+ end
+
+ # Called roughly every {#progress_rate}, this method should convey progress
+ # to the user.
+ #
+ # @return [void]
+ def indicate_progress
+ output.print '.' unless debug?
+ end
+
+ # How often progress should be conveyed to the user via
+ # {#indicate_progress}, in seconds. A third of a second, by default.
+ #
+ # @return [Float]
+ def progress_rate
+ 0.33
+ end
+
+ # Called before resolution begins.
+ #
+ # @return [void]
+ def before_resolution
+ output.print 'Resolving dependencies...'
+ end
+
+ # Called after resolution ends (either successfully or with an error).
+ # By default, prints a newline.
+ #
+ # @return [void]
+ def after_resolution
+ output.puts
+ end
+
+ # Conveys debug information to the user.
+ #
+ # @param [Integer] depth the current depth of the resolution process.
+ # @return [void]
+ def debug(depth = 0)
+ if debug?
+ debug_info = yield
+ debug_info = debug_info.inspect unless debug_info.is_a?(String)
+ debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" }
+ output.puts debug_info
+ end
+ end
+
+ # Whether or not debug messages should be printed.
+ # By default, whether or not the `MOLINILLO_DEBUG` environment variable is
+ # set.
+ #
+ # @return [Boolean]
+ def debug?
+ return @debug_mode if defined?(@debug_mode)
+ @debug_mode = ENV['MOLINILLO_DEBUG']
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb
new file mode 100644
index 0000000000..0eb665d17a
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb
@@ -0,0 +1,837 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ class Resolver
+ # A specific resolution from a given {Resolver}
+ class Resolution
+ # A conflict that the resolution process encountered
+ # @attr [Object] requirement the requirement that immediately led to the conflict
+ # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict
+ # @attr [Object, nil] existing the existing spec that was in conflict with
+ # the {#possibility}
+ # @attr [Object] possibility_set the set of specs that was unable to be
+ # activated due to a conflict.
+ # @attr [Object] locked_requirement the relevant locking requirement.
+ # @attr [Array<Array<Object>>] requirement_trees the different requirement
+ # trees that led to every requirement for the conflicting name.
+ # @attr [{String=>Object}] activated_by_name the already-activated specs.
+ # @attr [Object] underlying_error an error that has occurred during resolution, and
+ # will be raised at the end of it if no resolution is found.
+ Conflict = Struct.new(
+ :requirement,
+ :requirements,
+ :existing,
+ :possibility_set,
+ :locked_requirement,
+ :requirement_trees,
+ :activated_by_name,
+ :underlying_error
+ )
+
+ class Conflict
+ # @return [Object] a spec that was unable to be activated due to a conflict
+ def possibility
+ possibility_set && possibility_set.latest_version
+ end
+ end
+
+ # A collection of possibility states that share the same dependencies
+ # @attr [Array] dependencies the dependencies for this set of possibilities
+ # @attr [Array] possibilities the possibilities
+ PossibilitySet = Struct.new(:dependencies, :possibilities)
+
+ class PossibilitySet
+ # String representation of the possibility set, for debugging
+ def to_s
+ "[#{possibilities.join(', ')}]"
+ end
+
+ # @return [Object] most up-to-date dependency in the possibility set
+ def latest_version
+ possibilities.last
+ end
+ end
+
+ # Details of the state to unwind to when a conflict occurs, and the cause of the unwind
+ # @attr [Integer] state_index the index of the state to unwind to
+ # @attr [Object] state_requirement the requirement of the state we're unwinding to
+ # @attr [Array] requirement_tree for the requirement we're relaxing
+ # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict
+ # @attr [Array] requirement_trees for the conflict
+ # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind
+ UnwindDetails = Struct.new(
+ :state_index,
+ :state_requirement,
+ :requirement_tree,
+ :conflicting_requirements,
+ :requirement_trees,
+ :requirements_unwound_to_instead
+ )
+
+ class UnwindDetails
+ include Comparable
+
+ # We compare UnwindDetails when choosing which state to unwind to. If
+ # two options have the same state_index we prefer the one most
+ # removed from a requirement that caused the conflict. Both options
+ # would unwind to the same state, but a `grandparent` option will
+ # filter out fewer of its possibilities after doing so - where a state
+ # is both a `parent` and a `grandparent` to requirements that have
+ # caused a conflict this is the correct behaviour.
+ # @param [UnwindDetail] other UnwindDetail to be compared
+ # @return [Integer] integer specifying ordering
+ def <=>(other)
+ if state_index > other.state_index
+ 1
+ elsif state_index == other.state_index
+ reversed_requirement_tree_index <=> other.reversed_requirement_tree_index
+ else
+ -1
+ end
+ end
+
+ # @return [Integer] index of state requirement in reversed requirement tree
+ # (the conflicting requirement itself will be at position 0)
+ def reversed_requirement_tree_index
+ @reversed_requirement_tree_index ||=
+ if state_requirement
+ requirement_tree.reverse.index(state_requirement)
+ else
+ 999_999
+ end
+ end
+
+ # @return [Boolean] where the requirement of the state we're unwinding
+ # to directly caused the conflict. Note: in this case, it is
+ # impossible for the state we're unwinding to to be a parent of
+ # any of the other conflicting requirements (or we would have
+ # circularity)
+ def unwinding_to_primary_requirement?
+ requirement_tree.last == state_requirement
+ end
+
+ # @return [Array] array of sub-dependencies to avoid when choosing a
+ # new possibility for the state we've unwound to. Only relevant for
+ # non-primary unwinds
+ def sub_dependencies_to_avoid
+ @requirements_to_avoid ||=
+ requirement_trees.map do |tree|
+ index = tree.index(state_requirement)
+ tree[index + 1] if index
+ end.compact
+ end
+
+ # @return [Array] array of all the requirements that led to the need for
+ # this unwind
+ def all_requirements
+ @all_requirements ||= requirement_trees.flatten(1)
+ end
+ end
+
+ # @return [SpecificationProvider] the provider that knows about
+ # dependencies, requirements, specifications, versions, etc.
+ attr_reader :specification_provider
+
+ # @return [UI] the UI that knows how to communicate feedback about the
+ # resolution process back to the user
+ attr_reader :resolver_ui
+
+ # @return [DependencyGraph] the base dependency graph to which
+ # dependencies should be 'locked'
+ attr_reader :base
+
+ # @return [Array] the dependencies that were explicitly required
+ attr_reader :original_requested
+
+ # Initializes a new resolution.
+ # @param [SpecificationProvider] specification_provider
+ # see {#specification_provider}
+ # @param [UI] resolver_ui see {#resolver_ui}
+ # @param [Array] requested see {#original_requested}
+ # @param [DependencyGraph] base see {#base}
+ def initialize(specification_provider, resolver_ui, requested, base)
+ @specification_provider = specification_provider
+ @resolver_ui = resolver_ui
+ @original_requested = requested
+ @base = base
+ @states = []
+ @iteration_counter = 0
+ @parents_of = Hash.new { |h, k| h[k] = [] }
+ end
+
+ # Resolves the {#original_requested} dependencies into a full dependency
+ # graph
+ # @raise [ResolverError] if successful resolution is impossible
+ # @return [DependencyGraph] the dependency graph of successfully resolved
+ # dependencies
+ def resolve
+ start_resolution
+
+ while state
+ break if !state.requirement && state.requirements.empty?
+ indicate_progress
+ if state.respond_to?(:pop_possibility_state) # DependencyState
+ debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
+ state.pop_possibility_state.tap do |s|
+ if s
+ states.push(s)
+ activated.tag(s)
+ end
+ end
+ end
+ process_topmost_state
+ end
+
+ resolve_activated_specs
+ ensure
+ end_resolution
+ end
+
+ # @return [Integer] the number of resolver iterations in between calls to
+ # {#resolver_ui}'s {UI#indicate_progress} method
+ attr_accessor :iteration_rate
+ private :iteration_rate
+
+ # @return [Time] the time at which resolution began
+ attr_accessor :started_at
+ private :started_at
+
+ # @return [Array<ResolutionState>] the stack of states for the resolution
+ attr_accessor :states
+ private :states
+
+ private
+
+ # Sets up the resolution process
+ # @return [void]
+ def start_resolution
+ @started_at = Time.now
+
+ handle_missing_or_push_dependency_state(initial_state)
+
+ debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" }
+ resolver_ui.before_resolution
+ end
+
+ def resolve_activated_specs
+ activated.vertices.each do |_, vertex|
+ next unless vertex.payload
+
+ latest_version = vertex.payload.possibilities.reverse_each.find do |possibility|
+ vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) }
+ end
+
+ activated.set_payload(vertex.name, latest_version)
+ end
+ activated.freeze
+ end
+
+ # Ends the resolution process
+ # @return [void]
+ def end_resolution
+ resolver_ui.after_resolution
+ debug do
+ "Finished resolution (#{@iteration_counter} steps) " \
+ "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})"
+ end
+ debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state
+ debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state
+ end
+
+ require 'bundler/vendor/molinillo/lib/molinillo/state'
+ require 'bundler/vendor/molinillo/lib/molinillo/modules/specification_provider'
+
+ require 'bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state'
+ require 'bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider'
+
+ include Bundler::Molinillo::Delegates::ResolutionState
+ include Bundler::Molinillo::Delegates::SpecificationProvider
+
+ # Processes the topmost available {RequirementState} on the stack
+ # @return [void]
+ def process_topmost_state
+ if possibility
+ attempt_to_activate
+ else
+ create_conflict
+ unwind_for_conflict
+ end
+ rescue CircularDependencyError => underlying_error
+ create_conflict(underlying_error)
+ unwind_for_conflict
+ end
+
+ # @return [Object] the current possibility that the resolution is trying
+ # to activate
+ def possibility
+ possibilities.last
+ end
+
+ # @return [RequirementState] the current state the resolution is
+ # operating upon
+ def state
+ states.last
+ end
+
+ # Creates the initial state for the resolution, based upon the
+ # {#requested} dependencies
+ # @return [DependencyState] the initial state for the resolution
+ def initial_state
+ graph = DependencyGraph.new.tap do |dg|
+ original_requested.each do |requested|
+ vertex = dg.add_vertex(name_for(requested), nil, true)
+ vertex.explicit_requirements << requested
+ end
+ dg.tag(:initial_state)
+ end
+
+ requirements = sort_dependencies(original_requested, graph, {})
+ initial_requirement = requirements.shift
+ DependencyState.new(
+ initial_requirement && name_for(initial_requirement),
+ requirements,
+ graph,
+ initial_requirement,
+ possibilities_for_requirement(initial_requirement, graph),
+ 0,
+ {},
+ []
+ )
+ end
+
+ # Unwinds the states stack because a conflict has been encountered
+ # @return [void]
+ def unwind_for_conflict
+ details_for_unwind = build_details_for_unwind
+ unwind_options = unused_unwind_options
+ debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" }
+ conflicts.tap do |c|
+ sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1)
+ raise_error_unless_state(c)
+ activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
+ state.conflicts = c
+ state.unused_unwind_options = unwind_options
+ filter_possibilities_after_unwind(details_for_unwind)
+ index = states.size - 1
+ @parents_of.each { |_, a| a.reject! { |i| i >= index } }
+ state.unused_unwind_options.reject! { |uw| uw.state_index >= index }
+ end
+ end
+
+ # Raises a VersionConflict error, or any underlying error, if there is no
+ # current state
+ # @return [void]
+ def raise_error_unless_state(conflicts)
+ return if state
+
+ error = conflicts.values.map(&:underlying_error).compact.first
+ raise error || VersionConflict.new(conflicts, specification_provider)
+ end
+
+ # @return [UnwindDetails] Details of the nearest index to which we could unwind
+ def build_details_for_unwind
+ # Get the possible unwinds for the current conflict
+ current_conflict = conflicts[name]
+ binding_requirements = binding_requirements_for_conflict(current_conflict)
+ unwind_details = unwind_options_for_requirements(binding_requirements)
+
+ last_detail_for_current_unwind = unwind_details.sort.last
+ current_detail = last_detail_for_current_unwind
+
+ # Look for past conflicts that could be unwound to affect the
+ # requirement tree for the current conflict
+ relevant_unused_unwinds = unused_unwind_options.select do |alternative|
+ intersecting_requirements =
+ last_detail_for_current_unwind.all_requirements &
+ alternative.requirements_unwound_to_instead
+ next if intersecting_requirements.empty?
+ # Find the highest index unwind whilst looping through
+ current_detail = alternative if alternative > current_detail
+ alternative
+ end
+
+ # Add the current unwind options to the `unused_unwind_options` array.
+ # The "used" option will be filtered out during `unwind_for_conflict`.
+ state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 }
+
+ # Update the requirements_unwound_to_instead on any relevant unused unwinds
+ relevant_unused_unwinds.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
+ unwind_details.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
+
+ current_detail
+ end
+
+ # @param [Array<Object>] array of requirements that combine to create a conflict
+ # @return [Array<UnwindDetails>] array of UnwindDetails that have a chance
+ # of resolving the passed requirements
+ def unwind_options_for_requirements(binding_requirements)
+ unwind_details = []
+
+ trees = []
+ binding_requirements.reverse_each do |r|
+ partial_tree = [r]
+ trees << partial_tree
+ unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, [])
+
+ # If this requirement has alternative possibilities, check if any would
+ # satisfy the other requirements that created this conflict
+ requirement_state = find_state_for(r)
+ if conflict_fixing_possibilities?(requirement_state, binding_requirements)
+ unwind_details << UnwindDetails.new(
+ states.index(requirement_state),
+ r,
+ partial_tree,
+ binding_requirements,
+ trees,
+ []
+ )
+ end
+
+ # Next, look at the parent of this requirement, and check if the requirement
+ # could have been avoided if an alternative PossibilitySet had been chosen
+ parent_r = parent_of(r)
+ next if parent_r.nil?
+ partial_tree.unshift(parent_r)
+ requirement_state = find_state_for(parent_r)
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) }
+ unwind_details << UnwindDetails.new(
+ states.index(requirement_state),
+ parent_r,
+ partial_tree,
+ binding_requirements,
+ trees,
+ []
+ )
+ end
+
+ # Finally, look at the grandparent and up of this requirement, looking
+ # for any possibilities that wouldn't create their parent requirement
+ grandparent_r = parent_of(parent_r)
+ until grandparent_r.nil?
+ partial_tree.unshift(grandparent_r)
+ requirement_state = find_state_for(grandparent_r)
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) }
+ unwind_details << UnwindDetails.new(
+ states.index(requirement_state),
+ grandparent_r,
+ partial_tree,
+ binding_requirements,
+ trees,
+ []
+ )
+ end
+ parent_r = grandparent_r
+ grandparent_r = parent_of(parent_r)
+ end
+ end
+
+ unwind_details
+ end
+
+ # @param [DependencyState] state
+ # @param [Array] array of requirements
+ # @return [Boolean] whether or not the given state has any possibilities
+ # that could satisfy the given requirements
+ def conflict_fixing_possibilities?(state, binding_requirements)
+ return false unless state
+
+ state.possibilities.any? do |possibility_set|
+ possibility_set.possibilities.any? do |poss|
+ possibility_satisfies_requirements?(poss, binding_requirements)
+ end
+ end
+ end
+
+ # Filter's a state's possibilities to remove any that would not fix the
+ # conflict we've just rewound from
+ # @param [UnwindDetails] details of the conflict just unwound from
+ # @return [void]
+ def filter_possibilities_after_unwind(unwind_details)
+ return unless state && !state.possibilities.empty?
+
+ if unwind_details.unwinding_to_primary_requirement?
+ filter_possibilities_for_primary_unwind(unwind_details)
+ else
+ filter_possibilities_for_parent_unwind(unwind_details)
+ end
+ end
+
+ # Filter's a state's possibilities to remove any that would not satisfy
+ # the requirements in the conflict we've just rewound from
+ # @param [UnwindDetails] details of the conflict just unwound from
+ # @return [void]
+ def filter_possibilities_for_primary_unwind(unwind_details)
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
+ unwinds_to_state << unwind_details
+ unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements)
+
+ state.possibilities.reject! do |possibility_set|
+ possibility_set.possibilities.none? do |poss|
+ unwind_requirement_sets.any? do |requirements|
+ possibility_satisfies_requirements?(poss, requirements)
+ end
+ end
+ end
+ end
+
+ # @param [Object] possibility a single possibility
+ # @param [Array] requirements an array of requirements
+ # @return [Boolean] whether the possibility satisfies all of the
+ # given requirements
+ def possibility_satisfies_requirements?(possibility, requirements)
+ name = name_for(possibility)
+
+ activated.tag(:swap)
+ activated.set_payload(name, possibility) if activated.vertex_named(name)
+ satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) }
+ activated.rewind_to(:swap)
+
+ satisfied
+ end
+
+ # Filter's a state's possibilities to remove any that would (eventually)
+ # create a requirement in the conflict we've just rewound from
+ # @param [UnwindDetails] details of the conflict just unwound from
+ # @return [void]
+ def filter_possibilities_for_parent_unwind(unwind_details)
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
+ unwinds_to_state << unwind_details
+
+ primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq
+ parent_unwinds = unwinds_to_state.uniq - primary_unwinds
+
+ allowed_possibility_sets = Compatibility.flat_map(primary_unwinds) do |unwind|
+ states[unwind.state_index].possibilities.select do |possibility_set|
+ possibility_set.possibilities.any? do |poss|
+ possibility_satisfies_requirements?(poss, unwind.conflicting_requirements)
+ end
+ end
+ end
+
+ requirements_to_avoid = Compatibility.flat_map(parent_unwinds, &:sub_dependencies_to_avoid)
+
+ state.possibilities.reject! do |possibility_set|
+ !allowed_possibility_sets.include?(possibility_set) &&
+ (requirements_to_avoid - possibility_set.dependencies).empty?
+ end
+ end
+
+ # @param [Conflict] conflict
+ # @return [Array] minimal array of requirements that would cause the passed
+ # conflict to occur.
+ def binding_requirements_for_conflict(conflict)
+ return [conflict.requirement] if conflict.possibility.nil?
+
+ possible_binding_requirements = conflict.requirements.values.flatten(1).uniq
+
+ # When there’s a `CircularDependency` error the conflicting requirement
+ # (the one causing the circular) won’t be `conflict.requirement`
+ # (which won’t be for the right state, because we won’t have created it,
+ # because it’s circular).
+ # We need to make sure we have that requirement in the conflict’s list,
+ # otherwise we won’t be able to unwind properly, so we just return all
+ # the requirements for the conflict.
+ return possible_binding_requirements if conflict.underlying_error
+
+ possibilities = search_for(conflict.requirement)
+
+ # If all the requirements together don't filter out all possibilities,
+ # then the only two requirements we need to consider are the initial one
+ # (where the dependency's version was first chosen) and the last
+ if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities)
+ return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact
+ end
+
+ # Loop through the possible binding requirements, removing each one
+ # that doesn't bind. Use a `reverse_each` as we want the earliest set of
+ # binding requirements, and don't use `reject!` as we wish to refine the
+ # array *on each iteration*.
+ binding_requirements = possible_binding_requirements.dup
+ possible_binding_requirements.reverse_each do |req|
+ next if req == conflict.requirement
+ unless binding_requirement_in_set?(req, binding_requirements, possibilities)
+ binding_requirements -= [req]
+ end
+ end
+
+ binding_requirements
+ end
+
+ # @param [Object] requirement we wish to check
+ # @param [Array] array of requirements
+ # @param [Array] array of possibilities the requirements will be used to filter
+ # @return [Boolean] whether or not the given requirement is required to filter
+ # out all elements of the array of possibilities.
+ def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities)
+ possibilities.any? do |poss|
+ possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement])
+ end
+ end
+
+ # @return [Object] the requirement that led to `requirement` being added
+ # to the list of requirements.
+ def parent_of(requirement)
+ return unless requirement
+ return unless index = @parents_of[requirement].last
+ return unless parent_state = @states[index]
+ parent_state.requirement
+ end
+
+ # @return [Object] the requirement that led to a version of a possibility
+ # with the given name being activated.
+ def requirement_for_existing_name(name)
+ return nil unless vertex = activated.vertex_named(name)
+ return nil unless vertex.payload
+ states.find { |s| s.name == name }.requirement
+ end
+
+ # @return [ResolutionState] the state whose `requirement` is the given
+ # `requirement`.
+ def find_state_for(requirement)
+ return nil unless requirement
+ states.find { |i| requirement == i.requirement }
+ end
+
+ # @return [Conflict] a {Conflict} that reflects the failure to activate
+ # the {#possibility} in conjunction with the current {#state}
+ def create_conflict(underlying_error = nil)
+ vertex = activated.vertex_named(name)
+ locked_requirement = locked_requirement_named(name)
+
+ requirements = {}
+ unless vertex.explicit_requirements.empty?
+ requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
+ end
+ requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
+ vertex.incoming_edges.each do |edge|
+ (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement)
+ end
+
+ activated_by_name = {}
+ activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload }
+ conflicts[name] = Conflict.new(
+ requirement,
+ requirements,
+ vertex.payload && vertex.payload.latest_version,
+ possibility,
+ locked_requirement,
+ requirement_trees,
+ activated_by_name,
+ underlying_error
+ )
+ end
+
+ # @return [Array<Array<Object>>] The different requirement
+ # trees that led to every requirement for the current spec.
+ def requirement_trees
+ vertex = activated.vertex_named(name)
+ vertex.requirements.map { |r| requirement_tree_for(r) }
+ end
+
+ # @return [Array<Object>] the list of requirements that led to
+ # `requirement` being required.
+ def requirement_tree_for(requirement)
+ tree = []
+ while requirement
+ tree.unshift(requirement)
+ requirement = parent_of(requirement)
+ end
+ tree
+ end
+
+ # Indicates progress roughly once every second
+ # @return [void]
+ def indicate_progress
+ @iteration_counter += 1
+ @progress_rate ||= resolver_ui.progress_rate
+ if iteration_rate.nil?
+ if Time.now - started_at >= @progress_rate
+ self.iteration_rate = @iteration_counter
+ end
+ end
+
+ if iteration_rate && (@iteration_counter % iteration_rate) == 0
+ resolver_ui.indicate_progress
+ end
+ end
+
+ # Calls the {#resolver_ui}'s {UI#debug} method
+ # @param [Integer] depth the depth of the {#states} stack
+ # @param [Proc] block a block that yields a {#to_s}
+ # @return [void]
+ def debug(depth = 0, &block)
+ resolver_ui.debug(depth, &block)
+ end
+
+ # Attempts to activate the current {#possibility}
+ # @return [void]
+ def attempt_to_activate
+ debug(depth) { 'Attempting to activate ' + possibility.to_s }
+ existing_vertex = activated.vertex_named(name)
+ if existing_vertex.payload
+ debug(depth) { "Found existing spec (#{existing_vertex.payload})" }
+ attempt_to_filter_existing_spec(existing_vertex)
+ else
+ latest = possibility.latest_version
+ # use reject!(!satisfied) for 1.8.7 compatibility
+ possibility.possibilities.reject! do |possibility|
+ !requirement_satisfied_by?(requirement, activated, possibility)
+ end
+ if possibility.latest_version.nil?
+ # ensure there's a possibility for better error messages
+ possibility.possibilities << latest if latest
+ create_conflict
+ unwind_for_conflict
+ else
+ activate_new_spec
+ end
+ end
+ end
+
+ # Attempts to update the existing vertex's `PossibilitySet` with a filtered version
+ # @return [void]
+ def attempt_to_filter_existing_spec(vertex)
+ filtered_set = filtered_possibility_set(vertex)
+ if !filtered_set.possibilities.empty?
+ activated.set_payload(name, filtered_set)
+ new_requirements = requirements.dup
+ push_state_for_requirements(new_requirements, false)
+ else
+ create_conflict
+ debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" }
+ unwind_for_conflict
+ end
+ end
+
+ # Generates a filtered version of the existing vertex's `PossibilitySet` using the
+ # current state's `requirement`
+ # @param [Object] existing vertex
+ # @return [PossibilitySet] filtered possibility set
+ def filtered_possibility_set(vertex)
+ PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities)
+ end
+
+ # @param [String] requirement_name the spec name to search for
+ # @return [Object] the locked spec named `requirement_name`, if one
+ # is found on {#base}
+ def locked_requirement_named(requirement_name)
+ vertex = base.vertex_named(requirement_name)
+ vertex && vertex.payload
+ end
+
+ # Add the current {#possibility} to the dependency graph of the current
+ # {#state}
+ # @return [void]
+ def activate_new_spec
+ conflicts.delete(name)
+ debug(depth) { "Activated #{name} at #{possibility}" }
+ activated.set_payload(name, possibility)
+ require_nested_dependencies_for(possibility)
+ end
+
+ # Requires the dependencies that the recently activated spec has
+ # @param [Object] activated_possibility the PossibilitySet that has just been
+ # activated
+ # @return [void]
+ def require_nested_dependencies_for(possibility_set)
+ nested_dependencies = dependencies_for(possibility_set.latest_version)
+ debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
+ nested_dependencies.each do |d|
+ activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d)
+ parent_index = states.size - 1
+ parents = @parents_of[d]
+ parents << parent_index if parents.empty?
+ end
+
+ push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?)
+ end
+
+ # Pushes a new {DependencyState} that encapsulates both existing and new
+ # requirements
+ # @param [Array] new_requirements
+ # @return [void]
+ def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
+ new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
+ new_requirement = nil
+ loop do
+ new_requirement = new_requirements.shift
+ break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement }
+ end
+ new_name = new_requirement ? name_for(new_requirement) : ''.freeze
+ possibilities = possibilities_for_requirement(new_requirement)
+ handle_missing_or_push_dependency_state DependencyState.new(
+ new_name, new_requirements, new_activated,
+ new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup
+ )
+ end
+
+ # Checks a proposed requirement with any existing locked requirement
+ # before generating an array of possibilities for it.
+ # @param [Object] the proposed requirement
+ # @return [Array] possibilities
+ def possibilities_for_requirement(requirement, activated = self.activated)
+ return [] unless requirement
+ if locked_requirement_named(name_for(requirement))
+ return locked_requirement_possibility_set(requirement, activated)
+ end
+
+ group_possibilities(search_for(requirement))
+ end
+
+ # @param [Object] the proposed requirement
+ # @return [Array] possibility set containing only the locked requirement, if any
+ def locked_requirement_possibility_set(requirement, activated = self.activated)
+ all_possibilities = search_for(requirement)
+ locked_requirement = locked_requirement_named(name_for(requirement))
+
+ # Longwinded way to build a possibilities array with either the locked
+ # requirement or nothing in it. Required, since the API for
+ # locked_requirement isn't guaranteed.
+ locked_possibilities = all_possibilities.select do |possibility|
+ requirement_satisfied_by?(locked_requirement, activated, possibility)
+ end
+
+ group_possibilities(locked_possibilities)
+ end
+
+ # Build an array of PossibilitySets, with each element representing a group of
+ # dependency versions that all have the same sub-dependency version constraints
+ # and are contiguous.
+ # @param [Array] an array of possibilities
+ # @return [Array] an array of possibility sets
+ def group_possibilities(possibilities)
+ possibility_sets = []
+ current_possibility_set = nil
+
+ possibilities.reverse_each do |possibility|
+ dependencies = dependencies_for(possibility)
+ if current_possibility_set && current_possibility_set.dependencies == dependencies
+ current_possibility_set.possibilities.unshift(possibility)
+ else
+ possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility]))
+ current_possibility_set = possibility_sets.first
+ end
+ end
+
+ possibility_sets
+ end
+
+ # Pushes a new {DependencyState}.
+ # If the {#specification_provider} says to
+ # {SpecificationProvider#allow_missing?} that particular requirement, and
+ # there are no possibilities for that requirement, then `state` is not
+ # pushed, and the vertex in {#activated} is removed, and we continue
+ # resolving the remaining requirements.
+ # @param [DependencyState] state
+ # @return [void]
+ def handle_missing_or_push_dependency_state(state)
+ if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement)
+ state.activated.detach_vertex_named(state.name)
+ push_state_for_requirements(state.requirements.dup, false, state.activated)
+ else
+ states.push(state).tap { activated.tag(state) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb
new file mode 100644
index 0000000000..7d36858778
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph'
+
+module Bundler::Molinillo
+ # This class encapsulates a dependency resolver.
+ # The resolver is responsible for determining which set of dependencies to
+ # activate, with feedback from the {#specification_provider}
+ #
+ #
+ class Resolver
+ require 'bundler/vendor/molinillo/lib/molinillo/resolution'
+
+ # @return [SpecificationProvider] the specification provider used
+ # in the resolution process
+ attr_reader :specification_provider
+
+ # @return [UI] the UI module used to communicate back to the user
+ # during the resolution process
+ attr_reader :resolver_ui
+
+ # Initializes a new resolver.
+ # @param [SpecificationProvider] specification_provider
+ # see {#specification_provider}
+ # @param [UI] resolver_ui
+ # see {#resolver_ui}
+ def initialize(specification_provider, resolver_ui)
+ @specification_provider = specification_provider
+ @resolver_ui = resolver_ui
+ end
+
+ # Resolves the requested dependencies into a {DependencyGraph},
+ # locking to the base dependency graph (if specified)
+ # @param [Array] requested an array of 'requested' dependencies that the
+ # {#specification_provider} can understand
+ # @param [DependencyGraph,nil] base the base dependency graph to which
+ # dependencies should be 'locked'
+ def resolve(requested, base = DependencyGraph.new)
+ Resolution.new(specification_provider,
+ resolver_ui,
+ requested,
+ base).
+ resolve
+ end
+ end
+end
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/state.rb
new file mode 100644
index 0000000000..68fa1f54e3
--- /dev/null
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/state.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Bundler::Molinillo
+ # A state that a {Resolution} can be in
+ # @attr [String] name the name of the current requirement
+ # @attr [Array<Object>] requirements currently unsatisfied requirements
+ # @attr [DependencyGraph] activated the graph of activated dependencies
+ # @attr [Object] requirement the current requirement
+ # @attr [Object] possibilities the possibilities to satisfy the current requirement
+ # @attr [Integer] depth the depth of the resolution
+ # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name
+ # @attr [Array<UnwindDetails>] unused_unwind_options unwinds for previous conflicts that weren't explored
+ ResolutionState = Struct.new(
+ :name,
+ :requirements,
+ :activated,
+ :requirement,
+ :possibilities,
+ :depth,
+ :conflicts,
+ :unused_unwind_options
+ )
+
+ class ResolutionState
+ # Returns an empty resolution state
+ # @return [ResolutionState] an empty state
+ def self.empty
+ new(nil, [], DependencyGraph.new, nil, nil, 0, {}, [])
+ end
+ end
+
+ # A state that encapsulates a set of {#requirements} with an {Array} of
+ # possibilities
+ class DependencyState < ResolutionState
+ # Removes a possibility from `self`
+ # @return [PossibilityState] a state with a single possibility,
+ # the possibility that was removed from `self`
+ def pop_possibility_state
+ PossibilityState.new(
+ name,
+ requirements.dup,
+ activated,
+ requirement,
+ [possibilities.pop],
+ depth + 1,
+ conflicts.dup,
+ unused_unwind_options.dup
+ ).tap do |state|
+ state.activated.tag(state)
+ end
+ end
+ end
+
+ # A state that encapsulates a single possibility to fulfill the given
+ # {#requirement}
+ class PossibilityState < ResolutionState
+ end
+end
diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/faster.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/faster.rb
new file mode 100644
index 0000000000..e5e09080c2
--- /dev/null
+++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/faster.rb
@@ -0,0 +1,27 @@
+require 'net/protocol'
+
+##
+# Aaron Patterson's monkeypatch (accepted into 1.9.1) to fix Net::HTTP's speed
+# problems.
+#
+# http://gist.github.com/251244
+
+class Net::BufferedIO #:nodoc:
+ alias :old_rbuf_fill :rbuf_fill
+
+ def rbuf_fill
+ if @io.respond_to? :read_nonblock then
+ begin
+ @rbuf << @io.read_nonblock(65536)
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN => e
+ retry if IO.select [@io], nil, nil, @read_timeout
+ raise Timeout::Error, e.message
+ end
+ else # SSL sockets do not have read_nonblock
+ timeout @read_timeout do
+ @rbuf << @io.sysread(65536)
+ end
+ end
+ end
+end if RUBY_VERSION < '1.9'
+
diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
new file mode 100644
index 0000000000..7cbca5bc06
--- /dev/null
+++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
@@ -0,0 +1,1233 @@
+require 'net/http'
+begin
+ require 'net/https'
+rescue LoadError
+ # net/https or openssl
+end if RUBY_VERSION < '1.9' # but only for 1.8
+require 'bundler/vendor/net-http-persistent/lib/net/http/faster'
+require 'uri'
+require 'cgi' # for escaping
+
+begin
+ require 'net/http/pipeline'
+rescue LoadError
+end
+
+autoload :OpenSSL, 'openssl'
+
+##
+# Persistent connections for Net::HTTP
+#
+# Bundler::Persistent::Net::HTTP::Persistent maintains persistent connections across all the
+# servers you wish to talk to. For each host:port you communicate with a
+# single persistent connection is created.
+#
+# Multiple Bundler::Persistent::Net::HTTP::Persistent objects will share the same set of
+# connections.
+#
+# For each thread you start a new connection will be created. A
+# Bundler::Persistent::Net::HTTP::Persistent connection will not be shared across threads.
+#
+# You can shut down the HTTP connections when done by calling #shutdown. You
+# should name your Bundler::Persistent::Net::HTTP::Persistent object if you intend to call this
+# method.
+#
+# Example:
+#
+# require 'bundler/vendor/net-http-persistent/lib/net/http/persistent'
+#
+# uri = URI 'http://example.com/awesome/web/service'
+#
+# http = Bundler::Persistent::Net::HTTP::Persistent.new 'my_app_name'
+#
+# # perform a GET
+# response = http.request uri
+#
+# # or
+#
+# get = Net::HTTP::Get.new uri.request_uri
+# response = http.request get
+#
+# # create a POST
+# post_uri = uri + 'create'
+# post = Net::HTTP::Post.new post_uri.path
+# post.set_form_data 'some' => 'cool data'
+#
+# # perform the POST, the URI is always required
+# response http.request post_uri, post
+#
+# Note that for GET, HEAD and other requests that do not have a body you want
+# to use URI#request_uri not URI#path. The request_uri contains the query
+# params which are sent in the body for other requests.
+#
+# == SSL
+#
+# SSL connections are automatically created depending upon the scheme of the
+# URI. SSL connections are automatically verified against the default
+# certificate store for your computer. You can override this by changing
+# verify_mode or by specifying an alternate cert_store.
+#
+# Here are the SSL settings, see the individual methods for documentation:
+#
+# #certificate :: This client's certificate
+# #ca_file :: The certificate-authority
+# #cert_store :: An SSL certificate store
+# #private_key :: The client's SSL private key
+# #reuse_ssl_sessions :: Reuse a previously opened SSL session for a new
+# connection
+# #ssl_version :: Which specific SSL version to use
+# #verify_callback :: For server certificate verification
+# #verify_mode :: How connections should be verified
+#
+# == Proxies
+#
+# A proxy can be set through #proxy= or at initialization time by providing a
+# second argument to ::new. The proxy may be the URI of the proxy server or
+# <code>:ENV</code> which will consult environment variables.
+#
+# See #proxy= and #proxy_from_env for details.
+#
+# == Headers
+#
+# Headers may be specified for use in every request. #headers are appended to
+# any headers on the request. #override_headers replace existing headers on
+# the request.
+#
+# The difference between the two can be seen in setting the User-Agent. Using
+# <code>http.headers['User-Agent'] = 'MyUserAgent'</code> will send "Ruby,
+# MyUserAgent" while <code>http.override_headers['User-Agent'] =
+# 'MyUserAgent'</code> will send "MyUserAgent".
+#
+# == Tuning
+#
+# === Segregation
+#
+# By providing an application name to ::new you can separate your connections
+# from the connections of other applications.
+#
+# === Idle Timeout
+#
+# If a connection hasn't been used for this number of seconds it will automatically be
+# reset upon the next use to avoid attempting to send to a closed connection.
+# The default value is 5 seconds. nil means no timeout. Set through #idle_timeout.
+#
+# Reducing this value may help avoid the "too many connection resets" error
+# when sending non-idempotent requests while increasing this value will cause
+# fewer round-trips.
+#
+# === Read Timeout
+#
+# The amount of time allowed between reading two chunks from the socket. Set
+# through #read_timeout
+#
+# === Max Requests
+#
+# The number of requests that should be made before opening a new connection.
+# Typically many keep-alive capable servers tune this to 100 or less, so the
+# 101st request will fail with ECONNRESET. If unset (default), this value has no
+# effect, if set, connections will be reset on the request after max_requests.
+#
+# === Open Timeout
+#
+# The amount of time to wait for a connection to be opened. Set through
+# #open_timeout.
+#
+# === Socket Options
+#
+# Socket options may be set on newly-created connections. See #socket_options
+# for details.
+#
+# === Non-Idempotent Requests
+#
+# By default non-idempotent requests will not be retried per RFC 2616. By
+# setting retry_change_requests to true requests will automatically be retried
+# once.
+#
+# Only do this when you know that retrying a POST or other non-idempotent
+# request is safe for your application and will not create duplicate
+# resources.
+#
+# The recommended way to handle non-idempotent requests is the following:
+#
+# require 'bundler/vendor/net-http-persistent/lib/net/http/persistent'
+#
+# uri = URI 'http://example.com/awesome/web/service'
+# post_uri = uri + 'create'
+#
+# http = Bundler::Persistent::Net::HTTP::Persistent.new 'my_app_name'
+#
+# post = Net::HTTP::Post.new post_uri.path
+# # ... fill in POST request
+#
+# begin
+# response = http.request post_uri, post
+# rescue Bundler::Persistent::Net::HTTP::Persistent::Error
+#
+# # POST failed, make a new request to verify the server did not process
+# # the request
+# exists_uri = uri + '...'
+# response = http.get exists_uri
+#
+# # Retry if it failed
+# retry if response.code == '404'
+# end
+#
+# The method of determining if the resource was created or not is unique to
+# the particular service you are using. Of course, you will want to add
+# protection from infinite looping.
+#
+# === Connection Termination
+#
+# If you are done using the Bundler::Persistent::Net::HTTP::Persistent instance you may shut down
+# all the connections in the current thread with #shutdown. This is not
+# recommended for normal use, it should only be used when it will be several
+# minutes before you make another HTTP request.
+#
+# If you are using multiple threads, call #shutdown in each thread when the
+# thread is done making requests. If you don't call shutdown, that's OK.
+# Ruby will automatically garbage collect and shutdown your HTTP connections
+# when the thread terminates.
+
+class Bundler::Persistent::Net::HTTP::Persistent
+
+ ##
+ # The beginning of Time
+
+ EPOCH = Time.at 0 # :nodoc:
+
+ ##
+ # Is OpenSSL available? This test works with autoload
+
+ HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc:
+
+ ##
+ # The version of Bundler::Persistent::Net::HTTP::Persistent you are using
+
+ VERSION = '2.9.4'
+
+ ##
+ # Exceptions rescued for automatic retry on ruby 2.0.0. This overlaps with
+ # the exception list for ruby 1.x.
+
+ RETRIED_EXCEPTIONS = [ # :nodoc:
+ (Net::ReadTimeout if Net.const_defined? :ReadTimeout),
+ IOError,
+ EOFError,
+ Errno::ECONNRESET,
+ Errno::ECONNABORTED,
+ Errno::EPIPE,
+ (OpenSSL::SSL::SSLError if HAVE_OPENSSL),
+ Timeout::Error,
+ ].compact
+
+ ##
+ # Error class for errors raised by Bundler::Persistent::Net::HTTP::Persistent. Various
+ # SystemCallErrors are re-raised with a human-readable message under this
+ # class.
+
+ class Error < StandardError; end
+
+ ##
+ # Use this method to detect the idle timeout of the host at +uri+. The
+ # value returned can be used to configure #idle_timeout. +max+ controls the
+ # maximum idle timeout to detect.
+ #
+ # After
+ #
+ # Idle timeout detection is performed by creating a connection then
+ # performing a HEAD request in a loop until the connection terminates
+ # waiting one additional second per loop.
+ #
+ # NOTE: This may not work on ruby > 1.9.
+
+ def self.detect_idle_timeout uri, max = 10
+ uri = URI uri unless URI::Generic === uri
+ uri += '/'
+
+ req = Net::HTTP::Head.new uri.request_uri
+
+ http = new 'net-http-persistent detect_idle_timeout'
+
+ connection = http.connection_for uri
+
+ sleep_time = 0
+
+ loop do
+ response = connection.request req
+
+ $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG
+
+ unless Net::HTTPOK === response then
+ raise Error, "bad response code #{response.code} detecting idle timeout"
+ end
+
+ break if sleep_time >= max
+
+ sleep_time += 1
+
+ $stderr.puts "sleeping #{sleep_time}" if $DEBUG
+ sleep sleep_time
+ end
+ rescue
+ # ignore StandardErrors, we've probably found the idle timeout.
+ ensure
+ http.shutdown
+
+ return sleep_time unless $!
+ end
+
+ ##
+ # This client's OpenSSL::X509::Certificate
+
+ attr_reader :certificate
+
+ # For Net::HTTP parity
+ alias cert certificate
+
+ ##
+ # An SSL certificate authority. Setting this will set verify_mode to
+ # VERIFY_PEER.
+
+ attr_reader :ca_file
+
+ ##
+ # An SSL certificate store. Setting this will override the default
+ # certificate store. See verify_mode for more information.
+
+ attr_reader :cert_store
+
+ ##
+ # Sends debug_output to this IO via Net::HTTP#set_debug_output.
+ #
+ # Never use this method in production code, it causes a serious security
+ # hole.
+
+ attr_accessor :debug_output
+
+ ##
+ # Current connection generation
+
+ attr_reader :generation # :nodoc:
+
+ ##
+ # Where this instance's connections live in the thread local variables
+
+ attr_reader :generation_key # :nodoc:
+
+ ##
+ # Headers that are added to every request using Net::HTTP#add_field
+
+ attr_reader :headers
+
+ ##
+ # Maps host:port to an HTTP version. This allows us to enable version
+ # specific features.
+
+ attr_reader :http_versions
+
+ ##
+ # Maximum time an unused connection can remain idle before being
+ # automatically closed.
+
+ attr_accessor :idle_timeout
+
+ ##
+ # Maximum number of requests on a connection before it is considered expired
+ # and automatically closed.
+
+ attr_accessor :max_requests
+
+ ##
+ # The value sent in the Keep-Alive header. Defaults to 30. Not needed for
+ # HTTP/1.1 servers.
+ #
+ # This may not work correctly for HTTP/1.0 servers
+ #
+ # This method may be removed in a future version as RFC 2616 does not
+ # require this header.
+
+ attr_accessor :keep_alive
+
+ ##
+ # A name for this connection. Allows you to keep your connections apart
+ # from everybody else's.
+
+ attr_reader :name
+
+ ##
+ # Seconds to wait until a connection is opened. See Net::HTTP#open_timeout
+
+ attr_accessor :open_timeout
+
+ ##
+ # Headers that are added to every request using Net::HTTP#[]=
+
+ attr_reader :override_headers
+
+ ##
+ # This client's SSL private key
+
+ attr_reader :private_key
+
+ # For Net::HTTP parity
+ alias key private_key
+
+ ##
+ # The URL through which requests will be proxied
+
+ attr_reader :proxy_uri
+
+ ##
+ # List of host suffixes which will not be proxied
+
+ attr_reader :no_proxy
+
+ ##
+ # Seconds to wait until reading one block. See Net::HTTP#read_timeout
+
+ attr_accessor :read_timeout
+
+ ##
+ # Where this instance's request counts live in the thread local variables
+
+ attr_reader :request_key # :nodoc:
+
+ ##
+ # By default SSL sessions are reused to avoid extra SSL handshakes. Set
+ # this to false if you have problems communicating with an HTTPS server
+ # like:
+ #
+ # SSL_connect [...] read finished A: unexpected message (OpenSSL::SSL::SSLError)
+
+ attr_accessor :reuse_ssl_sessions
+
+ ##
+ # An array of options for Socket#setsockopt.
+ #
+ # By default the TCP_NODELAY option is set on sockets.
+ #
+ # To set additional options append them to this array:
+ #
+ # http.socket_options << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1]
+
+ attr_reader :socket_options
+
+ ##
+ # Current SSL connection generation
+
+ attr_reader :ssl_generation # :nodoc:
+
+ ##
+ # Where this instance's SSL connections live in the thread local variables
+
+ attr_reader :ssl_generation_key # :nodoc:
+
+ ##
+ # SSL version to use.
+ #
+ # By default, the version will be negotiated automatically between client
+ # and server. Ruby 1.9 and newer only.
+
+ attr_reader :ssl_version if RUBY_VERSION > '1.9'
+
+ ##
+ # Where this instance's last-use times live in the thread local variables
+
+ attr_reader :timeout_key # :nodoc:
+
+ ##
+ # SSL verification callback. Used when ca_file is set.
+
+ attr_reader :verify_callback
+
+ ##
+ # HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER which verifies
+ # the server certificate.
+ #
+ # If no ca_file or cert_store is set the default system certificate store is
+ # used.
+ #
+ # You can use +verify_mode+ to override any default values.
+
+ attr_reader :verify_mode
+
+ ##
+ # Enable retries of non-idempotent requests that change data (e.g. POST
+ # requests) when the server has disconnected.
+ #
+ # This will in the worst case lead to multiple requests with the same data,
+ # but it may be useful for some applications. Take care when enabling
+ # this option to ensure it is safe to POST or perform other non-idempotent
+ # requests to the server.
+
+ attr_accessor :retry_change_requests
+
+ ##
+ # Creates a new Bundler::Persistent::Net::HTTP::Persistent.
+ #
+ # Set +name+ to keep your connections apart from everybody else's. Not
+ # required currently, but highly recommended. Your library name should be
+ # good enough. This parameter will be required in a future version.
+ #
+ # +proxy+ may be set to a URI::HTTP or :ENV to pick up proxy options from
+ # the environment. See proxy_from_env for details.
+ #
+ # In order to use a URI for the proxy you may need to do some extra work
+ # beyond URI parsing if the proxy requires a password:
+ #
+ # proxy = URI 'http://proxy.example'
+ # proxy.user = 'AzureDiamond'
+ # proxy.password = 'hunter2'
+
+ def initialize name = nil, proxy = nil
+ @name = name
+
+ @debug_output = nil
+ @proxy_uri = nil
+ @no_proxy = []
+ @headers = {}
+ @override_headers = {}
+ @http_versions = {}
+ @keep_alive = 30
+ @open_timeout = nil
+ @read_timeout = nil
+ @idle_timeout = 5
+ @max_requests = nil
+ @socket_options = []
+
+ @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
+ Socket.const_defined? :TCP_NODELAY
+
+ key = ['net_http_persistent', name].compact
+ @generation_key = [key, 'generations' ].join('_').intern
+ @ssl_generation_key = [key, 'ssl_generations'].join('_').intern
+ @request_key = [key, 'requests' ].join('_').intern
+ @timeout_key = [key, 'timeouts' ].join('_').intern
+
+ @certificate = nil
+ @ca_file = nil
+ @private_key = nil
+ @ssl_version = nil
+ @verify_callback = nil
+ @verify_mode = nil
+ @cert_store = nil
+
+ @generation = 0 # incremented when proxy URI changes
+ @ssl_generation = 0 # incremented when SSL session variables change
+
+ if HAVE_OPENSSL then
+ @verify_mode = OpenSSL::SSL::VERIFY_PEER
+ @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session
+ end
+
+ @retry_change_requests = false
+
+ @ruby_1 = RUBY_VERSION < '2'
+ @retried_on_ruby_2 = !@ruby_1
+
+ self.proxy = proxy if proxy
+ end
+
+ ##
+ # Sets this client's OpenSSL::X509::Certificate
+
+ def certificate= certificate
+ @certificate = certificate
+
+ reconnect_ssl
+ end
+
+ # For Net::HTTP parity
+ alias cert= certificate=
+
+ ##
+ # Sets the SSL certificate authority file.
+
+ def ca_file= file
+ @ca_file = file
+
+ reconnect_ssl
+ end
+
+ ##
+ # Overrides the default SSL certificate store used for verifying
+ # connections.
+
+ def cert_store= store
+ @cert_store = store
+
+ reconnect_ssl
+ end
+
+ ##
+ # Finishes all connections on the given +thread+ that were created before
+ # the given +generation+ in the threads +generation_key+ list.
+ #
+ # See #shutdown for a bunch of scary warning about misusing this method.
+
+ def cleanup(generation, thread = Thread.current,
+ generation_key = @generation_key) # :nodoc:
+ timeouts = thread[@timeout_key]
+
+ (0...generation).each do |old_generation|
+ next unless thread[generation_key]
+
+ conns = thread[generation_key].delete old_generation
+
+ conns.each_value do |conn|
+ finish conn, thread
+
+ timeouts.delete conn.object_id if timeouts
+ end if conns
+ end
+ end
+
+ ##
+ # Creates a new connection for +uri+
+
+ def connection_for uri
+ Thread.current[@generation_key] ||= Hash.new { |h,k| h[k] = {} }
+ Thread.current[@ssl_generation_key] ||= Hash.new { |h,k| h[k] = {} }
+ Thread.current[@request_key] ||= Hash.new 0
+ Thread.current[@timeout_key] ||= Hash.new EPOCH
+
+ use_ssl = uri.scheme.downcase == 'https'
+
+ if use_ssl then
+ raise Bundler::Persistent::Net::HTTP::Persistent::Error, 'OpenSSL is not available' unless
+ HAVE_OPENSSL
+
+ ssl_generation = @ssl_generation
+
+ ssl_cleanup ssl_generation
+
+ connections = Thread.current[@ssl_generation_key][ssl_generation]
+ else
+ generation = @generation
+
+ cleanup generation
+
+ connections = Thread.current[@generation_key][generation]
+ end
+
+ net_http_args = [uri.host, uri.port]
+ connection_id = net_http_args.join ':'
+
+ if @proxy_uri and not proxy_bypass? uri.host, uri.port then
+ connection_id << @proxy_connection_id
+ net_http_args.concat @proxy_args
+ else
+ net_http_args.concat [nil, nil, nil, nil]
+ end
+
+ connection = connections[connection_id]
+
+ unless connection = connections[connection_id] then
+ connections[connection_id] = http_class.new(*net_http_args)
+ connection = connections[connection_id]
+ ssl connection if use_ssl
+ else
+ reset connection if expired? connection
+ end
+
+ start connection unless connection.started?
+
+ connection.read_timeout = @read_timeout if @read_timeout
+ connection.keep_alive_timeout = @idle_timeout if @idle_timeout && connection.respond_to?(:keep_alive_timeout=)
+
+ connection
+ rescue Errno::ECONNREFUSED
+ address = connection.proxy_address || connection.address
+ port = connection.proxy_port || connection.port
+
+ raise Error, "connection refused: #{address}:#{port}"
+ rescue Errno::EHOSTDOWN
+ address = connection.proxy_address || connection.address
+ port = connection.proxy_port || connection.port
+
+ raise Error, "host down: #{address}:#{port}"
+ end
+
+ ##
+ # Returns an error message containing the number of requests performed on
+ # this connection
+
+ def error_message connection
+ requests = Thread.current[@request_key][connection.object_id] - 1 # fixup
+ last_use = Thread.current[@timeout_key][connection.object_id]
+
+ age = Time.now - last_use
+
+ "after #{requests} requests on #{connection.object_id}, " \
+ "last used #{age} seconds ago"
+ end
+
+ ##
+ # URI::escape wrapper
+
+ def escape str
+ CGI.escape str if str
+ end
+
+ ##
+ # URI::unescape wrapper
+
+ def unescape str
+ CGI.unescape str if str
+ end
+
+
+ ##
+ # Returns true if the connection should be reset due to an idle timeout, or
+ # maximum request count, false otherwise.
+
+ def expired? connection
+ requests = Thread.current[@request_key][connection.object_id]
+ return true if @max_requests && requests >= @max_requests
+ return false unless @idle_timeout
+ return true if @idle_timeout.zero?
+
+ last_used = Thread.current[@timeout_key][connection.object_id]
+
+ Time.now - last_used > @idle_timeout
+ end
+
+ ##
+ # Starts the Net::HTTP +connection+
+
+ def start connection
+ connection.set_debug_output @debug_output if @debug_output
+ connection.open_timeout = @open_timeout if @open_timeout
+
+ connection.start
+
+ socket = connection.instance_variable_get :@socket
+
+ if socket then # for fakeweb
+ @socket_options.each do |option|
+ socket.io.setsockopt(*option)
+ end
+ end
+ end
+
+ ##
+ # Finishes the Net::HTTP +connection+
+
+ def finish connection, thread = Thread.current
+ if requests = thread[@request_key] then
+ requests.delete connection.object_id
+ end
+
+ connection.finish
+ rescue IOError
+ end
+
+ def http_class # :nodoc:
+ if RUBY_VERSION > '2.0' then
+ Net::HTTP
+ elsif [:Artifice, :FakeWeb, :WebMock].any? { |klass|
+ Object.const_defined?(klass)
+ } or not @reuse_ssl_sessions then
+ Net::HTTP
+ else
+ Bundler::Persistent::Net::HTTP::Persistent::SSLReuse
+ end
+ end
+
+ ##
+ # Returns the HTTP protocol version for +uri+
+
+ def http_version uri
+ @http_versions["#{uri.host}:#{uri.port}"]
+ end
+
+ ##
+ # Is +req+ idempotent according to RFC 2616?
+
+ def idempotent? req
+ case req
+ when Net::HTTP::Delete, Net::HTTP::Get, Net::HTTP::Head,
+ Net::HTTP::Options, Net::HTTP::Put, Net::HTTP::Trace then
+ true
+ end
+ end
+
+ ##
+ # Is the request +req+ idempotent or is retry_change_requests allowed.
+ #
+ # If +retried_on_ruby_2+ is true, true will be returned if we are on ruby,
+ # retry_change_requests is allowed and the request is not idempotent.
+
+ def can_retry? req, retried_on_ruby_2 = false
+ return @retry_change_requests && !idempotent?(req) if retried_on_ruby_2
+
+ @retry_change_requests || idempotent?(req)
+ end
+
+ if RUBY_VERSION > '1.9' then
+ ##
+ # Workaround for missing Net::HTTPHeader#connection_close? on Ruby 1.8
+
+ def connection_close? header
+ header.connection_close?
+ end
+
+ ##
+ # Workaround for missing Net::HTTPHeader#connection_keep_alive? on Ruby 1.8
+
+ def connection_keep_alive? header
+ header.connection_keep_alive?
+ end
+ else
+ ##
+ # Workaround for missing Net::HTTPRequest#connection_close? on Ruby 1.8
+
+ def connection_close? header
+ header['connection'] =~ /close/ or header['proxy-connection'] =~ /close/
+ end
+
+ ##
+ # Workaround for missing Net::HTTPRequest#connection_keep_alive? on Ruby
+ # 1.8
+
+ def connection_keep_alive? header
+ header['connection'] =~ /keep-alive/ or
+ header['proxy-connection'] =~ /keep-alive/
+ end
+ end
+
+ ##
+ # Deprecated in favor of #expired?
+
+ def max_age # :nodoc:
+ return Time.now + 1 unless @idle_timeout
+
+ Time.now - @idle_timeout
+ end
+
+ ##
+ # Adds "http://" to the String +uri+ if it is missing.
+
+ def normalize_uri uri
+ (uri =~ /^https?:/) ? uri : "http://#{uri}"
+ end
+
+ ##
+ # Pipelines +requests+ to the HTTP server at +uri+ yielding responses if a
+ # block is given. Returns all responses received.
+ #
+ # See
+ # Net::HTTP::Pipeline[http://docs.seattlerb.org/net-http-pipeline/Net/HTTP/Pipeline.html]
+ # for further details.
+ #
+ # Only if <tt>net-http-pipeline</tt> was required before
+ # <tt>net-http-persistent</tt> #pipeline will be present.
+
+ def pipeline uri, requests, &block # :yields: responses
+ connection = connection_for uri
+
+ connection.pipeline requests, &block
+ end
+
+ ##
+ # Sets this client's SSL private key
+
+ def private_key= key
+ @private_key = key
+
+ reconnect_ssl
+ end
+
+ # For Net::HTTP parity
+ alias key= private_key=
+
+ ##
+ # Sets the proxy server. The +proxy+ may be the URI of the proxy server,
+ # the symbol +:ENV+ which will read the proxy from the environment or nil to
+ # disable use of a proxy. See #proxy_from_env for details on setting the
+ # proxy from the environment.
+ #
+ # If the proxy URI is set after requests have been made, the next request
+ # will shut-down and re-open all connections.
+ #
+ # The +no_proxy+ query parameter can be used to specify hosts which shouldn't
+ # be reached via proxy; if set it should be a comma separated list of
+ # hostname suffixes, optionally with +:port+ appended, for example
+ # <tt>example.com,some.host:8080</tt>.
+
+ def proxy= proxy
+ @proxy_uri = case proxy
+ when :ENV then proxy_from_env
+ when URI::HTTP then proxy
+ when nil then # ignore
+ else raise ArgumentError, 'proxy must be :ENV or a URI::HTTP'
+ end
+
+ @no_proxy.clear
+
+ if @proxy_uri then
+ @proxy_args = [
+ @proxy_uri.host,
+ @proxy_uri.port,
+ unescape(@proxy_uri.user),
+ unescape(@proxy_uri.password),
+ ]
+
+ @proxy_connection_id = [nil, *@proxy_args].join ':'
+
+ if @proxy_uri.query then
+ @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? }
+ end
+ end
+
+ reconnect
+ reconnect_ssl
+ end
+
+ ##
+ # Creates a URI for an HTTP proxy server from ENV variables.
+ #
+ # If +HTTP_PROXY+ is set a proxy will be returned.
+ #
+ # If +HTTP_PROXY_USER+ or +HTTP_PROXY_PASS+ are set the URI is given the
+ # indicated user and password unless HTTP_PROXY contains either of these in
+ # the URI.
+ #
+ # The +NO_PROXY+ ENV variable can be used to specify hosts which shouldn't
+ # be reached via proxy; if set it should be a comma separated list of
+ # hostname suffixes, optionally with +:port+ appended, for example
+ # <tt>example.com,some.host:8080</tt>. When set to <tt>*</tt> no proxy will
+ # be returned.
+ #
+ # For Windows users, lowercase ENV variables are preferred over uppercase ENV
+ # variables.
+
+ def proxy_from_env
+ env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
+
+ return nil if env_proxy.nil? or env_proxy.empty?
+
+ uri = URI normalize_uri env_proxy
+
+ env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
+
+ # '*' is special case for always bypass
+ return nil if env_no_proxy == '*'
+
+ if env_no_proxy then
+ uri.query = "no_proxy=#{escape(env_no_proxy)}"
+ end
+
+ unless uri.user or uri.password then
+ uri.user = escape ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
+ uri.password = escape ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']
+ end
+
+ uri
+ end
+
+ ##
+ # Returns true when proxy should by bypassed for host.
+
+ def proxy_bypass? host, port
+ host = host.downcase
+ host_port = [host, port].join ':'
+
+ @no_proxy.each do |name|
+ return true if host[-name.length, name.length] == name or
+ host_port[-name.length, name.length] == name
+ end
+
+ false
+ end
+
+ ##
+ # Forces reconnection of HTTP connections.
+
+ def reconnect
+ @generation += 1
+ end
+
+ ##
+ # Forces reconnection of SSL connections.
+
+ def reconnect_ssl
+ @ssl_generation += 1
+ end
+
+ ##
+ # Finishes then restarts the Net::HTTP +connection+
+
+ def reset connection
+ Thread.current[@request_key].delete connection.object_id
+ Thread.current[@timeout_key].delete connection.object_id
+
+ finish connection
+
+ start connection
+ rescue Errno::ECONNREFUSED
+ e = Error.new "connection refused: #{connection.address}:#{connection.port}"
+ e.set_backtrace $@
+ raise e
+ rescue Errno::EHOSTDOWN
+ e = Error.new "host down: #{connection.address}:#{connection.port}"
+ e.set_backtrace $@
+ raise e
+ end
+
+ ##
+ # Makes a request on +uri+. If +req+ is nil a Net::HTTP::Get is performed
+ # against +uri+.
+ #
+ # If a block is passed #request behaves like Net::HTTP#request (the body of
+ # the response will not have been read).
+ #
+ # +req+ must be a Net::HTTPRequest subclass (see Net::HTTP for a list).
+ #
+ # If there is an error and the request is idempotent according to RFC 2616
+ # it will be retried automatically.
+
+ def request uri, req = nil, &block
+ retried = false
+ bad_response = false
+
+ req = request_setup req || uri
+
+ connection = connection_for uri
+ connection_id = connection.object_id
+
+ begin
+ Thread.current[@request_key][connection_id] += 1
+ response = connection.request req, &block
+
+ if connection_close?(req) or
+ (response.http_version <= '1.0' and
+ not connection_keep_alive?(response)) or
+ connection_close?(response) then
+ connection.finish
+ end
+ rescue Net::HTTPBadResponse => e
+ message = error_message connection
+
+ finish connection
+
+ raise Error, "too many bad responses #{message}" if
+ bad_response or not can_retry? req
+
+ bad_response = true
+ retry
+ rescue *RETRIED_EXCEPTIONS => e # retried on ruby 2
+ request_failed e, req, connection if
+ retried or not can_retry? req, @retried_on_ruby_2
+
+ reset connection
+
+ retried = true
+ retry
+ rescue Errno::EINVAL, Errno::ETIMEDOUT => e # not retried on ruby 2
+ request_failed e, req, connection if retried or not can_retry? req
+
+ reset connection
+
+ retried = true
+ retry
+ rescue Exception => e
+ finish connection
+
+ raise
+ ensure
+ Thread.current[@timeout_key][connection_id] = Time.now
+ end
+
+ @http_versions["#{uri.host}:#{uri.port}"] ||= response.http_version
+
+ response
+ end
+
+ ##
+ # Raises an Error for +exception+ which resulted from attempting the request
+ # +req+ on the +connection+.
+ #
+ # Finishes the +connection+.
+
+ def request_failed exception, req, connection # :nodoc:
+ due_to = "(due to #{exception.message} - #{exception.class})"
+ message = "too many connection resets #{due_to} #{error_message connection}"
+
+ finish connection
+
+
+ raise Error, message, exception.backtrace
+ end
+
+ ##
+ # Creates a GET request if +req_or_uri+ is a URI and adds headers to the
+ # request.
+ #
+ # Returns the request.
+
+ def request_setup req_or_uri # :nodoc:
+ req = if URI === req_or_uri then
+ Net::HTTP::Get.new req_or_uri.request_uri
+ else
+ req_or_uri
+ end
+
+ @headers.each do |pair|
+ req.add_field(*pair)
+ end
+
+ @override_headers.each do |name, value|
+ req[name] = value
+ end
+
+ unless req['Connection'] then
+ req.add_field 'Connection', 'keep-alive'
+ req.add_field 'Keep-Alive', @keep_alive
+ end
+
+ req
+ end
+
+ ##
+ # Shuts down all connections for +thread+.
+ #
+ # Uses the current thread by default.
+ #
+ # If you've used Bundler::Persistent::Net::HTTP::Persistent across multiple threads you should
+ # call this in each thread when you're done making HTTP requests.
+ #
+ # *NOTE*: Calling shutdown for another thread can be dangerous!
+ #
+ # If the thread is still using the connection it may cause an error! It is
+ # best to call #shutdown in the thread at the appropriate time instead!
+
+ def shutdown thread = Thread.current
+ generation = reconnect
+ cleanup generation, thread, @generation_key
+
+ ssl_generation = reconnect_ssl
+ cleanup ssl_generation, thread, @ssl_generation_key
+
+ thread[@request_key] = nil
+ thread[@timeout_key] = nil
+ end
+
+ ##
+ # Shuts down all connections in all threads
+ #
+ # *NOTE*: THIS METHOD IS VERY DANGEROUS!
+ #
+ # Do not call this method if other threads are still using their
+ # connections! Call #shutdown at the appropriate time instead!
+ #
+ # Use this method only as a last resort!
+
+ def shutdown_in_all_threads
+ Thread.list.each do |thread|
+ shutdown thread
+ end
+
+ nil
+ end
+
+ ##
+ # Enables SSL on +connection+
+
+ def ssl connection
+ connection.use_ssl = true
+
+ connection.ssl_version = @ssl_version if @ssl_version
+
+ connection.verify_mode = @verify_mode
+
+ if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE and
+ not Object.const_defined?(:I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG) then
+ warn <<-WARNING
+ !!!SECURITY WARNING!!!
+
+The SSL HTTP connection to:
+
+ #{connection.address}:#{connection.port}
+
+ !!!MAY NOT BE VERIFIED!!!
+
+On your platform your OpenSSL implementation is broken.
+
+There is no difference between the values of VERIFY_NONE and VERIFY_PEER.
+
+This means that attempting to verify the security of SSL connections may not
+work. This exposes you to man-in-the-middle exploits, snooping on the
+contents of your connection and other dangers to the security of your data.
+
+To disable this warning define the following constant at top-level in your
+application:
+
+ I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG = nil
+
+ WARNING
+ end
+
+ if @ca_file then
+ connection.ca_file = @ca_file
+ connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ connection.verify_callback = @verify_callback if @verify_callback
+ end
+
+ if @certificate and @private_key then
+ connection.cert = @certificate
+ connection.key = @private_key
+ end
+
+ connection.cert_store = if @cert_store then
+ @cert_store
+ else
+ store = OpenSSL::X509::Store.new
+ store.set_default_paths
+ store
+ end
+ end
+
+ ##
+ # Finishes all connections that existed before the given SSL parameter
+ # +generation+.
+
+ def ssl_cleanup generation # :nodoc:
+ cleanup generation, Thread.current, @ssl_generation_key
+ end
+
+ ##
+ # SSL version to use
+
+ def ssl_version= ssl_version
+ @ssl_version = ssl_version
+
+ reconnect_ssl
+ end if RUBY_VERSION > '1.9'
+
+ ##
+ # Sets the HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER.
+ #
+ # Setting this to VERIFY_NONE is a VERY BAD IDEA and should NEVER be used.
+ # Securely transfer the correct certificate and update the default
+ # certificate store or set the ca file instead.
+
+ def verify_mode= verify_mode
+ @verify_mode = verify_mode
+
+ reconnect_ssl
+ end
+
+ ##
+ # SSL verification callback.
+
+ def verify_callback= callback
+ @verify_callback = callback
+
+ reconnect_ssl
+ end
+
+end
+
+require 'bundler/vendor/net-http-persistent/lib/net/http/persistent/ssl_reuse'
+
diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/ssl_reuse.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/ssl_reuse.rb
new file mode 100644
index 0000000000..1b6b789f6d
--- /dev/null
+++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/ssl_reuse.rb
@@ -0,0 +1,129 @@
+##
+# This Net::HTTP subclass adds SSL session reuse and Server Name Indication
+# (SNI) RFC 3546.
+#
+# DO NOT DEPEND UPON THIS CLASS
+#
+# This class is an implementation detail and is subject to change or removal
+# at any time.
+
+class Bundler::Persistent::Net::HTTP::Persistent::SSLReuse < Net::HTTP
+
+ @is_proxy_class = false
+ @proxy_addr = nil
+ @proxy_port = nil
+ @proxy_user = nil
+ @proxy_pass = nil
+
+ def initialize address, port = nil # :nodoc:
+ super
+
+ @ssl_session = nil
+ end
+
+ ##
+ # From ruby trunk r33086 including http://redmine.ruby-lang.org/issues/5341
+
+ def connect # :nodoc:
+ D "opening connection to #{conn_address()}..."
+ s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) }
+ D "opened"
+ if use_ssl?
+ ssl_parameters = Hash.new
+ iv_list = instance_variables
+ SSL_ATTRIBUTES.each do |name|
+ ivname = "@#{name}".intern
+ if iv_list.include?(ivname) and
+ value = instance_variable_get(ivname)
+ ssl_parameters[name] = value
+ end
+ end
+ unless @ssl_context then
+ @ssl_context = OpenSSL::SSL::SSLContext.new
+ @ssl_context.set_params(ssl_parameters)
+ end
+ s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
+ s.sync_close = true
+ end
+ @socket = Net::BufferedIO.new(s)
+ @socket.read_timeout = @read_timeout
+ @socket.continue_timeout = @continue_timeout if
+ @socket.respond_to? :continue_timeout
+ @socket.debug_output = @debug_output
+ if use_ssl?
+ begin
+ if proxy?
+ @socket.writeline sprintf('CONNECT %s:%s HTTP/%s',
+ @address, @port, HTTPVersion)
+ @socket.writeline "Host: #{@address}:#{@port}"
+ if proxy_user
+ credential = ["#{proxy_user}:#{proxy_pass}"].pack('m')
+ credential.delete!("\r\n")
+ @socket.writeline "Proxy-Authorization: Basic #{credential}"
+ end
+ @socket.writeline ''
+ Net::HTTPResponse.read_new(@socket).value
+ end
+ s.session = @ssl_session if @ssl_session
+ # Server Name Indication (SNI) RFC 3546
+ s.hostname = @address if s.respond_to? :hostname=
+ timeout(@open_timeout) { s.connect }
+ if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
+ s.post_connection_check(@address)
+ end
+ @ssl_session = s.session
+ rescue => exception
+ D "Conn close because of connect error #{exception}"
+ @socket.close if @socket and not @socket.closed?
+ raise exception
+ end
+ end
+ on_connect
+ end if RUBY_VERSION > '1.9'
+
+ ##
+ # From ruby_1_8_7 branch r29865 including a modified
+ # http://redmine.ruby-lang.org/issues/5341
+
+ def connect # :nodoc:
+ D "opening connection to #{conn_address()}..."
+ s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) }
+ D "opened"
+ if use_ssl?
+ unless @ssl_context.verify_mode
+ warn "warning: peer certificate won't be verified in this SSL session"
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
+ s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
+ s.sync_close = true
+ end
+ @socket = Net::BufferedIO.new(s)
+ @socket.read_timeout = @read_timeout
+ @socket.debug_output = @debug_output
+ if use_ssl?
+ if proxy?
+ @socket.writeline sprintf('CONNECT %s:%s HTTP/%s',
+ @address, @port, HTTPVersion)
+ @socket.writeline "Host: #{@address}:#{@port}"
+ if proxy_user
+ credential = ["#{proxy_user}:#{proxy_pass}"].pack('m')
+ credential.delete!("\r\n")
+ @socket.writeline "Proxy-Authorization: Basic #{credential}"
+ end
+ @socket.writeline ''
+ Net::HTTPResponse.read_new(@socket).value
+ end
+ s.session = @ssl_session if @ssl_session
+ s.connect
+ if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
+ s.post_connection_check(@address)
+ end
+ @ssl_session = s.session
+ end
+ on_connect
+ end if RUBY_VERSION < '1.9'
+
+ private :connect
+
+end
+
diff --git a/lib/bundler/vendor/thor/lib/thor.rb b/lib/bundler/vendor/thor/lib/thor.rb
new file mode 100644
index 0000000000..999e8b7e61
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor.rb
@@ -0,0 +1,509 @@
+require "set"
+require "bundler/vendor/thor/lib/thor/base"
+
+class Bundler::Thor
+ class << self
+ # Allows for custom "Command" package naming.
+ #
+ # === Parameters
+ # name<String>
+ # options<Hash>
+ #
+ def package_name(name, _ = {})
+ @package_name = name.nil? || name == "" ? nil : name
+ end
+
+ # Sets the default command when thor is executed without an explicit command to be called.
+ #
+ # ==== Parameters
+ # meth<Symbol>:: name of the default command
+ #
+ def default_command(meth = nil)
+ if meth
+ @default_command = meth == :none ? "help" : meth.to_s
+ else
+ @default_command ||= from_superclass(:default_command, "help")
+ end
+ end
+ alias_method :default_task, :default_command
+
+ # Registers another Bundler::Thor subclass as a command.
+ #
+ # ==== Parameters
+ # klass<Class>:: Bundler::Thor subclass to register
+ # command<String>:: Subcommand name to use
+ # usage<String>:: Short usage for the subcommand
+ # description<String>:: Description for the subcommand
+ def register(klass, subcommand_name, usage, description, options = {})
+ if klass <= Bundler::Thor::Group
+ desc usage, description, options
+ define_method(subcommand_name) { |*args| invoke(klass, args) }
+ else
+ desc usage, description, options
+ subcommand subcommand_name, klass
+ end
+ end
+
+ # Defines the usage and the description of the next command.
+ #
+ # ==== Parameters
+ # usage<String>
+ # description<String>
+ # options<String>
+ #
+ def desc(usage, description, options = {})
+ if options[:for]
+ command = find_and_refresh_command(options[:for])
+ command.usage = usage if usage
+ command.description = description if description
+ else
+ @usage = usage
+ @desc = description
+ @hide = options[:hide] || false
+ end
+ end
+
+ # Defines the long description of the next command.
+ #
+ # ==== Parameters
+ # long description<String>
+ #
+ def long_desc(long_description, options = {})
+ if options[:for]
+ command = find_and_refresh_command(options[:for])
+ command.long_description = long_description if long_description
+ else
+ @long_desc = long_description
+ end
+ end
+
+ # Maps an input to a command. If you define:
+ #
+ # map "-T" => "list"
+ #
+ # Running:
+ #
+ # thor -T
+ #
+ # Will invoke the list command.
+ #
+ # ==== Parameters
+ # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command.
+ #
+ def map(mappings = nil)
+ @map ||= from_superclass(:map, {})
+
+ if mappings
+ mappings.each do |key, value|
+ if key.respond_to?(:each)
+ key.each { |subkey| @map[subkey] = value }
+ else
+ @map[key] = value
+ end
+ end
+ end
+
+ @map
+ end
+
+ # Declares the options for the next command to be declared.
+ #
+ # ==== Parameters
+ # Hash[Symbol => Object]:: The hash key is the name of the option and the value
+ # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
+ # or :required (string). If you give a value, the type of the value is used.
+ #
+ def method_options(options = nil)
+ @method_options ||= {}
+ build_options(options, @method_options) if options
+ @method_options
+ end
+
+ alias_method :options, :method_options
+
+ # Adds an option to the set of method options. If :for is given as option,
+ # it allows you to change the options from a previous defined command.
+ #
+ # def previous_command
+ # # magic
+ # end
+ #
+ # method_option :foo => :bar, :for => :previous_command
+ #
+ # def next_command
+ # # magic
+ # end
+ #
+ # ==== Parameters
+ # name<Symbol>:: The name of the argument.
+ # options<Hash>:: Described below.
+ #
+ # ==== Options
+ # :desc - Description for the argument.
+ # :required - If the argument is required or not.
+ # :default - Default value for this argument. It cannot be required and have default values.
+ # :aliases - Aliases for this option.
+ # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
+ # :banner - String to show on usage notes.
+ # :hide - If you want to hide this option from the help.
+ #
+ def method_option(name, options = {})
+ scope = if options[:for]
+ find_and_refresh_command(options[:for]).options
+ else
+ method_options
+ end
+
+ build_option(name, options, scope)
+ end
+ alias_method :option, :method_option
+
+ # Prints help information for the given command.
+ #
+ # ==== Parameters
+ # shell<Bundler::Thor::Shell>
+ # command_name<String>
+ #
+ def command_help(shell, command_name)
+ meth = normalize_command_name(command_name)
+ command = all_commands[meth]
+ handle_no_command_error(meth) unless command
+
+ shell.say "Usage:"
+ shell.say " #{banner(command)}"
+ shell.say
+ class_options_help(shell, nil => command.options.values)
+ if command.long_description
+ shell.say "Description:"
+ shell.print_wrapped(command.long_description, :indent => 2)
+ else
+ shell.say command.description
+ end
+ end
+ alias_method :task_help, :command_help
+
+ # Prints help information for this class.
+ #
+ # ==== Parameters
+ # shell<Bundler::Thor::Shell>
+ #
+ def help(shell, subcommand = false)
+ list = printable_commands(true, subcommand)
+ Bundler::Thor::Util.thor_classes_in(self).each do |klass|
+ list += klass.printable_commands(false)
+ end
+ list.sort! { |a, b| a[0] <=> b[0] }
+
+ if defined?(@package_name) && @package_name
+ shell.say "#{@package_name} commands:"
+ else
+ shell.say "Commands:"
+ end
+
+ shell.print_table(list, :indent => 2, :truncate => true)
+ shell.say
+ class_options_help(shell)
+ end
+
+ # Returns commands ready to be printed.
+ def printable_commands(all = true, subcommand = false)
+ (all ? all_commands : commands).map do |_, command|
+ next if command.hidden?
+ item = []
+ item << banner(command, false, subcommand)
+ item << (command.description ? "# #{command.description.gsub(/\s+/m, ' ')}" : "")
+ item
+ end.compact
+ end
+ alias_method :printable_tasks, :printable_commands
+
+ def subcommands
+ @subcommands ||= from_superclass(:subcommands, [])
+ end
+ alias_method :subtasks, :subcommands
+
+ def subcommand_classes
+ @subcommand_classes ||= {}
+ end
+
+ def subcommand(subcommand, subcommand_class)
+ subcommands << subcommand.to_s
+ subcommand_class.subcommand_help subcommand
+ subcommand_classes[subcommand.to_s] = subcommand_class
+
+ define_method(subcommand) do |*args|
+ args, opts = Bundler::Thor::Arguments.split(args)
+ invoke_args = [args, opts, {:invoked_via_subcommand => true, :class_options => options}]
+ invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h")
+ invoke subcommand_class, *invoke_args
+ end
+ subcommand_class.commands.each do |_meth, command|
+ command.ancestor_name = subcommand
+ end
+ end
+ alias_method :subtask, :subcommand
+
+ # Extend check unknown options to accept a hash of conditions.
+ #
+ # === Parameters
+ # options<Hash>: A hash containing :only and/or :except keys
+ def check_unknown_options!(options = {})
+ @check_unknown_options ||= {}
+ options.each do |key, value|
+ if value
+ @check_unknown_options[key] = Array(value)
+ else
+ @check_unknown_options.delete(key)
+ end
+ end
+ @check_unknown_options
+ end
+
+ # Overwrite check_unknown_options? to take subcommands and options into account.
+ def check_unknown_options?(config) #:nodoc:
+ options = check_unknown_options
+ return false unless options
+
+ command = config[:current_command]
+ return true unless command
+
+ name = command.name
+
+ if subcommands.include?(name)
+ false
+ elsif options[:except]
+ !options[:except].include?(name.to_sym)
+ elsif options[:only]
+ options[:only].include?(name.to_sym)
+ else
+ true
+ end
+ end
+
+ # Stop parsing of options as soon as an unknown option or a regular
+ # argument is encountered. All remaining arguments are passed to the command.
+ # This is useful if you have a command that can receive arbitrary additional
+ # options, and where those additional options should not be handled by
+ # Bundler::Thor.
+ #
+ # ==== Example
+ #
+ # To better understand how this is useful, let's consider a command that calls
+ # an external command. A user may want to pass arbitrary options and
+ # arguments to that command. The command itself also accepts some options,
+ # which should be handled by Bundler::Thor.
+ #
+ # class_option "verbose", :type => :boolean
+ # stop_on_unknown_option! :exec
+ # check_unknown_options! :except => :exec
+ #
+ # desc "exec", "Run a shell command"
+ # def exec(*args)
+ # puts "diagnostic output" if options[:verbose]
+ # Kernel.exec(*args)
+ # end
+ #
+ # Here +exec+ can be called with +--verbose+ to get diagnostic output,
+ # e.g.:
+ #
+ # $ thor exec --verbose echo foo
+ # diagnostic output
+ # foo
+ #
+ # But if +--verbose+ is given after +echo+, it is passed to +echo+ instead:
+ #
+ # $ thor exec echo --verbose foo
+ # --verbose foo
+ #
+ # ==== Parameters
+ # Symbol ...:: A list of commands that should be affected.
+ def stop_on_unknown_option!(*command_names)
+ stop_on_unknown_option.merge(command_names)
+ end
+
+ def stop_on_unknown_option?(command) #:nodoc:
+ command && stop_on_unknown_option.include?(command.name.to_sym)
+ end
+
+ # Disable the check for required options for the given commands.
+ # This is useful if you have a command that does not need the required options
+ # to work, like help.
+ #
+ # ==== Parameters
+ # Symbol ...:: A list of commands that should be affected.
+ def disable_required_check!(*command_names)
+ disable_required_check.merge(command_names)
+ end
+
+ def disable_required_check?(command) #:nodoc:
+ command && disable_required_check.include?(command.name.to_sym)
+ end
+
+ protected
+
+ def stop_on_unknown_option #:nodoc:
+ @stop_on_unknown_option ||= Set.new
+ end
+
+ # help command has the required check disabled by default.
+ def disable_required_check #:nodoc:
+ @disable_required_check ||= Set.new([:help])
+ end
+
+ # The method responsible for dispatching given the args.
+ def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength
+ meth ||= retrieve_command_name(given_args)
+ command = all_commands[normalize_command_name(meth)]
+
+ if !command && config[:invoked_via_subcommand]
+ # We're a subcommand and our first argument didn't match any of our
+ # commands. So we put it back and call our default command.
+ given_args.unshift(meth)
+ command = all_commands[normalize_command_name(default_command)]
+ end
+
+ if command
+ args, opts = Bundler::Thor::Options.split(given_args)
+ if stop_on_unknown_option?(command) && !args.empty?
+ # given_args starts with a non-option, so we treat everything as
+ # ordinary arguments
+ args.concat opts
+ opts.clear
+ end
+ else
+ args = given_args
+ opts = nil
+ command = dynamic_command_class.new(meth)
+ end
+
+ opts = given_opts || opts || []
+ config[:current_command] = command
+ config[:command_options] = command.options
+
+ instance = new(args, opts, config)
+ yield instance if block_given?
+ args = instance.args
+ trailing = args[Range.new(arguments.size, -1)]
+ instance.invoke_command(command, trailing || [])
+ end
+
+ # The banner for this class. You can customize it if you are invoking the
+ # thor class by another ways which is not the Bundler::Thor::Runner. It receives
+ # the command that is going to be invoked and a boolean which indicates if
+ # the namespace should be displayed as arguments.
+ #
+ def banner(command, namespace = nil, subcommand = false)
+ "#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
+ end
+
+ def baseclass #:nodoc:
+ Bundler::Thor
+ end
+
+ def dynamic_command_class #:nodoc:
+ Bundler::Thor::DynamicCommand
+ end
+
+ def create_command(meth) #:nodoc:
+ @usage ||= nil
+ @desc ||= nil
+ @long_desc ||= nil
+ @hide ||= nil
+
+ if @usage && @desc
+ base_class = @hide ? Bundler::Thor::HiddenCommand : Bundler::Thor::Command
+ commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
+ @usage, @desc, @long_desc, @method_options, @hide = nil
+ true
+ elsif all_commands[meth] || meth == "method_missing"
+ true
+ else
+ puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " \
+ "Call desc if you want this method to be available as command or declare it inside a " \
+ "no_commands{} block. Invoked from #{caller[1].inspect}."
+ false
+ end
+ end
+ alias_method :create_task, :create_command
+
+ def initialize_added #:nodoc:
+ class_options.merge!(method_options)
+ @method_options = nil
+ end
+
+ # Retrieve the command name from given args.
+ def retrieve_command_name(args) #:nodoc:
+ meth = args.first.to_s unless args.empty?
+ args.shift if meth && (map[meth] || meth !~ /^\-/)
+ end
+ alias_method :retrieve_task_name, :retrieve_command_name
+
+ # receives a (possibly nil) command name and returns a name that is in
+ # the commands hash. In addition to normalizing aliases, this logic
+ # will determine if a shortened command is an unambiguous substring of
+ # a command or alias.
+ #
+ # +normalize_command_name+ also converts names like +animal-prison+
+ # into +animal_prison+.
+ def normalize_command_name(meth) #:nodoc:
+ return default_command.to_s.tr("-", "_") unless meth
+
+ possibilities = find_command_possibilities(meth)
+ raise AmbiguousTaskError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]" if possibilities.size > 1
+
+ if possibilities.empty?
+ meth ||= default_command
+ elsif map[meth]
+ meth = map[meth]
+ else
+ meth = possibilities.first
+ end
+
+ meth.to_s.tr("-", "_") # treat foo-bar as foo_bar
+ end
+ alias_method :normalize_task_name, :normalize_command_name
+
+ # this is the logic that takes the command name passed in by the user
+ # and determines whether it is an unambiguous substrings of a command or
+ # alias name.
+ def find_command_possibilities(meth)
+ len = meth.to_s.length
+ possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort
+ unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
+
+ if possibilities.include?(meth)
+ [meth]
+ elsif unique_possibilities.size == 1
+ unique_possibilities
+ else
+ possibilities
+ end
+ end
+ alias_method :find_task_possibilities, :find_command_possibilities
+
+ def subcommand_help(cmd)
+ desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
+ class_eval "
+ def help(command = nil, subcommand = true); super; end
+"
+ end
+ alias_method :subtask_help, :subcommand_help
+ end
+
+ include Bundler::Thor::Base
+
+ map HELP_MAPPINGS => :help
+
+ desc "help [COMMAND]", "Describe available commands or one specific command"
+ def help(command = nil, subcommand = false)
+ if command
+ if self.class.subcommands.include? command
+ self.class.subcommand_classes[command].help(shell, true)
+ else
+ self.class.command_help(shell, command)
+ end
+ else
+ self.class.help(shell, subcommand)
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/actions.rb b/lib/bundler/vendor/thor/lib/thor/actions.rb
new file mode 100644
index 0000000000..e6698572a9
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/actions.rb
@@ -0,0 +1,321 @@
+require "uri"
+require "bundler/vendor/thor/lib/thor/core_ext/io_binary_read"
+require "bundler/vendor/thor/lib/thor/actions/create_file"
+require "bundler/vendor/thor/lib/thor/actions/create_link"
+require "bundler/vendor/thor/lib/thor/actions/directory"
+require "bundler/vendor/thor/lib/thor/actions/empty_directory"
+require "bundler/vendor/thor/lib/thor/actions/file_manipulation"
+require "bundler/vendor/thor/lib/thor/actions/inject_into_file"
+
+class Bundler::Thor
+ module Actions
+ attr_accessor :behavior
+
+ def self.included(base) #:nodoc:
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ # Hold source paths for one Bundler::Thor instance. source_paths_for_search is the
+ # method responsible to gather source_paths from this current class,
+ # inherited paths and the source root.
+ #
+ def source_paths
+ @_source_paths ||= []
+ end
+
+ # Stores and return the source root for this class
+ def source_root(path = nil)
+ @_source_root = path if path
+ @_source_root ||= nil
+ end
+
+ # Returns the source paths in the following order:
+ #
+ # 1) This class source paths
+ # 2) Source root
+ # 3) Parents source paths
+ #
+ def source_paths_for_search
+ paths = []
+ paths += source_paths
+ paths << source_root if source_root
+ paths += from_superclass(:source_paths, [])
+ paths
+ end
+
+ # Add runtime options that help actions execution.
+ #
+ def add_runtime_options!
+ class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
+ :desc => "Overwrite files that already exist"
+
+ class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
+ :desc => "Run but do not make any changes"
+
+ class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
+ :desc => "Suppress status output"
+
+ class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
+ :desc => "Skip files that already exist"
+ end
+ end
+
+ # Extends initializer to add more configuration options.
+ #
+ # ==== Configuration
+ # behavior<Symbol>:: The actions default behavior. Can be :invoke or :revoke.
+ # It also accepts :force, :skip and :pretend to set the behavior
+ # and the respective option.
+ #
+ # destination_root<String>:: The root directory needed for some actions.
+ #
+ def initialize(args = [], options = {}, config = {})
+ self.behavior = case config[:behavior].to_s
+ when "force", "skip"
+ _cleanup_options_and_set(options, config[:behavior])
+ :invoke
+ when "revoke"
+ :revoke
+ else
+ :invoke
+ end
+
+ super
+ self.destination_root = config[:destination_root]
+ end
+
+ # Wraps an action object and call it accordingly to the thor class behavior.
+ #
+ def action(instance) #:nodoc:
+ if behavior == :revoke
+ instance.revoke!
+ else
+ instance.invoke!
+ end
+ end
+
+ # Returns the root for this thor class (also aliased as destination root).
+ #
+ def destination_root
+ @destination_stack.last
+ end
+
+ # Sets the root for this thor class. Relatives path are added to the
+ # directory where the script was invoked and expanded.
+ #
+ def destination_root=(root)
+ @destination_stack ||= []
+ @destination_stack[0] = File.expand_path(root || "")
+ end
+
+ # Returns the given path relative to the absolute root (ie, root where
+ # the script started).
+ #
+ def relative_to_original_destination_root(path, remove_dot = true)
+ path = path.dup
+ if path.gsub!(@destination_stack[0], ".")
+ remove_dot ? (path[2..-1] || "") : path
+ else
+ path
+ end
+ end
+
+ # Holds source paths in instance so they can be manipulated.
+ #
+ def source_paths
+ @source_paths ||= self.class.source_paths_for_search
+ end
+
+ # Receives a file or directory and search for it in the source paths.
+ #
+ def find_in_source_paths(file)
+ possible_files = [file, file + TEMPLATE_EXTNAME]
+ relative_root = relative_to_original_destination_root(destination_root, false)
+
+ source_paths.each do |source|
+ possible_files.each do |f|
+ source_file = File.expand_path(f, File.join(source, relative_root))
+ return source_file if File.exist?(source_file)
+ end
+ end
+
+ message = "Could not find #{file.inspect} in any of your source paths. ".dup
+
+ unless self.class.source_root
+ message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. "
+ end
+
+ message << if source_paths.empty?
+ "Currently you have no source paths."
+ else
+ "Your current source paths are: \n#{source_paths.join("\n")}"
+ end
+
+ raise Error, message
+ end
+
+ # Do something in the root or on a provided subfolder. If a relative path
+ # is given it's referenced from the current root. The full path is yielded
+ # to the block you provide. The path is set back to the previous path when
+ # the method exits.
+ #
+ # ==== Parameters
+ # dir<String>:: the directory to move to.
+ # config<Hash>:: give :verbose => true to log and use padding.
+ #
+ def inside(dir = "", config = {}, &block)
+ verbose = config.fetch(:verbose, false)
+ pretend = options[:pretend]
+
+ say_status :inside, dir, verbose
+ shell.padding += 1 if verbose
+ @destination_stack.push File.expand_path(dir, destination_root)
+
+ # If the directory doesnt exist and we're not pretending
+ if !File.exist?(destination_root) && !pretend
+ require "fileutils"
+ FileUtils.mkdir_p(destination_root)
+ end
+
+ if pretend
+ # In pretend mode, just yield down to the block
+ block.arity == 1 ? yield(destination_root) : yield
+ else
+ require "fileutils"
+ FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
+ end
+
+ @destination_stack.pop
+ shell.padding -= 1 if verbose
+ end
+
+ # Goes to the root and execute the given block.
+ #
+ def in_root
+ inside(@destination_stack.first) { yield }
+ end
+
+ # Loads an external file and execute it in the instance binding.
+ #
+ # ==== Parameters
+ # path<String>:: The path to the file to execute. Can be a web address or
+ # a relative path from the source root.
+ #
+ # ==== Examples
+ #
+ # apply "http://gist.github.com/103208"
+ #
+ # apply "recipes/jquery.rb"
+ #
+ def apply(path, config = {})
+ verbose = config.fetch(:verbose, true)
+ is_uri = path =~ %r{^https?\://}
+ path = find_in_source_paths(path) unless is_uri
+
+ say_status :apply, path, verbose
+ shell.padding += 1 if verbose
+
+ contents = if is_uri
+ open(path, "Accept" => "application/x-thor-template", &:read)
+ else
+ open(path, &:read)
+ end
+
+ instance_eval(contents, path)
+ shell.padding -= 1 if verbose
+ end
+
+ # Executes a command returning the contents of the command.
+ #
+ # ==== Parameters
+ # command<String>:: the command to be executed.
+ # config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output. Specify :with
+ # to append an executable to command execution.
+ #
+ # ==== Example
+ #
+ # inside('vendor') do
+ # run('ln -s ~/edge rails')
+ # end
+ #
+ def run(command, config = {})
+ return unless behavior == :invoke
+
+ destination = relative_to_original_destination_root(destination_root, false)
+ desc = "#{command} from #{destination.inspect}"
+
+ if config[:with]
+ desc = "#{File.basename(config[:with].to_s)} #{desc}"
+ command = "#{config[:with]} #{command}"
+ end
+
+ say_status :run, desc, config.fetch(:verbose, true)
+
+ unless options[:pretend]
+ config[:capture] ? `#{command}` : system(command.to_s)
+ end
+ end
+
+ # Executes a ruby script (taking into account WIN32 platform quirks).
+ #
+ # ==== Parameters
+ # command<String>:: the command to be executed.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ def run_ruby_script(command, config = {})
+ return unless behavior == :invoke
+ run command, config.merge(:with => Bundler::Thor::Util.ruby_command)
+ end
+
+ # Run a thor command. A hash of options can be given and it's converted to
+ # switches.
+ #
+ # ==== Parameters
+ # command<String>:: the command to be invoked
+ # args<Array>:: arguments to the command
+ # config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output.
+ # Other options are given as parameter to Bundler::Thor.
+ #
+ #
+ # ==== Examples
+ #
+ # thor :install, "http://gist.github.com/103208"
+ # #=> thor install http://gist.github.com/103208
+ #
+ # thor :list, :all => true, :substring => 'rails'
+ # #=> thor list --all --substring=rails
+ #
+ def thor(command, *args)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ verbose = config.key?(:verbose) ? config.delete(:verbose) : true
+ pretend = config.key?(:pretend) ? config.delete(:pretend) : false
+ capture = config.key?(:capture) ? config.delete(:capture) : false
+
+ args.unshift(command)
+ args.push Bundler::Thor::Options.to_switches(config)
+ command = args.join(" ").strip
+
+ run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture
+ end
+
+ protected
+
+ # Allow current root to be shared between invocations.
+ #
+ def _shared_configuration #:nodoc:
+ super.merge!(:destination_root => destination_root)
+ end
+
+ def _cleanup_options_and_set(options, key) #:nodoc:
+ case options
+ when Array
+ %w(--force -f --skip -s).each { |i| options.delete(i) }
+ options << "--#{key}"
+ when Hash
+ [:force, :skip, "force", "skip"].each { |i| options.delete(i) }
+ options.merge!(key => true)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb b/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb
new file mode 100644
index 0000000000..97d22d9bbd
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb
@@ -0,0 +1,104 @@
+require "bundler/vendor/thor/lib/thor/actions/empty_directory"
+
+class Bundler::Thor
+ module Actions
+ # Create a new file relative to the destination root with the given data,
+ # which is the return value of a block or a data string.
+ #
+ # ==== Parameters
+ # destination<String>:: the relative path to the destination root.
+ # data<String|NilClass>:: the data to append to the file.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # create_file "lib/fun_party.rb" do
+ # hostname = ask("What is the virtual hostname I should use?")
+ # "vhost.name = #{hostname}"
+ # end
+ #
+ # create_file "config/apache.conf", "your apache config"
+ #
+ def create_file(destination, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ data = args.first
+ action CreateFile.new(self, destination, block || data.to_s, config)
+ end
+ alias_method :add_file, :create_file
+
+ # CreateFile is a subset of Template, which instead of rendering a file with
+ # ERB, it gets the content from the user.
+ #
+ class CreateFile < EmptyDirectory #:nodoc:
+ attr_reader :data
+
+ def initialize(base, destination, data, config = {})
+ @data = data
+ super(base, destination, config)
+ end
+
+ # Checks if the content of the file at the destination is identical to the rendered result.
+ #
+ # ==== Returns
+ # Boolean:: true if it is identical, false otherwise.
+ #
+ def identical?
+ exists? && File.binread(destination) == render
+ end
+
+ # Holds the content to be added to the file.
+ #
+ def render
+ @render ||= if data.is_a?(Proc)
+ data.call
+ else
+ data
+ end
+ end
+
+ def invoke!
+ invoke_with_conflict_check do
+ require "fileutils"
+ FileUtils.mkdir_p(File.dirname(destination))
+ File.open(destination, "wb") { |f| f.write render }
+ end
+ given_destination
+ end
+
+ protected
+
+ # Now on conflict we check if the file is identical or not.
+ #
+ def on_conflict_behavior(&block)
+ if identical?
+ say_status :identical, :blue
+ else
+ options = base.options.merge(config)
+ force_or_skip_or_conflict(options[:force], options[:skip], &block)
+ end
+ end
+
+ # If force is true, run the action, otherwise check if it's not being
+ # skipped. If both are false, show the file_collision menu, if the menu
+ # returns true, force it, otherwise skip.
+ #
+ def force_or_skip_or_conflict(force, skip, &block)
+ if force
+ say_status :force, :yellow
+ yield unless pretend?
+ elsif skip
+ say_status :skip, :yellow
+ else
+ say_status :conflict, :red
+ force_or_skip_or_conflict(force_on_collision?, true, &block)
+ end
+ end
+
+ # Shows the file collision menu to the user and gets the result.
+ #
+ def force_on_collision?
+ base.shell.file_collision(destination) { render }
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb b/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb
new file mode 100644
index 0000000000..3a664401b4
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb
@@ -0,0 +1,60 @@
+require "bundler/vendor/thor/lib/thor/actions/create_file"
+
+class Bundler::Thor
+ module Actions
+ # Create a new file relative to the destination root from the given source.
+ #
+ # ==== Parameters
+ # destination<String>:: the relative path to the destination root.
+ # source<String|NilClass>:: the relative path to the source root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ # :: give :symbolic => false for hard link.
+ #
+ # ==== Examples
+ #
+ # create_link "config/apache.conf", "/etc/apache.conf"
+ #
+ def create_link(destination, *args)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ source = args.first
+ action CreateLink.new(self, destination, source, config)
+ end
+ alias_method :add_link, :create_link
+
+ # CreateLink is a subset of CreateFile, which instead of taking a block of
+ # data, just takes a source string from the user.
+ #
+ class CreateLink < CreateFile #:nodoc:
+ attr_reader :data
+
+ # Checks if the content of the file at the destination is identical to the rendered result.
+ #
+ # ==== Returns
+ # Boolean:: true if it is identical, false otherwise.
+ #
+ def identical?
+ exists? && File.identical?(render, destination)
+ end
+
+ def invoke!
+ invoke_with_conflict_check do
+ require "fileutils"
+ FileUtils.mkdir_p(File.dirname(destination))
+ # Create a symlink by default
+ config[:symbolic] = true if config[:symbolic].nil?
+ File.unlink(destination) if exists?
+ if config[:symbolic]
+ File.symlink(render, destination)
+ else
+ File.link(render, destination)
+ end
+ end
+ given_destination
+ end
+
+ def exists?
+ super || File.symlink?(destination)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/actions/directory.rb b/lib/bundler/vendor/thor/lib/thor/actions/directory.rb
new file mode 100644
index 0000000000..f555f7b7e0
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/actions/directory.rb
@@ -0,0 +1,118 @@
+require "bundler/vendor/thor/lib/thor/actions/empty_directory"
+
+class Bundler::Thor
+ module Actions
+ # Copies recursively the files from source directory to root directory.
+ # If any of the files finishes with .tt, it's considered to be a template
+ # and is placed in the destination without the extension .tt. If any
+ # empty directory is found, it's copied and all .empty_directory files are
+ # ignored. If any file name is wrapped within % signs, the text within
+ # the % signs will be executed as a method and replaced with the returned
+ # value. Let's suppose a doc directory with the following files:
+ #
+ # doc/
+ # components/.empty_directory
+ # README
+ # rdoc.rb.tt
+ # %app_name%.rb
+ #
+ # When invoked as:
+ #
+ # directory "doc"
+ #
+ # It will create a doc directory in the destination with the following
+ # files (assuming that the `app_name` method returns the value "blog"):
+ #
+ # doc/
+ # components/
+ # README
+ # rdoc.rb
+ # blog.rb
+ #
+ # <b>Encoded path note:</b> Since Bundler::Thor internals use Object#respond_to? to check if it can
+ # expand %something%, this `something` should be a public method in the class calling
+ # #directory. If a method is private, Bundler::Thor stack raises PrivateMethodEncodedError.
+ #
+ # ==== Parameters
+ # source<String>:: the relative path to the source root.
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ # If :recursive => false, does not look for paths recursively.
+ # If :mode => :preserve, preserve the file mode from the source.
+ # If :exclude_pattern => /regexp/, prevents copying files that match that regexp.
+ #
+ # ==== Examples
+ #
+ # directory "doc"
+ # directory "doc", "docs", :recursive => false
+ #
+ def directory(source, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ destination = args.first || source
+ action Directory.new(self, source, destination || source, config, &block)
+ end
+
+ class Directory < EmptyDirectory #:nodoc:
+ attr_reader :source
+
+ def initialize(base, source, destination = nil, config = {}, &block)
+ @source = File.expand_path(base.find_in_source_paths(source.to_s))
+ @block = block
+ super(base, destination, {:recursive => true}.merge(config))
+ end
+
+ def invoke!
+ base.empty_directory given_destination, config
+ execute!
+ end
+
+ def revoke!
+ execute!
+ end
+
+ protected
+
+ def execute!
+ lookup = Util.escape_globs(source)
+ lookup = config[:recursive] ? File.join(lookup, "**") : lookup
+ lookup = file_level_lookup(lookup)
+
+ files(lookup).sort.each do |file_source|
+ next if File.directory?(file_source)
+ next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern])
+ file_destination = File.join(given_destination, file_source.gsub(source, "."))
+ file_destination.gsub!("/./", "/")
+
+ case file_source
+ when /\.empty_directory$/
+ dirname = File.dirname(file_destination).gsub(%r{/\.$}, "")
+ next if dirname == given_destination
+ base.empty_directory(dirname, config)
+ when /#{TEMPLATE_EXTNAME}$/
+ base.template(file_source, file_destination[0..-4], config, &@block)
+ else
+ base.copy_file(file_source, file_destination, config, &@block)
+ end
+ end
+ end
+
+ if RUBY_VERSION < "2.0"
+ def file_level_lookup(previous_lookup)
+ File.join(previous_lookup, "{*,.[a-z]*}")
+ end
+
+ def files(lookup)
+ Dir[lookup]
+ end
+ else
+ def file_level_lookup(previous_lookup)
+ File.join(previous_lookup, "*")
+ end
+
+ def files(lookup)
+ Dir.glob(lookup, File::FNM_DOTMATCH)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb b/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb
new file mode 100644
index 0000000000..284d92c19a
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb
@@ -0,0 +1,143 @@
+class Bundler::Thor
+ module Actions
+ # Creates an empty directory.
+ #
+ # ==== Parameters
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # empty_directory "doc"
+ #
+ def empty_directory(destination, config = {})
+ action EmptyDirectory.new(self, destination, config)
+ end
+
+ # Class which holds create directory logic. This is the base class for
+ # other actions like create_file and directory.
+ #
+ # This implementation is based in Templater actions, created by Jonas Nicklas
+ # and Michael S. Klishin under MIT LICENSE.
+ #
+ class EmptyDirectory #:nodoc:
+ attr_reader :base, :destination, :given_destination, :relative_destination, :config
+
+ # Initializes given the source and destination.
+ #
+ # ==== Parameters
+ # base<Bundler::Thor::Base>:: A Bundler::Thor::Base instance
+ # source<String>:: Relative path to the source of this file
+ # destination<String>:: Relative path to the destination of this file
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ def initialize(base, destination, config = {})
+ @base = base
+ @config = {:verbose => true}.merge(config)
+ self.destination = destination
+ end
+
+ # Checks if the destination file already exists.
+ #
+ # ==== Returns
+ # Boolean:: true if the file exists, false otherwise.
+ #
+ def exists?
+ ::File.exist?(destination)
+ end
+
+ def invoke!
+ invoke_with_conflict_check do
+ require "fileutils"
+ ::FileUtils.mkdir_p(destination)
+ end
+ end
+
+ def revoke!
+ say_status :remove, :red
+ require "fileutils"
+ ::FileUtils.rm_rf(destination) if !pretend? && exists?
+ given_destination
+ end
+
+ protected
+
+ # Shortcut for pretend.
+ #
+ def pretend?
+ base.options[:pretend]
+ end
+
+ # Sets the absolute destination value from a relative destination value.
+ # It also stores the given and relative destination. Let's suppose our
+ # script is being executed on "dest", it sets the destination root to
+ # "dest". The destination, given_destination and relative_destination
+ # are related in the following way:
+ #
+ # inside "bar" do
+ # empty_directory "baz"
+ # end
+ #
+ # destination #=> dest/bar/baz
+ # relative_destination #=> bar/baz
+ # given_destination #=> baz
+ #
+ def destination=(destination)
+ return unless destination
+ @given_destination = convert_encoded_instructions(destination.to_s)
+ @destination = ::File.expand_path(@given_destination, base.destination_root)
+ @relative_destination = base.relative_to_original_destination_root(@destination)
+ end
+
+ # Filenames in the encoded form are converted. If you have a file:
+ #
+ # %file_name%.rb
+ #
+ # It calls #file_name from the base and replaces %-string with the
+ # return value (should be String) of #file_name:
+ #
+ # user.rb
+ #
+ # The method referenced can be either public or private.
+ #
+ def convert_encoded_instructions(filename)
+ filename.gsub(/%(.*?)%/) do |initial_string|
+ method = $1.strip
+ base.respond_to?(method, true) ? base.send(method) : initial_string
+ end
+ end
+
+ # Receives a hash of options and just execute the block if some
+ # conditions are met.
+ #
+ def invoke_with_conflict_check(&block)
+ if exists?
+ on_conflict_behavior(&block)
+ else
+ yield unless pretend?
+ say_status :create, :green
+ end
+
+ destination
+ rescue Errno::EISDIR, Errno::EEXIST
+ on_file_clash_behavior
+ end
+
+ def on_file_clash_behavior
+ say_status :file_clash, :red
+ end
+
+ # What to do when the destination file already exists.
+ #
+ def on_conflict_behavior
+ say_status :exist, :blue
+ end
+
+ # Shortcut to say_status shell method.
+ #
+ def say_status(status, color)
+ base.shell.say_status status, relative_destination, color if config[:verbose]
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb
new file mode 100644
index 0000000000..4c83bebc86
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb
@@ -0,0 +1,364 @@
+require "erb"
+
+class Bundler::Thor
+ module Actions
+ # Copies the file from the relative source to the relative destination. If
+ # the destination is not given it's assumed to be equal to the source.
+ #
+ # ==== Parameters
+ # source<String>:: the relative path to the source root.
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status, and
+ # :mode => :preserve, to preserve the file mode from the source.
+
+ #
+ # ==== Examples
+ #
+ # copy_file "README", "doc/README"
+ #
+ # copy_file "doc/README"
+ #
+ def copy_file(source, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ destination = args.first || source
+ source = File.expand_path(find_in_source_paths(source.to_s))
+
+ create_file destination, nil, config do
+ content = File.binread(source)
+ content = yield(content) if block
+ content
+ end
+ if config[:mode] == :preserve
+ mode = File.stat(source).mode
+ chmod(destination, mode, config)
+ end
+ end
+
+ # Links the file from the relative source to the relative destination. If
+ # the destination is not given it's assumed to be equal to the source.
+ #
+ # ==== Parameters
+ # source<String>:: the relative path to the source root.
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # link_file "README", "doc/README"
+ #
+ # link_file "doc/README"
+ #
+ def link_file(source, *args)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ destination = args.first || source
+ source = File.expand_path(find_in_source_paths(source.to_s))
+
+ create_link destination, source, config
+ end
+
+ # Gets the content at the given address and places it at the given relative
+ # destination. If a block is given instead of destination, the content of
+ # the url is yielded and used as location.
+ #
+ # ==== Parameters
+ # source<String>:: the address of the given content.
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # get "http://gist.github.com/103208", "doc/README"
+ #
+ # get "http://gist.github.com/103208" do |content|
+ # content.split("\n").first
+ # end
+ #
+ def get(source, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ destination = args.first
+
+ if source =~ %r{^https?\://}
+ require "open-uri"
+ else
+ source = File.expand_path(find_in_source_paths(source.to_s))
+ end
+
+ render = open(source) { |input| input.binmode.read }
+
+ destination ||= if block_given?
+ block.arity == 1 ? yield(render) : yield
+ else
+ File.basename(source)
+ end
+
+ create_file destination, render, config
+ end
+
+ # Gets an ERB template at the relative source, executes it and makes a copy
+ # at the relative destination. If the destination is not given it's assumed
+ # to be equal to the source removing .tt from the filename.
+ #
+ # ==== Parameters
+ # source<String>:: the relative path to the source root.
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # template "README", "doc/README"
+ #
+ # template "doc/README"
+ #
+ def template(source, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ destination = args.first || source.sub(/#{TEMPLATE_EXTNAME}$/, "")
+
+ source = File.expand_path(find_in_source_paths(source.to_s))
+ context = config.delete(:context) || instance_eval("binding")
+
+ create_file destination, nil, config do
+ content = CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer").tap do |erb|
+ erb.filename = source
+ end.result(context)
+ content = yield(content) if block
+ content
+ end
+ end
+
+ # Changes the mode of the given file or directory.
+ #
+ # ==== Parameters
+ # mode<Integer>:: the file mode
+ # path<String>:: the name of the file to change mode
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # chmod "script/server", 0755
+ #
+ def chmod(path, mode, config = {})
+ return unless behavior == :invoke
+ path = File.expand_path(path, destination_root)
+ say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
+ unless options[:pretend]
+ require "fileutils"
+ FileUtils.chmod_R(mode, path)
+ end
+ end
+
+ # Prepend text to a file. Since it depends on insert_into_file, it's reversible.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # data<String>:: the data to prepend to the file, can be also given as a block.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"'
+ #
+ # prepend_to_file 'config/environments/test.rb' do
+ # 'config.gem "rspec"'
+ # end
+ #
+ def prepend_to_file(path, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config[:after] = /\A/
+ insert_into_file(path, *(args << config), &block)
+ end
+ alias_method :prepend_file, :prepend_to_file
+
+ # Append text to a file. Since it depends on insert_into_file, it's reversible.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # data<String>:: the data to append to the file, can be also given as a block.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # append_to_file 'config/environments/test.rb', 'config.gem "rspec"'
+ #
+ # append_to_file 'config/environments/test.rb' do
+ # 'config.gem "rspec"'
+ # end
+ #
+ def append_to_file(path, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config[:before] = /\z/
+ insert_into_file(path, *(args << config), &block)
+ end
+ alias_method :append_file, :append_to_file
+
+ # Injects text right after the class definition. Since it depends on
+ # insert_into_file, it's reversible.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # klass<String|Class>:: the class to be manipulated
+ # data<String>:: the data to append to the class, can be also given as a block.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n"
+ #
+ # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
+ # " filter_parameter :password\n"
+ # end
+ #
+ def inject_into_class(path, klass, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config[:after] = /class #{klass}\n|class #{klass} .*\n/
+ insert_into_file(path, *(args << config), &block)
+ end
+
+ # Injects text right after the module definition. Since it depends on
+ # insert_into_file, it's reversible.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # module_name<String|Class>:: the module to be manipulated
+ # data<String>:: the data to append to the class, can be also given as a block.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n"
+ #
+ # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do
+ # " def help; 'help'; end\n"
+ # end
+ #
+ def inject_into_module(path, module_name, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config[:after] = /module #{module_name}\n|module #{module_name} .*\n/
+ insert_into_file(path, *(args << config), &block)
+ end
+
+ # Run a regular expression replacement on a file.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # flag<Regexp|String>:: the regexp or string to be replaced
+ # replacement<String>:: the replacement, can be also given as a block
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
+ #
+ # gsub_file 'README', /rake/, :green do |match|
+ # match << " no more. Use thor!"
+ # end
+ #
+ def gsub_file(path, flag, *args, &block)
+ return unless behavior == :invoke
+ config = args.last.is_a?(Hash) ? args.pop : {}
+
+ path = File.expand_path(path, destination_root)
+ say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
+
+ unless options[:pretend]
+ content = File.binread(path)
+ content.gsub!(flag, *args, &block)
+ File.open(path, "wb") { |file| file.write(content) }
+ end
+ end
+
+ # Uncomment all lines matching a given regex. It will leave the space
+ # which existed before the comment hash in tact but will remove any spacing
+ # between the comment hash and the beginning of the line.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # flag<Regexp|String>:: the regexp or string used to decide which lines to uncomment
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # uncomment_lines 'config/initializers/session_store.rb', /active_record/
+ #
+ def uncomment_lines(path, flag, *args)
+ flag = flag.respond_to?(:source) ? flag.source : flag
+
+ gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args)
+ end
+
+ # Comment all lines matching a given regex. It will leave the space
+ # which existed before the beginning of the line in tact and will insert
+ # a single space after the comment hash.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # flag<Regexp|String>:: the regexp or string used to decide which lines to comment
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # comment_lines 'config/initializers/session_store.rb', /cookie_store/
+ #
+ def comment_lines(path, flag, *args)
+ flag = flag.respond_to?(:source) ? flag.source : flag
+
+ gsub_file(path, /^(\s*)([^#|\n]*#{flag})/, '\1# \2', *args)
+ end
+
+ # Removes a file at the given location.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # remove_file 'README'
+ # remove_file 'app/controllers/application_controller.rb'
+ #
+ def remove_file(path, config = {})
+ return unless behavior == :invoke
+ path = File.expand_path(path, destination_root)
+
+ say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
+ if !options[:pretend] && File.exist?(path)
+ require "fileutils"
+ ::FileUtils.rm_rf(path)
+ end
+ end
+ alias_method :remove_dir, :remove_file
+
+ attr_accessor :output_buffer
+ private :output_buffer, :output_buffer=
+
+ private
+
+ def concat(string)
+ @output_buffer.concat(string)
+ end
+
+ def capture(*args)
+ with_output_buffer { yield(*args) }
+ end
+
+ def with_output_buffer(buf = "".dup) #:nodoc:
+ raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen?
+ old_buffer = output_buffer
+ self.output_buffer = buf
+ yield
+ output_buffer
+ ensure
+ self.output_buffer = old_buffer
+ end
+
+ # Bundler::Thor::Actions#capture depends on what kind of buffer is used in ERB.
+ # Thus CapturableERB fixes ERB to use String buffer.
+ class CapturableERB < ERB
+ def set_eoutvar(compiler, eoutvar = "_erbout")
+ compiler.put_cmd = "#{eoutvar}.concat"
+ compiler.insert_cmd = "#{eoutvar}.concat"
+ compiler.pre_cmd = ["#{eoutvar} = ''.dup"]
+ compiler.post_cmd = [eoutvar]
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb
new file mode 100644
index 0000000000..349b26ff65
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb
@@ -0,0 +1,109 @@
+require "bundler/vendor/thor/lib/thor/actions/empty_directory"
+
+class Bundler::Thor
+ module Actions
+ # Injects the given content into a file. Different from gsub_file, this
+ # method is reversible.
+ #
+ # ==== Parameters
+ # destination<String>:: Relative path to the destination root
+ # data<String>:: Data to add to the file. Can be given as a block.
+ # config<Hash>:: give :verbose => false to not log the status and the flag
+ # for injection (:after or :before) or :force => true for
+ # insert two or more times the same content.
+ #
+ # ==== Examples
+ #
+ # insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
+ #
+ # insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
+ # gems = ask "Which gems would you like to add?"
+ # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
+ # end
+ #
+ def insert_into_file(destination, *args, &block)
+ data = block_given? ? block : args.shift
+ config = args.shift
+ action InjectIntoFile.new(self, destination, data, config)
+ end
+ alias_method :inject_into_file, :insert_into_file
+
+ class InjectIntoFile < EmptyDirectory #:nodoc:
+ attr_reader :replacement, :flag, :behavior
+
+ def initialize(base, destination, data, config)
+ super(base, destination, {:verbose => true}.merge(config))
+
+ @behavior, @flag = if @config.key?(:after)
+ [:after, @config.delete(:after)]
+ else
+ [:before, @config.delete(:before)]
+ end
+
+ @replacement = data.is_a?(Proc) ? data.call : data
+ @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
+ end
+
+ def invoke!
+ say_status :invoke
+
+ content = if @behavior == :after
+ '\0' + replacement
+ else
+ replacement + '\0'
+ end
+
+ if exists?
+ replace!(/#{flag}/, content, config[:force])
+ else
+ unless pretend?
+ raise Bundler::Thor::Error, "The file #{ destination } does not appear to exist"
+ end
+ end
+ end
+
+ def revoke!
+ say_status :revoke
+
+ regexp = if @behavior == :after
+ content = '\1\2'
+ /(#{flag})(.*)(#{Regexp.escape(replacement)})/m
+ else
+ content = '\2\3'
+ /(#{Regexp.escape(replacement)})(.*)(#{flag})/m
+ end
+
+ replace!(regexp, content, true)
+ end
+
+ protected
+
+ def say_status(behavior)
+ status = if behavior == :invoke
+ if flag == /\A/
+ :prepend
+ elsif flag == /\z/
+ :append
+ else
+ :insert
+ end
+ else
+ :subtract
+ end
+
+ super(status, config[:verbose])
+ end
+
+ # Adds the content to the file.
+ #
+ def replace!(regexp, string, force)
+ return if pretend?
+ content = File.read(destination)
+ if force || !content.include?(replacement)
+ content.gsub!(regexp, string)
+ File.open(destination, "wb") { |file| file.write(content) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/base.rb b/lib/bundler/vendor/thor/lib/thor/base.rb
new file mode 100644
index 0000000000..9bd1077170
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/base.rb
@@ -0,0 +1,679 @@
+require "bundler/vendor/thor/lib/thor/command"
+require "bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access"
+require "bundler/vendor/thor/lib/thor/core_ext/ordered_hash"
+require "bundler/vendor/thor/lib/thor/error"
+require "bundler/vendor/thor/lib/thor/invocation"
+require "bundler/vendor/thor/lib/thor/parser"
+require "bundler/vendor/thor/lib/thor/shell"
+require "bundler/vendor/thor/lib/thor/line_editor"
+require "bundler/vendor/thor/lib/thor/util"
+
+class Bundler::Thor
+ autoload :Actions, "bundler/vendor/thor/lib/thor/actions"
+ autoload :RakeCompat, "bundler/vendor/thor/lib/thor/rake_compat"
+ autoload :Group, "bundler/vendor/thor/lib/thor/group"
+
+ # Shortcuts for help.
+ HELP_MAPPINGS = %w(-h -? --help -D)
+
+ # Bundler::Thor methods that should not be overwritten by the user.
+ THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
+ action add_file create_file in_root inside run run_ruby_script)
+
+ TEMPLATE_EXTNAME = ".tt"
+
+ module Base
+ attr_accessor :options, :parent_options, :args
+
+ # It receives arguments in an Array and two hashes, one for options and
+ # other for configuration.
+ #
+ # Notice that it does not check if all required arguments were supplied.
+ # It should be done by the parser.
+ #
+ # ==== Parameters
+ # args<Array[Object]>:: An array of objects. The objects are applied to their
+ # respective accessors declared with <tt>argument</tt>.
+ #
+ # options<Hash>:: An options hash that will be available as self.options.
+ # The hash given is converted to a hash with indifferent
+ # access, magic predicates (options.skip?) and then frozen.
+ #
+ # config<Hash>:: Configuration for this Bundler::Thor class.
+ #
+ def initialize(args = [], local_options = {}, config = {})
+ parse_options = self.class.class_options
+
+ # The start method splits inbound arguments at the first argument
+ # that looks like an option (starts with - or --). It then calls
+ # new, passing in the two halves of the arguments Array as the
+ # first two parameters.
+
+ command_options = config.delete(:command_options) # hook for start
+ parse_options = parse_options.merge(command_options) if command_options
+ if local_options.is_a?(Array)
+ array_options = local_options
+ hash_options = {}
+ else
+ # Handle the case where the class was explicitly instantiated
+ # with pre-parsed options.
+ array_options = []
+ hash_options = local_options
+ end
+
+ # Let Bundler::Thor::Options parse the options first, so it can remove
+ # declared options from the array. This will leave us with
+ # a list of arguments that weren't declared.
+ stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command]
+ disable_required_check = self.class.disable_required_check? config[:current_command]
+ opts = Bundler::Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check)
+ self.options = opts.parse(array_options)
+ self.options = config[:class_options].merge(options) if config[:class_options]
+
+ # If unknown options are disallowed, make sure that none of the
+ # remaining arguments looks like an option.
+ opts.check_unknown! if self.class.check_unknown_options?(config)
+
+ # Add the remaining arguments from the options parser to the
+ # arguments passed in to initialize. Then remove any positional
+ # arguments declared using #argument (this is primarily used
+ # by Bundler::Thor::Group). Tis will leave us with the remaining
+ # positional arguments.
+ to_parse = args
+ to_parse += opts.remaining unless self.class.strict_args_position?(config)
+
+ thor_args = Bundler::Thor::Arguments.new(self.class.arguments)
+ thor_args.parse(to_parse).each { |k, v| __send__("#{k}=", v) }
+ @args = thor_args.remaining
+ end
+
+ class << self
+ def included(base) #:nodoc:
+ base.extend ClassMethods
+ base.send :include, Invocation
+ base.send :include, Shell
+ end
+
+ # Returns the classes that inherits from Bundler::Thor or Bundler::Thor::Group.
+ #
+ # ==== Returns
+ # Array[Class]
+ #
+ def subclasses
+ @subclasses ||= []
+ end
+
+ # Returns the files where the subclasses are kept.
+ #
+ # ==== Returns
+ # Hash[path<String> => Class]
+ #
+ def subclass_files
+ @subclass_files ||= Hash.new { |h, k| h[k] = [] }
+ end
+
+ # Whenever a class inherits from Bundler::Thor or Bundler::Thor::Group, we should track the
+ # class and the file on Bundler::Thor::Base. This is the method responsable for it.
+ #
+ def register_klass_file(klass) #:nodoc:
+ file = caller[1].match(/(.*):\d+/)[1]
+ Bundler::Thor::Base.subclasses << klass unless Bundler::Thor::Base.subclasses.include?(klass)
+
+ file_subclasses = Bundler::Thor::Base.subclass_files[File.expand_path(file)]
+ file_subclasses << klass unless file_subclasses.include?(klass)
+ end
+ end
+
+ module ClassMethods
+ def attr_reader(*) #:nodoc:
+ no_commands { super }
+ end
+
+ def attr_writer(*) #:nodoc:
+ no_commands { super }
+ end
+
+ def attr_accessor(*) #:nodoc:
+ no_commands { super }
+ end
+
+ # If you want to raise an error for unknown options, call check_unknown_options!
+ # This is disabled by default to allow dynamic invocations.
+ def check_unknown_options!
+ @check_unknown_options = true
+ end
+
+ def check_unknown_options #:nodoc:
+ @check_unknown_options ||= from_superclass(:check_unknown_options, false)
+ end
+
+ def check_unknown_options?(config) #:nodoc:
+ !!check_unknown_options
+ end
+
+ # If you want to raise an error when the default value of an option does not match
+ # the type call check_default_type!
+ # This is disabled by default for compatibility.
+ def check_default_type!
+ @check_default_type = true
+ end
+
+ def check_default_type #:nodoc:
+ @check_default_type ||= from_superclass(:check_default_type, false)
+ end
+
+ def check_default_type? #:nodoc:
+ !!check_default_type
+ end
+
+ # If true, option parsing is suspended as soon as an unknown option or a
+ # regular argument is encountered. All remaining arguments are passed to
+ # the command as regular arguments.
+ def stop_on_unknown_option?(command_name) #:nodoc:
+ false
+ end
+
+ # If true, option set will not suspend the execution of the command when
+ # a required option is not provided.
+ def disable_required_check?(command_name) #:nodoc:
+ false
+ end
+
+ # If you want only strict string args (useful when cascading thor classes),
+ # call strict_args_position! This is disabled by default to allow dynamic
+ # invocations.
+ def strict_args_position!
+ @strict_args_position = true
+ end
+
+ def strict_args_position #:nodoc:
+ @strict_args_position ||= from_superclass(:strict_args_position, false)
+ end
+
+ def strict_args_position?(config) #:nodoc:
+ !!strict_args_position
+ end
+
+ # Adds an argument to the class and creates an attr_accessor for it.
+ #
+ # Arguments are different from options in several aspects. The first one
+ # is how they are parsed from the command line, arguments are retrieved
+ # from position:
+ #
+ # thor command NAME
+ #
+ # Instead of:
+ #
+ # thor command --name=NAME
+ #
+ # Besides, arguments are used inside your code as an accessor (self.argument),
+ # while options are all kept in a hash (self.options).
+ #
+ # Finally, arguments cannot have type :default or :boolean but can be
+ # optional (supplying :optional => :true or :required => false), although
+ # you cannot have a required argument after a non-required argument. If you
+ # try it, an error is raised.
+ #
+ # ==== Parameters
+ # name<Symbol>:: The name of the argument.
+ # options<Hash>:: Described below.
+ #
+ # ==== Options
+ # :desc - Description for the argument.
+ # :required - If the argument is required or not.
+ # :optional - If the argument is optional or not.
+ # :type - The type of the argument, can be :string, :hash, :array, :numeric.
+ # :default - Default value for this argument. It cannot be required and have default values.
+ # :banner - String to show on usage notes.
+ #
+ # ==== Errors
+ # ArgumentError:: Raised if you supply a required argument after a non required one.
+ #
+ def argument(name, options = {})
+ is_thor_reserved_word?(name, :argument)
+ no_commands { attr_accessor name }
+
+ required = if options.key?(:optional)
+ !options[:optional]
+ elsif options.key?(:required)
+ options[:required]
+ else
+ options[:default].nil?
+ end
+
+ remove_argument name
+
+ if required
+ arguments.each do |argument|
+ next if argument.required?
+ raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " \
+ "the non-required argument #{argument.human_name.inspect}."
+ end
+ end
+
+ options[:required] = required
+
+ arguments << Bundler::Thor::Argument.new(name, options)
+ end
+
+ # Returns this class arguments, looking up in the ancestors chain.
+ #
+ # ==== Returns
+ # Array[Bundler::Thor::Argument]
+ #
+ def arguments
+ @arguments ||= from_superclass(:arguments, [])
+ end
+
+ # Adds a bunch of options to the set of class options.
+ #
+ # class_options :foo => false, :bar => :required, :baz => :string
+ #
+ # If you prefer more detailed declaration, check class_option.
+ #
+ # ==== Parameters
+ # Hash[Symbol => Object]
+ #
+ def class_options(options = nil)
+ @class_options ||= from_superclass(:class_options, {})
+ build_options(options, @class_options) if options
+ @class_options
+ end
+
+ # Adds an option to the set of class options
+ #
+ # ==== Parameters
+ # name<Symbol>:: The name of the argument.
+ # options<Hash>:: Described below.
+ #
+ # ==== Options
+ # :desc:: -- Description for the argument.
+ # :required:: -- If the argument is required or not.
+ # :default:: -- Default value for this argument.
+ # :group:: -- The group for this options. Use by class options to output options in different levels.
+ # :aliases:: -- Aliases for this option. <b>Note:</b> Bundler::Thor follows a convention of one-dash-one-letter options. Thus aliases like "-something" wouldn't be parsed; use either "\--something" or "-s" instead.
+ # :type:: -- The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
+ # :banner:: -- String to show on usage notes.
+ # :hide:: -- If you want to hide this option from the help.
+ #
+ def class_option(name, options = {})
+ build_option(name, options, class_options)
+ end
+
+ # Removes a previous defined argument. If :undefine is given, undefine
+ # accessors as well.
+ #
+ # ==== Parameters
+ # names<Array>:: Arguments to be removed
+ #
+ # ==== Examples
+ #
+ # remove_argument :foo
+ # remove_argument :foo, :bar, :baz, :undefine => true
+ #
+ def remove_argument(*names)
+ options = names.last.is_a?(Hash) ? names.pop : {}
+
+ names.each do |name|
+ arguments.delete_if { |a| a.name == name.to_s }
+ undef_method name, "#{name}=" if options[:undefine]
+ end
+ end
+
+ # Removes a previous defined class option.
+ #
+ # ==== Parameters
+ # names<Array>:: Class options to be removed
+ #
+ # ==== Examples
+ #
+ # remove_class_option :foo
+ # remove_class_option :foo, :bar, :baz
+ #
+ def remove_class_option(*names)
+ names.each do |name|
+ class_options.delete(name)
+ end
+ end
+
+ # Defines the group. This is used when thor list is invoked so you can specify
+ # that only commands from a pre-defined group will be shown. Defaults to standard.
+ #
+ # ==== Parameters
+ # name<String|Symbol>
+ #
+ def group(name = nil)
+ if name
+ @group = name.to_s
+ else
+ @group ||= from_superclass(:group, "standard")
+ end
+ end
+
+ # Returns the commands for this Bundler::Thor class.
+ #
+ # ==== Returns
+ # OrderedHash:: An ordered hash with commands names as keys and Bundler::Thor::Command
+ # objects as values.
+ #
+ def commands
+ @commands ||= Bundler::Thor::CoreExt::OrderedHash.new
+ end
+ alias_method :tasks, :commands
+
+ # Returns the commands for this Bundler::Thor class and all subclasses.
+ #
+ # ==== Returns
+ # OrderedHash:: An ordered hash with commands names as keys and Bundler::Thor::Command
+ # objects as values.
+ #
+ def all_commands
+ @all_commands ||= from_superclass(:all_commands, Bundler::Thor::CoreExt::OrderedHash.new)
+ @all_commands.merge!(commands)
+ end
+ alias_method :all_tasks, :all_commands
+
+ # Removes a given command from this Bundler::Thor class. This is usually done if you
+ # are inheriting from another class and don't want it to be available
+ # anymore.
+ #
+ # By default it only remove the mapping to the command. But you can supply
+ # :undefine => true to undefine the method from the class as well.
+ #
+ # ==== Parameters
+ # name<Symbol|String>:: The name of the command to be removed
+ # options<Hash>:: You can give :undefine => true if you want commands the method
+ # to be undefined from the class as well.
+ #
+ def remove_command(*names)
+ options = names.last.is_a?(Hash) ? names.pop : {}
+
+ names.each do |name|
+ commands.delete(name.to_s)
+ all_commands.delete(name.to_s)
+ undef_method name if options[:undefine]
+ end
+ end
+ alias_method :remove_task, :remove_command
+
+ # All methods defined inside the given block are not added as commands.
+ #
+ # So you can do:
+ #
+ # class MyScript < Bundler::Thor
+ # no_commands do
+ # def this_is_not_a_command
+ # end
+ # end
+ # end
+ #
+ # You can also add the method and remove it from the command list:
+ #
+ # class MyScript < Bundler::Thor
+ # def this_is_not_a_command
+ # end
+ # remove_command :this_is_not_a_command
+ # end
+ #
+ def no_commands
+ @no_commands = true
+ yield
+ ensure
+ @no_commands = false
+ end
+ alias_method :no_tasks, :no_commands
+
+ # Sets the namespace for the Bundler::Thor or Bundler::Thor::Group class. By default the
+ # namespace is retrieved from the class name. If your Bundler::Thor class is named
+ # Scripts::MyScript, the help method, for example, will be called as:
+ #
+ # thor scripts:my_script -h
+ #
+ # If you change the namespace:
+ #
+ # namespace :my_scripts
+ #
+ # You change how your commands are invoked:
+ #
+ # thor my_scripts -h
+ #
+ # Finally, if you change your namespace to default:
+ #
+ # namespace :default
+ #
+ # Your commands can be invoked with a shortcut. Instead of:
+ #
+ # thor :my_command
+ #
+ def namespace(name = nil)
+ if name
+ @namespace = name.to_s
+ else
+ @namespace ||= Bundler::Thor::Util.namespace_from_thor_class(self)
+ end
+ end
+
+ # Parses the command and options from the given args, instantiate the class
+ # and invoke the command. This method is used when the arguments must be parsed
+ # from an array. If you are inside Ruby and want to use a Bundler::Thor class, you
+ # can simply initialize it:
+ #
+ # script = MyScript.new(args, options, config)
+ # script.invoke(:command, first_arg, second_arg, third_arg)
+ #
+ def start(given_args = ARGV, config = {})
+ config[:shell] ||= Bundler::Thor::Base.shell.new
+ dispatch(nil, given_args.dup, nil, config)
+ rescue Bundler::Thor::Error => e
+ config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
+ exit(1) if exit_on_failure?
+ rescue Errno::EPIPE
+ # This happens if a thor command is piped to something like `head`,
+ # which closes the pipe when it's done reading. This will also
+ # mean that if the pipe is closed, further unnecessary
+ # computation will not occur.
+ exit(0)
+ end
+
+ # Allows to use private methods from parent in child classes as commands.
+ #
+ # ==== Parameters
+ # names<Array>:: Method names to be used as commands
+ #
+ # ==== Examples
+ #
+ # public_command :foo
+ # public_command :foo, :bar, :baz
+ #
+ def public_command(*names)
+ names.each do |name|
+ class_eval "def #{name}(*); super end"
+ end
+ end
+ alias_method :public_task, :public_command
+
+ def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
+ raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." if has_namespace
+ raise UndefinedCommandError, "Could not find command #{command.inspect}."
+ end
+ alias_method :handle_no_task_error, :handle_no_command_error
+
+ def handle_argument_error(command, error, args, arity) #:nodoc:
+ name = [command.ancestor_name, command.name].compact.join(" ")
+ msg = "ERROR: \"#{basename} #{name}\" was called with ".dup
+ msg << "no arguments" if args.empty?
+ msg << "arguments " << args.inspect unless args.empty?
+ msg << "\nUsage: #{banner(command).inspect}"
+ raise InvocationError, msg
+ end
+
+ protected
+
+ # Prints the class options per group. If an option does not belong to
+ # any group, it's printed as Class option.
+ #
+ def class_options_help(shell, groups = {}) #:nodoc:
+ # Group options by group
+ class_options.each do |_, value|
+ groups[value.group] ||= []
+ groups[value.group] << value
+ end
+
+ # Deal with default group
+ global_options = groups.delete(nil) || []
+ print_options(shell, global_options)
+
+ # Print all others
+ groups.each do |group_name, options|
+ print_options(shell, options, group_name)
+ end
+ end
+
+ # Receives a set of options and print them.
+ def print_options(shell, options, group_name = nil)
+ return if options.empty?
+
+ list = []
+ padding = options.map { |o| o.aliases.size }.max.to_i * 4
+
+ options.each do |option|
+ next if option.hide
+ item = [option.usage(padding)]
+ item.push(option.description ? "# #{option.description}" : "")
+
+ list << item
+ list << ["", "# Default: #{option.default}"] if option.show_default?
+ list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum
+ end
+
+ shell.say(group_name ? "#{group_name} options:" : "Options:")
+ shell.print_table(list, :indent => 2)
+ shell.say ""
+ end
+
+ # Raises an error if the word given is a Bundler::Thor reserved word.
+ def is_thor_reserved_word?(word, type) #:nodoc:
+ return false unless THOR_RESERVED_WORDS.include?(word.to_s)
+ raise "#{word.inspect} is a Bundler::Thor reserved word and cannot be defined as #{type}"
+ end
+
+ # Build an option and adds it to the given scope.
+ #
+ # ==== Parameters
+ # name<Symbol>:: The name of the argument.
+ # options<Hash>:: Described in both class_option and method_option.
+ # scope<Hash>:: Options hash that is being built up
+ def build_option(name, options, scope) #:nodoc:
+ scope[name] = Bundler::Thor::Option.new(name, options.merge(:check_default_type => check_default_type?))
+ end
+
+ # Receives a hash of options, parse them and add to the scope. This is a
+ # fast way to set a bunch of options:
+ #
+ # build_options :foo => true, :bar => :required, :baz => :string
+ #
+ # ==== Parameters
+ # Hash[Symbol => Object]
+ def build_options(options, scope) #:nodoc:
+ options.each do |key, value|
+ scope[key] = Bundler::Thor::Option.parse(key, value)
+ end
+ end
+
+ # Finds a command with the given name. If the command belongs to the current
+ # class, just return it, otherwise dup it and add the fresh copy to the
+ # current command hash.
+ def find_and_refresh_command(name) #:nodoc:
+ if commands[name.to_s]
+ commands[name.to_s]
+ elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition
+ commands[name.to_s] = command.clone
+ else
+ raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found."
+ end
+ end
+ alias_method :find_and_refresh_task, :find_and_refresh_command
+
+ # Everytime someone inherits from a Bundler::Thor class, register the klass
+ # and file into baseclass.
+ def inherited(klass)
+ Bundler::Thor::Base.register_klass_file(klass)
+ klass.instance_variable_set(:@no_commands, false)
+ end
+
+ # Fire this callback whenever a method is added. Added methods are
+ # tracked as commands by invoking the create_command method.
+ def method_added(meth)
+ meth = meth.to_s
+
+ if meth == "initialize"
+ initialize_added
+ return
+ end
+
+ # Return if it's not a public instance method
+ return unless public_method_defined?(meth.to_sym)
+
+ @no_commands ||= false
+ return if @no_commands || !create_command(meth)
+
+ is_thor_reserved_word?(meth, :command)
+ Bundler::Thor::Base.register_klass_file(self)
+ end
+
+ # Retrieves a value from superclass. If it reaches the baseclass,
+ # returns default.
+ def from_superclass(method, default = nil)
+ if self == baseclass || !superclass.respond_to?(method, true)
+ default
+ else
+ value = superclass.send(method)
+
+ # Ruby implements `dup` on Object, but raises a `TypeError`
+ # if the method is called on immediates. As a result, we
+ # don't have a good way to check whether dup will succeed
+ # without calling it and rescuing the TypeError.
+ begin
+ value.dup
+ rescue TypeError
+ value
+ end
+
+ end
+ end
+
+ # A flag that makes the process exit with status 1 if any error happens.
+ def exit_on_failure?
+ false
+ end
+
+ #
+ # The basename of the program invoking the thor class.
+ #
+ def basename
+ File.basename($PROGRAM_NAME).split(" ").first
+ end
+
+ # SIGNATURE: Sets the baseclass. This is where the superclass lookup
+ # finishes.
+ def baseclass #:nodoc:
+ end
+
+ # SIGNATURE: Creates a new command if valid_command? is true. This method is
+ # called when a new method is added to the class.
+ def create_command(meth) #:nodoc:
+ end
+ alias_method :create_task, :create_command
+
+ # SIGNATURE: Defines behavior when the initialize method is added to the
+ # class.
+ def initialize_added #:nodoc:
+ end
+
+ # SIGNATURE: The hook invoked by start.
+ def dispatch(command, given_args, given_opts, config) #:nodoc:
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/command.rb b/lib/bundler/vendor/thor/lib/thor/command.rb
new file mode 100644
index 0000000000..c636948e5d
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/command.rb
@@ -0,0 +1,135 @@
+class Bundler::Thor
+ class Command < Struct.new(:name, :description, :long_description, :usage, :options, :ancestor_name)
+ FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
+
+ def initialize(name, description, long_description, usage, options = nil)
+ super(name.to_s, description, long_description, usage, options || {})
+ end
+
+ def initialize_copy(other) #:nodoc:
+ super(other)
+ self.options = other.options.dup if other.options
+ end
+
+ def hidden?
+ false
+ end
+
+ # By default, a command invokes a method in the thor class. You can change this
+ # implementation to create custom commands.
+ def run(instance, args = [])
+ arity = nil
+
+ if private_method?(instance)
+ instance.class.handle_no_command_error(name)
+ elsif public_method?(instance)
+ arity = instance.method(name).arity
+ instance.__send__(name, *args)
+ elsif local_method?(instance, :method_missing)
+ instance.__send__(:method_missing, name.to_sym, *args)
+ else
+ instance.class.handle_no_command_error(name)
+ end
+ rescue ArgumentError => e
+ handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e)
+ rescue NoMethodError => e
+ handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (raise e)
+ end
+
+ # Returns the formatted usage by injecting given required arguments
+ # and required options into the given usage.
+ def formatted_usage(klass, namespace = true, subcommand = false)
+ if ancestor_name
+ formatted = "#{ancestor_name} ".dup # add space
+ elsif namespace
+ namespace = klass.namespace
+ formatted = "#{namespace.gsub(/^(default)/, '')}:".dup
+ end
+ formatted ||= "#{klass.namespace.split(':').last} ".dup if subcommand
+
+ formatted ||= "".dup
+
+ # Add usage with required arguments
+ formatted << if klass && !klass.arguments.empty?
+ usage.to_s.gsub(/^#{name}/) do |match|
+ match << " " << klass.arguments.map(&:usage).compact.join(" ")
+ end
+ else
+ usage.to_s
+ end
+
+ # Add required options
+ formatted << " #{required_options}"
+
+ # Strip and go!
+ formatted.strip
+ end
+
+ protected
+
+ def not_debugging?(instance)
+ !(instance.class.respond_to?(:debugging) && instance.class.debugging)
+ end
+
+ def required_options
+ @required_options ||= options.map { |_, o| o.usage if o.required? }.compact.sort.join(" ")
+ end
+
+ # Given a target, checks if this class name is a public method.
+ def public_method?(instance) #:nodoc:
+ !(instance.public_methods & [name.to_s, name.to_sym]).empty?
+ end
+
+ def private_method?(instance)
+ !(instance.private_methods & [name.to_s, name.to_sym]).empty?
+ end
+
+ def local_method?(instance, name)
+ methods = instance.public_methods(false) + instance.private_methods(false) + instance.protected_methods(false)
+ !(methods & [name.to_s, name.to_sym]).empty?
+ end
+
+ def sans_backtrace(backtrace, caller) #:nodoc:
+ saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ %r{^kernel/} && RUBY_ENGINE =~ /rbx/) }
+ saned - caller
+ end
+
+ def handle_argument_error?(instance, error, caller)
+ not_debugging?(instance) && (error.message =~ /wrong number of arguments/ || error.message =~ /given \d*, expected \d*/) && begin
+ saned = sans_backtrace(error.backtrace, caller)
+ # Ruby 1.9 always include the called method in the backtrace
+ saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9")
+ end
+ end
+
+ def handle_no_method_error?(instance, error, caller)
+ not_debugging?(instance) &&
+ error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
+ end
+ end
+ Task = Command
+
+ # A command that is hidden in help messages but still invocable.
+ class HiddenCommand < Command
+ def hidden?
+ true
+ end
+ end
+ HiddenTask = HiddenCommand
+
+ # A dynamic command that handles method missing scenarios.
+ class DynamicCommand < Command
+ def initialize(name, options = nil)
+ super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options)
+ end
+
+ def run(instance, args = [])
+ if (instance.methods & [name.to_s, name.to_sym]).empty?
+ super
+ else
+ instance.class.handle_no_command_error(name)
+ end
+ end
+ end
+ DynamicTask = DynamicCommand
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb
new file mode 100644
index 0000000000..c167aa33b8
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb
@@ -0,0 +1,97 @@
+class Bundler::Thor
+ module CoreExt #:nodoc:
+ # A hash with indifferent access and magic predicates.
+ #
+ # hash = Bundler::Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
+ #
+ # hash[:foo] #=> 'bar'
+ # hash['foo'] #=> 'bar'
+ # hash.foo? #=> true
+ #
+ class HashWithIndifferentAccess < ::Hash #:nodoc:
+ def initialize(hash = {})
+ super()
+ hash.each do |key, value|
+ self[convert_key(key)] = value
+ end
+ end
+
+ def [](key)
+ super(convert_key(key))
+ end
+
+ def []=(key, value)
+ super(convert_key(key), value)
+ end
+
+ def delete(key)
+ super(convert_key(key))
+ end
+
+ def fetch(key, *args)
+ super(convert_key(key), *args)
+ end
+
+ def key?(key)
+ super(convert_key(key))
+ end
+
+ def values_at(*indices)
+ indices.map { |key| self[convert_key(key)] }
+ end
+
+ def merge(other)
+ dup.merge!(other)
+ end
+
+ def merge!(other)
+ other.each do |key, value|
+ self[convert_key(key)] = value
+ end
+ self
+ end
+
+ def reverse_merge(other)
+ self.class.new(other).merge(self)
+ end
+
+ def reverse_merge!(other_hash)
+ replace(reverse_merge(other_hash))
+ end
+
+ def replace(other_hash)
+ super(other_hash)
+ end
+
+ # Convert to a Hash with String keys.
+ def to_hash
+ Hash.new(default).merge!(self)
+ end
+
+ protected
+
+ def convert_key(key)
+ key.is_a?(Symbol) ? key.to_s : key
+ end
+
+ # Magic predicates. For instance:
+ #
+ # options.force? # => !!options['force']
+ # options.shebang # => "/usr/lib/local/ruby"
+ # options.test_framework?(:rspec) # => options[:test_framework] == :rspec
+ #
+ def method_missing(method, *args)
+ method = method.to_s
+ if method =~ /^(\w+)\?$/
+ if args.empty?
+ !!self[$1]
+ else
+ self[$1] == args.first
+ end
+ else
+ self[method]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb b/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb
new file mode 100644
index 0000000000..0f6e2e0af2
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb
@@ -0,0 +1,12 @@
+class IO #:nodoc:
+ class << self
+ unless method_defined? :binread
+ def binread(file, *args)
+ raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3
+ File.open(file, "rb") do |f|
+ f.read(*args)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb b/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb
new file mode 100644
index 0000000000..76f1e43c65
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb
@@ -0,0 +1,129 @@
+class Bundler::Thor
+ module CoreExt
+ class OrderedHash < ::Hash
+ if RUBY_VERSION < "1.9"
+ def initialize(*args, &block)
+ super
+ @keys = []
+ end
+
+ def initialize_copy(other)
+ super
+ # make a deep copy of keys
+ @keys = other.keys
+ end
+
+ def []=(key, value)
+ @keys << key unless key?(key)
+ super
+ end
+
+ def delete(key)
+ if key? key
+ index = @keys.index(key)
+ @keys.delete_at index
+ end
+ super
+ end
+
+ def delete_if
+ super
+ sync_keys!
+ self
+ end
+
+ alias_method :reject!, :delete_if
+
+ def reject(&block)
+ dup.reject!(&block)
+ end
+
+ def keys
+ @keys.dup
+ end
+
+ def values
+ @keys.map { |key| self[key] }
+ end
+
+ def to_hash
+ self
+ end
+
+ def to_a
+ @keys.map { |key| [key, self[key]] }
+ end
+
+ def each_key
+ return to_enum(:each_key) unless block_given?
+ @keys.each { |key| yield(key) }
+ self
+ end
+
+ def each_value
+ return to_enum(:each_value) unless block_given?
+ @keys.each { |key| yield(self[key]) }
+ self
+ end
+
+ def each
+ return to_enum(:each) unless block_given?
+ @keys.each { |key| yield([key, self[key]]) }
+ self
+ end
+
+ def each_pair
+ return to_enum(:each_pair) unless block_given?
+ @keys.each { |key| yield(key, self[key]) }
+ self
+ end
+
+ alias_method :select, :find_all
+
+ def clear
+ super
+ @keys.clear
+ self
+ end
+
+ def shift
+ k = @keys.first
+ v = delete(k)
+ [k, v]
+ end
+
+ def merge!(other_hash)
+ if block_given?
+ other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
+ else
+ other_hash.each { |k, v| self[k] = v }
+ end
+ self
+ end
+
+ alias_method :update, :merge!
+
+ def merge(other_hash, &block)
+ dup.merge!(other_hash, &block)
+ end
+
+ # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
+ def replace(other)
+ super
+ @keys = other.keys
+ self
+ end
+
+ def inspect
+ "#<#{self.class} #{super}>"
+ end
+
+ private
+
+ def sync_keys!
+ @keys.delete_if { |k| !key?(k) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/error.rb b/lib/bundler/vendor/thor/lib/thor/error.rb
new file mode 100644
index 0000000000..2f816081f3
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/error.rb
@@ -0,0 +1,32 @@
+class Bundler::Thor
+ # Bundler::Thor::Error is raised when it's caused by wrong usage of thor classes. Those
+ # errors have their backtrace suppressed and are nicely shown to the user.
+ #
+ # Errors that are caused by the developer, like declaring a method which
+ # overwrites a thor keyword, SHOULD NOT raise a Bundler::Thor::Error. This way, we
+ # ensure that developer errors are shown with full backtrace.
+ class Error < StandardError
+ end
+
+ # Raised when a command was not found.
+ class UndefinedCommandError < Error
+ end
+ UndefinedTaskError = UndefinedCommandError
+
+ class AmbiguousCommandError < Error
+ end
+ AmbiguousTaskError = AmbiguousCommandError
+
+ # Raised when a command was found, but not invoked properly.
+ class InvocationError < Error
+ end
+
+ class UnknownArgumentError < Error
+ end
+
+ class RequiredArgumentMissingError < InvocationError
+ end
+
+ class MalformattedArgumentError < InvocationError
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/group.rb b/lib/bundler/vendor/thor/lib/thor/group.rb
new file mode 100644
index 0000000000..05ddc10cd3
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/group.rb
@@ -0,0 +1,281 @@
+require "bundler/vendor/thor/lib/thor/base"
+
+# Bundler::Thor has a special class called Bundler::Thor::Group. The main difference to Bundler::Thor class
+# is that it invokes all commands at once. It also include some methods that allows
+# invocations to be done at the class method, which are not available to Bundler::Thor
+# commands.
+class Bundler::Thor::Group
+ class << self
+ # The description for this Bundler::Thor::Group. If none is provided, but a source root
+ # exists, tries to find the USAGE one folder above it, otherwise searches
+ # in the superclass.
+ #
+ # ==== Parameters
+ # description<String>:: The description for this Bundler::Thor::Group.
+ #
+ def desc(description = nil)
+ if description
+ @desc = description
+ else
+ @desc ||= from_superclass(:desc, nil)
+ end
+ end
+
+ # Prints help information.
+ #
+ # ==== Options
+ # short:: When true, shows only usage.
+ #
+ def help(shell)
+ shell.say "Usage:"
+ shell.say " #{banner}\n"
+ shell.say
+ class_options_help(shell)
+ shell.say desc if desc
+ end
+
+ # Stores invocations for this class merging with superclass values.
+ #
+ def invocations #:nodoc:
+ @invocations ||= from_superclass(:invocations, {})
+ end
+
+ # Stores invocation blocks used on invoke_from_option.
+ #
+ def invocation_blocks #:nodoc:
+ @invocation_blocks ||= from_superclass(:invocation_blocks, {})
+ end
+
+ # Invoke the given namespace or class given. It adds an instance
+ # method that will invoke the klass and command. You can give a block to
+ # configure how it will be invoked.
+ #
+ # The namespace/class given will have its options showed on the help
+ # usage. Check invoke_from_option for more information.
+ #
+ def invoke(*names, &block)
+ options = names.last.is_a?(Hash) ? names.pop : {}
+ verbose = options.fetch(:verbose, true)
+
+ names.each do |name|
+ invocations[name] = false
+ invocation_blocks[name] = block if block_given?
+
+ class_eval <<-METHOD, __FILE__, __LINE__
+ def _invoke_#{name.to_s.gsub(/\W/, '_')}
+ klass, command = self.class.prepare_for_invocation(nil, #{name.inspect})
+
+ if klass
+ say_status :invoke, #{name.inspect}, #{verbose.inspect}
+ block = self.class.invocation_blocks[#{name.inspect}]
+ _invoke_for_class_method klass, command, &block
+ else
+ say_status :error, %(#{name.inspect} [not found]), :red
+ end
+ end
+ METHOD
+ end
+ end
+
+ # Invoke a thor class based on the value supplied by the user to the
+ # given option named "name". A class option must be created before this
+ # method is invoked for each name given.
+ #
+ # ==== Examples
+ #
+ # class GemGenerator < Bundler::Thor::Group
+ # class_option :test_framework, :type => :string
+ # invoke_from_option :test_framework
+ # end
+ #
+ # ==== Boolean options
+ #
+ # In some cases, you want to invoke a thor class if some option is true or
+ # false. This is automatically handled by invoke_from_option. Then the
+ # option name is used to invoke the generator.
+ #
+ # ==== Preparing for invocation
+ #
+ # In some cases you want to customize how a specified hook is going to be
+ # invoked. You can do that by overwriting the class method
+ # prepare_for_invocation. The class method must necessarily return a klass
+ # and an optional command.
+ #
+ # ==== Custom invocations
+ #
+ # You can also supply a block to customize how the option is going to be
+ # invoked. The block receives two parameters, an instance of the current
+ # class and the klass to be invoked.
+ #
+ def invoke_from_option(*names, &block)
+ options = names.last.is_a?(Hash) ? names.pop : {}
+ verbose = options.fetch(:verbose, :white)
+
+ names.each do |name|
+ unless class_options.key?(name)
+ raise ArgumentError, "You have to define the option #{name.inspect} " \
+ "before setting invoke_from_option."
+ end
+
+ invocations[name] = true
+ invocation_blocks[name] = block if block_given?
+
+ class_eval <<-METHOD, __FILE__, __LINE__
+ def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
+ return unless options[#{name.inspect}]
+
+ value = options[#{name.inspect}]
+ value = #{name.inspect} if TrueClass === value
+ klass, command = self.class.prepare_for_invocation(#{name.inspect}, value)
+
+ if klass
+ say_status :invoke, value, #{verbose.inspect}
+ block = self.class.invocation_blocks[#{name.inspect}]
+ _invoke_for_class_method klass, command, &block
+ else
+ say_status :error, %(\#{value} [not found]), :red
+ end
+ end
+ METHOD
+ end
+ end
+
+ # Remove a previously added invocation.
+ #
+ # ==== Examples
+ #
+ # remove_invocation :test_framework
+ #
+ def remove_invocation(*names)
+ names.each do |name|
+ remove_command(name)
+ remove_class_option(name)
+ invocations.delete(name)
+ invocation_blocks.delete(name)
+ end
+ end
+
+ # Overwrite class options help to allow invoked generators options to be
+ # shown recursively when invoking a generator.
+ #
+ def class_options_help(shell, groups = {}) #:nodoc:
+ get_options_from_invocations(groups, class_options) do |klass|
+ klass.send(:get_options_from_invocations, groups, class_options)
+ end
+ super(shell, groups)
+ end
+
+ # Get invocations array and merge options from invocations. Those
+ # options are added to group_options hash. Options that already exists
+ # in base_options are not added twice.
+ #
+ def get_options_from_invocations(group_options, base_options) #:nodoc: # rubocop:disable MethodLength
+ invocations.each do |name, from_option|
+ value = if from_option
+ option = class_options[name]
+ option.type == :boolean ? name : option.default
+ else
+ name
+ end
+ next unless value
+
+ klass, _ = prepare_for_invocation(name, value)
+ next unless klass && klass.respond_to?(:class_options)
+
+ value = value.to_s
+ human_name = value.respond_to?(:classify) ? value.classify : value
+
+ group_options[human_name] ||= []
+ group_options[human_name] += klass.class_options.values.select do |class_option|
+ base_options[class_option.name.to_sym].nil? && class_option.group.nil? &&
+ !group_options.values.flatten.any? { |i| i.name == class_option.name }
+ end
+
+ yield klass if block_given?
+ end
+ end
+
+ # Returns commands ready to be printed.
+ def printable_commands(*)
+ item = []
+ item << banner
+ item << (desc ? "# #{desc.gsub(/\s+/m, ' ')}" : "")
+ [item]
+ end
+ alias_method :printable_tasks, :printable_commands
+
+ def handle_argument_error(command, error, _args, arity) #:nodoc:
+ msg = "#{basename} #{command.name} takes #{arity} argument".dup
+ msg << "s" if arity > 1
+ msg << ", but it should not."
+ raise error, msg
+ end
+
+ protected
+
+ # The method responsible for dispatching given the args.
+ def dispatch(command, given_args, given_opts, config) #:nodoc:
+ if Bundler::Thor::HELP_MAPPINGS.include?(given_args.first)
+ help(config[:shell])
+ return
+ end
+
+ args, opts = Bundler::Thor::Options.split(given_args)
+ opts = given_opts || opts
+
+ instance = new(args, opts, config)
+ yield instance if block_given?
+
+ if command
+ instance.invoke_command(all_commands[command])
+ else
+ instance.invoke_all
+ end
+ end
+
+ # The banner for this class. You can customize it if you are invoking the
+ # thor class by another ways which is not the Bundler::Thor::Runner.
+ def banner
+ "#{basename} #{self_command.formatted_usage(self, false)}"
+ end
+
+ # Represents the whole class as a command.
+ def self_command #:nodoc:
+ Bundler::Thor::DynamicCommand.new(namespace, class_options)
+ end
+ alias_method :self_task, :self_command
+
+ def baseclass #:nodoc:
+ Bundler::Thor::Group
+ end
+
+ def create_command(meth) #:nodoc:
+ commands[meth.to_s] = Bundler::Thor::Command.new(meth, nil, nil, nil, nil)
+ true
+ end
+ alias_method :create_task, :create_command
+ end
+
+ include Bundler::Thor::Base
+
+protected
+
+ # Shortcut to invoke with padding and block handling. Use internally by
+ # invoke and invoke_from_option class methods.
+ def _invoke_for_class_method(klass, command = nil, *args, &block) #:nodoc:
+ with_padding do
+ if block
+ case block.arity
+ when 3
+ yield(self, klass, command)
+ when 2
+ yield(self, klass)
+ when 1
+ instance_exec(klass, &block)
+ end
+ else
+ invoke klass, command, *args
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/invocation.rb b/lib/bundler/vendor/thor/lib/thor/invocation.rb
new file mode 100644
index 0000000000..866d2212a7
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/invocation.rb
@@ -0,0 +1,177 @@
+class Bundler::Thor
+ module Invocation
+ def self.included(base) #:nodoc:
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ # This method is responsible for receiving a name and find the proper
+ # class and command for it. The key is an optional parameter which is
+ # available only in class methods invocations (i.e. in Bundler::Thor::Group).
+ def prepare_for_invocation(key, name) #:nodoc:
+ case name
+ when Symbol, String
+ Bundler::Thor::Util.find_class_and_command_by_namespace(name.to_s, !key)
+ else
+ name
+ end
+ end
+ end
+
+ # Make initializer aware of invocations and the initialization args.
+ def initialize(args = [], options = {}, config = {}, &block) #:nodoc:
+ @_invocations = config[:invocations] || Hash.new { |h, k| h[k] = [] }
+ @_initializer = [args, options, config]
+ super
+ end
+
+ # Make the current command chain accessible with in a Bundler::Thor-(sub)command
+ def current_command_chain
+ @_invocations.values.flatten.map(&:to_sym)
+ end
+
+ # Receives a name and invokes it. The name can be a string (either "command" or
+ # "namespace:command"), a Bundler::Thor::Command, a Class or a Bundler::Thor instance. If the
+ # command cannot be guessed by name, it can also be supplied as second argument.
+ #
+ # You can also supply the arguments, options and configuration values for
+ # the command to be invoked, if none is given, the same values used to
+ # initialize the invoker are used to initialize the invoked.
+ #
+ # When no name is given, it will invoke the default command of the current class.
+ #
+ # ==== Examples
+ #
+ # class A < Bundler::Thor
+ # def foo
+ # invoke :bar
+ # invoke "b:hello", ["Erik"]
+ # end
+ #
+ # def bar
+ # invoke "b:hello", ["Erik"]
+ # end
+ # end
+ #
+ # class B < Bundler::Thor
+ # def hello(name)
+ # puts "hello #{name}"
+ # end
+ # end
+ #
+ # You can notice that the method "foo" above invokes two commands: "bar",
+ # which belongs to the same class and "hello" which belongs to the class B.
+ #
+ # By using an invocation system you ensure that a command is invoked only once.
+ # In the example above, invoking "foo" will invoke "b:hello" just once, even
+ # if it's invoked later by "bar" method.
+ #
+ # When class A invokes class B, all arguments used on A initialization are
+ # supplied to B. This allows lazy parse of options. Let's suppose you have
+ # some rspec commands:
+ #
+ # class Rspec < Bundler::Thor::Group
+ # class_option :mock_framework, :type => :string, :default => :rr
+ #
+ # def invoke_mock_framework
+ # invoke "rspec:#{options[:mock_framework]}"
+ # end
+ # end
+ #
+ # As you noticed, it invokes the given mock framework, which might have its
+ # own options:
+ #
+ # class Rspec::RR < Bundler::Thor::Group
+ # class_option :style, :type => :string, :default => :mock
+ # end
+ #
+ # Since it's not rspec concern to parse mock framework options, when RR
+ # is invoked all options are parsed again, so RR can extract only the options
+ # that it's going to use.
+ #
+ # If you want Rspec::RR to be initialized with its own set of options, you
+ # have to do that explicitly:
+ #
+ # invoke "rspec:rr", [], :style => :foo
+ #
+ # Besides giving an instance, you can also give a class to invoke:
+ #
+ # invoke Rspec::RR, [], :style => :foo
+ #
+ def invoke(name = nil, *args)
+ if name.nil?
+ warn "[Bundler::Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}"
+ return invoke_all
+ end
+
+ args.unshift(nil) if args.first.is_a?(Array) || args.first.nil?
+ command, args, opts, config = args
+
+ klass, command = _retrieve_class_and_command(name, command)
+ raise "Missing Bundler::Thor class for invoke #{name}" unless klass
+ raise "Expected Bundler::Thor class, got #{klass}" unless klass <= Bundler::Thor::Base
+
+ args, opts, config = _parse_initialization_options(args, opts, config)
+ klass.send(:dispatch, command, args, opts, config) do |instance|
+ instance.parent_options = options
+ end
+ end
+
+ # Invoke the given command if the given args.
+ def invoke_command(command, *args) #:nodoc:
+ current = @_invocations[self.class]
+
+ unless current.include?(command.name)
+ current << command.name
+ command.run(self, *args)
+ end
+ end
+ alias_method :invoke_task, :invoke_command
+
+ # Invoke all commands for the current instance.
+ def invoke_all #:nodoc:
+ self.class.all_commands.map { |_, command| invoke_command(command) }
+ end
+
+ # Invokes using shell padding.
+ def invoke_with_padding(*args)
+ with_padding { invoke(*args) }
+ end
+
+ protected
+
+ # Configuration values that are shared between invocations.
+ def _shared_configuration #:nodoc:
+ {:invocations => @_invocations}
+ end
+
+ # This method simply retrieves the class and command to be invoked.
+ # If the name is nil or the given name is a command in the current class,
+ # use the given name and return self as class. Otherwise, call
+ # prepare_for_invocation in the current class.
+ def _retrieve_class_and_command(name, sent_command = nil) #:nodoc:
+ if name.nil?
+ [self.class, nil]
+ elsif self.class.all_commands[name.to_s]
+ [self.class, name.to_s]
+ else
+ klass, command = self.class.prepare_for_invocation(nil, name)
+ [klass, command || sent_command]
+ end
+ end
+ alias_method :_retrieve_class_and_task, :_retrieve_class_and_command
+
+ # Initialize klass using values stored in the @_initializer.
+ def _parse_initialization_options(args, opts, config) #:nodoc:
+ stored_args, stored_opts, stored_config = @_initializer
+
+ args ||= stored_args.dup
+ opts ||= stored_opts.dup
+
+ config ||= {}
+ config = stored_config.merge(_shared_configuration).merge!(config)
+
+ [args, opts, config]
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/line_editor.rb b/lib/bundler/vendor/thor/lib/thor/line_editor.rb
new file mode 100644
index 0000000000..ce81a17484
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/line_editor.rb
@@ -0,0 +1,17 @@
+require "bundler/vendor/thor/lib/thor/line_editor/basic"
+require "bundler/vendor/thor/lib/thor/line_editor/readline"
+
+class Bundler::Thor
+ module LineEditor
+ def self.readline(prompt, options = {})
+ best_available.new(prompt, options).readline
+ end
+
+ def self.best_available
+ [
+ Bundler::Thor::LineEditor::Readline,
+ Bundler::Thor::LineEditor::Basic
+ ].detect(&:available?)
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/line_editor/basic.rb b/lib/bundler/vendor/thor/lib/thor/line_editor/basic.rb
new file mode 100644
index 0000000000..0adb2b3137
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/line_editor/basic.rb
@@ -0,0 +1,37 @@
+class Bundler::Thor
+ module LineEditor
+ class Basic
+ attr_reader :prompt, :options
+
+ def self.available?
+ true
+ end
+
+ def initialize(prompt, options)
+ @prompt = prompt
+ @options = options
+ end
+
+ def readline
+ $stdout.print(prompt)
+ get_input
+ end
+
+ private
+
+ def get_input
+ if echo?
+ $stdin.gets
+ else
+ # Lazy-load io/console since it is gem-ified as of 2.3
+ require "io/console" if RUBY_VERSION > "1.9.2"
+ $stdin.noecho(&:gets)
+ end
+ end
+
+ def echo?
+ options.fetch(:echo, true)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/line_editor/readline.rb b/lib/bundler/vendor/thor/lib/thor/line_editor/readline.rb
new file mode 100644
index 0000000000..dd39cff35d
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/line_editor/readline.rb
@@ -0,0 +1,88 @@
+begin
+ require "readline"
+rescue LoadError
+end
+
+class Bundler::Thor
+ module LineEditor
+ class Readline < Basic
+ def self.available?
+ Object.const_defined?(:Readline)
+ end
+
+ def readline
+ if echo?
+ ::Readline.completion_append_character = nil
+ # Ruby 1.8.7 does not allow Readline.completion_proc= to receive nil.
+ if complete = completion_proc
+ ::Readline.completion_proc = complete
+ end
+ ::Readline.readline(prompt, add_to_history?)
+ else
+ super
+ end
+ end
+
+ private
+
+ def add_to_history?
+ options.fetch(:add_to_history, true)
+ end
+
+ def completion_proc
+ if use_path_completion?
+ proc { |text| PathCompletion.new(text).matches }
+ elsif completion_options.any?
+ proc do |text|
+ completion_options.select { |option| option.start_with?(text) }
+ end
+ end
+ end
+
+ def completion_options
+ options.fetch(:limited_to, [])
+ end
+
+ def use_path_completion?
+ options.fetch(:path, false)
+ end
+
+ class PathCompletion
+ attr_reader :text
+ private :text
+
+ def initialize(text)
+ @text = text
+ end
+
+ def matches
+ relative_matches
+ end
+
+ private
+
+ def relative_matches
+ absolute_matches.map { |path| path.sub(base_path, "") }
+ end
+
+ def absolute_matches
+ Dir[glob_pattern].map do |path|
+ if File.directory?(path)
+ "#{path}/"
+ else
+ path
+ end
+ end
+ end
+
+ def glob_pattern
+ "#{base_path}#{text}*"
+ end
+
+ def base_path
+ "#{Dir.pwd}/"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/parser.rb b/lib/bundler/vendor/thor/lib/thor/parser.rb
new file mode 100644
index 0000000000..08f80e565d
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/parser.rb
@@ -0,0 +1,4 @@
+require "bundler/vendor/thor/lib/thor/parser/argument"
+require "bundler/vendor/thor/lib/thor/parser/arguments"
+require "bundler/vendor/thor/lib/thor/parser/option"
+require "bundler/vendor/thor/lib/thor/parser/options"
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb
new file mode 100644
index 0000000000..dfe7398583
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb
@@ -0,0 +1,70 @@
+class Bundler::Thor
+ class Argument #:nodoc:
+ VALID_TYPES = [:numeric, :hash, :array, :string]
+
+ attr_reader :name, :description, :enum, :required, :type, :default, :banner
+ alias_method :human_name, :name
+
+ def initialize(name, options = {})
+ class_name = self.class.name.split("::").last
+
+ type = options[:type]
+
+ raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
+ raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
+
+ @name = name.to_s
+ @description = options[:desc]
+ @required = options.key?(:required) ? options[:required] : true
+ @type = (type || :string).to_sym
+ @default = options[:default]
+ @banner = options[:banner] || default_banner
+ @enum = options[:enum]
+
+ validate! # Trigger specific validations
+ end
+
+ def usage
+ required? ? banner : "[#{banner}]"
+ end
+
+ def required?
+ required
+ end
+
+ def show_default?
+ case default
+ when Array, String, Hash
+ !default.empty?
+ else
+ default
+ end
+ end
+
+ protected
+
+ def validate!
+ raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
+ raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array)
+ end
+
+ def valid_type?(type)
+ self.class::VALID_TYPES.include?(type.to_sym)
+ end
+
+ def default_banner
+ case type
+ when :boolean
+ nil
+ when :string, :default
+ human_name.upcase
+ when :numeric
+ "N"
+ when :hash
+ "key:value"
+ when :array
+ "one two three"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb
new file mode 100644
index 0000000000..1fd790f4b7
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb
@@ -0,0 +1,175 @@
+class Bundler::Thor
+ class Arguments #:nodoc: # rubocop:disable ClassLength
+ NUMERIC = /[-+]?(\d*\.\d+|\d+)/
+
+ # Receives an array of args and returns two arrays, one with arguments
+ # and one with switches.
+ #
+ def self.split(args)
+ arguments = []
+
+ args.each do |item|
+ break if item =~ /^-/
+ arguments << item
+ end
+
+ [arguments, args[Range.new(arguments.size, -1)]]
+ end
+
+ def self.parse(*args)
+ to_parse = args.pop
+ new(*args).parse(to_parse)
+ end
+
+ # Takes an array of Bundler::Thor::Argument objects.
+ #
+ def initialize(arguments = [])
+ @assigns = {}
+ @non_assigned_required = []
+ @switches = arguments
+
+ arguments.each do |argument|
+ if !argument.default.nil?
+ @assigns[argument.human_name] = argument.default
+ elsif argument.required?
+ @non_assigned_required << argument
+ end
+ end
+ end
+
+ def parse(args)
+ @pile = args.dup
+
+ @switches.each do |argument|
+ break unless peek
+ @non_assigned_required.delete(argument)
+ @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
+ end
+
+ check_requirement!
+ @assigns
+ end
+
+ def remaining
+ @pile
+ end
+
+ private
+
+ def no_or_skip?(arg)
+ arg =~ /^--(no|skip)-([-\w]+)$/
+ $2
+ end
+
+ def last?
+ @pile.empty?
+ end
+
+ def peek
+ @pile.first
+ end
+
+ def shift
+ @pile.shift
+ end
+
+ def unshift(arg)
+ if arg.is_a?(Array)
+ @pile = arg + @pile
+ else
+ @pile.unshift(arg)
+ end
+ end
+
+ def current_is_value?
+ peek && peek.to_s !~ /^-/
+ end
+
+ # Runs through the argument array getting strings that contains ":" and
+ # mark it as a hash:
+ #
+ # [ "name:string", "age:integer" ]
+ #
+ # Becomes:
+ #
+ # { "name" => "string", "age" => "integer" }
+ #
+ def parse_hash(name)
+ return shift if peek.is_a?(Hash)
+ hash = {}
+
+ while current_is_value? && peek.include?(":")
+ key, value = shift.split(":", 2)
+ raise MalformattedArgumentError, "You can't specify '#{key}' more than once in option '#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}" if hash.include? key
+ hash[key] = value
+ end
+ hash
+ end
+
+ # Runs through the argument array getting all strings until no string is
+ # found or a switch is found.
+ #
+ # ["a", "b", "c"]
+ #
+ # And returns it as an array:
+ #
+ # ["a", "b", "c"]
+ #
+ def parse_array(name)
+ return shift if peek.is_a?(Array)
+ array = []
+ array << shift while current_is_value?
+ array
+ end
+
+ # Check if the peek is numeric format and return a Float or Integer.
+ # Check if the peek is included in enum if enum is provided.
+ # Otherwise raises an error.
+ #
+ def parse_numeric(name)
+ return shift if peek.is_a?(Numeric)
+
+ unless peek =~ NUMERIC && $& == peek
+ raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
+ end
+
+ value = $&.index(".") ? shift.to_f : shift.to_i
+ if @switches.is_a?(Hash) && switch = @switches[name]
+ if switch.enum && !switch.enum.include?(value)
+ raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
+ end
+ end
+ value
+ end
+
+ # Parse string:
+ # for --string-arg, just return the current value in the pile
+ # for --no-string-arg, nil
+ # Check if the peek is included in enum if enum is provided. Otherwise raises an error.
+ #
+ def parse_string(name)
+ if no_or_skip?(name)
+ nil
+ else
+ value = shift
+ if @switches.is_a?(Hash) && switch = @switches[name]
+ if switch.enum && !switch.enum.include?(value)
+ raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
+ end
+ end
+ value
+ end
+ end
+
+ # Raises an error if @non_assigned_required array is not empty.
+ #
+ def check_requirement!
+ return if @non_assigned_required.empty?
+ names = @non_assigned_required.map do |o|
+ o.respond_to?(:switch_name) ? o.switch_name : o.human_name
+ end.join("', '")
+ class_name = self.class.name.split("::").last.downcase
+ raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/option.rb b/lib/bundler/vendor/thor/lib/thor/parser/option.rb
new file mode 100644
index 0000000000..85169b56c8
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/parser/option.rb
@@ -0,0 +1,146 @@
+class Bundler::Thor
+ class Option < Argument #:nodoc:
+ attr_reader :aliases, :group, :lazy_default, :hide
+
+ VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
+
+ def initialize(name, options = {})
+ @check_default_type = options[:check_default_type]
+ options[:required] = false unless options.key?(:required)
+ super
+ @lazy_default = options[:lazy_default]
+ @group = options[:group].to_s.capitalize if options[:group]
+ @aliases = Array(options[:aliases])
+ @hide = options[:hide]
+ end
+
+ # This parse quick options given as method_options. It makes several
+ # assumptions, but you can be more specific using the option method.
+ #
+ # parse :foo => "bar"
+ # #=> Option foo with default value bar
+ #
+ # parse [:foo, :baz] => "bar"
+ # #=> Option foo with default value bar and alias :baz
+ #
+ # parse :foo => :required
+ # #=> Required option foo without default value
+ #
+ # parse :foo => 2
+ # #=> Option foo with default value 2 and type numeric
+ #
+ # parse :foo => :numeric
+ # #=> Option foo without default value and type numeric
+ #
+ # parse :foo => true
+ # #=> Option foo with default value true and type boolean
+ #
+ # The valid types are :boolean, :numeric, :hash, :array and :string. If none
+ # is given a default type is assumed. This default type accepts arguments as
+ # string (--foo=value) or booleans (just --foo).
+ #
+ # By default all options are optional, unless :required is given.
+ #
+ def self.parse(key, value)
+ if key.is_a?(Array)
+ name, *aliases = key
+ else
+ name = key
+ aliases = []
+ end
+
+ name = name.to_s
+ default = value
+
+ type = case value
+ when Symbol
+ default = nil
+ if VALID_TYPES.include?(value)
+ value
+ elsif required = (value == :required) # rubocop:disable AssignmentInCondition
+ :string
+ end
+ when TrueClass, FalseClass
+ :boolean
+ when Numeric
+ :numeric
+ when Hash, Array, String
+ value.class.name.downcase.to_sym
+ end
+
+ new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases)
+ end
+
+ def switch_name
+ @switch_name ||= dasherized? ? name : dasherize(name)
+ end
+
+ def human_name
+ @human_name ||= dasherized? ? undasherize(name) : name
+ end
+
+ def usage(padding = 0)
+ sample = if banner && !banner.to_s.empty?
+ "#{switch_name}=#{banner}".dup
+ else
+ switch_name
+ end
+
+ sample = "[#{sample}]".dup unless required?
+
+ if boolean?
+ sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
+ end
+
+ if aliases.empty?
+ (" " * padding) << sample
+ else
+ "#{aliases.join(', ')}, #{sample}"
+ end
+ end
+
+ VALID_TYPES.each do |type|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{type}?
+ self.type == #{type.inspect}
+ end
+ RUBY
+ end
+
+ protected
+
+ def validate!
+ raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
+ validate_default_type! if @check_default_type
+ end
+
+ def validate_default_type!
+ default_type = case @default
+ when nil
+ return
+ when TrueClass, FalseClass
+ required? ? :string : :boolean
+ when Numeric
+ :numeric
+ when Symbol
+ :string
+ when Hash, Array, String
+ @default.class.name.downcase.to_sym
+ end
+
+ raise ArgumentError, "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
+ end
+
+ def dasherized?
+ name.index("-") == 0
+ end
+
+ def undasherize(str)
+ str.sub(/^-{1,2}/, "")
+ end
+
+ def dasherize(str)
+ (str.length > 1 ? "--" : "-") + str.tr("_", "-")
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb
new file mode 100644
index 0000000000..70f6366842
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb
@@ -0,0 +1,221 @@
+class Bundler::Thor
+ class Options < Arguments #:nodoc: # rubocop:disable ClassLength
+ LONG_RE = /^(--\w+(?:-\w+)*)$/
+ SHORT_RE = /^(-[a-z])$/i
+ EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
+ SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
+ SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
+ OPTS_END = "--".freeze
+
+ # Receives a hash and makes it switches.
+ def self.to_switches(options)
+ options.map do |key, value|
+ case value
+ when true
+ "--#{key}"
+ when Array
+ "--#{key} #{value.map(&:inspect).join(' ')}"
+ when Hash
+ "--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}"
+ when nil, false
+ nil
+ else
+ "--#{key} #{value.inspect}"
+ end
+ end.compact.join(" ")
+ end
+
+ # Takes a hash of Bundler::Thor::Option and a hash with defaults.
+ #
+ # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
+ # an unknown option or a regular argument.
+ def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false)
+ @stop_on_unknown = stop_on_unknown
+ @disable_required_check = disable_required_check
+ options = hash_options.values
+ super(options)
+
+ # Add defaults
+ defaults.each do |key, value|
+ @assigns[key.to_s] = value
+ @non_assigned_required.delete(hash_options[key])
+ end
+
+ @shorts = {}
+ @switches = {}
+ @extra = []
+
+ options.each do |option|
+ @switches[option.switch_name] = option
+
+ option.aliases.each do |short|
+ name = short.to_s.sub(/^(?!\-)/, "-")
+ @shorts[name] ||= option.switch_name
+ end
+ end
+ end
+
+ def remaining
+ @extra
+ end
+
+ def peek
+ return super unless @parsing_options
+
+ result = super
+ if result == OPTS_END
+ shift
+ @parsing_options = false
+ super
+ else
+ result
+ end
+ end
+
+ def parse(args) # rubocop:disable MethodLength
+ @pile = args.dup
+ @parsing_options = true
+
+ while peek
+ if parsing_options?
+ match, is_switch = current_is_switch?
+ shifted = shift
+
+ if is_switch
+ case shifted
+ when SHORT_SQ_RE
+ unshift($1.split("").map { |f| "-#{f}" })
+ next
+ when EQ_RE, SHORT_NUM
+ unshift($2)
+ switch = $1
+ when LONG_RE, SHORT_RE
+ switch = $1
+ end
+
+ switch = normalize_switch(switch)
+ option = switch_option(switch)
+ @assigns[option.human_name] = parse_peek(switch, option)
+ elsif @stop_on_unknown
+ @parsing_options = false
+ @extra << shifted
+ @extra << shift while peek
+ break
+ elsif match
+ @extra << shifted
+ @extra << shift while peek && peek !~ /^-/
+ else
+ @extra << shifted
+ end
+ else
+ @extra << shift
+ end
+ end
+
+ check_requirement! unless @disable_required_check
+
+ assigns = Bundler::Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
+ assigns.freeze
+ assigns
+ end
+
+ def check_unknown!
+ # an unknown option starts with - or -- and has no more --'s afterward.
+ unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ }
+ raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty?
+ end
+
+ protected
+
+ # Check if the current value in peek is a registered switch.
+ #
+ # Two booleans are returned. The first is true if the current value
+ # starts with a hyphen; the second is true if it is a registered switch.
+ def current_is_switch?
+ case peek
+ when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
+ [true, switch?($1)]
+ when SHORT_SQ_RE
+ [true, $1.split("").any? { |f| switch?("-#{f}") }]
+ else
+ [false, false]
+ end
+ end
+
+ def current_is_switch_formatted?
+ case peek
+ when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
+ true
+ else
+ false
+ end
+ end
+
+ def current_is_value?
+ peek && (!parsing_options? || super)
+ end
+
+ def switch?(arg)
+ switch_option(normalize_switch(arg))
+ end
+
+ def switch_option(arg)
+ if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition
+ @switches[arg] || @switches["--#{match}"]
+ else
+ @switches[arg]
+ end
+ end
+
+ # Check if the given argument is actually a shortcut.
+ #
+ def normalize_switch(arg)
+ (@shorts[arg] || arg).tr("_", "-")
+ end
+
+ def parsing_options?
+ peek
+ @parsing_options
+ end
+
+ # Parse boolean values which can be given as --foo=true, --foo or --no-foo.
+ #
+ def parse_boolean(switch)
+ if current_is_value?
+ if ["true", "TRUE", "t", "T", true].include?(peek)
+ shift
+ true
+ elsif ["false", "FALSE", "f", "F", false].include?(peek)
+ shift
+ false
+ else
+ !no_or_skip?(switch)
+ end
+ else
+ @switches.key?(switch) || !no_or_skip?(switch)
+ end
+ end
+
+ # Parse the value at the peek analyzing if it requires an input or not.
+ #
+ def parse_peek(switch, option)
+ if parsing_options? && (current_is_switch_formatted? || last?)
+ if option.boolean?
+ # No problem for boolean types
+ elsif no_or_skip?(switch)
+ return nil # User set value to nil
+ elsif option.string? && !option.required?
+ # Return the default if there is one, else the human name
+ return option.lazy_default || option.default || option.human_name
+ elsif option.lazy_default
+ return option.lazy_default
+ else
+ raise MalformattedArgumentError, "No value provided for option '#{switch}'"
+ end
+ end
+
+ @non_assigned_required.delete(option)
+ send(:"parse_#{option.type}", switch)
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/rake_compat.rb b/lib/bundler/vendor/thor/lib/thor/rake_compat.rb
new file mode 100644
index 0000000000..60282e2991
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/rake_compat.rb
@@ -0,0 +1,71 @@
+require "rake"
+require "rake/dsl_definition"
+
+class Bundler::Thor
+ # Adds a compatibility layer to your Bundler::Thor classes which allows you to use
+ # rake package tasks. For example, to use rspec rake tasks, one can do:
+ #
+ # require 'bundler/vendor/thor/lib/thor/rake_compat'
+ # require 'rspec/core/rake_task'
+ #
+ # class Default < Bundler::Thor
+ # include Bundler::Thor::RakeCompat
+ #
+ # RSpec::Core::RakeTask.new(:spec) do |t|
+ # t.spec_opts = ['--options', './.rspec']
+ # t.spec_files = FileList['spec/**/*_spec.rb']
+ # end
+ # end
+ #
+ module RakeCompat
+ include Rake::DSL if defined?(Rake::DSL)
+
+ def self.rake_classes
+ @rake_classes ||= []
+ end
+
+ def self.included(base)
+ # Hack. Make rakefile point to invoker, so rdoc task is generated properly.
+ rakefile = File.basename(caller[0].match(/(.*):\d+/)[1])
+ Rake.application.instance_variable_set(:@rakefile, rakefile)
+ rake_classes << base
+ end
+ end
+end
+
+# override task on (main), for compatibility with Rake 0.9
+instance_eval do
+ alias rake_namespace namespace
+
+ def task(*)
+ task = super
+
+ if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition
+ non_namespaced_name = task.name.split(":").last
+
+ description = non_namespaced_name
+ description << task.arg_names.map { |n| n.to_s.upcase }.join(" ")
+ description.strip!
+
+ klass.desc description, Rake.application.last_description || non_namespaced_name
+ Rake.application.last_description = nil
+ klass.send :define_method, non_namespaced_name do |*args|
+ Rake::Task[task.name.to_sym].invoke(*args)
+ end
+ end
+
+ task
+ end
+
+ def namespace(name)
+ if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition
+ const_name = Bundler::Thor::Util.camel_case(name.to_s).to_sym
+ klass.const_set(const_name, Class.new(Bundler::Thor))
+ new_klass = klass.const_get(const_name)
+ Bundler::Thor::RakeCompat.rake_classes << new_klass
+ end
+
+ super
+ Bundler::Thor::RakeCompat.rake_classes.pop
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb
new file mode 100644
index 0000000000..b110b8d478
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/runner.rb
@@ -0,0 +1,324 @@
+require "bundler/vendor/thor/lib/thor"
+require "bundler/vendor/thor/lib/thor/group"
+require "bundler/vendor/thor/lib/thor/core_ext/io_binary_read"
+
+require "yaml"
+require "digest"
+require "pathname"
+
+class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLength
+ map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
+
+ def self.banner(command, all = false, subcommand = false)
+ "thor " + command.formatted_usage(self, all, subcommand)
+ end
+
+ def self.exit_on_failure?
+ true
+ end
+
+ # Override Bundler::Thor#help so it can give information about any class and any method.
+ #
+ def help(meth = nil)
+ if meth && !respond_to?(meth)
+ initialize_thorfiles(meth)
+ klass, command = Bundler::Thor::Util.find_class_and_command_by_namespace(meth)
+ self.class.handle_no_command_error(command, false) if klass.nil?
+ klass.start(["-h", command].compact, :shell => shell)
+ else
+ super
+ end
+ end
+
+ # If a command is not found on Bundler::Thor::Runner, method missing is invoked and
+ # Bundler::Thor::Runner is then responsible for finding the command in all classes.
+ #
+ def method_missing(meth, *args)
+ meth = meth.to_s
+ initialize_thorfiles(meth)
+ klass, command = Bundler::Thor::Util.find_class_and_command_by_namespace(meth)
+ self.class.handle_no_command_error(command, false) if klass.nil?
+ args.unshift(command) if command
+ klass.start(args, :shell => shell)
+ end
+
+ desc "install NAME", "Install an optionally named Bundler::Thor file into your system commands"
+ method_options :as => :string, :relative => :boolean, :force => :boolean
+ def install(name) # rubocop:disable MethodLength
+ initialize_thorfiles
+
+ # If a directory name is provided as the argument, look for a 'main.thor'
+ # command in said directory.
+ begin
+ if File.directory?(File.expand_path(name))
+ base = File.join(name, "main.thor")
+ package = :directory
+ contents = open(base, &:read)
+ else
+ base = name
+ package = :file
+ contents = open(name, &:read)
+ end
+ rescue OpenURI::HTTPError
+ raise Error, "Error opening URI '#{name}'"
+ rescue Errno::ENOENT
+ raise Error, "Error opening file '#{name}'"
+ end
+
+ say "Your Bundler::Thorfile contains:"
+ say contents
+
+ unless options["force"]
+ return false if no?("Do you wish to continue [y/N]?")
+ end
+
+ as = options["as"] || begin
+ first_line = contents.split("\n")[0]
+ (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
+ end
+
+ unless as
+ basename = File.basename(name)
+ as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
+ as = basename if as.empty?
+ end
+
+ location = if options[:relative] || name =~ %r{^https?://}
+ name
+ else
+ File.expand_path(name)
+ end
+
+ thor_yaml[as] = {
+ :filename => Digest(:MD5).hexdigest(name + as),
+ :location => location,
+ :namespaces => Bundler::Thor::Util.namespaces_in_content(contents, base)
+ }
+
+ save_yaml(thor_yaml)
+ say "Storing thor file in your system repository"
+ destination = File.join(thor_root, thor_yaml[as][:filename])
+
+ if package == :file
+ File.open(destination, "w") { |f| f.puts contents }
+ else
+ require "fileutils"
+ FileUtils.cp_r(name, destination)
+ end
+
+ thor_yaml[as][:filename] # Indicate success
+ end
+
+ desc "version", "Show Bundler::Thor version"
+ def version
+ require "bundler/vendor/thor/lib/thor/version"
+ say "Bundler::Thor #{Bundler::Thor::VERSION}"
+ end
+
+ desc "uninstall NAME", "Uninstall a named Bundler::Thor module"
+ def uninstall(name)
+ raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
+ say "Uninstalling #{name}."
+ require "fileutils"
+ FileUtils.rm_rf(File.join(thor_root, (thor_yaml[name][:filename]).to_s))
+
+ thor_yaml.delete(name)
+ save_yaml(thor_yaml)
+
+ puts "Done."
+ end
+
+ desc "update NAME", "Update a Bundler::Thor file from its original location"
+ def update(name)
+ raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
+
+ say "Updating '#{name}' from #{thor_yaml[name][:location]}"
+
+ old_filename = thor_yaml[name][:filename]
+ self.options = options.merge("as" => name)
+
+ if File.directory? File.expand_path(name)
+ require "fileutils"
+ FileUtils.rm_rf(File.join(thor_root, old_filename))
+
+ thor_yaml.delete(old_filename)
+ save_yaml(thor_yaml)
+
+ filename = install(name)
+ else
+ filename = install(thor_yaml[name][:location])
+ end
+
+ File.delete(File.join(thor_root, old_filename)) unless filename == old_filename
+ end
+
+ desc "installed", "List the installed Bundler::Thor modules and commands"
+ method_options :internal => :boolean
+ def installed
+ initialize_thorfiles(nil, true)
+ display_klasses(true, options["internal"])
+ end
+
+ desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)"
+ method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
+ def list(search = "")
+ initialize_thorfiles
+
+ search = ".*#{search}" if options["substring"]
+ search = /^#{search}.*/i
+ group = options[:group] || "standard"
+
+ klasses = Bundler::Thor::Base.subclasses.select do |k|
+ (options[:all] || k.group == group) && k.namespace =~ search
+ end
+
+ display_klasses(false, false, klasses)
+ end
+
+private
+
+ def thor_root
+ Bundler::Thor::Util.thor_root
+ end
+
+ def thor_yaml
+ @thor_yaml ||= begin
+ yaml_file = File.join(thor_root, "thor.yml")
+ yaml = YAML.load_file(yaml_file) if File.exist?(yaml_file)
+ yaml || {}
+ end
+ end
+
+ # Save the yaml file. If none exists in thor root, creates one.
+ #
+ def save_yaml(yaml)
+ yaml_file = File.join(thor_root, "thor.yml")
+
+ unless File.exist?(yaml_file)
+ require "fileutils"
+ FileUtils.mkdir_p(thor_root)
+ yaml_file = File.join(thor_root, "thor.yml")
+ FileUtils.touch(yaml_file)
+ end
+
+ File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
+ end
+
+ # Load the Bundler::Thorfiles. If relevant_to is supplied, looks for specific files
+ # in the thor_root instead of loading them all.
+ #
+ # By default, it also traverses the current path until find Bundler::Thor files, as
+ # described in thorfiles. This look up can be skipped by supplying
+ # skip_lookup true.
+ #
+ def initialize_thorfiles(relevant_to = nil, skip_lookup = false)
+ thorfiles(relevant_to, skip_lookup).each do |f|
+ Bundler::Thor::Util.load_thorfile(f, nil, options[:debug]) unless Bundler::Thor::Base.subclass_files.keys.include?(File.expand_path(f))
+ end
+ end
+
+ # Finds Bundler::Thorfiles by traversing from your current directory down to the root
+ # directory of your system. If at any time we find a Bundler::Thor file, we stop.
+ #
+ # We also ensure that system-wide Bundler::Thorfiles are loaded first, so local
+ # Bundler::Thorfiles can override them.
+ #
+ # ==== Example
+ #
+ # If we start at /Users/wycats/dev/thor ...
+ #
+ # 1. /Users/wycats/dev/thor
+ # 2. /Users/wycats/dev
+ # 3. /Users/wycats <-- we find a Bundler::Thorfile here, so we stop
+ #
+ # Suppose we start at c:\Documents and Settings\james\dev\thor ...
+ #
+ # 1. c:\Documents and Settings\james\dev\thor
+ # 2. c:\Documents and Settings\james\dev
+ # 3. c:\Documents and Settings\james
+ # 4. c:\Documents and Settings
+ # 5. c:\ <-- no Bundler::Thorfiles found!
+ #
+ def thorfiles(relevant_to = nil, skip_lookup = false)
+ thorfiles = []
+
+ unless skip_lookup
+ Pathname.pwd.ascend do |path|
+ thorfiles = Bundler::Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten
+ break unless thorfiles.empty?
+ end
+ end
+
+ files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Bundler::Thor::Util.thor_root_glob)
+ files += thorfiles
+ files -= ["#{thor_root}/thor.yml"]
+
+ files.map! do |file|
+ File.directory?(file) ? File.join(file, "main.thor") : file
+ end
+ end
+
+ # Load Bundler::Thorfiles relevant to the given method. If you provide "foo:bar" it
+ # will load all thor files in the thor.yaml that has "foo" e "foo:bar"
+ # namespaces registered.
+ #
+ def thorfiles_relevant_to(meth)
+ lookup = [meth, meth.split(":")[0...-1].join(":")]
+
+ files = thor_yaml.select do |_, v|
+ v[:namespaces] && !(v[:namespaces] & lookup).empty?
+ end
+
+ files.map { |_, v| File.join(thor_root, (v[:filename]).to_s) }
+ end
+
+ # Display information about the given klasses. If with_module is given,
+ # it shows a table with information extracted from the yaml file.
+ #
+ def display_klasses(with_modules = false, show_internal = false, klasses = Bundler::Thor::Base.subclasses)
+ klasses -= [Bundler::Thor, Bundler::Thor::Runner, Bundler::Thor::Group] unless show_internal
+
+ raise Error, "No Bundler::Thor commands available" if klasses.empty?
+ show_modules if with_modules && !thor_yaml.empty?
+
+ list = Hash.new { |h, k| h[k] = [] }
+ groups = klasses.select { |k| k.ancestors.include?(Bundler::Thor::Group) }
+
+ # Get classes which inherit from Bundler::Thor
+ (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_commands(false) }
+
+ # Get classes which inherit from Bundler::Thor::Base
+ groups.map! { |k| k.printable_commands(false).first }
+ list["root"] = groups
+
+ # Order namespaces with default coming first
+ list = list.sort { |a, b| a[0].sub(/^default/, "") <=> b[0].sub(/^default/, "") }
+ list.each { |n, commands| display_commands(n, commands) unless commands.empty? }
+ end
+
+ def display_commands(namespace, list) #:nodoc:
+ list.sort! { |a, b| a[0] <=> b[0] }
+
+ say shell.set_color(namespace, :blue, true)
+ say "-" * namespace.size
+
+ print_table(list, :truncate => true)
+ say
+ end
+ alias_method :display_tasks, :display_commands
+
+ def show_modules #:nodoc:
+ info = []
+ labels = %w(Modules Namespaces)
+
+ info << labels
+ info << ["-" * labels[0].size, "-" * labels[1].size]
+
+ thor_yaml.each do |name, hash|
+ info << [name, hash[:namespaces].join(", ")]
+ end
+
+ print_table info
+ say ""
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/shell.rb b/lib/bundler/vendor/thor/lib/thor/shell.rb
new file mode 100644
index 0000000000..e945549324
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/shell.rb
@@ -0,0 +1,81 @@
+require "rbconfig"
+
+class Bundler::Thor
+ module Base
+ class << self
+ attr_writer :shell
+
+ # Returns the shell used in all Bundler::Thor classes. If you are in a Unix platform
+ # it will use a colored log, otherwise it will use a basic one without color.
+ #
+ def shell
+ @shell ||= if ENV["THOR_SHELL"] && !ENV["THOR_SHELL"].empty?
+ Bundler::Thor::Shell.const_get(ENV["THOR_SHELL"])
+ elsif RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ && !ENV["ANSICON"]
+ Bundler::Thor::Shell::Basic
+ else
+ Bundler::Thor::Shell::Color
+ end
+ end
+ end
+ end
+
+ module Shell
+ SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
+ attr_writer :shell
+
+ autoload :Basic, "bundler/vendor/thor/lib/thor/shell/basic"
+ autoload :Color, "bundler/vendor/thor/lib/thor/shell/color"
+ autoload :HTML, "bundler/vendor/thor/lib/thor/shell/html"
+
+ # Add shell to initialize config values.
+ #
+ # ==== Configuration
+ # shell<Object>:: An instance of the shell to be used.
+ #
+ # ==== Examples
+ #
+ # class MyScript < Bundler::Thor
+ # argument :first, :type => :numeric
+ # end
+ #
+ # MyScript.new [1.0], { :foo => :bar }, :shell => Bundler::Thor::Shell::Basic.new
+ #
+ def initialize(args = [], options = {}, config = {})
+ super
+ self.shell = config[:shell]
+ shell.base ||= self if shell.respond_to?(:base)
+ end
+
+ # Holds the shell for the given Bundler::Thor instance. If no shell is given,
+ # it gets a default shell from Bundler::Thor::Base.shell.
+ def shell
+ @shell ||= Bundler::Thor::Base.shell.new
+ end
+
+ # Common methods that are delegated to the shell.
+ SHELL_DELEGATED_METHODS.each do |method|
+ module_eval <<-METHOD, __FILE__, __LINE__
+ def #{method}(*args,&block)
+ shell.#{method}(*args,&block)
+ end
+ METHOD
+ end
+
+ # Yields the given block with padding.
+ def with_padding
+ shell.padding += 1
+ yield
+ ensure
+ shell.padding -= 1
+ end
+
+ protected
+
+ # Allow shell to be shared between invocations.
+ #
+ def _shared_configuration #:nodoc:
+ super.merge!(:shell => shell)
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
new file mode 100644
index 0000000000..5162390efd
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
@@ -0,0 +1,437 @@
+class Bundler::Thor
+ module Shell
+ class Basic
+ attr_accessor :base
+ attr_reader :padding
+
+ # Initialize base, mute and padding to nil.
+ #
+ def initialize #:nodoc:
+ @base = nil
+ @mute = false
+ @padding = 0
+ @always_force = false
+ end
+
+ # Mute everything that's inside given block
+ #
+ def mute
+ @mute = true
+ yield
+ ensure
+ @mute = false
+ end
+
+ # Check if base is muted
+ #
+ def mute?
+ @mute
+ end
+
+ # Sets the output padding, not allowing less than zero values.
+ #
+ def padding=(value)
+ @padding = [0, value].max
+ end
+
+ # Sets the output padding while executing a block and resets it.
+ #
+ def indent(count = 1)
+ orig_padding = padding
+ self.padding = padding + count
+ yield
+ self.padding = orig_padding
+ end
+
+ # Asks something to the user and receives a response.
+ #
+ # If asked to limit the correct responses, you can pass in an
+ # array of acceptable answers. If one of those is not supplied,
+ # they will be shown a message stating that one of those answers
+ # must be given and re-asked the question.
+ #
+ # If asking for sensitive information, the :echo option can be set
+ # to false to mask user input from $stdin.
+ #
+ # If the required input is a path, then set the path option to
+ # true. This will enable tab completion for file paths relative
+ # to the current working directory on systems that support
+ # Readline.
+ #
+ # ==== Example
+ # ask("What is your name?")
+ #
+ # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
+ #
+ # ask("What is your password?", :echo => false)
+ #
+ # ask("Where should the file be saved?", :path => true)
+ #
+ def ask(statement, *args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ color = args.first
+
+ if options[:limited_to]
+ ask_filtered(statement, color, options)
+ else
+ ask_simply(statement, color, options)
+ end
+ end
+
+ # Say (print) something to the user. If the sentence ends with a whitespace
+ # or tab character, a new line is not appended (print + flush). Otherwise
+ # are passed straight to puts (behavior got from Highline).
+ #
+ # ==== Example
+ # say("I know you knew that.")
+ #
+ def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
+ buffer = prepare_message(message, *color)
+ buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
+
+ stdout.print(buffer)
+ stdout.flush
+ end
+
+ # Say a status with the given color and appends the message. Since this
+ # method is used frequently by actions, it allows nil or false to be given
+ # in log_status, avoiding the message from being shown. If a Symbol is
+ # given in log_status, it's used as the color.
+ #
+ def say_status(status, message, log_status = true)
+ return if quiet? || log_status == false
+ spaces = " " * (padding + 1)
+ color = log_status.is_a?(Symbol) ? log_status : :green
+
+ status = status.to_s.rjust(12)
+ status = set_color status, color, true if color
+
+ buffer = "#{status}#{spaces}#{message}"
+ buffer = "#{buffer}\n" unless buffer.end_with?("\n")
+
+ stdout.print(buffer)
+ stdout.flush
+ end
+
+ # Make a question the to user and returns true if the user replies "y" or
+ # "yes".
+ #
+ def yes?(statement, color = nil)
+ !!(ask(statement, color, :add_to_history => false) =~ is?(:yes))
+ end
+
+ # Make a question the to user and returns true if the user replies "n" or
+ # "no".
+ #
+ def no?(statement, color = nil)
+ !!(ask(statement, color, :add_to_history => false) =~ is?(:no))
+ end
+
+ # Prints values in columns
+ #
+ # ==== Parameters
+ # Array[String, String, ...]
+ #
+ def print_in_columns(array)
+ return if array.empty?
+ colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
+ array.each_with_index do |value, index|
+ # Don't output trailing spaces when printing the last column
+ if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
+ stdout.puts value
+ else
+ stdout.printf("%-#{colwidth}s", value)
+ end
+ end
+ end
+
+ # Prints a table.
+ #
+ # ==== Parameters
+ # Array[Array[String, String, ...]]
+ #
+ # ==== Options
+ # indent<Integer>:: Indent the first column by indent value.
+ # colwidth<Integer>:: Force the first column to colwidth spaces wide.
+ #
+ def print_table(array, options = {}) # rubocop:disable MethodLength
+ return if array.empty?
+
+ formats = []
+ indent = options[:indent].to_i
+ colwidth = options[:colwidth]
+ options[:truncate] = terminal_width if options[:truncate] == true
+
+ formats << "%-#{colwidth + 2}s".dup if colwidth
+ start = colwidth ? 1 : 0
+
+ colcount = array.max { |a, b| a.size <=> b.size }.size
+
+ maximas = []
+
+ start.upto(colcount - 1) do |index|
+ maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
+ maximas << maxima
+ formats << if index == colcount - 1
+ # Don't output 2 trailing spaces when printing the last column
+ "%-s".dup
+ else
+ "%-#{maxima + 2}s".dup
+ end
+ end
+
+ formats[0] = formats[0].insert(0, " " * indent)
+ formats << "%s"
+
+ array.each do |row|
+ sentence = "".dup
+
+ row.each_with_index do |column, index|
+ maxima = maximas[index]
+
+ f = if column.is_a?(Numeric)
+ if index == row.size - 1
+ # Don't output 2 trailing spaces when printing the last column
+ "%#{maxima}s"
+ else
+ "%#{maxima}s "
+ end
+ else
+ formats[index]
+ end
+ sentence << f % column.to_s
+ end
+
+ sentence = truncate(sentence, options[:truncate]) if options[:truncate]
+ stdout.puts sentence
+ end
+ end
+
+ # Prints a long string, word-wrapping the text to the current width of the
+ # terminal display. Ideal for printing heredocs.
+ #
+ # ==== Parameters
+ # String
+ #
+ # ==== Options
+ # indent<Integer>:: Indent each line of the printed paragraph by indent value.
+ #
+ def print_wrapped(message, options = {})
+ indent = options[:indent] || 0
+ width = terminal_width - indent
+ paras = message.split("\n\n")
+
+ paras.map! do |unwrapped|
+ unwrapped.strip.tr("\n", " ").squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }
+ end
+
+ paras.each do |para|
+ para.split("\n").each do |line|
+ stdout.puts line.insert(0, " " * indent)
+ end
+ stdout.puts unless para == paras.last
+ end
+ end
+
+ # Deals with file collision and returns true if the file should be
+ # overwritten and false otherwise. If a block is given, it uses the block
+ # response as the content for the diff.
+ #
+ # ==== Parameters
+ # destination<String>:: the destination file to solve conflicts
+ # block<Proc>:: an optional block that returns the value to be used in diff
+ #
+ def file_collision(destination)
+ return true if @always_force
+ options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
+
+ loop do
+ answer = ask(
+ %[Overwrite #{destination}? (enter "h" for help) #{options}],
+ :add_to_history => false
+ )
+
+ case answer
+ when nil
+ say ""
+ return true
+ when is?(:yes), is?(:force), ""
+ return true
+ when is?(:no), is?(:skip)
+ return false
+ when is?(:always)
+ return @always_force = true
+ when is?(:quit)
+ say "Aborting..."
+ raise SystemExit
+ when is?(:diff)
+ show_diff(destination, yield) if block_given?
+ say "Retrying..."
+ else
+ say file_collision_help
+ end
+ end
+ end
+
+ # This code was copied from Rake, available under MIT-LICENSE
+ # Copyright (c) 2003, 2004 Jim Weirich
+ def terminal_width
+ result = if ENV["THOR_COLUMNS"]
+ ENV["THOR_COLUMNS"].to_i
+ else
+ unix? ? dynamic_width : 80
+ end
+ result < 10 ? 80 : result
+ rescue
+ 80
+ end
+
+ # Called if something goes wrong during the execution. This is used by Bundler::Thor
+ # internally and should not be used inside your scripts. If something went
+ # wrong, you can always raise an exception. If you raise a Bundler::Thor::Error, it
+ # will be rescued and wrapped in the method below.
+ #
+ def error(statement)
+ stderr.puts statement
+ end
+
+ # Apply color to the given string with optional bold. Disabled in the
+ # Bundler::Thor::Shell::Basic class.
+ #
+ def set_color(string, *) #:nodoc:
+ string
+ end
+
+ protected
+
+ def prepare_message(message, *color)
+ spaces = " " * padding
+ spaces + set_color(message.to_s, *color)
+ end
+
+ def can_display_colors?
+ false
+ end
+
+ def lookup_color(color)
+ return color unless color.is_a?(Symbol)
+ self.class.const_get(color.to_s.upcase)
+ end
+
+ def stdout
+ $stdout
+ end
+
+ def stderr
+ $stderr
+ end
+
+ def is?(value) #:nodoc:
+ value = value.to_s
+
+ if value.size == 1
+ /\A#{value}\z/i
+ else
+ /\A(#{value}|#{value[0, 1]})\z/i
+ end
+ end
+
+ def file_collision_help #:nodoc:
+ <<-HELP
+ Y - yes, overwrite
+ n - no, do not overwrite
+ a - all, overwrite this and all others
+ q - quit, abort
+ d - diff, show the differences between the old and the new
+ h - help, show this help
+ HELP
+ end
+
+ def show_diff(destination, content) #:nodoc:
+ diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u"
+
+ require "tempfile"
+ Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
+ temp.write content
+ temp.rewind
+ system %(#{diff_cmd} "#{destination}" "#{temp.path}")
+ end
+ end
+
+ def quiet? #:nodoc:
+ mute? || (base && base.options[:quiet])
+ end
+
+ # Calculate the dynamic width of the terminal
+ def dynamic_width
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
+ end
+
+ def dynamic_width_stty
+ `stty size 2>/dev/null`.split[1].to_i
+ end
+
+ def dynamic_width_tput
+ `tput cols 2>/dev/null`.to_i
+ end
+
+ def unix?
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
+ end
+
+ def truncate(string, width)
+ as_unicode do
+ chars = string.chars.to_a
+ if chars.length <= width
+ chars.join
+ else
+ chars[0, width - 3].join + "..."
+ end
+ end
+ end
+
+ if "".respond_to?(:encode)
+ def as_unicode
+ yield
+ end
+ else
+ def as_unicode
+ old = $KCODE
+ $KCODE = "U"
+ yield
+ ensure
+ $KCODE = old
+ end
+ end
+
+ def ask_simply(statement, color, options)
+ default = options[:default]
+ message = [statement, ("(#{default})" if default), nil].uniq.join(" ")
+ message = prepare_message(message, *color)
+ result = Bundler::Thor::LineEditor.readline(message, options)
+
+ return unless result
+
+ result = result.strip
+
+ if default && result == ""
+ default
+ else
+ result
+ end
+ end
+
+ def ask_filtered(statement, color, options)
+ answer_set = options[:limited_to]
+ correct_answer = nil
+ until correct_answer
+ answers = answer_set.join(", ")
+ answer = ask_simply("#{statement} [#{answers}]", color, options)
+ correct_answer = answer_set.include?(answer) ? answer : nil
+ say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
+ end
+ correct_answer
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/shell/color.rb b/lib/bundler/vendor/thor/lib/thor/shell/color.rb
new file mode 100644
index 0000000000..da289cb50c
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/shell/color.rb
@@ -0,0 +1,149 @@
+require "bundler/vendor/thor/lib/thor/shell/basic"
+
+class Bundler::Thor
+ module Shell
+ # Inherit from Bundler::Thor::Shell::Basic and add set_color behavior. Check
+ # Bundler::Thor::Shell::Basic to see all available methods.
+ #
+ class Color < Basic
+ # Embed in a String to clear all previous ANSI sequences.
+ CLEAR = "\e[0m"
+ # The start of an ANSI bold sequence.
+ BOLD = "\e[1m"
+
+ # Set the terminal's foreground ANSI color to black.
+ BLACK = "\e[30m"
+ # Set the terminal's foreground ANSI color to red.
+ RED = "\e[31m"
+ # Set the terminal's foreground ANSI color to green.
+ GREEN = "\e[32m"
+ # Set the terminal's foreground ANSI color to yellow.
+ YELLOW = "\e[33m"
+ # Set the terminal's foreground ANSI color to blue.
+ BLUE = "\e[34m"
+ # Set the terminal's foreground ANSI color to magenta.
+ MAGENTA = "\e[35m"
+ # Set the terminal's foreground ANSI color to cyan.
+ CYAN = "\e[36m"
+ # Set the terminal's foreground ANSI color to white.
+ WHITE = "\e[37m"
+
+ # Set the terminal's background ANSI color to black.
+ ON_BLACK = "\e[40m"
+ # Set the terminal's background ANSI color to red.
+ ON_RED = "\e[41m"
+ # Set the terminal's background ANSI color to green.
+ ON_GREEN = "\e[42m"
+ # Set the terminal's background ANSI color to yellow.
+ ON_YELLOW = "\e[43m"
+ # Set the terminal's background ANSI color to blue.
+ ON_BLUE = "\e[44m"
+ # Set the terminal's background ANSI color to magenta.
+ ON_MAGENTA = "\e[45m"
+ # Set the terminal's background ANSI color to cyan.
+ ON_CYAN = "\e[46m"
+ # Set the terminal's background ANSI color to white.
+ ON_WHITE = "\e[47m"
+
+ # Set color by using a string or one of the defined constants. If a third
+ # option is set to true, it also adds bold to the string. This is based
+ # on Highline implementation and it automatically appends CLEAR to the end
+ # of the returned String.
+ #
+ # Pass foreground, background and bold options to this method as
+ # symbols.
+ #
+ # Example:
+ #
+ # set_color "Hi!", :red, :on_white, :bold
+ #
+ # The available colors are:
+ #
+ # :bold
+ # :black
+ # :red
+ # :green
+ # :yellow
+ # :blue
+ # :magenta
+ # :cyan
+ # :white
+ # :on_black
+ # :on_red
+ # :on_green
+ # :on_yellow
+ # :on_blue
+ # :on_magenta
+ # :on_cyan
+ # :on_white
+ def set_color(string, *colors)
+ if colors.compact.empty? || !can_display_colors?
+ string
+ elsif colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
+ ansi_colors = colors.map { |color| lookup_color(color) }
+ "#{ansi_colors.join}#{string}#{CLEAR}"
+ else
+ # The old API was `set_color(color, bold=boolean)`. We
+ # continue to support the old API because you should never
+ # break old APIs unnecessarily :P
+ foreground, bold = colors
+ foreground = self.class.const_get(foreground.to_s.upcase) if foreground.is_a?(Symbol)
+
+ bold = bold ? BOLD : ""
+ "#{bold}#{foreground}#{string}#{CLEAR}"
+ end
+ end
+
+ protected
+
+ def can_display_colors?
+ stdout.tty?
+ end
+
+ # Overwrite show_diff to show diff with colors if Diff::LCS is
+ # available.
+ #
+ def show_diff(destination, content) #:nodoc:
+ if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
+ actual = File.binread(destination).to_s.split("\n")
+ content = content.to_s.split("\n")
+
+ Diff::LCS.sdiff(actual, content).each do |diff|
+ output_diff_line(diff)
+ end
+ else
+ super
+ end
+ end
+
+ def output_diff_line(diff) #:nodoc:
+ case diff.action
+ when "-"
+ say "- #{diff.old_element.chomp}", :red, true
+ when "+"
+ say "+ #{diff.new_element.chomp}", :green, true
+ when "!"
+ say "- #{diff.old_element.chomp}", :red, true
+ say "+ #{diff.new_element.chomp}", :green, true
+ else
+ say " #{diff.old_element.chomp}", nil, true
+ end
+ end
+
+ # Check if Diff::LCS is loaded. If it is, use it to create pretty output
+ # for diff.
+ #
+ def diff_lcs_loaded? #:nodoc:
+ return true if defined?(Diff::LCS)
+ return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
+
+ @diff_lcs_loaded = begin
+ require "diff/lcs"
+ true
+ rescue LoadError
+ false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/shell/html.rb b/lib/bundler/vendor/thor/lib/thor/shell/html.rb
new file mode 100644
index 0000000000..83d2054988
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/shell/html.rb
@@ -0,0 +1,126 @@
+require "bundler/vendor/thor/lib/thor/shell/basic"
+
+class Bundler::Thor
+ module Shell
+ # Inherit from Bundler::Thor::Shell::Basic and add set_color behavior. Check
+ # Bundler::Thor::Shell::Basic to see all available methods.
+ #
+ class HTML < Basic
+ # The start of an HTML bold sequence.
+ BOLD = "font-weight: bold"
+
+ # Set the terminal's foreground HTML color to black.
+ BLACK = "color: black"
+ # Set the terminal's foreground HTML color to red.
+ RED = "color: red"
+ # Set the terminal's foreground HTML color to green.
+ GREEN = "color: green"
+ # Set the terminal's foreground HTML color to yellow.
+ YELLOW = "color: yellow"
+ # Set the terminal's foreground HTML color to blue.
+ BLUE = "color: blue"
+ # Set the terminal's foreground HTML color to magenta.
+ MAGENTA = "color: magenta"
+ # Set the terminal's foreground HTML color to cyan.
+ CYAN = "color: cyan"
+ # Set the terminal's foreground HTML color to white.
+ WHITE = "color: white"
+
+ # Set the terminal's background HTML color to black.
+ ON_BLACK = "background-color: black"
+ # Set the terminal's background HTML color to red.
+ ON_RED = "background-color: red"
+ # Set the terminal's background HTML color to green.
+ ON_GREEN = "background-color: green"
+ # Set the terminal's background HTML color to yellow.
+ ON_YELLOW = "background-color: yellow"
+ # Set the terminal's background HTML color to blue.
+ ON_BLUE = "background-color: blue"
+ # Set the terminal's background HTML color to magenta.
+ ON_MAGENTA = "background-color: magenta"
+ # Set the terminal's background HTML color to cyan.
+ ON_CYAN = "background-color: cyan"
+ # Set the terminal's background HTML color to white.
+ ON_WHITE = "background-color: white"
+
+ # Set color by using a string or one of the defined constants. If a third
+ # option is set to true, it also adds bold to the string. This is based
+ # on Highline implementation and it automatically appends CLEAR to the end
+ # of the returned String.
+ #
+ def set_color(string, *colors)
+ if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
+ html_colors = colors.map { |color| lookup_color(color) }
+ "<span style=\"#{html_colors.join('; ')};\">#{string}</span>"
+ else
+ color, bold = colors
+ html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
+ styles = [html_color]
+ styles << BOLD if bold
+ "<span style=\"#{styles.join('; ')};\">#{string}</span>"
+ end
+ end
+
+ # Ask something to the user and receives a response.
+ #
+ # ==== Example
+ # ask("What is your name?")
+ #
+ # TODO: Implement #ask for Bundler::Thor::Shell::HTML
+ def ask(statement, color = nil)
+ raise NotImplementedError, "Implement #ask for Bundler::Thor::Shell::HTML"
+ end
+
+ protected
+
+ def can_display_colors?
+ true
+ end
+
+ # Overwrite show_diff to show diff with colors if Diff::LCS is
+ # available.
+ #
+ def show_diff(destination, content) #:nodoc:
+ if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
+ actual = File.binread(destination).to_s.split("\n")
+ content = content.to_s.split("\n")
+
+ Diff::LCS.sdiff(actual, content).each do |diff|
+ output_diff_line(diff)
+ end
+ else
+ super
+ end
+ end
+
+ def output_diff_line(diff) #:nodoc:
+ case diff.action
+ when "-"
+ say "- #{diff.old_element.chomp}", :red, true
+ when "+"
+ say "+ #{diff.new_element.chomp}", :green, true
+ when "!"
+ say "- #{diff.old_element.chomp}", :red, true
+ say "+ #{diff.new_element.chomp}", :green, true
+ else
+ say " #{diff.old_element.chomp}", nil, true
+ end
+ end
+
+ # Check if Diff::LCS is loaded. If it is, use it to create pretty output
+ # for diff.
+ #
+ def diff_lcs_loaded? #:nodoc:
+ return true if defined?(Diff::LCS)
+ return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
+
+ @diff_lcs_loaded = begin
+ require "diff/lcs"
+ true
+ rescue LoadError
+ false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/util.rb b/lib/bundler/vendor/thor/lib/thor/util.rb
new file mode 100644
index 0000000000..5d03177a28
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/util.rb
@@ -0,0 +1,268 @@
+require "rbconfig"
+
+class Bundler::Thor
+ module Sandbox #:nodoc:
+ end
+
+ # This module holds several utilities:
+ #
+ # 1) Methods to convert thor namespaces to constants and vice-versa.
+ #
+ # Bundler::Thor::Util.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz"
+ #
+ # 2) Loading thor files and sandboxing:
+ #
+ # Bundler::Thor::Util.load_thorfile("~/.thor/foo")
+ #
+ module Util
+ class << self
+ # Receives a namespace and search for it in the Bundler::Thor::Base subclasses.
+ #
+ # ==== Parameters
+ # namespace<String>:: The namespace to search for.
+ #
+ def find_by_namespace(namespace)
+ namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/
+ Bundler::Thor::Base.subclasses.detect { |klass| klass.namespace == namespace }
+ end
+
+ # Receives a constant and converts it to a Bundler::Thor namespace. Since Bundler::Thor
+ # commands can be added to a sandbox, this method is also responsable for
+ # removing the sandbox namespace.
+ #
+ # This method should not be used in general because it's used to deal with
+ # older versions of Bundler::Thor. On current versions, if you need to get the
+ # namespace from a class, just call namespace on it.
+ #
+ # ==== Parameters
+ # constant<Object>:: The constant to be converted to the thor path.
+ #
+ # ==== Returns
+ # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
+ #
+ def namespace_from_thor_class(constant)
+ constant = constant.to_s.gsub(/^Bundler::Thor::Sandbox::/, "")
+ constant = snake_case(constant).squeeze(":")
+ constant
+ end
+
+ # Given the contents, evaluate it inside the sandbox and returns the
+ # namespaces defined in the sandbox.
+ #
+ # ==== Parameters
+ # contents<String>
+ #
+ # ==== Returns
+ # Array[Object]
+ #
+ def namespaces_in_content(contents, file = __FILE__)
+ old_constants = Bundler::Thor::Base.subclasses.dup
+ Bundler::Thor::Base.subclasses.clear
+
+ load_thorfile(file, contents)
+
+ new_constants = Bundler::Thor::Base.subclasses.dup
+ Bundler::Thor::Base.subclasses.replace(old_constants)
+
+ new_constants.map!(&:namespace)
+ new_constants.compact!
+ new_constants
+ end
+
+ # Returns the thor classes declared inside the given class.
+ #
+ def thor_classes_in(klass)
+ stringfied_constants = klass.constants.map(&:to_s)
+ Bundler::Thor::Base.subclasses.select do |subclass|
+ next unless subclass.name
+ stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", ""))
+ end
+ end
+
+ # Receives a string and convert it to snake case. SnakeCase returns snake_case.
+ #
+ # ==== Parameters
+ # String
+ #
+ # ==== Returns
+ # String
+ #
+ def snake_case(str)
+ return str.downcase if str =~ /^[A-Z_]+$/
+ str.gsub(/\B[A-Z]/, '_\&').squeeze("_") =~ /_*(.*)/
+ $+.downcase
+ end
+
+ # Receives a string and convert it to camel case. camel_case returns CamelCase.
+ #
+ # ==== Parameters
+ # String
+ #
+ # ==== Returns
+ # String
+ #
+ def camel_case(str)
+ return str if str !~ /_/ && str =~ /[A-Z]+.*/
+ str.split("_").map(&:capitalize).join
+ end
+
+ # Receives a namespace and tries to retrieve a Bundler::Thor or Bundler::Thor::Group class
+ # from it. It first searches for a class using the all the given namespace,
+ # if it's not found, removes the highest entry and searches for the class
+ # again. If found, returns the highest entry as the class name.
+ #
+ # ==== Examples
+ #
+ # class Foo::Bar < Bundler::Thor
+ # def baz
+ # end
+ # end
+ #
+ # class Baz::Foo < Bundler::Thor::Group
+ # end
+ #
+ # Bundler::Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default command
+ # Bundler::Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil
+ # Bundler::Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz"
+ #
+ # ==== Parameters
+ # namespace<String>
+ #
+ def find_class_and_command_by_namespace(namespace, fallback = true)
+ if namespace.include?(":") # look for a namespaced command
+ pieces = namespace.split(":")
+ command = pieces.pop
+ klass = Bundler::Thor::Util.find_by_namespace(pieces.join(":"))
+ end
+ unless klass # look for a Bundler::Thor::Group with the right name
+ klass = Bundler::Thor::Util.find_by_namespace(namespace)
+ command = nil
+ end
+ if !klass && fallback # try a command in the default namespace
+ command = namespace
+ klass = Bundler::Thor::Util.find_by_namespace("")
+ end
+ [klass, command]
+ end
+ alias_method :find_class_and_task_by_namespace, :find_class_and_command_by_namespace
+
+ # Receives a path and load the thor file in the path. The file is evaluated
+ # inside the sandbox to avoid namespacing conflicts.
+ #
+ def load_thorfile(path, content = nil, debug = false)
+ content ||= File.binread(path)
+
+ begin
+ Bundler::Thor::Sandbox.class_eval(content, path)
+ rescue StandardError => e
+ $stderr.puts("WARNING: unable to load thorfile #{path.inspect}: #{e.message}")
+ if debug
+ $stderr.puts(*e.backtrace)
+ else
+ $stderr.puts(e.backtrace.first)
+ end
+ end
+ end
+
+ def user_home
+ @@user_home ||= if ENV["HOME"]
+ ENV["HOME"]
+ elsif ENV["USERPROFILE"]
+ ENV["USERPROFILE"]
+ elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"]
+ File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"])
+ elsif ENV["APPDATA"]
+ ENV["APPDATA"]
+ else
+ begin
+ File.expand_path("~")
+ rescue
+ if File::ALT_SEPARATOR
+ "C:/"
+ else
+ "/"
+ end
+ end
+ end
+ end
+
+ # Returns the root where thor files are located, depending on the OS.
+ #
+ def thor_root
+ File.join(user_home, ".thor").tr('\\', "/")
+ end
+
+ # Returns the files in the thor root. On Windows thor_root will be something
+ # like this:
+ #
+ # C:\Documents and Settings\james\.thor
+ #
+ # If we don't #gsub the \ character, Dir.glob will fail.
+ #
+ def thor_root_glob
+ files = Dir["#{escape_globs(thor_root)}/*"]
+
+ files.map! do |file|
+ File.directory?(file) ? File.join(file, "main.thor") : file
+ end
+ end
+
+ # Where to look for Bundler::Thor files.
+ #
+ def globs_for(path)
+ path = escape_globs(path)
+ ["#{path}/Bundler::Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
+ end
+
+ # Return the path to the ruby interpreter taking into account multiple
+ # installations and windows extensions.
+ #
+ def ruby_command
+ @ruby_command ||= begin
+ ruby_name = RbConfig::CONFIG["ruby_install_name"]
+ ruby = File.join(RbConfig::CONFIG["bindir"], ruby_name)
+ ruby << RbConfig::CONFIG["EXEEXT"]
+
+ # avoid using different name than ruby (on platforms supporting links)
+ if ruby_name != "ruby" && File.respond_to?(:readlink)
+ begin
+ alternate_ruby = File.join(RbConfig::CONFIG["bindir"], "ruby")
+ alternate_ruby << RbConfig::CONFIG["EXEEXT"]
+
+ # ruby is a symlink
+ if File.symlink? alternate_ruby
+ linked_ruby = File.readlink alternate_ruby
+
+ # symlink points to 'ruby_install_name'
+ ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby
+ end
+ rescue NotImplementedError # rubocop:disable HandleExceptions
+ # just ignore on windows
+ end
+ end
+
+ # escape string in case path to ruby executable contain spaces.
+ ruby.sub!(/.*\s.*/m, '"\&"')
+ ruby
+ end
+ end
+
+ # Returns a string that has had any glob characters escaped.
+ # The glob characters are `* ? { } [ ]`.
+ #
+ # ==== Examples
+ #
+ # Bundler::Thor::Util.escape_globs('[apps]') # => '\[apps\]'
+ #
+ # ==== Parameters
+ # String
+ #
+ # ==== Returns
+ # String
+ #
+ def escape_globs(path)
+ path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/version.rb b/lib/bundler/vendor/thor/lib/thor/version.rb
new file mode 100644
index 0000000000..df8f18821a
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/version.rb
@@ -0,0 +1,3 @@
+class Bundler::Thor
+ VERSION = "0.20.0"
+end
diff --git a/lib/bundler/vendored_fileutils.rb b/lib/bundler/vendored_fileutils.rb
new file mode 100644
index 0000000000..d14e98baf7
--- /dev/null
+++ b/lib/bundler/vendored_fileutils.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Bundler; end
+if RUBY_VERSION >= "2.4"
+ require "bundler/vendor/fileutils/lib/fileutils"
+else
+ # the version we vendor is 2.4+
+ require "fileutils"
+end
diff --git a/lib/bundler/vendored_molinillo.rb b/lib/bundler/vendored_molinillo.rb
new file mode 100644
index 0000000000..061b634f72
--- /dev/null
+++ b/lib/bundler/vendored_molinillo.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+module Bundler; end
+require "bundler/vendor/molinillo/lib/molinillo"
diff --git a/lib/bundler/vendored_persistent.rb b/lib/bundler/vendored_persistent.rb
new file mode 100644
index 0000000000..de9c42fcc1
--- /dev/null
+++ b/lib/bundler/vendored_persistent.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+# We forcibly require OpenSSL, because net/http/persistent will only autoload
+# it. On some Rubies, autoload fails but explicit require succeeds.
+begin
+ require "openssl"
+rescue LoadError
+ # some Ruby builds don't have OpenSSL
+end
+module Bundler
+ module Persistent
+ module Net
+ module HTTP
+ end
+ end
+ end
+end
+require "bundler/vendor/net-http-persistent/lib/net/http/persistent"
+
+module Bundler
+ class PersistentHTTP < Persistent::Net::HTTP::Persistent
+ def connection_for(uri)
+ connection = super
+ warn_old_tls_version_rubygems_connection(uri, connection)
+ connection
+ end
+
+ def warn_old_tls_version_rubygems_connection(uri, connection)
+ return unless connection.use_ssl?
+ return unless (uri.host || "").end_with?("rubygems.org")
+
+ socket = connection.instance_variable_get(:@socket)
+ return unless socket
+ socket_io = socket.io
+ return unless socket_io.respond_to?(:ssl_version)
+ ssl_version = socket_io.ssl_version
+
+ case ssl_version
+ when /TLSv([\d\.]+)/
+ version = Gem::Version.new($1)
+ if version < Gem::Version.new("1.2")
+ Bundler.ui.warn \
+ "Warning: Your Ruby version is compiled against a copy of OpenSSL that is very old. " \
+ "Starting in January 2018, RubyGems.org will refuse connection requests from these " \
+ "very old versions of OpenSSL. If you will need to continue installing gems after " \
+ "January 2018, please follow this guide to upgrade: http://ruby.to/tls-outdated.",
+ :wrap => true
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendored_thor.rb b/lib/bundler/vendored_thor.rb
new file mode 100644
index 0000000000..8cca090f55
--- /dev/null
+++ b/lib/bundler/vendored_thor.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Bundler
+ def self.require_thor_actions
+ Kernel.send(:require, "bundler/vendor/thor/lib/thor/actions")
+ end
+end
+require "bundler/vendor/thor/lib/thor"
diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb
new file mode 100644
index 0000000000..02ec96adc9
--- /dev/null
+++ b/lib/bundler/version.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: false
+
+# Ruby 1.9.3 and old RubyGems don't play nice with frozen version strings
+# rubocop:disable MutableConstant
+
+module Bundler
+ # We're doing this because we might write tests that deal
+ # with other versions of bundler and we are unsure how to
+ # handle this better.
+ VERSION = "1.17.1" unless defined?(::Bundler::VERSION)
+
+ def self.overwrite_loaded_gem_version
+ begin
+ require "rubygems"
+ rescue LoadError
+ return
+ end
+ return unless bundler_spec = Gem.loaded_specs["bundler"]
+ return if bundler_spec.version == VERSION
+ bundler_spec.version = Bundler::VERSION
+ end
+ private_class_method :overwrite_loaded_gem_version
+ overwrite_loaded_gem_version
+
+ def self.bundler_major_version
+ @bundler_major_version ||= VERSION.split(".").first.to_i
+ end
+end
diff --git a/lib/bundler/version_ranges.rb b/lib/bundler/version_ranges.rb
new file mode 100644
index 0000000000..ec25716cde
--- /dev/null
+++ b/lib/bundler/version_ranges.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module Bundler
+ module VersionRanges
+ NEq = Struct.new(:version)
+ ReqR = Struct.new(:left, :right)
+ class ReqR
+ Endpoint = Struct.new(:version, :inclusive)
+ def to_s
+ "#{left.inclusive ? "[" : "("}#{left.version}, #{right.version}#{right.inclusive ? "]" : ")"}"
+ end
+ INFINITY = Object.new.freeze
+ ZERO = Gem::Version.new("0.a")
+
+ def cover?(v)
+ return false if left.inclusive && left.version > v
+ return false if !left.inclusive && left.version >= v
+
+ if right.version != INFINITY
+ return false if right.inclusive && right.version < v
+ return false if !right.inclusive && right.version <= v
+ end
+
+ true
+ end
+
+ def empty?
+ left.version == right.version && !(left.inclusive && right.inclusive)
+ end
+
+ def single?
+ left.version == right.version
+ end
+
+ UNIVERSAL = ReqR.new(ReqR::Endpoint.new(Gem::Version.new("0.a"), true), ReqR::Endpoint.new(ReqR::INFINITY, false)).freeze
+ end
+
+ def self.for_many(requirements)
+ requirements = requirements.map(&:requirements).flatten(1).map {|r| r.join(" ") }
+ requirements << ">= 0.a" if requirements.empty?
+ requirement = Gem::Requirement.new(requirements)
+ self.for(requirement)
+ end
+
+ def self.for(requirement)
+ ranges = requirement.requirements.map do |op, v|
+ case op
+ when "=" then ReqR.new(ReqR::Endpoint.new(v, true), ReqR::Endpoint.new(v, true))
+ when "!=" then NEq.new(v)
+ when ">=" then ReqR.new(ReqR::Endpoint.new(v, true), ReqR::Endpoint.new(ReqR::INFINITY, false))
+ when ">" then ReqR.new(ReqR::Endpoint.new(v, false), ReqR::Endpoint.new(ReqR::INFINITY, false))
+ when "<" then ReqR.new(ReqR::Endpoint.new(ReqR::ZERO, true), ReqR::Endpoint.new(v, false))
+ when "<=" then ReqR.new(ReqR::Endpoint.new(ReqR::ZERO, true), ReqR::Endpoint.new(v, true))
+ when "~>" then ReqR.new(ReqR::Endpoint.new(v, true), ReqR::Endpoint.new(v.bump, false))
+ else raise "unknown version op #{op} in requirement #{requirement}"
+ end
+ end.uniq
+ ranges, neqs = ranges.partition {|r| !r.is_a?(NEq) }
+
+ [ranges.sort_by {|range| [range.left.version, range.left.inclusive ? 0 : 1] }, neqs.map(&:version)]
+ end
+
+ def self.empty?(ranges, neqs)
+ !ranges.reduce(ReqR::UNIVERSAL) do |last_range, curr_range|
+ next false unless last_range
+ next false if curr_range.single? && neqs.include?(curr_range.left.version)
+ next curr_range if last_range.right.version == ReqR::INFINITY
+ case last_range.right.version <=> curr_range.left.version
+ when 1 then next curr_range
+ when 0 then next(last_range.right.inclusive && curr_range.left.inclusive && !neqs.include?(curr_range.left.version) && curr_range)
+ when -1 then next false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vlad.rb b/lib/bundler/vlad.rb
new file mode 100644
index 0000000000..68181e7db8
--- /dev/null
+++ b/lib/bundler/vlad.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require "bundler/shared_helpers"
+Bundler::SharedHelpers.major_deprecation 2,
+ "The Bundler task for Vlad"
+
+# Vlad task for Bundler.
+#
+# Add "require 'bundler/vlad'" in your Vlad deploy.rb, and
+# include the vlad:bundle:install task in your vlad:deploy task.
+require "bundler/deployment"
+
+include Rake::DSL if defined? Rake::DSL
+
+namespace :vlad do
+ Bundler::Deployment.define_task(Rake::RemoteTask, :remote_task, :roles => :app)
+end
diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb
new file mode 100644
index 0000000000..e91cfa7805
--- /dev/null
+++ b/lib/bundler/worker.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require "thread"
+
+module Bundler
+ class Worker
+ POISON = Object.new
+
+ class WrappedException < StandardError
+ attr_reader :exception
+ def initialize(exn)
+ @exception = exn
+ end
+ end
+
+ # @return [String] the name of the worker
+ attr_reader :name
+
+ # Creates a worker pool of specified size
+ #
+ # @param size [Integer] Size of pool
+ # @param name [String] name the name of the worker
+ # @param func [Proc] job to run in inside the worker pool
+ def initialize(size, name, func)
+ @name = name
+ @request_queue = Queue.new
+ @response_queue = Queue.new
+ @func = func
+ @size = size
+ @threads = nil
+ SharedHelpers.trap("INT") { abort_threads }
+ end
+
+ # Enqueue a request to be executed in the worker pool
+ #
+ # @param obj [String] mostly it is name of spec that should be downloaded
+ def enq(obj)
+ create_threads unless @threads
+ @request_queue.enq obj
+ end
+
+ # Retrieves results of job function being executed in worker pool
+ def deq
+ result = @response_queue.deq
+ raise result.exception if result.is_a?(WrappedException)
+ result
+ end
+
+ def stop
+ stop_threads
+ end
+
+ private
+
+ def process_queue(i)
+ loop do
+ obj = @request_queue.deq
+ break if obj.equal? POISON
+ @response_queue.enq apply_func(obj, i)
+ end
+ end
+
+ def apply_func(obj, i)
+ @func.call(obj, i)
+ rescue Exception => e
+ WrappedException.new(e)
+ end
+
+ # Stop the worker threads by sending a poison object down the request queue
+ # so as worker threads after retrieving it, shut themselves down
+ def stop_threads
+ return unless @threads
+ @threads.each { @request_queue.enq POISON }
+ @threads.each(&:join)
+ @threads = nil
+ end
+
+ def abort_threads
+ return unless @threads
+ Bundler.ui.debug("\n#{caller.join("\n")}")
+ @threads.each(&:exit)
+ exit 1
+ end
+
+ def create_threads
+ creation_errors = []
+
+ @threads = Array.new(@size) do |i|
+ begin
+ Thread.start { process_queue(i) }.tap do |thread|
+ thread.name = "#{name} Worker ##{i}" if thread.respond_to?(:name=)
+ end
+ rescue ThreadError => e
+ creation_errors << e
+ nil
+ end
+ end.compact
+
+ return if creation_errors.empty?
+
+ message = "Failed to create threads for the #{name} worker: #{creation_errors.map(&:to_s).uniq.join(", ")}"
+ raise ThreadCreationError, message if @threads.empty?
+ Bundler.ui.info message
+ end
+ end
+end
diff --git a/lib/bundler/yaml_serializer.rb b/lib/bundler/yaml_serializer.rb
new file mode 100644
index 0000000000..0fd81c40ef
--- /dev/null
+++ b/lib/bundler/yaml_serializer.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+module Bundler
+ # A stub yaml serializer that can handle only hashes and strings (as of now).
+ module YAMLSerializer
+ module_function
+
+ def dump(hash)
+ yaml = String.new("---")
+ yaml << dump_hash(hash)
+ end
+
+ def dump_hash(hash)
+ yaml = String.new("\n")
+ hash.each do |k, v|
+ yaml << k << ":"
+ if v.is_a?(Hash)
+ yaml << dump_hash(v).gsub(/^(?!$)/, " ") # indent all non-empty lines
+ elsif v.is_a?(Array) # Expected to be array of strings
+ yaml << "\n- " << v.map {|s| s.to_s.gsub(/\s+/, " ").inspect }.join("\n- ") << "\n"
+ else
+ yaml << " " << v.to_s.gsub(/\s+/, " ").inspect << "\n"
+ end
+ end
+ yaml
+ end
+
+ ARRAY_REGEX = /
+ ^
+ (?:[ ]*-[ ]) # '- ' before array items
+ (['"]?) # optional opening quote
+ (.*) # value
+ \1 # matching closing quote
+ $
+ /xo
+
+ HASH_REGEX = /
+ ^
+ ([ ]*) # indentations
+ (.+) # key
+ (?::(?=(?:\s|$))) # : (without the lookahead the #key includes this when : is present in value)
+ [ ]?
+ (?: !\s)? # optional exclamation mark found with ruby 1.9.3
+ (['"]?) # optional opening quote
+ (.*) # value
+ \3 # matching closing quote
+ $
+ /xo
+
+ def load(str)
+ res = {}
+ stack = [res]
+ last_hash = nil
+ last_empty_key = nil
+ str.split(/\r?\n/).each do |line|
+ if match = HASH_REGEX.match(line)
+ indent, key, quote, val = match.captures
+ key = convert_to_backward_compatible_key(key)
+ depth = indent.scan(/ /).length
+ if quote.empty? && val.empty?
+ new_hash = {}
+ stack[depth][key] = new_hash
+ stack[depth + 1] = new_hash
+ last_empty_key = key
+ last_hash = stack[depth]
+ else
+ stack[depth][key] = val
+ end
+ elsif match = ARRAY_REGEX.match(line)
+ _, val = match.captures
+ last_hash[last_empty_key] = [] unless last_hash[last_empty_key].is_a?(Array)
+
+ last_hash[last_empty_key].push(val)
+ end
+ end
+ res
+ end
+
+ # for settings' keys
+ def convert_to_backward_compatible_key(key)
+ key = "#{key}/" if key =~ /https?:/i && key !~ %r{/\Z}
+ key = key.gsub(".", "__") if key.include?(".")
+ key
+ end
+
+ class << self
+ private :dump_hash, :convert_to_backward_compatible_key
+ end
+ end
+end
diff --git a/man/bundle-add.1 b/man/bundle-add.1
new file mode 100644
index 0000000000..c8b5bcec70
--- /dev/null
+++ b/man/bundle-add.1
@@ -0,0 +1,58 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-ADD" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
+.
+.SH "SYNOPSIS"
+\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-skip\-install] [\-\-strict] [\-\-optimistic]
+.
+.SH "DESCRIPTION"
+Adds the named gem to the Gemfile and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\.
+.
+.P
+Example:
+.
+.P
+bundle add rails
+.
+.P
+bundle add rails \-\-version "< 3\.0, > 1\.1"
+.
+.P
+bundle add rails \-\-version "~> 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development"
+.
+.P
+bundle add rails \-\-skip\-install
+.
+.P
+bundle add rails \-\-group "development, test"
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-version\fR, \fB\-v\fR
+Specify version requirements(s) for the added gem\.
+.
+.TP
+\fB\-\-group\fR, \fB\-g\fR
+Specify the group(s) for the added gem\. Multiple groups should be separated by commas\.
+.
+.TP
+\fB\-\-source\fR, , \fB\-s\fR
+Specify the source for the added gem\.
+.
+.TP
+\fB\-\-skip\-install\fR
+Adds the gem to the Gemfile but does not install it\.
+.
+.TP
+\fB\-\-optimistic\fR
+Adds optimistic declaration of version
+.
+.TP
+\fB\-\-strict\fR
+Adds strict declaration of version
+
diff --git a/man/bundle-add.1.txt b/man/bundle-add.1.txt
new file mode 100644
index 0000000000..eda61e2dd5
--- /dev/null
+++ b/man/bundle-add.1.txt
@@ -0,0 +1,52 @@
+BUNDLE-ADD(1) BUNDLE-ADD(1)
+
+
+
+1mNAME0m
+ 1mbundle-add 22m- Add gem to the Gemfile and run bundle install
+
+1mSYNOPSIS0m
+ 1mbundle add 4m22mGEM_NAME24m [--group=GROUP] [--version=VERSION]
+ [--source=SOURCE] [--skip-install] [--strict] [--optimistic]
+
+1mDESCRIPTION0m
+ Adds the named gem to the Gemfile and run 1mbundle install22m. 1mbundle0m
+ 1minstall 22mcan be avoided by using the flag 1m--skip-install22m.
+
+ Example:
+
+ bundle add rails
+
+ bundle add rails --version "< 3.0, > 1.1"
+
+ bundle add rails --version "~> 5.0.0" --source "https://gems.exam-
+ ple.com" --group "development"
+
+ bundle add rails --skip-install
+
+ bundle add rails --group "development, test"
+
+1mOPTIONS0m
+ 1m--version22m, 1m-v0m
+ Specify version requirements(s) for the added gem.
+
+ 1m--group22m, 1m-g0m
+ Specify the group(s) for the added gem. Multiple groups should
+ be separated by commas.
+
+ 1m--source22m, , 1m-s0m
+ Specify the source for the added gem.
+
+ 1m--skip-install0m
+ Adds the gem to the Gemfile but does not install it.
+
+ 1m--optimistic0m
+ Adds optimistic declaration of version
+
+ 1m--strict0m
+ Adds strict declaration of version
+
+
+
+
+ October 2018 BUNDLE-ADD(1)
diff --git a/man/bundle-add.ronn b/man/bundle-add.ronn
new file mode 100644
index 0000000000..1e2d732ec6
--- /dev/null
+++ b/man/bundle-add.ronn
@@ -0,0 +1,40 @@
+bundle-add(1) -- Add gem to the Gemfile and run bundle install
+================================================================
+
+## SYNOPSIS
+
+`bundle add` <GEM_NAME> [--group=GROUP] [--version=VERSION] [--source=SOURCE] [--skip-install] [--strict] [--optimistic]
+
+## DESCRIPTION
+Adds the named gem to the Gemfile and run `bundle install`. `bundle install` can be avoided by using the flag `--skip-install`.
+
+Example:
+
+bundle add rails
+
+bundle add rails --version "< 3.0, > 1.1"
+
+bundle add rails --version "~> 5.0.0" --source "https://gems.example.com" --group "development"
+
+bundle add rails --skip-install
+
+bundle add rails --group "development, test"
+
+## OPTIONS
+* `--version`, `-v`:
+ Specify version requirements(s) for the added gem.
+
+* `--group`, `-g`:
+ Specify the group(s) for the added gem. Multiple groups should be separated by commas.
+
+* `--source`, , `-s`:
+ Specify the source for the added gem.
+
+* `--skip-install`:
+ Adds the gem to the Gemfile but does not install it.
+
+* `--optimistic`:
+ Adds optimistic declaration of version
+
+* `--strict`:
+ Adds strict declaration of version
diff --git a/man/bundle-binstubs.1 b/man/bundle-binstubs.1
new file mode 100644
index 0000000000..9757abb570
--- /dev/null
+++ b/man/bundle-binstubs.1
@@ -0,0 +1,40 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-BINSTUBS" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-binstubs\fR \- Install the binstubs of the listed gems
+.
+.SH "SYNOPSIS"
+\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-path PATH] [\-\-standalone]
+.
+.SH "DESCRIPTION"
+Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it into \fBbin/\fR\. Binstubs are a shortcut\-or alternative\- to always using \fBbundle exec\fR\. This gives you a file that can by run directly, and one that will always run the correct gem version used by the application\.
+.
+.P
+For example, if you run \fBbundle binstubs rspec\-core\fR, Bundler will create the file \fBbin/rspec\fR\. That file will contain enough code to load Bundler, tell it to load the bundled gems, and then run rspec\.
+.
+.P
+This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the \fB\-\-path\fR directory if one has been set\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-force\fR
+Overwrite existing binstubs if they exist\.
+.
+.TP
+\fB\-\-path\fR
+The location to install the specified binstubs to\. This defaults to \fBbin\fR\.
+.
+.TP
+\fB\-\-standalone\fR
+Makes binstubs that can work without depending on Rubygems or Bundler at runtime\.
+.
+.TP
+\fB\-\-shebang\fR
+Specify a different shebang executable name than the default (default \'ruby\')
+.
+.SH "BUNDLE INSTALL \-\-BINSTUBS"
+To create binstubs for all the gems in the bundle you can use the \fB\-\-binstubs\fR flag in bundle install(1) \fIbundle\-install\.1\.html\fR\.
diff --git a/man/bundle-binstubs.1.txt b/man/bundle-binstubs.1.txt
new file mode 100644
index 0000000000..adc0a1304f
--- /dev/null
+++ b/man/bundle-binstubs.1.txt
@@ -0,0 +1,48 @@
+BUNDLE-BINSTUBS(1) BUNDLE-BINSTUBS(1)
+
+
+
+1mNAME0m
+ 1mbundle-binstubs 22m- Install the binstubs of the listed gems
+
+1mSYNOPSIS0m
+ 1mbundle binstubs 4m22mGEM_NAME24m [--force] [--path PATH] [--standalone]
+
+1mDESCRIPTION0m
+ Binstubs are scripts that wrap around executables. Bundler creates a
+ small Ruby file (a binstub) that loads Bundler, runs the command, and
+ puts it into 1mbin/22m. Binstubs are a shortcut-or alternative- to always
+ using 1mbundle exec22m. This gives you a file that can by run directly, and
+ one that will always run the correct gem version used by the applica-
+ tion.
+
+ For example, if you run 1mbundle binstubs rspec-core22m, Bundler will create
+ the file 1mbin/rspec22m. That file will contain enough code to load Bundler,
+ tell it to load the bundled gems, and then run rspec.
+
+ This command generates binstubs for executables in 1mGEM_NAME22m. Binstubs
+ are put into 1mbin22m, or the 1m--path 22mdirectory if one has been set. Calling
+ binstubs with [GEM [GEM]] will create binstubs for all given gems.
+
+1mOPTIONS0m
+ 1m--force0m
+ Overwrite existing binstubs if they exist.
+
+ 1m--path 22mThe location to install the specified binstubs to. This defaults
+ to 1mbin22m.
+
+ 1m--standalone0m
+ Makes binstubs that can work without depending on Rubygems or
+ Bundler at runtime.
+
+ 1m--shebang0m
+ Specify a different shebang executable name than the default
+ (default 'ruby')
+
+1mBUNDLE INSTALL --BINSTUBS0m
+ To create binstubs for all the gems in the bundle you can use the
+ 1m--binstubs 22mflag in bundle install(1) 4mbundle-install.1.html24m.
+
+
+
+ October 2018 BUNDLE-BINSTUBS(1)
diff --git a/man/bundle-binstubs.ronn b/man/bundle-binstubs.ronn
new file mode 100644
index 0000000000..c1ae0988cd
--- /dev/null
+++ b/man/bundle-binstubs.ronn
@@ -0,0 +1,43 @@
+bundle-binstubs(1) -- Install the binstubs of the listed gems
+=============================================================
+
+## SYNOPSIS
+
+`bundle binstubs` <GEM_NAME> [--force] [--path PATH] [--standalone]
+
+## DESCRIPTION
+
+Binstubs are scripts that wrap around executables. Bundler creates a
+small Ruby file (a binstub) that loads Bundler, runs the command,
+and puts it into `bin/`. Binstubs are a shortcut-or alternative-
+to always using `bundle exec`. This gives you a file that can by run
+directly, and one that will always run the correct gem version
+used by the application.
+
+For example, if you run `bundle binstubs rspec-core`, Bundler will create
+the file `bin/rspec`. That file will contain enough code to load Bundler,
+tell it to load the bundled gems, and then run rspec.
+
+This command generates binstubs for executables in `GEM_NAME`.
+Binstubs are put into `bin`, or the `--path` directory if one has been set.
+Calling binstubs with [GEM [GEM]] will create binstubs for all given gems.
+
+## OPTIONS
+
+* `--force`:
+ Overwrite existing binstubs if they exist.
+
+* `--path`:
+ The location to install the specified binstubs to. This defaults to `bin`.
+
+* `--standalone`:
+ Makes binstubs that can work without depending on Rubygems or Bundler at
+ runtime.
+
+* `--shebang`:
+ Specify a different shebang executable name than the default (default 'ruby')
+
+## BUNDLE INSTALL --BINSTUBS
+
+To create binstubs for all the gems in the bundle you can use the `--binstubs`
+flag in [bundle install(1)](bundle-install.1.html).
diff --git a/man/bundle-check.1 b/man/bundle-check.1
new file mode 100644
index 0000000000..a68b9cc4af
--- /dev/null
+++ b/man/bundle-check.1
@@ -0,0 +1,31 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-CHECK" "1" "June 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems
+.
+.SH "SYNOPSIS"
+\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE] [\-\-path=PATH]
+.
+.SH "DESCRIPTION"
+\fBcheck\fR searches the local machine for each of the gems requested in the Gemfile\. If all gems are found, Bundler prints a success message and exits with a status of 0\.
+.
+.P
+If not, the first missing gem is listed and Bundler exits status 1\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-dry\-run\fR
+Locks the [\fBGemfile(5)\fR][Gemfile(5)] before running the command\.
+.
+.TP
+\fB\-\-gemfile\fR
+Use the specified gemfile instead of the [\fBGemfile(5)\fR][Gemfile(5)]\.
+.
+.TP
+\fB\-\-path\fR
+Specify a different path than the system default (\fB$BUNDLE_PATH\fR or \fB$GEM_HOME\fR)\. Bundler will remember this value for future installs on this machine\.
+
diff --git a/man/bundle-check.1.txt b/man/bundle-check.1.txt
new file mode 100644
index 0000000000..dc02055003
--- /dev/null
+++ b/man/bundle-check.1.txt
@@ -0,0 +1,33 @@
+BUNDLE-CHECK(1) BUNDLE-CHECK(1)
+
+
+
+1mNAME0m
+ 1mbundle-check 22m- Verifies if dependencies are satisfied by installed gems
+
+1mSYNOPSIS0m
+ 1mbundle check 22m[--dry-run] [--gemfile=FILE] [--path=PATH]
+
+1mDESCRIPTION0m
+ 1mcheck 22msearches the local machine for each of the gems requested in the
+ Gemfile. If all gems are found, Bundler prints a success message and
+ exits with a status of 0.
+
+ If not, the first missing gem is listed and Bundler exits status 1.
+
+1mOPTIONS0m
+ 1m--dry-run0m
+ Locks the [1mGemfile(5)22m][Gemfile(5)] before running the command.
+
+ 1m--gemfile0m
+ Use the specified gemfile instead of the [1mGemfile(5)22m][Gem-
+ file(5)].
+
+ 1m--path 22mSpecify a different path than the system default (1m$BUNDLE_PATH0m
+ or 1m$GEM_HOME22m). Bundler will remember this value for future
+ installs on this machine.
+
+
+
+
+ June 2018 BUNDLE-CHECK(1)
diff --git a/man/bundle-check.ronn b/man/bundle-check.ronn
new file mode 100644
index 0000000000..f2846b8ff2
--- /dev/null
+++ b/man/bundle-check.ronn
@@ -0,0 +1,26 @@
+bundle-check(1) -- Verifies if dependencies are satisfied by installed gems
+===========================================================================
+
+## SYNOPSIS
+
+`bundle check` [--dry-run]
+ [--gemfile=FILE]
+ [--path=PATH]
+
+## DESCRIPTION
+
+`check` searches the local machine for each of the gems requested in the
+Gemfile. If all gems are found, Bundler prints a success message and exits with
+a status of 0.
+
+If not, the first missing gem is listed and Bundler exits status 1.
+
+## OPTIONS
+
+* `--dry-run`:
+ Locks the [`Gemfile(5)`][Gemfile(5)] before running the command.
+* `--gemfile`:
+ Use the specified gemfile instead of the [`Gemfile(5)`][Gemfile(5)].
+* `--path`:
+ Specify a different path than the system default (`$BUNDLE_PATH` or `$GEM_HOME`).
+ Bundler will remember this value for future installs on this machine.
diff --git a/man/bundle-clean.1 b/man/bundle-clean.1
new file mode 100644
index 0000000000..7c4985cb80
--- /dev/null
+++ b/man/bundle-clean.1
@@ -0,0 +1,24 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-CLEAN" "1" "May 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory
+.
+.SH "SYNOPSIS"
+\fBbundle clean\fR [\-\-dry\-run] [\-\-force]
+.
+.SH "DESCRIPTION"
+This command will remove all unused gems in your bundler directory\. This is useful when you have made many changes to your gem dependencies\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-dry\-run\fR
+Print the changes, but do not clean the unused gems\.
+.
+.TP
+\fB\-\-force\fR
+Force a clean even if \fB\-\-path\fR is not set\.
+
diff --git a/man/bundle-clean.1.txt b/man/bundle-clean.1.txt
new file mode 100644
index 0000000000..dd5e1f32e3
--- /dev/null
+++ b/man/bundle-clean.1.txt
@@ -0,0 +1,26 @@
+BUNDLE-CLEAN(1) BUNDLE-CLEAN(1)
+
+
+
+1mNAME0m
+ 1mbundle-clean 22m- Cleans up unused gems in your bundler directory
+
+1mSYNOPSIS0m
+ 1mbundle clean 22m[--dry-run] [--force]
+
+1mDESCRIPTION0m
+ This command will remove all unused gems in your bundler directory.
+ This is useful when you have made many changes to your gem dependen-
+ cies.
+
+1mOPTIONS0m
+ 1m--dry-run0m
+ Print the changes, but do not clean the unused gems.
+
+ 1m--force0m
+ Force a clean even if 1m--path 22mis not set.
+
+
+
+
+ May 2018 BUNDLE-CLEAN(1)
diff --git a/man/bundle-clean.ronn b/man/bundle-clean.ronn
new file mode 100644
index 0000000000..de23991782
--- /dev/null
+++ b/man/bundle-clean.ronn
@@ -0,0 +1,18 @@
+bundle-clean(1) -- Cleans up unused gems in your bundler directory
+==================================================================
+
+## SYNOPSIS
+
+`bundle clean` [--dry-run] [--force]
+
+## DESCRIPTION
+
+This command will remove all unused gems in your bundler directory. This is
+useful when you have made many changes to your gem dependencies.
+
+## OPTIONS
+
+* `--dry-run`:
+ Print the changes, but do not clean the unused gems.
+* `--force`:
+ Force a clean even if `--path` is not set.
diff --git a/man/bundle-config.1 b/man/bundle-config.1
new file mode 100644
index 0000000000..c250975a6c
--- /dev/null
+++ b/man/bundle-config.1
@@ -0,0 +1,481 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-CONFIG" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-config\fR \- Set bundler configuration options
+.
+.SH "SYNOPSIS"
+\fBbundle config\fR [\fIname\fR [\fIvalue\fR]]
+.
+.SH "DESCRIPTION"
+This command allows you to interact with Bundler\'s configuration system\.
+.
+.P
+Bundler loads configuration settings in this order:
+.
+.IP "1." 4
+Local config (\fBapp/\.bundle/config\fR)
+.
+.IP "2." 4
+Environmental variables (\fBENV\fR)
+.
+.IP "3." 4
+Global config (\fB~/\.bundle/config\fR)
+.
+.IP "4." 4
+Bundler default config
+.
+.IP "" 0
+.
+.P
+Executing \fBbundle config\fR with no parameters will print a list of all bundler configuration for the current bundle, and where that configuration was set\.
+.
+.P
+Executing \fBbundle config <name>\fR will print the value of that configuration setting, and where it was set\.
+.
+.P
+Executing \fBbundle config <name> <value>\fR will set that configuration to the value specified for all bundles executed as the current user\. The configuration will be stored in \fB~/\.bundle/config\fR\. If \fIname\fR already is set, \fIname\fR will be overridden and user will be warned\.
+.
+.P
+Executing \fBbundle config \-\-global <name> <value>\fR works the same as above\.
+.
+.P
+Executing \fBbundle config \-\-local <name> <value>\fR will set that configuration to the local application\. The configuration will be stored in \fBapp/\.bundle/config\fR\.
+.
+.P
+Executing \fBbundle config \-\-delete <name>\fR will delete the configuration in both local and global sources\. Not compatible with \-\-global or \-\-local flag\.
+.
+.P
+Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\.
+.
+.P
+Executing \fBbundle config disable_multisource true\fR upgrades the warning about the Gemfile containing multiple primary sources to an error\. Executing \fBbundle config \-\-delete disable_multisource\fR downgrades this error to a warning\.
+.
+.SH "REMEMBERING OPTIONS"
+Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are not remembered between commands\. If these options must be remembered,they must be set using \fBbundle config\fR (e\.g\., \fBbundle config path foo\fR)\.
+.
+.P
+The options that can be configured are:
+.
+.TP
+\fBbin\fR
+Creates a directory (defaults to \fB~/bin\fR) and place any executables from the gem there\. These executables run in Bundler\'s context\. If used, you might add this directory to your environment\'s \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\.
+.
+.TP
+\fBdeployment\fR
+In deployment mode, Bundler will \'roll\-out\' the bundle for \fBproduction\fR use\. Please check carefully if you want to have this option enabled in \fBdevelopment\fR or \fBtest\fR environments\.
+.
+.TP
+\fBpath\fR
+The location to install the specified gems to\. This defaults to Rubygems\' setting\. Bundler shares this location with Rubygems, \fBgem install \.\.\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \.\.\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\.
+.
+.TP
+\fBwithout\fR
+A space\-separated list of groups referencing gems to skip during installation\.
+.
+.TP
+\fBwith\fR
+A space\-separated list of groups referencing gems to include during installation\.
+.
+.SH "BUILD OPTIONS"
+You can use \fBbundle config\fR to give Bundler the flags to pass to the gem installer every time bundler tries to install a particular gem\.
+.
+.P
+A very common example, the \fBmysql\fR gem, requires Snow Leopard users to pass configuration flags to \fBgem install\fR to specify where to find the \fBmysql_config\fR executable\.
+.
+.IP "" 4
+.
+.nf
+
+gem install mysql \-\- \-\-with\-mysql\-config=/usr/local/mysql/bin/mysql_config
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Since the specific location of that executable can change from machine to machine, you can specify these flags on a per\-machine basis\.
+.
+.IP "" 4
+.
+.nf
+
+bundle config build\.mysql \-\-with\-mysql\-config=/usr/local/mysql/bin/mysql_config
+.
+.fi
+.
+.IP "" 0
+.
+.P
+After running this command, every time bundler needs to install the \fBmysql\fR gem, it will pass along the flags you specified\.
+.
+.SH "CONFIGURATION KEYS"
+Configuration keys in bundler have two forms: the canonical form and the environment variable form\.
+.
+.P
+For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn\'t install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\.
+.
+.P
+The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\.
+.
+.P
+Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\.
+.
+.SH "LIST OF AVAILABLE KEYS"
+The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\.
+.
+.IP "\(bu" 4
+\fBallow_bundler_dependency_conflicts\fR (\fBBUNDLE_ALLOW_BUNDLER_DEPENDENCY_CONFLICTS\fR): Allow resolving to specifications that have dependencies on \fBbundler\fR that are incompatible with the running Bundler version\.
+.
+.IP "\(bu" 4
+\fBallow_deployment_source_credential_changes\fR (\fBBUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES\fR): When in deployment mode, allow changing the credentials to a gem\'s source\. Ex: \fBhttps://some\.host\.com/gems/path/\fR \-> \fBhttps://user_name:password@some\.host\.com/gems/path\fR
+.
+.IP "\(bu" 4
+\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\.
+.
+.IP "\(bu" 4
+\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\.
+.
+.IP "\(bu" 4
+\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\.
+.
+.IP "\(bu" 4
+\fBbin\fR (\fBBUNDLE_BIN\fR): Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\.
+.
+.IP "\(bu" 4
+\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\.
+.
+.IP "\(bu" 4
+\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\.
+.
+.IP "\(bu" 4
+\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/bundle\fR\.
+.
+.IP "\(bu" 4
+\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\.
+.
+.IP "\(bu" 4
+\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
+.
+.IP "\(bu" 4
+\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\.
+.
+.IP "\(bu" 4
+\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\.
+.
+.IP "\(bu" 4
+\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\.
+.
+.IP "\(bu" 4
+\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\.
+.
+.IP "\(bu" 4
+\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\.
+.
+.IP "\(bu" 4
+\fBdisable_multisource\fR (\fBBUNDLE_DISABLE_MULTISOURCE\fR): When set, Gemfiles containing multiple sources will produce errors instead of warnings\. Use \fBbundle config \-\-delete disable_multisource\fR to unset\.
+.
+.IP "\(bu" 4
+\fBdisable_platform_warnings\fR (\fBBUNDLE_DISABLE_PLATFORM_WARNINGS\fR): Disable warnings during bundle install when a dependency is unused on the current platform\.
+.
+.IP "\(bu" 4
+\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems\' normal location\.
+.
+.IP "\(bu" 4
+\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\.
+.
+.IP "\(bu" 4
+\fBerror_on_stderr\fR (\fBBUNDLE_ERROR_ON_STDERR\fR): Print Bundler errors to stderr\.
+.
+.IP "\(bu" 4
+\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine\'s platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\.
+.
+.IP "\(bu" 4
+\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. Defaults to \fBtrue\fR when \fB\-\-deployment\fR is used\.
+.
+.IP "\(bu" 4
+\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\.
+.
+.IP "\(bu" 4
+\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\.
+.
+.IP "\(bu" 4
+\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\.
+.
+.IP "\(bu" 4
+\fBglobal_path_appends_ruby_scope\fR (\fBBUNDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE\fR): Whether Bundler should append the Ruby scope (e\.g\. engine and ABI version) to a globally\-configured path\.
+.
+.IP "\(bu" 4
+\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
+.
+.IP "\(bu" 4
+\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR) Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
+.
+.IP "\(bu" 4
+\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to 1\.
+.
+.IP "\(bu" 4
+\fBlist_command\fR (\fBBUNDLE_LIST_COMMAND\fR) Enable new list command feature
+.
+.IP "\(bu" 4
+\fBmajor_deprecations\fR (\fBBUNDLE_MAJOR_DEPRECATIONS\fR): Whether Bundler should print deprecation warnings for behavior that will be changed in the next major version\.
+.
+.IP "\(bu" 4
+\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\.
+.
+.IP "\(bu" 4
+\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\.
+.
+.IP "\(bu" 4
+\fBonly_update_to_newer_versions\fR (\fBBUNDLE_ONLY_UPDATE_TO_NEWER_VERSIONS\fR): During \fBbundle update\fR, only resolve to newer versions of the gems in the lockfile\.
+.
+.IP "\(bu" 4
+\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\. When \-\-deployment is used, defaults to vendor/bundle\.
+.
+.IP "\(bu" 4
+\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
+.
+.IP "\(bu" 4
+\fBpath_relative_to_cwd\fR (\fBPATH_RELATIVE_TO_CWD\fR) Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\.
+.
+.IP "\(bu" 4
+\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler\'s experimental plugin system\.
+.
+.IP "\(bu" 4
+\fBprefer_gems_rb\fR (\fBBUNDLE_PREFER_GEMS_RB\fR) Prefer \fBgems\.rb\fR to \fBGemfile\fR when Bundler is searching for a Gemfile\.
+.
+.IP "\(bu" 4
+\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR) Print only version number from \fBbundler \-\-version\fR\.
+.
+.IP "\(bu" 4
+\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\.
+.
+.IP "\(bu" 4
+\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\.
+.
+.IP "\(bu" 4
+\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR): Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\.
+.
+.IP "\(bu" 4
+\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\.
+.
+.IP "\(bu" 4
+\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\.
+.
+.IP "\(bu" 4
+\fBskip_default_git_sources\fR (\fBBUNDLE_SKIP_DEFAULT_GIT_SOURCES\fR): Whether Bundler should skip adding default git source shortcuts to the Gemfile DSL\.
+.
+.IP "\(bu" 4
+\fBspecific_platform\fR (\fBBUNDLE_SPECIFIC_PLATFORM\fR): Allow bundler to resolve for the specific running platform and store it in the lockfile, instead of only using a generic platform\. A specific platform is the exact platform triple reported by \fBGem::Platform\.local\fR, such as \fBx86_64\-darwin\-16\fR or \fBuniversal\-java\-1\.8\fR\. On the other hand, generic platforms are those such as \fBruby\fR, \fBmswin\fR, or \fBjava\fR\. In this example, \fBx86_64\-darwin\-16\fR would map to \fBruby\fR and \fBuniversal\-java\-1\.8\fR to \fBjava\fR\.
+.
+.IP "\(bu" 4
+\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\.
+.
+.IP "\(bu" 4
+\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\.
+.
+.IP "\(bu" 4
+\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\.
+.
+.IP "\(bu" 4
+\fBsuppress_install_using_messages\fR (\fBBUNDLE_SUPPRESS_INSTALL_USING_MESSAGES\fR): Avoid printing \fBUsing \.\.\.\fR messages during installation when the version of a gem has not changed\.
+.
+.IP "\(bu" 4
+\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\.
+.
+.IP "\(bu" 4
+\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\.
+.
+.IP "\(bu" 4
+\fBunlock_source_unlocks_spec\fR (\fBBUNDLE_UNLOCK_SOURCE_UNLOCKS_SPEC\fR): Whether running \fBbundle update \-\-source NAME\fR unlocks a gem with the given name\. Defaults to \fBtrue\fR\.
+.
+.IP "\(bu" 4
+\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR) Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
+.
+.IP "\(bu" 4
+\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\.
+.
+.IP "\(bu" 4
+\fBwith\fR (\fBBUNDLE_WITH\fR): A \fB:\fR\-separated list of groups whose gems bundler should install\.
+.
+.IP "\(bu" 4
+\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A \fB:\fR\-separated list of groups whose gems bundler should not install\.
+.
+.IP "" 0
+.
+.P
+In general, you should set these settings per\-application by using the applicable flag to the bundle install(1) \fIbundle\-install\.1\.html\fR or bundle package(1) \fIbundle\-package\.1\.html\fR command\.
+.
+.P
+You can set them globally either via environment variables or \fBbundle config\fR, whichever is preferable for your setup\. If you use both, environment variables will take preference over global settings\.
+.
+.SH "LOCAL GIT REPOS"
+Bundler also allows you to work against a git repository locally instead of using the remote version\. This can be achieved by setting up a local override:
+.
+.IP "" 4
+.
+.nf
+
+bundle config local\.GEM_NAME /path/to/local/git/repository
+.
+.fi
+.
+.IP "" 0
+.
+.P
+For example, in order to use a local Rack repository, a developer could call:
+.
+.IP "" 4
+.
+.nf
+
+bundle config local\.rack ~/Work/git/rack
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Now instead of checking out the remote git repository, the local override will be used\. Similar to a path source, every time the local git repository change, changes will be automatically picked up by Bundler\. This means a commit in the local git repo will update the revision in the \fBGemfile\.lock\fR to the local git repo revision\. This requires the same attention as git submodules\. Before pushing to the remote, you need to ensure the local override was pushed, otherwise you may point to a commit that only exists in your local machine\. You\'ll also need to CGI escape your usernames and passwords as well\.
+.
+.P
+Bundler does many checks to ensure a developer won\'t work with invalid references\. Particularly, we force a developer to specify a branch in the \fBGemfile\fR in order to use this feature\. If the branch specified in the \fBGemfile\fR and the current branch in the local git repository do not match, Bundler will abort\. This ensures that a developer is always working against the correct branches, and prevents accidental locking to a different branch\.
+.
+.P
+Finally, Bundler also ensures that the current revision in the \fBGemfile\.lock\fR exists in the local git repository\. By doing this, Bundler forces you to fetch the latest changes in the remotes\.
+.
+.SH "MIRRORS OF GEM SOURCES"
+Bundler supports overriding gem sources with mirrors\. This allows you to configure rubygems\.org as the gem source in your Gemfile while still using your mirror to fetch gems\.
+.
+.IP "" 4
+.
+.nf
+
+bundle config mirror\.SOURCE_URL MIRROR_URL
+.
+.fi
+.
+.IP "" 0
+.
+.P
+For example, to use a mirror of rubygems\.org hosted at rubygems\-mirror\.org:
+.
+.IP "" 4
+.
+.nf
+
+bundle config mirror\.http://rubygems\.org http://rubygems\-mirror\.org
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Each mirror also provides a fallback timeout setting\. If the mirror does not respond within the fallback timeout, Bundler will try to use the original server instead of the mirror\.
+.
+.IP "" 4
+.
+.nf
+
+bundle config mirror\.SOURCE_URL\.fallback_timeout TIMEOUT
+.
+.fi
+.
+.IP "" 0
+.
+.P
+For example, to fall back to rubygems\.org after 3 seconds:
+.
+.IP "" 4
+.
+.nf
+
+bundle config mirror\.https://rubygems\.org\.fallback_timeout 3
+.
+.fi
+.
+.IP "" 0
+.
+.P
+The default fallback timeout is 0\.1 seconds, but the setting can currently only accept whole seconds (for example, 1, 15, or 30)\.
+.
+.SH "CREDENTIALS FOR GEM SOURCES"
+Bundler allows you to configure credentials for any gem source, which allows you to avoid putting secrets into your Gemfile\.
+.
+.IP "" 4
+.
+.nf
+
+bundle config SOURCE_HOSTNAME USERNAME:PASSWORD
+.
+.fi
+.
+.IP "" 0
+.
+.P
+For example, to save the credentials of user \fBclaudette\fR for the gem source at \fBgems\.longerous\.com\fR, you would run:
+.
+.IP "" 4
+.
+.nf
+
+bundle config gems\.longerous\.com claudette:s00pers3krit
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Or you can set the credentials as an environment variable like this:
+.
+.IP "" 4
+.
+.nf
+
+export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+For gems with a git source with HTTP(S) URL you can specify credentials like so:
+.
+.IP "" 4
+.
+.nf
+
+bundle config https://github\.com/bundler/bundler\.git username:password
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Or you can set the credentials as an environment variable like so:
+.
+.IP "" 4
+.
+.nf
+
+export BUNDLE_GITHUB__COM=username:password
+.
+.fi
+.
+.IP "" 0
+.
+.P
+This is especially useful for private repositories on hosts such as Github, where you can use personal OAuth tokens:
+.
+.IP "" 4
+.
+.nf
+
+export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x\-oauth\-basic
+.
+.fi
+.
+.IP "" 0
+
diff --git a/man/bundle-config.1.txt b/man/bundle-config.1.txt
new file mode 100644
index 0000000000..0a999e8a97
--- /dev/null
+++ b/man/bundle-config.1.txt
@@ -0,0 +1,513 @@
+BUNDLE-CONFIG(1) BUNDLE-CONFIG(1)
+
+
+
+1mNAME0m
+ 1mbundle-config 22m- Set bundler configuration options
+
+1mSYNOPSIS0m
+ 1mbundle config 22m[4mname24m [4mvalue24m]]
+
+1mDESCRIPTION0m
+ This command allows you to interact with Bundler's configuration sys-
+ tem.
+
+ Bundler loads configuration settings in this order:
+
+ 1. Local config (1mapp/.bundle/config22m)
+
+ 2. Environmental variables (1mENV22m)
+
+ 3. Global config (1m~/.bundle/config22m)
+
+ 4. Bundler default config
+
+
+
+ Executing 1mbundle config 22mwith no parameters will print a list of all
+ bundler configuration for the current bundle, and where that configura-
+ tion was set.
+
+ Executing 1mbundle config <name> 22mwill print the value of that configura-
+ tion setting, and where it was set.
+
+ Executing 1mbundle config <name> <value> 22mwill set that configuration to
+ the value specified for all bundles executed as the current user. The
+ configuration will be stored in 1m~/.bundle/config22m. If 4mname24m already is
+ set, 4mname24m will be overridden and user will be warned.
+
+ Executing 1mbundle config --global <name> <value> 22mworks the same as
+ above.
+
+ Executing 1mbundle config --local <name> <value> 22mwill set that configura-
+ tion to the local application. The configuration will be stored in
+ 1mapp/.bundle/config22m.
+
+ Executing 1mbundle config --delete <name> 22mwill delete the configuration
+ in both local and global sources. Not compatible with --global or
+ --local flag.
+
+ Executing bundle with the 1mBUNDLE_IGNORE_CONFIG 22menvironment variable set
+ will cause it to ignore all configuration.
+
+ Executing 1mbundle config disable_multisource true 22mupgrades the warning
+ about the Gemfile containing multiple primary sources to an error. Exe-
+ cuting 1mbundle config --delete disable_multisource 22mdowngrades this error
+ to a warning.
+
+1mREMEMBERING OPTIONS0m
+ Flags passed to 1mbundle install 22mor the Bundler runtime, such as 1m--path0m
+ 1mfoo 22mor 1m--without production22m, are not remembered between commands. If
+ these options must be remembered,they must be set using 1mbundle config0m
+ (e.g., 1mbundle config path foo22m).
+
+ The options that can be configured are:
+
+ 1mbin 22mCreates a directory (defaults to 1m~/bin22m) and place any executa-
+ bles from the gem there. These executables run in Bundler's con-
+ text. If used, you might add this directory to your environ-
+ ment's 1mPATH 22mvariable. For instance, if the 1mrails 22mgem comes with
+ a 1mrails 22mexecutable, this flag will create a 1mbin/rails 22mexecutable
+ that ensures that all referred dependencies will be resolved
+ using the bundled gems.
+
+ 1mdeployment0m
+ In deployment mode, Bundler will 'roll-out' the bundle for 1mpro-0m
+ 1mduction 22muse. Please check carefully if you want to have this
+ option enabled in 1mdevelopment 22mor 1mtest 22menvironments.
+
+ 1mpath 22mThe location to install the specified gems to. This defaults to
+ Rubygems' setting. Bundler shares this location with Rubygems,
+ 1mgem install ... 22mwill have gem installed there, too. Therefore,
+ gems installed without a 1m--path ... 22msetting will show up by
+ calling 1mgem list22m. Accordingly, gems installed to other locations
+ will not get listed.
+
+ 1mwithout0m
+ A space-separated list of groups referencing gems to skip during
+ installation.
+
+ 1mwith 22mA space-separated list of groups referencing gems to include
+ during installation.
+
+1mBUILD OPTIONS0m
+ You can use 1mbundle config 22mto give Bundler the flags to pass to the gem
+ installer every time bundler tries to install a particular gem.
+
+ A very common example, the 1mmysql 22mgem, requires Snow Leopard users to
+ pass configuration flags to 1mgem install 22mto specify where to find the
+ 1mmysql_config 22mexecutable.
+
+
+
+ gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
+
+
+
+ Since the specific location of that executable can change from machine
+ to machine, you can specify these flags on a per-machine basis.
+
+
+
+ bundle config build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config
+
+
+
+ After running this command, every time bundler needs to install the
+ 1mmysql 22mgem, it will pass along the flags you specified.
+
+1mCONFIGURATION KEYS0m
+ Configuration keys in bundler have two forms: the canonical form and
+ the environment variable form.
+
+ For instance, passing the 1m--without 22mflag to bundle install(1) 4mbun-0m
+ 4mdle-install.1.html24m prevents Bundler from installing certain groups
+ specified in the Gemfile(5). Bundler persists this value in 1mapp/.bun-0m
+ 1mdle/config 22mso that calls to 1mBundler.setup 22mdo not try to find gems from
+ the 1mGemfile 22mthat you didn't install. Additionally, subsequent calls to
+ bundle install(1) 4mbundle-install.1.html24m remember this setting and skip
+ those groups.
+
+ The canonical form of this configuration is 1m"without"22m. To convert the
+ canonical form to the environment variable form, capitalize it, and
+ prepend 1mBUNDLE_22m. The environment variable form of 1m"without" 22mis 1mBUN-0m
+ 1mDLE_WITHOUT22m.
+
+ Any periods in the configuration keys must be replaced with two under-
+ scores when setting it via environment variables. The configuration key
+ 1mlocal.rack 22mbecomes the environment variable 1mBUNDLE_LOCAL__RACK22m.
+
+1mLIST OF AVAILABLE KEYS0m
+ The following is a list of all configuration keys and their purpose.
+ You can learn more about their operation in bundle install(1) 4mbun-0m
+ 4mdle-install.1.html24m.
+
+ o 1mallow_bundler_dependency_conflicts 22m(1mBUNDLE_ALLOW_BUNDLER_DEPEN-0m
+ 1mDENCY_CONFLICTS22m): Allow resolving to specifications that have
+ dependencies on 1mbundler 22mthat are incompatible with the running
+ Bundler version.
+
+ o 1mallow_deployment_source_credential_changes 22m(1mBUNDLE_ALLOW_DEPLOY-0m
+ 1mMENT_SOURCE_CREDENTIAL_CHANGES22m): When in deployment mode, allow
+ changing the credentials to a gem's source. Ex:
+ 1mhttps://some.host.com/gems/path/ 22m-> 1mhttps://user_name:pass-0m
+ 1mword@some.host.com/gems/path0m
+
+ o 1mallow_offline_install 22m(1mBUNDLE_ALLOW_OFFLINE_INSTALL22m): Allow Bundler
+ to use cached data when installing without network access.
+
+ o 1mauto_clean_without_path 22m(1mBUNDLE_AUTO_CLEAN_WITHOUT_PATH22m): Automati-
+ cally run 1mbundle clean 22mafter installing when an explicit 1mpath 22mhas
+ not been set and Bundler is not installing into the system gems.
+
+ o 1mauto_install 22m(1mBUNDLE_AUTO_INSTALL22m): Automatically run 1mbundle0m
+ 1minstall 22mwhen gems are missing.
+
+ o 1mbin 22m(1mBUNDLE_BIN22m): Install executables from gems in the bundle to
+ the specified directory. Defaults to 1mfalse22m.
+
+ o 1mcache_all 22m(1mBUNDLE_CACHE_ALL22m): Cache all gems, including path and
+ git gems.
+
+ o 1mcache_all_platforms 22m(1mBUNDLE_CACHE_ALL_PLATFORMS22m): Cache gems for
+ all platforms.
+
+ o 1mcache_path 22m(1mBUNDLE_CACHE_PATH22m): The directory that bundler will
+ place cached gems in when running 1mbundle package22m, and that bundler
+ will look in when installing gems. Defaults to 1mvendor/bundle22m.
+
+ o 1mclean 22m(1mBUNDLE_CLEAN22m): Whether Bundler should run 1mbundle clean 22mauto-
+ matically after 1mbundle install22m.
+
+ o 1mconsole 22m(1mBUNDLE_CONSOLE22m): The console that 1mbundle console 22mstarts.
+ Defaults to 1mirb22m.
+
+ o 1mdefault_install_uses_path 22m(1mBUNDLE_DEFAULT_INSTALL_USES_PATH22m):
+ Whether a 1mbundle install 22mwithout an explicit 1m--path 22margument
+ defaults to installing gems in 1m.bundle22m.
+
+ o 1mdeployment 22m(1mBUNDLE_DEPLOYMENT22m): Disallow changes to the 1mGemfile22m.
+ When the 1mGemfile 22mis changed and the lockfile has not been updated,
+ running Bundler commands will be blocked.
+
+ o 1mdisable_checksum_validation 22m(1mBUNDLE_DISABLE_CHECKSUM_VALIDATION22m):
+ Allow installing gems even if they do not match the checksum pro-
+ vided by RubyGems.
+
+ o 1mdisable_exec_load 22m(1mBUNDLE_DISABLE_EXEC_LOAD22m): Stop Bundler from
+ using 1mload 22mto launch an executable in-process in 1mbundle exec22m.
+
+ o 1mdisable_local_branch_check 22m(1mBUNDLE_DISABLE_LOCAL_BRANCH_CHECK22m):
+ Allow Bundler to use a local git override without a branch speci-
+ fied in the Gemfile.
+
+ o 1mdisable_multisource 22m(1mBUNDLE_DISABLE_MULTISOURCE22m): When set, Gem-
+ files containing multiple sources will produce errors instead of
+ warnings. Use 1mbundle config --delete disable_multisource 22mto unset.
+
+ o 1mdisable_platform_warnings 22m(1mBUNDLE_DISABLE_PLATFORM_WARNINGS22m): Dis-
+ able warnings during bundle install when a dependency is unused on
+ the current platform.
+
+ o 1mdisable_shared_gems 22m(1mBUNDLE_DISABLE_SHARED_GEMS22m): Stop Bundler from
+ accessing gems installed to RubyGems' normal location.
+
+ o 1mdisable_version_check 22m(1mBUNDLE_DISABLE_VERSION_CHECK22m): Stop Bundler
+ from checking if a newer Bundler version is available on
+ rubygems.org.
+
+ o 1merror_on_stderr 22m(1mBUNDLE_ERROR_ON_STDERR22m): Print Bundler errors to
+ stderr.
+
+ o 1mforce_ruby_platform 22m(1mBUNDLE_FORCE_RUBY_PLATFORM22m): Ignore the cur-
+ rent machine's platform and install only 1mruby 22mplatform gems. As a
+ result, gems with native extensions will be compiled from source.
+
+ o 1mfrozen 22m(1mBUNDLE_FROZEN22m): Disallow changes to the 1mGemfile22m. When the
+ 1mGemfile 22mis changed and the lockfile has not been updated, running
+ Bundler commands will be blocked. Defaults to 1mtrue 22mwhen 1m--deploy-0m
+ 1mment 22mis used.
+
+ o 1mgem.push_key 22m(1mBUNDLE_GEM__PUSH_KEY22m): Sets the 1m--key 22mparameter for
+ 1mgem push 22mwhen using the 1mrake release 22mcommand with a private gem-
+ stash server.
+
+ o 1mgemfile 22m(1mBUNDLE_GEMFILE22m): The name of the file that bundler should
+ use as the 1mGemfile22m. This location of this file also sets the root
+ of the project, which is used to resolve relative paths in the 1mGem-0m
+ 1mfile22m, among other things. By default, bundler will search up from
+ the current working directory until it finds a 1mGemfile22m.
+
+ o 1mglobal_gem_cache 22m(1mBUNDLE_GLOBAL_GEM_CACHE22m): Whether Bundler should
+ cache all gems globally, rather than locally to the installing Ruby
+ installation.
+
+ o 1mglobal_path_appends_ruby_scope 22m(1mBUN-0m
+ 1mDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE22m): Whether Bundler should append
+ the Ruby scope (e.g. engine and ABI version) to a globally-config-
+ ured path.
+
+ o 1mignore_messages 22m(1mBUNDLE_IGNORE_MESSAGES22m): When set, no post install
+ messages will be printed. To silence a single gem, use dot notation
+ like 1mignore_messages.httparty true22m.
+
+ o 1minit_gems_rb 22m(1mBUNDLE_INIT_GEMS_RB22m) Generate a 1mgems.rb 22minstead of a
+ 1mGemfile 22mwhen running 1mbundle init22m.
+
+ o 1mjobs 22m(1mBUNDLE_JOBS22m): The number of gems Bundler can install in par-
+ allel. Defaults to 1.
+
+ o 1mlist_command 22m(1mBUNDLE_LIST_COMMAND22m) Enable new list command feature
+
+ o 1mmajor_deprecations 22m(1mBUNDLE_MAJOR_DEPRECATIONS22m): Whether Bundler
+ should print deprecation warnings for behavior that will be changed
+ in the next major version.
+
+ o 1mno_install 22m(1mBUNDLE_NO_INSTALL22m): Whether 1mbundle package 22mshould skip
+ installing gems.
+
+ o 1mno_prune 22m(1mBUNDLE_NO_PRUNE22m): Whether Bundler should leave outdated
+ gems unpruned when caching.
+
+ o 1monly_update_to_newer_versions 22m(1mBUNDLE_ONLY_UPDATE_TO_NEWER_VER-0m
+ 1mSIONS22m): During 1mbundle update22m, only resolve to newer versions of the
+ gems in the lockfile.
+
+ o 1mpath 22m(1mBUNDLE_PATH22m): The location on disk where all gems in your
+ bundle will be located regardless of 1m$GEM_HOME 22mor 1m$GEM_PATH 22mvalues.
+ Bundle gems not found in this location will be installed by 1mbundle0m
+ 1minstall22m. Defaults to 1mGem.dir22m. When --deployment is used, defaults
+ to vendor/bundle.
+
+ o 1mpath.system 22m(1mBUNDLE_PATH__SYSTEM22m): Whether Bundler will install
+ gems into the default system path (1mGem.dir22m).
+
+ o 1mpath_relative_to_cwd 22m(1mPATH_RELATIVE_TO_CWD22m) Makes 1m--path 22mrelative
+ to the CWD instead of the 1mGemfile22m.
+
+ o 1mplugins 22m(1mBUNDLE_PLUGINS22m): Enable Bundler's experimental plugin sys-
+ tem.
+
+ o 1mprefer_gems_rb 22m(1mBUNDLE_PREFER_GEMS_RB22m) Prefer 1mgems.rb 22mto 1mGemfile0m
+ when Bundler is searching for a Gemfile.
+
+ o 1mprint_only_version_number 22m(1mBUNDLE_PRINT_ONLY_VERSION_NUMBER22m) Print
+ only version number from 1mbundler --version22m.
+
+ o 1mredirect 22m(1mBUNDLE_REDIRECT22m): The number of redirects allowed for
+ network requests. Defaults to 1m522m.
+
+ o 1mretry 22m(1mBUNDLE_RETRY22m): The number of times to retry failed network
+ requests. Defaults to 1m322m.
+
+ o 1msetup_makes_kernel_gem_public 22m(1mBUNDLE_SETUP_MAKES_KERNEL_GEM_PUB-0m
+ 1mLIC22m): Have 1mBundler.setup 22mmake the 1mKernel#gem 22mmethod public, even
+ though RubyGems declares it as private.
+
+ o 1mshebang 22m(1mBUNDLE_SHEBANG22m): The program name that should be invoked
+ for generated binstubs. Defaults to the ruby install name used to
+ generate the binstub.
+
+ o 1msilence_root_warning 22m(1mBUNDLE_SILENCE_ROOT_WARNING22m): Silence the
+ warning Bundler prints when installing gems as root.
+
+ o 1mskip_default_git_sources 22m(1mBUNDLE_SKIP_DEFAULT_GIT_SOURCES22m): Whether
+ Bundler should skip adding default git source shortcuts to the Gem-
+ file DSL.
+
+ o 1mspecific_platform 22m(1mBUNDLE_SPECIFIC_PLATFORM22m): Allow bundler to
+ resolve for the specific running platform and store it in the lock-
+ file, instead of only using a generic platform. A specific platform
+ is the exact platform triple reported by 1mGem::Platform.local22m, such
+ as 1mx86_64-darwin-16 22mor 1muniversal-java-1.822m. On the other hand,
+ generic platforms are those such as 1mruby22m, 1mmswin22m, or 1mjava22m. In this
+ example, 1mx86_64-darwin-16 22mwould map to 1mruby 22mand 1muniversal-java-1.80m
+ to 1mjava22m.
+
+ o 1mssl_ca_cert 22m(1mBUNDLE_SSL_CA_CERT22m): Path to a designated CA certifi-
+ cate file or folder containing multiple certificates for trusted
+ CAs in PEM format.
+
+ o 1mssl_client_cert 22m(1mBUNDLE_SSL_CLIENT_CERT22m): Path to a designated file
+ containing a X.509 client certificate and key in PEM format.
+
+ o 1mssl_verify_mode 22m(1mBUNDLE_SSL_VERIFY_MODE22m): The SSL verification mode
+ Bundler uses when making HTTPS requests. Defaults to verify peer.
+
+ o 1msuppress_install_using_messages 22m(1mBUNDLE_SUPPRESS_INSTALL_USING_MES-0m
+ 1mSAGES22m): Avoid printing 1mUsing ... 22mmessages during installation when
+ the version of a gem has not changed.
+
+ o 1msystem_bindir 22m(1mBUNDLE_SYSTEM_BINDIR22m): The location where RubyGems
+ installs binstubs. Defaults to 1mGem.bindir22m.
+
+ o 1mtimeout 22m(1mBUNDLE_TIMEOUT22m): The seconds allowed before timing out for
+ network requests. Defaults to 1m1022m.
+
+ o 1munlock_source_unlocks_spec 22m(1mBUNDLE_UNLOCK_SOURCE_UNLOCKS_SPEC22m):
+ Whether running 1mbundle update --source NAME 22munlocks a gem with the
+ given name. Defaults to 1mtrue22m.
+
+ o 1mupdate_requires_all_flag 22m(1mBUNDLE_UPDATE_REQUIRES_ALL_FLAG22m) Require
+ passing 1m--all 22mto 1mbundle update 22mwhen everything should be updated,
+ and disallow passing no options to 1mbundle update22m.
+
+ o 1muser_agent 22m(1mBUNDLE_USER_AGENT22m): The custom user agent fragment
+ Bundler includes in API requests.
+
+ o 1mwith 22m(1mBUNDLE_WITH22m): A 1m:22m-separated list of groups whose gems bundler
+ should install.
+
+ o 1mwithout 22m(1mBUNDLE_WITHOUT22m): A 1m:22m-separated list of groups whose gems
+ bundler should not install.
+
+
+
+ In general, you should set these settings per-application by using the
+ applicable flag to the bundle install(1) 4mbundle-install.1.html24m or bun-
+ dle package(1) 4mbundle-package.1.html24m command.
+
+ You can set them globally either via environment variables or 1mbundle0m
+ 1mconfig22m, whichever is preferable for your setup. If you use both, envi-
+ ronment variables will take preference over global settings.
+
+1mLOCAL GIT REPOS0m
+ Bundler also allows you to work against a git repository locally
+ instead of using the remote version. This can be achieved by setting up
+ a local override:
+
+
+
+ bundle config local.GEM_NAME /path/to/local/git/repository
+
+
+
+ For example, in order to use a local Rack repository, a developer could
+ call:
+
+
+
+ bundle config local.rack ~/Work/git/rack
+
+
+
+ Now instead of checking out the remote git repository, the local over-
+ ride will be used. Similar to a path source, every time the local git
+ repository change, changes will be automatically picked up by Bundler.
+ This means a commit in the local git repo will update the revision in
+ the 1mGemfile.lock 22mto the local git repo revision. This requires the same
+ attention as git submodules. Before pushing to the remote, you need to
+ ensure the local override was pushed, otherwise you may point to a com-
+ mit that only exists in your local machine. You'll also need to CGI
+ escape your usernames and passwords as well.
+
+ Bundler does many checks to ensure a developer won't work with invalid
+ references. Particularly, we force a developer to specify a branch in
+ the 1mGemfile 22min order to use this feature. If the branch specified in
+ the 1mGemfile 22mand the current branch in the local git repository do not
+ match, Bundler will abort. This ensures that a developer is always
+ working against the correct branches, and prevents accidental locking
+ to a different branch.
+
+ Finally, Bundler also ensures that the current revision in the 1mGem-0m
+ 1mfile.lock 22mexists in the local git repository. By doing this, Bundler
+ forces you to fetch the latest changes in the remotes.
+
+1mMIRRORS OF GEM SOURCES0m
+ Bundler supports overriding gem sources with mirrors. This allows you
+ to configure rubygems.org as the gem source in your Gemfile while still
+ using your mirror to fetch gems.
+
+
+
+ bundle config mirror.SOURCE_URL MIRROR_URL
+
+
+
+ For example, to use a mirror of rubygems.org hosted at rubygems-mir-
+ ror.org:
+
+
+
+ bundle config mirror.http://rubygems.org http://rubygems-mirror.org
+
+
+
+ Each mirror also provides a fallback timeout setting. If the mirror
+ does not respond within the fallback timeout, Bundler will try to use
+ the original server instead of the mirror.
+
+
+
+ bundle config mirror.SOURCE_URL.fallback_timeout TIMEOUT
+
+
+
+ For example, to fall back to rubygems.org after 3 seconds:
+
+
+
+ bundle config mirror.https://rubygems.org.fallback_timeout 3
+
+
+
+ The default fallback timeout is 0.1 seconds, but the setting can cur-
+ rently only accept whole seconds (for example, 1, 15, or 30).
+
+1mCREDENTIALS FOR GEM SOURCES0m
+ Bundler allows you to configure credentials for any gem source, which
+ allows you to avoid putting secrets into your Gemfile.
+
+
+
+ bundle config SOURCE_HOSTNAME USERNAME:PASSWORD
+
+
+
+ For example, to save the credentials of user 1mclaudette 22mfor the gem
+ source at 1mgems.longerous.com22m, you would run:
+
+
+
+ bundle config gems.longerous.com claudette:s00pers3krit
+
+
+
+ Or you can set the credentials as an environment variable like this:
+
+
+
+ export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit"
+
+
+
+ For gems with a git source with HTTP(S) URL you can specify credentials
+ like so:
+
+
+
+ bundle config https://github.com/bundler/bundler.git username:password
+
+
+
+ Or you can set the credentials as an environment variable like so:
+
+
+
+ export BUNDLE_GITHUB__COM=username:password
+
+
+
+ This is especially useful for private repositories on hosts such as
+ Github, where you can use personal OAuth tokens:
+
+
+
+ export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x-oauth-basic
+
+
+
+
+
+
+ October 2018 BUNDLE-CONFIG(1)
diff --git a/man/bundle-config.ronn b/man/bundle-config.ronn
new file mode 100644
index 0000000000..b5c97ae82d
--- /dev/null
+++ b/man/bundle-config.ronn
@@ -0,0 +1,397 @@
+bundle-config(1) -- Set bundler configuration options
+=====================================================
+
+## SYNOPSIS
+
+`bundle config` [<name> [<value>]]
+
+## DESCRIPTION
+
+This command allows you to interact with Bundler's configuration system.
+
+Bundler loads configuration settings in this order:
+
+1. Local config (`app/.bundle/config`)
+2. Environmental variables (`ENV`)
+3. Global config (`~/.bundle/config`)
+4. Bundler default config
+
+Executing `bundle config` with no parameters will print a list of all
+bundler configuration for the current bundle, and where that configuration
+was set.
+
+Executing `bundle config <name>` will print the value of that configuration
+setting, and where it was set.
+
+Executing `bundle config <name> <value>` will set that configuration to the
+value specified for all bundles executed as the current user. The configuration
+will be stored in `~/.bundle/config`. If <name> already is set, <name> will be
+overridden and user will be warned.
+
+Executing `bundle config --global <name> <value>` works the same as above.
+
+Executing `bundle config --local <name> <value>` will set that configuration to
+the local application. The configuration will be stored in `app/.bundle/config`.
+
+Executing `bundle config --delete <name>` will delete the configuration in both
+local and global sources. Not compatible with --global or --local flag.
+
+Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will
+cause it to ignore all configuration.
+
+Executing `bundle config disable_multisource true` upgrades the warning about
+the Gemfile containing multiple primary sources to an error. Executing `bundle
+config --delete disable_multisource` downgrades this error to a warning.
+
+## REMEMBERING OPTIONS
+
+Flags passed to `bundle install` or the Bundler runtime,
+such as `--path foo` or `--without production`, are not remembered between commands.
+If these options must be remembered,they must be set using `bundle config`
+(e.g., `bundle config path foo`).
+
+The options that can be configured are:
+
+* `bin`:
+ Creates a directory (defaults to `~/bin`) and place any executables from the
+ gem there. These executables run in Bundler's context. If used, you might add
+ this directory to your environment's `PATH` variable. For instance, if the
+ `rails` gem comes with a `rails` executable, this flag will create a
+ `bin/rails` executable that ensures that all referred dependencies will be
+ resolved using the bundled gems.
+
+* `deployment`:
+ In deployment mode, Bundler will 'roll-out' the bundle for
+ `production` use. Please check carefully if you want to have this option
+ enabled in `development` or `test` environments.
+
+* `path`:
+ The location to install the specified gems to. This defaults to Rubygems'
+ setting. Bundler shares this location with Rubygems, `gem install ...` will
+ have gem installed there, too. Therefore, gems installed without a
+ `--path ...` setting will show up by calling `gem list`. Accordingly, gems
+ installed to other locations will not get listed.
+
+* `without`:
+ A space-separated list of groups referencing gems to skip during installation.
+
+* `with`:
+ A space-separated list of groups referencing gems to include during installation.
+
+## BUILD OPTIONS
+
+You can use `bundle config` to give Bundler the flags to pass to the gem
+installer every time bundler tries to install a particular gem.
+
+A very common example, the `mysql` gem, requires Snow Leopard users to
+pass configuration flags to `gem install` to specify where to find the
+`mysql_config` executable.
+
+ gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
+
+Since the specific location of that executable can change from machine
+to machine, you can specify these flags on a per-machine basis.
+
+ bundle config build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config
+
+After running this command, every time bundler needs to install the
+`mysql` gem, it will pass along the flags you specified.
+
+## CONFIGURATION KEYS
+
+Configuration keys in bundler have two forms: the canonical form and the
+environment variable form.
+
+For instance, passing the `--without` flag to [bundle install(1)](bundle-install.1.html)
+prevents Bundler from installing certain groups specified in the Gemfile(5). Bundler
+persists this value in `app/.bundle/config` so that calls to `Bundler.setup`
+do not try to find gems from the `Gemfile` that you didn't install. Additionally,
+subsequent calls to [bundle install(1)](bundle-install.1.html) remember this setting
+and skip those groups.
+
+The canonical form of this configuration is `"without"`. To convert the canonical
+form to the environment variable form, capitalize it, and prepend `BUNDLE_`. The
+environment variable form of `"without"` is `BUNDLE_WITHOUT`.
+
+Any periods in the configuration keys must be replaced with two underscores when
+setting it via environment variables. The configuration key `local.rack` becomes
+the environment variable `BUNDLE_LOCAL__RACK`.
+
+## LIST OF AVAILABLE KEYS
+
+The following is a list of all configuration keys and their purpose. You can
+learn more about their operation in [bundle install(1)](bundle-install.1.html).
+
+* `allow_bundler_dependency_conflicts` (`BUNDLE_ALLOW_BUNDLER_DEPENDENCY_CONFLICTS`):
+ Allow resolving to specifications that have dependencies on `bundler` that
+ are incompatible with the running Bundler version.
+* `allow_deployment_source_credential_changes` (`BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES`):
+ When in deployment mode, allow changing the credentials to a gem's source.
+ Ex: `https://some.host.com/gems/path/` -> `https://user_name:password@some.host.com/gems/path`
+* `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`):
+ Allow Bundler to use cached data when installing without network access.
+* `auto_clean_without_path` (`BUNDLE_AUTO_CLEAN_WITHOUT_PATH`):
+ Automatically run `bundle clean` after installing when an explicit `path`
+ has not been set and Bundler is not installing into the system gems.
+* `auto_install` (`BUNDLE_AUTO_INSTALL`):
+ Automatically run `bundle install` when gems are missing.
+* `bin` (`BUNDLE_BIN`):
+ Install executables from gems in the bundle to the specified directory.
+ Defaults to `false`.
+* `cache_all` (`BUNDLE_CACHE_ALL`):
+ Cache all gems, including path and git gems.
+* `cache_all_platforms` (`BUNDLE_CACHE_ALL_PLATFORMS`):
+ Cache gems for all platforms.
+* `cache_path` (`BUNDLE_CACHE_PATH`):
+ The directory that bundler will place cached gems in when running
+ <code>bundle package</code>, and that bundler will look in when installing gems.
+ Defaults to `vendor/bundle`.
+* `clean` (`BUNDLE_CLEAN`):
+ Whether Bundler should run `bundle clean` automatically after
+ `bundle install`.
+* `console` (`BUNDLE_CONSOLE`):
+ The console that `bundle console` starts. Defaults to `irb`.
+* `default_install_uses_path` (`BUNDLE_DEFAULT_INSTALL_USES_PATH`):
+ Whether a `bundle install` without an explicit `--path` argument defaults
+ to installing gems in `.bundle`.
+* `deployment` (`BUNDLE_DEPLOYMENT`):
+ Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the
+ lockfile has not been updated, running Bundler commands will be blocked.
+* `disable_checksum_validation` (`BUNDLE_DISABLE_CHECKSUM_VALIDATION`):
+ Allow installing gems even if they do not match the checksum provided by
+ RubyGems.
+* `disable_exec_load` (`BUNDLE_DISABLE_EXEC_LOAD`):
+ Stop Bundler from using `load` to launch an executable in-process in
+ `bundle exec`.
+* `disable_local_branch_check` (`BUNDLE_DISABLE_LOCAL_BRANCH_CHECK`):
+ Allow Bundler to use a local git override without a branch specified in the
+ Gemfile.
+* `disable_multisource` (`BUNDLE_DISABLE_MULTISOURCE`):
+ When set, Gemfiles containing multiple sources will produce errors
+ instead of warnings.
+ Use `bundle config --delete disable_multisource` to unset.
+* `disable_platform_warnings` (`BUNDLE_DISABLE_PLATFORM_WARNINGS`):
+ Disable warnings during bundle install when a dependency is unused on the current platform.
+* `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`):
+ Stop Bundler from accessing gems installed to RubyGems' normal location.
+* `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`):
+ Stop Bundler from checking if a newer Bundler version is available on
+ rubygems.org.
+* `error_on_stderr` (`BUNDLE_ERROR_ON_STDERR`):
+ Print Bundler errors to stderr.
+* `force_ruby_platform` (`BUNDLE_FORCE_RUBY_PLATFORM`):
+ Ignore the current machine's platform and install only `ruby` platform gems.
+ As a result, gems with native extensions will be compiled from source.
+* `frozen` (`BUNDLE_FROZEN`):
+ Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the
+ lockfile has not been updated, running Bundler commands will be blocked.
+ Defaults to `true` when `--deployment` is used.
+* `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`):
+ Sets the `--key` parameter for `gem push` when using the `rake release`
+ command with a private gemstash server.
+* `gemfile` (`BUNDLE_GEMFILE`):
+ The name of the file that bundler should use as the `Gemfile`. This location
+ of this file also sets the root of the project, which is used to resolve
+ relative paths in the `Gemfile`, among other things. By default, bundler
+ will search up from the current working directory until it finds a
+ `Gemfile`.
+* `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`):
+ Whether Bundler should cache all gems globally, rather than locally to the
+ installing Ruby installation.
+* `global_path_appends_ruby_scope` (`BUNDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE`):
+ Whether Bundler should append the Ruby scope (e.g. engine and ABI version)
+ to a globally-configured path.
+* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`): When set, no post install
+ messages will be printed. To silence a single gem, use dot notation like
+ `ignore_messages.httparty true`.
+* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`)
+ Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`.
+* `jobs` (`BUNDLE_JOBS`):
+ The number of gems Bundler can install in parallel. Defaults to 1.
+* `list_command` (`BUNDLE_LIST_COMMAND`)
+ Enable new list command feature
+* `major_deprecations` (`BUNDLE_MAJOR_DEPRECATIONS`):
+ Whether Bundler should print deprecation warnings for behavior that will
+ be changed in the next major version.
+* `no_install` (`BUNDLE_NO_INSTALL`):
+ Whether `bundle package` should skip installing gems.
+* `no_prune` (`BUNDLE_NO_PRUNE`):
+ Whether Bundler should leave outdated gems unpruned when caching.
+* `only_update_to_newer_versions` (`BUNDLE_ONLY_UPDATE_TO_NEWER_VERSIONS`):
+ During `bundle update`, only resolve to newer versions of the gems in the
+ lockfile.
+* `path` (`BUNDLE_PATH`):
+ The location on disk where all gems in your bundle will be located regardless
+ of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location
+ will be installed by `bundle install`. Defaults to `Gem.dir`. When --deployment
+ is used, defaults to vendor/bundle.
+* `path.system` (`BUNDLE_PATH__SYSTEM`):
+ Whether Bundler will install gems into the default system path (`Gem.dir`).
+* `path_relative_to_cwd` (`PATH_RELATIVE_TO_CWD`)
+ Makes `--path` relative to the CWD instead of the `Gemfile`.
+* `plugins` (`BUNDLE_PLUGINS`):
+ Enable Bundler's experimental plugin system.
+* `prefer_gems_rb` (`BUNDLE_PREFER_GEMS_RB`)
+ Prefer `gems.rb` to `Gemfile` when Bundler is searching for a Gemfile.
+* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`)
+ Print only version number from `bundler --version`.
+* `redirect` (`BUNDLE_REDIRECT`):
+ The number of redirects allowed for network requests. Defaults to `5`.
+* `retry` (`BUNDLE_RETRY`):
+ The number of times to retry failed network requests. Defaults to `3`.
+* `setup_makes_kernel_gem_public` (`BUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC`):
+ Have `Bundler.setup` make the `Kernel#gem` method public, even though
+ RubyGems declares it as private.
+* `shebang` (`BUNDLE_SHEBANG`):
+ The program name that should be invoked for generated binstubs. Defaults to
+ the ruby install name used to generate the binstub.
+* `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`):
+ Silence the warning Bundler prints when installing gems as root.
+* `skip_default_git_sources` (`BUNDLE_SKIP_DEFAULT_GIT_SOURCES`):
+ Whether Bundler should skip adding default git source shortcuts to the
+ Gemfile DSL.
+* `specific_platform` (`BUNDLE_SPECIFIC_PLATFORM`):
+ Allow bundler to resolve for the specific running platform and store it in
+ the lockfile, instead of only using a generic platform.
+ A specific platform is the exact platform triple reported by
+ `Gem::Platform.local`, such as `x86_64-darwin-16` or `universal-java-1.8`.
+ On the other hand, generic platforms are those such as `ruby`, `mswin`, or
+ `java`. In this example, `x86_64-darwin-16` would map to `ruby` and
+ `universal-java-1.8` to `java`.
+* `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`):
+ Path to a designated CA certificate file or folder containing multiple
+ certificates for trusted CAs in PEM format.
+* `ssl_client_cert` (`BUNDLE_SSL_CLIENT_CERT`):
+ Path to a designated file containing a X.509 client certificate
+ and key in PEM format.
+* `ssl_verify_mode` (`BUNDLE_SSL_VERIFY_MODE`):
+ The SSL verification mode Bundler uses when making HTTPS requests.
+ Defaults to verify peer.
+* `suppress_install_using_messages` (`BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES`):
+ Avoid printing `Using ...` messages during installation when the version of
+ a gem has not changed.
+* `system_bindir` (`BUNDLE_SYSTEM_BINDIR`):
+ The location where RubyGems installs binstubs. Defaults to `Gem.bindir`.
+* `timeout` (`BUNDLE_TIMEOUT`):
+ The seconds allowed before timing out for network requests. Defaults to `10`.
+* `unlock_source_unlocks_spec` (`BUNDLE_UNLOCK_SOURCE_UNLOCKS_SPEC`):
+ Whether running `bundle update --source NAME` unlocks a gem with the given
+ name. Defaults to `true`.
+* `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`)
+ Require passing `--all` to `bundle update` when everything should be updated,
+ and disallow passing no options to `bundle update`.
+* `user_agent` (`BUNDLE_USER_AGENT`):
+ The custom user agent fragment Bundler includes in API requests.
+* `with` (`BUNDLE_WITH`):
+ A `:`-separated list of groups whose gems bundler should install.
+* `without` (`BUNDLE_WITHOUT`):
+ A `:`-separated list of groups whose gems bundler should not install.
+
+In general, you should set these settings per-application by using the applicable
+flag to the [bundle install(1)](bundle-install.1.html) or [bundle package(1)](bundle-package.1.html) command.
+
+You can set them globally either via environment variables or `bundle config`,
+whichever is preferable for your setup. If you use both, environment variables
+will take preference over global settings.
+
+## LOCAL GIT REPOS
+
+Bundler also allows you to work against a git repository locally
+instead of using the remote version. This can be achieved by setting
+up a local override:
+
+ bundle config local.GEM_NAME /path/to/local/git/repository
+
+For example, in order to use a local Rack repository, a developer could call:
+
+ bundle config local.rack ~/Work/git/rack
+
+Now instead of checking out the remote git repository, the local
+override will be used. Similar to a path source, every time the local
+git repository change, changes will be automatically picked up by
+Bundler. This means a commit in the local git repo will update the
+revision in the `Gemfile.lock` to the local git repo revision. This
+requires the same attention as git submodules. Before pushing to
+the remote, you need to ensure the local override was pushed, otherwise
+you may point to a commit that only exists in your local machine.
+You'll also need to CGI escape your usernames and passwords as well.
+
+Bundler does many checks to ensure a developer won't work with
+invalid references. Particularly, we force a developer to specify
+a branch in the `Gemfile` in order to use this feature. If the branch
+specified in the `Gemfile` and the current branch in the local git
+repository do not match, Bundler will abort. This ensures that
+a developer is always working against the correct branches, and prevents
+accidental locking to a different branch.
+
+Finally, Bundler also ensures that the current revision in the
+`Gemfile.lock` exists in the local git repository. By doing this, Bundler
+forces you to fetch the latest changes in the remotes.
+
+## MIRRORS OF GEM SOURCES
+
+Bundler supports overriding gem sources with mirrors. This allows you to
+configure rubygems.org as the gem source in your Gemfile while still using your
+mirror to fetch gems.
+
+ bundle config mirror.SOURCE_URL MIRROR_URL
+
+For example, to use a mirror of rubygems.org hosted at rubygems-mirror.org:
+
+ bundle config mirror.http://rubygems.org http://rubygems-mirror.org
+
+Each mirror also provides a fallback timeout setting. If the mirror does not
+respond within the fallback timeout, Bundler will try to use the original
+server instead of the mirror.
+
+ bundle config mirror.SOURCE_URL.fallback_timeout TIMEOUT
+
+For example, to fall back to rubygems.org after 3 seconds:
+
+ bundle config mirror.https://rubygems.org.fallback_timeout 3
+
+The default fallback timeout is 0.1 seconds, but the setting can currently
+only accept whole seconds (for example, 1, 15, or 30).
+
+## CREDENTIALS FOR GEM SOURCES
+
+Bundler allows you to configure credentials for any gem source, which allows
+you to avoid putting secrets into your Gemfile.
+
+ bundle config SOURCE_HOSTNAME USERNAME:PASSWORD
+
+For example, to save the credentials of user `claudette` for the gem source at
+`gems.longerous.com`, you would run:
+
+ bundle config gems.longerous.com claudette:s00pers3krit
+
+Or you can set the credentials as an environment variable like this:
+
+ export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit"
+
+For gems with a git source with HTTP(S) URL you can specify credentials like so:
+
+ bundle config https://github.com/bundler/bundler.git username:password
+
+Or you can set the credentials as an environment variable like so:
+
+ export BUNDLE_GITHUB__COM=username:password
+
+This is especially useful for private repositories on hosts such as Github,
+where you can use personal OAuth tokens:
+
+ export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x-oauth-basic
+
+
+## CONFIGURE BUNDLER DIRECTORIES
+
+Bundler's home, config, cache and plugin directories are able to be configured
+through environment variables. The default location for Bundler's home directory is
+`~/.bundle`, which all directories inherit from by default. The following
+outlines the available environment variables and their default values
+
+ BUNDLE_USER_HOME : $HOME/.bundle
+ BUNDLE_USER_CACHE : $BUNDLE_USER_HOME/cache
+ BUNDLE_USER_CONFIG : $BUNDLE_USER_HOME/config
+ BUNDLE_USER_PLUGIN : $BUNDLE_USER_HOME/plugin
+
diff --git a/man/bundle-doctor.1 b/man/bundle-doctor.1
new file mode 100644
index 0000000000..e31d99c856
--- /dev/null
+++ b/man/bundle-doctor.1
@@ -0,0 +1,44 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-DOCTOR" "1" "June 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-doctor\fR \- Checks the bundle for common problems
+.
+.SH "SYNOPSIS"
+\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE]
+.
+.SH "DESCRIPTION"
+Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\.
+.
+.P
+Examples of common problems caught by bundle\-doctor include:
+.
+.IP "\(bu" 4
+Invalid Bundler settings
+.
+.IP "\(bu" 4
+Mismatched Ruby versions
+.
+.IP "\(bu" 4
+Mismatched platforms
+.
+.IP "\(bu" 4
+Uninstalled gems
+.
+.IP "\(bu" 4
+Missing dependencies
+.
+.IP "" 0
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-quiet\fR
+Only output warnings and errors\.
+.
+.TP
+\fB\-\-gemfile=<gemfile>\fR
+The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project\'s root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\.
+
diff --git a/man/bundle-doctor.1.txt b/man/bundle-doctor.1.txt
new file mode 100644
index 0000000000..b4144b6bca
--- /dev/null
+++ b/man/bundle-doctor.1.txt
@@ -0,0 +1,44 @@
+BUNDLE-DOCTOR(1) BUNDLE-DOCTOR(1)
+
+
+
+1mNAME0m
+ 1mbundle-doctor 22m- Checks the bundle for common problems
+
+1mSYNOPSIS0m
+ 1mbundle doctor 22m[--quiet] [--gemfile=GEMFILE]
+
+1mDESCRIPTION0m
+ Checks your Gemfile and gem environment for common problems. If issues
+ are detected, Bundler prints them and exits status 1. Otherwise,
+ Bundler prints a success message and exits status 0.
+
+ Examples of common problems caught by bundle-doctor include:
+
+ o Invalid Bundler settings
+
+ o Mismatched Ruby versions
+
+ o Mismatched platforms
+
+ o Uninstalled gems
+
+ o Missing dependencies
+
+
+
+1mOPTIONS0m
+ 1m--quiet0m
+ Only output warnings and errors.
+
+ 1m--gemfile=<gemfile>0m
+ The location of the Gemfile(5) which Bundler should use. This
+ defaults to a Gemfile(5) in the current working directory. In
+ general, Bundler will assume that the location of the Gemfile(5)
+ is also the project's root and will try to find 1mGemfile.lock 22mand
+ 1mvendor/cache 22mrelative to this location.
+
+
+
+
+ June 2018 BUNDLE-DOCTOR(1)
diff --git a/man/bundle-doctor.ronn b/man/bundle-doctor.ronn
new file mode 100644
index 0000000000..271ee800ad
--- /dev/null
+++ b/man/bundle-doctor.ronn
@@ -0,0 +1,33 @@
+bundle-doctor(1) -- Checks the bundle for common problems
+=========================================================
+
+## SYNOPSIS
+
+`bundle doctor` [--quiet]
+ [--gemfile=GEMFILE]
+
+## DESCRIPTION
+
+Checks your Gemfile and gem environment for common problems. If issues
+are detected, Bundler prints them and exits status 1. Otherwise,
+Bundler prints a success message and exits status 0.
+
+Examples of common problems caught by bundle-doctor include:
+
+* Invalid Bundler settings
+* Mismatched Ruby versions
+* Mismatched platforms
+* Uninstalled gems
+* Missing dependencies
+
+## OPTIONS
+
+* `--quiet`:
+ Only output warnings and errors.
+
+* `--gemfile=<gemfile>`:
+ The location of the Gemfile(5) which Bundler should use. This defaults
+ to a Gemfile(5) in the current working directory. In general, Bundler
+ will assume that the location of the Gemfile(5) is also the project's
+ root and will try to find `Gemfile.lock` and `vendor/cache` relative
+ to this location.
diff --git a/man/bundle-exec.1 b/man/bundle-exec.1
new file mode 100644
index 0000000000..48c446c206
--- /dev/null
+++ b/man/bundle-exec.1
@@ -0,0 +1,165 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-EXEC" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-exec\fR \- Execute a command in the context of the bundle
+.
+.SH "SYNOPSIS"
+\fBbundle exec\fR [\-\-keep\-file\-descriptors] \fIcommand\fR
+.
+.SH "DESCRIPTION"
+This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\.
+.
+.P
+Essentially, if you would normally have run something like \fBrspec spec/my_spec\.rb\fR, and you want to use the gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] and installed via bundle install(1) \fIbundle\-install\.1\.html\fR, you should run \fBbundle exec rspec spec/my_spec\.rb\fR\.
+.
+.P
+Note that \fBbundle exec\fR does not require that an executable is available on your shell\'s \fB$PATH\fR\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-keep\-file\-descriptors\fR
+Exec in Ruby 2\.0 began discarding non\-standard file descriptors\. When this flag is passed, exec will revert to the 1\.9 behaviour of passing all file descriptors to the new process\.
+.
+.SH "BUNDLE INSTALL \-\-BINSTUBS"
+If you use the \fB\-\-binstubs\fR flag in bundle install(1) \fIbundle\-install\.1\.html\fR, Bundler will automatically create a directory (which defaults to \fBapp_root/bin\fR) containing all of the executables available from gems in the bundle\.
+.
+.P
+After using \fB\-\-binstubs\fR, \fBbin/rspec spec/my_spec\.rb\fR is identical to \fBbundle exec rspec spec/my_spec\.rb\fR\.
+.
+.SH "ENVIRONMENT MODIFICATIONS"
+\fBbundle exec\fR makes a number of changes to the shell environment, then executes the command you specify in full\.
+.
+.IP "\(bu" 4
+make sure that it\'s still possible to shell out to \fBbundle\fR from inside a command invoked by \fBbundle exec\fR (using \fB$BUNDLE_BIN_PATH\fR)
+.
+.IP "\(bu" 4
+put the directory containing executables (like \fBrails\fR, \fBrspec\fR, \fBrackup\fR) for your bundle on \fB$PATH\fR
+.
+.IP "\(bu" 4
+make sure that if bundler is invoked in the subshell, it uses the same \fBGemfile\fR (by setting \fBBUNDLE_GEMFILE\fR)
+.
+.IP "\(bu" 4
+add \fB\-rbundler/setup\fR to \fB$RUBYOPT\fR, which makes sure that Ruby programs invoked in the subshell can see the gems in the bundle
+.
+.IP "" 0
+.
+.P
+It also modifies Rubygems:
+.
+.IP "\(bu" 4
+disallow loading additional gems not in the bundle
+.
+.IP "\(bu" 4
+modify the \fBgem\fR method to be a no\-op if a gem matching the requirements is in the bundle, and to raise a \fBGem::LoadError\fR if it\'s not
+.
+.IP "\(bu" 4
+Define \fBGem\.refresh\fR to be a no\-op, since the source index is always frozen when using bundler, and to prevent gems from the system leaking into the environment
+.
+.IP "\(bu" 4
+Override \fBGem\.bin_path\fR to use the gems in the bundle, making system executables work
+.
+.IP "\(bu" 4
+Add all gems in the bundle into Gem\.loaded_specs
+.
+.IP "" 0
+.
+.P
+Finally, \fBbundle exec\fR also implicitly modifies \fBGemfile\.lock\fR if the lockfile and the Gemfile do not match\. Bundler needs the Gemfile to determine things such as a gem\'s groups, \fBautorequire\fR, and platforms, etc\., and that information isn\'t stored in the lockfile\. The Gemfile and lockfile must be synced in order to \fBbundle exec\fR successfully, so \fBbundle exec\fR updates the lockfile beforehand\.
+.
+.SS "Loading"
+By default, when attempting to \fBbundle exec\fR to a file with a ruby shebang, Bundler will \fBKernel\.load\fR that file instead of using \fBKernel\.exec\fR\. For the vast majority of cases, this is a performance improvement\. In a rare few cases, this could cause some subtle side\-effects (such as dependence on the exact contents of \fB$0\fR or \fB__FILE__\fR) and the optimization can be disabled by enabling the \fBdisable_exec_load\fR setting\.
+.
+.SS "Shelling out"
+Any Ruby code that opens a subshell (like \fBsystem\fR, backticks, or \fB%x{}\fR) will automatically use the current Bundler environment\. If you need to shell out to a Ruby command that is not part of your current bundle, use the \fBwith_clean_env\fR method with a block\. Any subshells created inside the block will be given the environment present before Bundler was activated\. For example, Homebrew commands run Ruby, but don\'t work inside a bundle:
+.
+.IP "" 4
+.
+.nf
+
+Bundler\.with_clean_env do
+ `brew install wget`
+end
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Using \fBwith_clean_env\fR is also necessary if you are shelling out to a different bundle\. Any Bundler commands run in a subshell will inherit the current Gemfile, so commands that need to run in the context of a different bundle also need to use \fBwith_clean_env\fR\.
+.
+.IP "" 4
+.
+.nf
+
+Bundler\.with_clean_env do
+ Dir\.chdir "/other/bundler/project" do
+ `bundle exec \./script`
+ end
+end
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Bundler provides convenience helpers that wrap \fBsystem\fR and \fBexec\fR, and they can be used like this:
+.
+.IP "" 4
+.
+.nf
+
+Bundler\.clean_system(\'brew install wget\')
+Bundler\.clean_exec(\'brew install wget\')
+.
+.fi
+.
+.IP "" 0
+.
+.SH "RUBYGEMS PLUGINS"
+At present, the Rubygems plugin system requires all files named \fBrubygems_plugin\.rb\fR on the load path of \fIany\fR installed gem when any Ruby code requires \fBrubygems\.rb\fR\. This includes executables installed into the system, like \fBrails\fR, \fBrackup\fR, and \fBrspec\fR\.
+.
+.P
+Since Rubygems plugins can contain arbitrary Ruby code, they commonly end up activating themselves or their dependencies\.
+.
+.P
+For instance, the \fBgemcutter 0\.5\fR gem depended on \fBjson_pure\fR\. If you had that version of gemcutter installed (even if you \fIalso\fR had a newer version without this problem), Rubygems would activate \fBgemcutter 0\.5\fR and \fBjson_pure <latest>\fR\.
+.
+.P
+If your Gemfile(5) also contained \fBjson_pure\fR (or a gem with a dependency on \fBjson_pure\fR), the latest version on your system might conflict with the version in your Gemfile(5), or the snapshot version in your \fBGemfile\.lock\fR\.
+.
+.P
+If this happens, bundler will say:
+.
+.IP "" 4
+.
+.nf
+
+You have already activated json_pure 1\.4\.6 but your Gemfile
+requires json_pure 1\.4\.3\. Consider using bundle exec\.
+.
+.fi
+.
+.IP "" 0
+.
+.P
+In this situation, you almost certainly want to remove the underlying gem with the problematic gem plugin\. In general, the authors of these plugins (in this case, the \fBgemcutter\fR gem) have released newer versions that are more careful in their plugins\.
+.
+.P
+You can find a list of all the gems containing gem plugins by running
+.
+.IP "" 4
+.
+.nf
+
+ruby \-rubygems \-e "puts Gem\.find_files(\'rubygems_plugin\.rb\')"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+At the very least, you should remove all but the newest version of each gem plugin, and also remove all gem plugins that you aren\'t using (\fBgem uninstall gem_name\fR)\.
diff --git a/man/bundle-exec.1.txt b/man/bundle-exec.1.txt
new file mode 100644
index 0000000000..9494d9f6ad
--- /dev/null
+++ b/man/bundle-exec.1.txt
@@ -0,0 +1,178 @@
+BUNDLE-EXEC(1) BUNDLE-EXEC(1)
+
+
+
+1mNAME0m
+ 1mbundle-exec 22m- Execute a command in the context of the bundle
+
+1mSYNOPSIS0m
+ 1mbundle exec 22m[--keep-file-descriptors] 4mcommand0m
+
+1mDESCRIPTION0m
+ This command executes the command, making all gems specified in the
+ [1mGemfile(5)22m][Gemfile(5)] available to 1mrequire 22min Ruby programs.
+
+ Essentially, if you would normally have run something like 1mrspec0m
+ 1mspec/my_spec.rb22m, and you want to use the gems specified in the [1mGem-0m
+ 1mfile(5)22m][Gemfile(5)] and installed via bundle install(1) 4mbun-0m
+ 4mdle-install.1.html24m, you should run 1mbundle exec rspec spec/my_spec.rb22m.
+
+ Note that 1mbundle exec 22mdoes not require that an executable is available
+ on your shell's 1m$PATH22m.
+
+1mOPTIONS0m
+ 1m--keep-file-descriptors0m
+ Exec in Ruby 2.0 began discarding non-standard file descriptors.
+ When this flag is passed, exec will revert to the 1.9 behaviour
+ of passing all file descriptors to the new process.
+
+1mBUNDLE INSTALL --BINSTUBS0m
+ If you use the 1m--binstubs 22mflag in bundle install(1) 4mbun-0m
+ 4mdle-install.1.html24m, Bundler will automatically create a directory
+ (which defaults to 1mapp_root/bin22m) containing all of the executables
+ available from gems in the bundle.
+
+ After using 1m--binstubs22m, 1mbin/rspec spec/my_spec.rb 22mis identical to 1mbun-0m
+ 1mdle exec rspec spec/my_spec.rb22m.
+
+1mENVIRONMENT MODIFICATIONS0m
+ 1mbundle exec 22mmakes a number of changes to the shell environment, then
+ executes the command you specify in full.
+
+ o make sure that it's still possible to shell out to 1mbundle 22mfrom
+ inside a command invoked by 1mbundle exec 22m(using 1m$BUNDLE_BIN_PATH22m)
+
+ o put the directory containing executables (like 1mrails22m, 1mrspec22m,
+ 1mrackup22m) for your bundle on 1m$PATH0m
+
+ o make sure that if bundler is invoked in the subshell, it uses the
+ same 1mGemfile 22m(by setting 1mBUNDLE_GEMFILE22m)
+
+ o add 1m-rbundler/setup 22mto 1m$RUBYOPT22m, which makes sure that Ruby pro-
+ grams invoked in the subshell can see the gems in the bundle
+
+
+
+ It also modifies Rubygems:
+
+ o disallow loading additional gems not in the bundle
+
+ o modify the 1mgem 22mmethod to be a no-op if a gem matching the require-
+ ments is in the bundle, and to raise a 1mGem::LoadError 22mif it's not
+
+ o Define 1mGem.refresh 22mto be a no-op, since the source index is always
+ frozen when using bundler, and to prevent gems from the system
+ leaking into the environment
+
+ o Override 1mGem.bin_path 22mto use the gems in the bundle, making system
+ executables work
+
+ o Add all gems in the bundle into Gem.loaded_specs
+
+
+
+ Finally, 1mbundle exec 22malso implicitly modifies 1mGemfile.lock 22mif the lock-
+ file and the Gemfile do not match. Bundler needs the Gemfile to deter-
+ mine things such as a gem's groups, 1mautorequire22m, and platforms, etc.,
+ and that information isn't stored in the lockfile. The Gemfile and
+ lockfile must be synced in order to 1mbundle exec 22msuccessfully, so 1mbundle0m
+ 1mexec 22mupdates the lockfile beforehand.
+
+ 1mLoading0m
+ By default, when attempting to 1mbundle exec 22mto a file with a ruby she-
+ bang, Bundler will 1mKernel.load 22mthat file instead of using 1mKernel.exec22m.
+ For the vast majority of cases, this is a performance improvement. In a
+ rare few cases, this could cause some subtle side-effects (such as
+ dependence on the exact contents of 1m$0 22mor 1m__FILE__22m) and the optimiza-
+ tion can be disabled by enabling the 1mdisable_exec_load 22msetting.
+
+ 1mShelling out0m
+ Any Ruby code that opens a subshell (like 1msystem22m, backticks, or 1m%x{}22m)
+ will automatically use the current Bundler environment. If you need to
+ shell out to a Ruby command that is not part of your current bundle,
+ use the 1mwith_clean_env 22mmethod with a block. Any subshells created
+ inside the block will be given the environment present before Bundler
+ was activated. For example, Homebrew commands run Ruby, but don't work
+ inside a bundle:
+
+
+
+ Bundler.with_clean_env do
+ `brew install wget`
+ end
+
+
+
+ Using 1mwith_clean_env 22mis also necessary if you are shelling out to a
+ different bundle. Any Bundler commands run in a subshell will inherit
+ the current Gemfile, so commands that need to run in the context of a
+ different bundle also need to use 1mwith_clean_env22m.
+
+
+
+ Bundler.with_clean_env do
+ Dir.chdir "/other/bundler/project" do
+ `bundle exec ./script`
+ end
+ end
+
+
+
+ Bundler provides convenience helpers that wrap 1msystem 22mand 1mexec22m, and
+ they can be used like this:
+
+
+
+ Bundler.clean_system('brew install wget')
+ Bundler.clean_exec('brew install wget')
+
+
+
+1mRUBYGEMS PLUGINS0m
+ At present, the Rubygems plugin system requires all files named
+ 1mrubygems_plugin.rb 22mon the load path of 4many24m installed gem when any Ruby
+ code requires 1mrubygems.rb22m. This includes executables installed into the
+ system, like 1mrails22m, 1mrackup22m, and 1mrspec22m.
+
+ Since Rubygems plugins can contain arbitrary Ruby code, they commonly
+ end up activating themselves or their dependencies.
+
+ For instance, the 1mgemcutter 0.5 22mgem depended on 1mjson_pure22m. If you had
+ that version of gemcutter installed (even if you 4malso24m had a newer ver-
+ sion without this problem), Rubygems would activate 1mgemcutter 0.5 22mand
+ 1mjson_pure <latest>22m.
+
+ If your Gemfile(5) also contained 1mjson_pure 22m(or a gem with a dependency
+ on 1mjson_pure22m), the latest version on your system might conflict with
+ the version in your Gemfile(5), or the snapshot version in your 1mGem-0m
+ 1mfile.lock22m.
+
+ If this happens, bundler will say:
+
+
+
+ You have already activated json_pure 1.4.6 but your Gemfile
+ requires json_pure 1.4.3. Consider using bundle exec.
+
+
+
+ In this situation, you almost certainly want to remove the underlying
+ gem with the problematic gem plugin. In general, the authors of these
+ plugins (in this case, the 1mgemcutter 22mgem) have released newer versions
+ that are more careful in their plugins.
+
+ You can find a list of all the gems containing gem plugins by running
+
+
+
+ ruby -rubygems -e "puts Gem.find_files('rubygems_plugin.rb')"
+
+
+
+ At the very least, you should remove all but the newest version of each
+ gem plugin, and also remove all gem plugins that you aren't using (1mgem0m
+ 1muninstall gem_name22m).
+
+
+
+ October 2018 BUNDLE-EXEC(1)
diff --git a/man/bundle-exec.ronn b/man/bundle-exec.ronn
new file mode 100644
index 0000000000..aa680f4c5d
--- /dev/null
+++ b/man/bundle-exec.ronn
@@ -0,0 +1,152 @@
+bundle-exec(1) -- Execute a command in the context of the bundle
+================================================================
+
+## SYNOPSIS
+
+`bundle exec` [--keep-file-descriptors] <command>
+
+## DESCRIPTION
+
+This command executes the command, making all gems specified in the
+[`Gemfile(5)`][Gemfile(5)] available to `require` in Ruby programs.
+
+Essentially, if you would normally have run something like
+`rspec spec/my_spec.rb`, and you want to use the gems specified
+in the [`Gemfile(5)`][Gemfile(5)] and installed via [bundle install(1)](bundle-install.1.html), you
+should run `bundle exec rspec spec/my_spec.rb`.
+
+Note that `bundle exec` does not require that an executable is
+available on your shell's `$PATH`.
+
+## OPTIONS
+
+* `--keep-file-descriptors`:
+ Exec in Ruby 2.0 began discarding non-standard file descriptors. When this
+ flag is passed, exec will revert to the 1.9 behaviour of passing all file
+ descriptors to the new process.
+
+## BUNDLE INSTALL --BINSTUBS
+
+If you use the `--binstubs` flag in [bundle install(1)](bundle-install.1.html), Bundler will
+automatically create a directory (which defaults to `app_root/bin`)
+containing all of the executables available from gems in the bundle.
+
+After using `--binstubs`, `bin/rspec spec/my_spec.rb` is identical
+to `bundle exec rspec spec/my_spec.rb`.
+
+## ENVIRONMENT MODIFICATIONS
+
+`bundle exec` makes a number of changes to the shell environment,
+then executes the command you specify in full.
+
+* make sure that it's still possible to shell out to `bundle`
+ from inside a command invoked by `bundle exec` (using
+ `$BUNDLE_BIN_PATH`)
+* put the directory containing executables (like `rails`, `rspec`,
+ `rackup`) for your bundle on `$PATH`
+* make sure that if bundler is invoked in the subshell, it uses
+ the same `Gemfile` (by setting `BUNDLE_GEMFILE`)
+* add `-rbundler/setup` to `$RUBYOPT`, which makes sure that
+ Ruby programs invoked in the subshell can see the gems in
+ the bundle
+
+It also modifies Rubygems:
+
+* disallow loading additional gems not in the bundle
+* modify the `gem` method to be a no-op if a gem matching
+ the requirements is in the bundle, and to raise a
+ `Gem::LoadError` if it's not
+* Define `Gem.refresh` to be a no-op, since the source
+ index is always frozen when using bundler, and to
+ prevent gems from the system leaking into the environment
+* Override `Gem.bin_path` to use the gems in the bundle,
+ making system executables work
+* Add all gems in the bundle into Gem.loaded_specs
+
+Finally, `bundle exec` also implicitly modifies `Gemfile.lock` if the lockfile
+and the Gemfile do not match. Bundler needs the Gemfile to determine things
+such as a gem's groups, `autorequire`, and platforms, etc., and that
+information isn't stored in the lockfile. The Gemfile and lockfile must be
+synced in order to `bundle exec` successfully, so `bundle exec`
+updates the lockfile beforehand.
+
+### Loading
+
+By default, when attempting to `bundle exec` to a file with a ruby shebang,
+Bundler will `Kernel.load` that file instead of using `Kernel.exec`. For the
+vast majority of cases, this is a performance improvement. In a rare few cases,
+this could cause some subtle side-effects (such as dependence on the exact
+contents of `$0` or `__FILE__`) and the optimization can be disabled by enabling
+the `disable_exec_load` setting.
+
+### Shelling out
+
+Any Ruby code that opens a subshell (like `system`, backticks, or `%x{}`) will
+automatically use the current Bundler environment. If you need to shell out to
+a Ruby command that is not part of your current bundle, use the
+`with_clean_env` method with a block. Any subshells created inside the block
+will be given the environment present before Bundler was activated. For
+example, Homebrew commands run Ruby, but don't work inside a bundle:
+
+ Bundler.with_clean_env do
+ `brew install wget`
+ end
+
+Using `with_clean_env` is also necessary if you are shelling out to a different
+bundle. Any Bundler commands run in a subshell will inherit the current
+Gemfile, so commands that need to run in the context of a different bundle also
+need to use `with_clean_env`.
+
+ Bundler.with_clean_env do
+ Dir.chdir "/other/bundler/project" do
+ `bundle exec ./script`
+ end
+ end
+
+Bundler provides convenience helpers that wrap `system` and `exec`, and they
+can be used like this:
+
+ Bundler.clean_system('brew install wget')
+ Bundler.clean_exec('brew install wget')
+
+
+## RUBYGEMS PLUGINS
+
+At present, the Rubygems plugin system requires all files
+named `rubygems_plugin.rb` on the load path of _any_ installed
+gem when any Ruby code requires `rubygems.rb`. This includes
+executables installed into the system, like `rails`, `rackup`,
+and `rspec`.
+
+Since Rubygems plugins can contain arbitrary Ruby code, they
+commonly end up activating themselves or their dependencies.
+
+For instance, the `gemcutter 0.5` gem depended on `json_pure`.
+If you had that version of gemcutter installed (even if
+you _also_ had a newer version without this problem), Rubygems
+would activate `gemcutter 0.5` and `json_pure <latest>`.
+
+If your Gemfile(5) also contained `json_pure` (or a gem
+with a dependency on `json_pure`), the latest version on
+your system might conflict with the version in your
+Gemfile(5), or the snapshot version in your `Gemfile.lock`.
+
+If this happens, bundler will say:
+
+ You have already activated json_pure 1.4.6 but your Gemfile
+ requires json_pure 1.4.3. Consider using bundle exec.
+
+In this situation, you almost certainly want to remove the
+underlying gem with the problematic gem plugin. In general,
+the authors of these plugins (in this case, the `gemcutter`
+gem) have released newer versions that are more careful in
+their plugins.
+
+You can find a list of all the gems containing gem plugins
+by running
+
+ ruby -rubygems -e "puts Gem.find_files('rubygems_plugin.rb')"
+
+At the very least, you should remove all but the newest
+version of each gem plugin, and also remove all gem plugins
+that you aren't using (`gem uninstall gem_name`).
diff --git a/man/bundle-gem.1 b/man/bundle-gem.1
new file mode 100644
index 0000000000..9aa6183240
--- /dev/null
+++ b/man/bundle-gem.1
@@ -0,0 +1,80 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-GEM" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem
+.
+.SH "SYNOPSIS"
+\fBbundle gem\fR \fIGEM_NAME\fR \fIOPTIONS\fR
+.
+.SH "DESCRIPTION"
+Generates a directory named \fBGEM_NAME\fR with a \fBRakefile\fR, \fBGEM_NAME\.gemspec\fR, and other supporting files and directories that can be used to develop a rubygem with that name\.
+.
+.P
+Run \fBrake \-T\fR in the resulting project for a list of Rake tasks that can be used to test and publish the gem to rubygems\.org\.
+.
+.P
+The generated project skeleton can be customized with OPTIONS, as explained below\. Note that these options can also be specified via Bundler\'s global configuration file using the following names:
+.
+.IP "\(bu" 4
+\fBgem\.coc\fR
+.
+.IP "\(bu" 4
+\fBgem\.mit\fR
+.
+.IP "\(bu" 4
+\fBgem\.test\fR
+.
+.IP "" 0
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-exe\fR or \fB\-b\fR or \fB\-\-bin\fR
+Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\.
+.
+.TP
+\fB\-\-no\-exe\fR
+Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\.
+.
+.TP
+\fB\-\-coc\fR
+Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\.
+.
+.TP
+\fB\-\-no\-coc\fR
+Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\.
+.
+.TP
+\fB\-\-ext\fR
+Add boilerplate for C extension code to the generated project\. This behavior is disabled by default\.
+.
+.TP
+\fB\-\-no\-ext\fR
+Do not add C extension code (overrides \fB\-\-ext\fR specified in the global config)\.
+.
+.TP
+\fB\-\-mit\fR
+Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\.
+.
+.TP
+\fB\-\-no\-mit\fR
+Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\.
+.
+.TP
+\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR
+Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR and \fBrspec\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\. If no option is specified, the default testing framework is RSpec\.
+.
+.TP
+\fB\-e\fR, \fB\-\-edit[=EDITOR]\fR
+Open the resulting GEM_NAME\.gemspec in EDITOR, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\.
+.
+.SH "SEE ALSO"
+.
+.IP "\(bu" 4
+bundle config(1) \fIbundle\-config\.1\.html\fR
+.
+.IP "" 0
+
diff --git a/man/bundle-gem.1.txt b/man/bundle-gem.1.txt
new file mode 100644
index 0000000000..762fc35ae9
--- /dev/null
+++ b/man/bundle-gem.1.txt
@@ -0,0 +1,91 @@
+BUNDLE-GEM(1) BUNDLE-GEM(1)
+
+
+
+1mNAME0m
+ 1mbundle-gem 22m- Generate a project skeleton for creating a rubygem
+
+1mSYNOPSIS0m
+ 1mbundle gem 4m22mGEM_NAME24m 4mOPTIONS0m
+
+1mDESCRIPTION0m
+ Generates a directory named 1mGEM_NAME 22mwith a 1mRakefile22m, 1mGEM_NAME.gemspec22m,
+ and other supporting files and directories that can be used to develop
+ a rubygem with that name.
+
+ Run 1mrake -T 22min the resulting project for a list of Rake tasks that can
+ be used to test and publish the gem to rubygems.org.
+
+ The generated project skeleton can be customized with OPTIONS, as
+ explained below. Note that these options can also be specified via
+ Bundler's global configuration file using the following names:
+
+ o 1mgem.coc0m
+
+ o 1mgem.mit0m
+
+ o 1mgem.test0m
+
+
+
+1mOPTIONS0m
+ 1m--exe 22mor 1m-b 22mor 1m--bin0m
+ Specify that Bundler should create a binary executable (as
+ 1mexe/GEM_NAME22m) in the generated rubygem project. This binary will
+ also be added to the 1mGEM_NAME.gemspec 22mmanifest. This behavior is
+ disabled by default.
+
+ 1m--no-exe0m
+ Do not create a binary (overrides 1m--exe 22mspecified in the global
+ config).
+
+ 1m--coc 22mAdd a 1mCODE_OF_CONDUCT.md 22mfile to the root of the generated
+ project. If this option is unspecified, an interactive prompt
+ will be displayed and the answer will be saved in Bundler's
+ global config for future 1mbundle gem 22muse.
+
+ 1m--no-coc0m
+ Do not create a 1mCODE_OF_CONDUCT.md 22m(overrides 1m--coc 22mspecified in
+ the global config).
+
+ 1m--ext 22mAdd boilerplate for C extension code to the generated project.
+ This behavior is disabled by default.
+
+ 1m--no-ext0m
+ Do not add C extension code (overrides 1m--ext 22mspecified in the
+ global config).
+
+ 1m--mit 22mAdd an MIT license to a 1mLICENSE.txt 22mfile in the root of the gen-
+ erated project. Your name from the global git config is used for
+ the copyright statement. If this option is unspecified, an
+ interactive prompt will be displayed and the answer will be
+ saved in Bundler's global config for future 1mbundle gem 22muse.
+
+ 1m--no-mit0m
+ Do not create a 1mLICENSE.txt 22m(overrides 1m--mit 22mspecified in the
+ global config).
+
+ 1m-t22m, 1m--test=minitest22m, 1m--test=rspec0m
+ Specify the test framework that Bundler should use when generat-
+ ing the project. Acceptable values are 1mminitest 22mand 1mrspec22m. The
+ 1mGEM_NAME.gemspec 22mwill be configured and a skeleton test/spec
+ directory will be created based on this option. If this option
+ is unspecified, an interactive prompt will be displayed and the
+ answer will be saved in Bundler's global config for future 1mbun-0m
+ 1mdle gem 22muse. If no option is specified, the default testing
+ framework is RSpec.
+
+ 1m-e22m, 1m--edit[=EDITOR]0m
+ Open the resulting GEM_NAME.gemspec in EDITOR, or the default
+ editor if not specified. The default is 1m$BUNDLER_EDITOR22m, 1m$VIS-0m
+ 1mUAL22m, or 1m$EDITOR22m.
+
+1mSEE ALSO0m
+ o bundle config(1) 4mbundle-config.1.html0m
+
+
+
+
+
+
+ October 2018 BUNDLE-GEM(1)
diff --git a/man/bundle-gem.ronn b/man/bundle-gem.ronn
new file mode 100644
index 0000000000..cf3d037df2
--- /dev/null
+++ b/man/bundle-gem.ronn
@@ -0,0 +1,78 @@
+bundle-gem(1) -- Generate a project skeleton for creating a rubygem
+====================================================================
+
+## SYNOPSIS
+
+`bundle gem` <GEM_NAME> [OPTIONS]
+
+## DESCRIPTION
+
+Generates a directory named `GEM_NAME` with a `Rakefile`, `GEM_NAME.gemspec`,
+and other supporting files and directories that can be used to develop a
+rubygem with that name.
+
+Run `rake -T` in the resulting project for a list of Rake tasks that can be used
+to test and publish the gem to rubygems.org.
+
+The generated project skeleton can be customized with OPTIONS, as explained
+below. Note that these options can also be specified via Bundler's global
+configuration file using the following names:
+
+* `gem.coc`
+* `gem.mit`
+* `gem.test`
+
+## OPTIONS
+
+* `--exe` or `-b` or `--bin`:
+ Specify that Bundler should create a binary executable (as `exe/GEM_NAME`)
+ in the generated rubygem project. This binary will also be added to the
+ `GEM_NAME.gemspec` manifest. This behavior is disabled by default.
+
+* `--no-exe`:
+ Do not create a binary (overrides `--exe` specified in the global config).
+
+* `--coc`:
+ Add a `CODE_OF_CONDUCT.md` file to the root of the generated project. If
+ this option is unspecified, an interactive prompt will be displayed and the
+ answer will be saved in Bundler's global config for future `bundle gem` use.
+
+* `--no-coc`:
+ Do not create a `CODE_OF_CONDUCT.md` (overrides `--coc` specified in the
+ global config).
+
+* `--ext`:
+ Add boilerplate for C extension code to the generated project. This behavior
+ is disabled by default.
+
+* `--no-ext`:
+ Do not add C extension code (overrides `--ext` specified in the global
+ config).
+
+* `--mit`:
+ Add an MIT license to a `LICENSE.txt` file in the root of the generated
+ project. Your name from the global git config is used for the copyright
+ statement. If this option is unspecified, an interactive prompt will be
+ displayed and the answer will be saved in Bundler's global config for future
+ `bundle gem` use.
+
+* `--no-mit`:
+ Do not create a `LICENSE.txt` (overrides `--mit` specified in the global
+ config).
+
+* `-t`, `--test=minitest`, `--test=rspec`:
+ Specify the test framework that Bundler should use when generating the
+ project. Acceptable values are `minitest` and `rspec`. The `GEM_NAME.gemspec`
+ will be configured and a skeleton test/spec directory will be created based
+ on this option. If this option is unspecified, an interactive prompt will be
+ displayed and the answer will be saved in Bundler's global config for future
+ `bundle gem` use.
+ If no option is specified, the default testing framework is RSpec.
+
+* `-e`, `--edit[=EDITOR]`:
+ Open the resulting GEM_NAME.gemspec in EDITOR, or the default editor if not
+ specified. The default is `$BUNDLER_EDITOR`, `$VISUAL`, or `$EDITOR`.
+
+## SEE ALSO
+
+* [bundle config(1)](bundle-config.1.html)
diff --git a/man/bundle-info.1 b/man/bundle-info.1
new file mode 100644
index 0000000000..d04fa24a91
--- /dev/null
+++ b/man/bundle-info.1
@@ -0,0 +1,20 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-INFO" "1" "May 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-info\fR \- Show information for the given gem in your bundle
+.
+.SH "SYNOPSIS"
+\fBbundle info\fR [GEM] [\-\-path]
+.
+.SH "DESCRIPTION"
+Print the basic information about the provided GEM such as homepage, version, path and summary\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-path\fR
+Print the path of the given gem
+
diff --git a/man/bundle-info.1.txt b/man/bundle-info.1.txt
new file mode 100644
index 0000000000..022e8d84ff
--- /dev/null
+++ b/man/bundle-info.1.txt
@@ -0,0 +1,21 @@
+BUNDLE-INFO(1) BUNDLE-INFO(1)
+
+
+
+1mNAME0m
+ 1mbundle-info 22m- Show information for the given gem in your bundle
+
+1mSYNOPSIS0m
+ 1mbundle info 22m[GEM] [--path]
+
+1mDESCRIPTION0m
+ Print the basic information about the provided GEM such as homepage,
+ version, path and summary.
+
+1mOPTIONS0m
+ 1m--path 22mPrint the path of the given gem
+
+
+
+
+ May 2018 BUNDLE-INFO(1)
diff --git a/man/bundle-info.ronn b/man/bundle-info.ronn
new file mode 100644
index 0000000000..47e457aa3c
--- /dev/null
+++ b/man/bundle-info.ronn
@@ -0,0 +1,17 @@
+bundle-info(1) -- Show information for the given gem in your bundle
+=========================================================================
+
+## SYNOPSIS
+
+`bundle info` [GEM]
+ [--path]
+
+## DESCRIPTION
+
+Print the basic information about the provided GEM such as homepage, version,
+path and summary.
+
+## OPTIONS
+
+* `--path`:
+Print the path of the given gem
diff --git a/man/bundle-init.1 b/man/bundle-init.1
new file mode 100644
index 0000000000..6f80b1c68d
--- /dev/null
+++ b/man/bundle-init.1
@@ -0,0 +1,25 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-INIT" "1" "June 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-init\fR \- Generates a Gemfile into the current working directory
+.
+.SH "SYNOPSIS"
+\fBbundle init\fR [\-\-gemspec=FILE]
+.
+.SH "DESCRIPTION"
+Init generates a default [\fBGemfile(5)\fR][Gemfile(5)] in the current working directory\. When adding a [\fBGemfile(5)\fR][Gemfile(5)] to a gem with a gemspec, the \fB\-\-gemspec\fR option will automatically add each dependency listed in the gemspec file to the newly created [\fBGemfile(5)\fR][Gemfile(5)]\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-gemspec\fR
+Use the specified \.gemspec to create the [\fBGemfile(5)\fR][Gemfile(5)]
+.
+.SH "FILES"
+Included in the default [\fBGemfile(5)\fR][Gemfile(5)] generated is the line \fB# frozen_string_literal: true\fR\. This is a magic comment supported for the first time in Ruby 2\.3\. The presence of this line results in all string literals in the file being implicitly frozen\.
+.
+.SH "SEE ALSO"
+Gemfile(5) \fIhttp://bundler\.io/man/gemfile\.5\.html\fR
diff --git a/man/bundle-init.1.txt b/man/bundle-init.1.txt
new file mode 100644
index 0000000000..3c34dafc47
--- /dev/null
+++ b/man/bundle-init.1.txt
@@ -0,0 +1,34 @@
+BUNDLE-INIT(1) BUNDLE-INIT(1)
+
+
+
+1mNAME0m
+ 1mbundle-init 22m- Generates a Gemfile into the current working directory
+
+1mSYNOPSIS0m
+ 1mbundle init 22m[--gemspec=FILE]
+
+1mDESCRIPTION0m
+ Init generates a default [1mGemfile(5)22m][Gemfile(5)] in the current work-
+ ing directory. When adding a [1mGemfile(5)22m][Gemfile(5)] to a gem with a
+ gemspec, the 1m--gemspec 22moption will automatically add each dependency
+ listed in the gemspec file to the newly created [1mGemfile(5)22m][Gem-
+ file(5)].
+
+1mOPTIONS0m
+ 1m--gemspec0m
+ Use the specified .gemspec to create the [1mGemfile(5)22m][Gem-
+ file(5)]
+
+1mFILES0m
+ Included in the default [1mGemfile(5)22m][Gemfile(5)] generated is the line
+ 1m# frozen_string_literal: true22m. This is a magic comment supported for
+ the first time in Ruby 2.3. The presence of this line results in all
+ string literals in the file being implicitly frozen.
+
+1mSEE ALSO0m
+ Gemfile(5) 4mhttp://bundler.io/man/gemfile.5.html0m
+
+
+
+ June 2018 BUNDLE-INIT(1)
diff --git a/man/bundle-init.ronn b/man/bundle-init.ronn
new file mode 100644
index 0000000000..7504af7bab
--- /dev/null
+++ b/man/bundle-init.ronn
@@ -0,0 +1,29 @@
+bundle-init(1) -- Generates a Gemfile into the current working directory
+========================================================================
+
+## SYNOPSIS
+
+`bundle init` [--gemspec=FILE]
+
+## DESCRIPTION
+
+Init generates a default [`Gemfile(5)`][Gemfile(5)] in the current working directory. When
+adding a [`Gemfile(5)`][Gemfile(5)] to a gem with a gemspec, the `--gemspec` option will
+automatically add each dependency listed in the gemspec file to the newly
+created [`Gemfile(5)`][Gemfile(5)].
+
+## OPTIONS
+
+* `--gemspec`:
+ Use the specified .gemspec to create the [`Gemfile(5)`][Gemfile(5)]
+
+## FILES
+
+Included in the default [`Gemfile(5)`][Gemfile(5)]
+generated is the line `# frozen_string_literal: true`. This is a magic comment
+supported for the first time in Ruby 2.3. The presence of this line
+results in all string literals in the file being implicitly frozen.
+
+## SEE ALSO
+
+[Gemfile(5)](http://bundler.io/man/gemfile.5.html)
diff --git a/man/bundle-inject.1 b/man/bundle-inject.1
new file mode 100644
index 0000000000..3c66ff8dce
--- /dev/null
+++ b/man/bundle-inject.1
@@ -0,0 +1,33 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-INJECT" "1" "June 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile
+.
+.SH "SYNOPSIS"
+\fBbundle inject\fR [GEM] [VERSION]
+.
+.SH "DESCRIPTION"
+Adds the named gem(s) with their version requirements to the resolved [\fBGemfile(5)\fR][Gemfile(5)]\.
+.
+.P
+This command will add the gem to both your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock if it isn\'t listed yet\.
+.
+.P
+Example:
+.
+.IP "" 4
+.
+.nf
+
+bundle install
+bundle inject \'rack\' \'> 0\'
+.
+.fi
+.
+.IP "" 0
+.
+.P
+This will inject the \'rack\' gem with a version greater than 0 in your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock
diff --git a/man/bundle-inject.1.txt b/man/bundle-inject.1.txt
new file mode 100644
index 0000000000..e7fba5d517
--- /dev/null
+++ b/man/bundle-inject.1.txt
@@ -0,0 +1,32 @@
+BUNDLE-INJECT(1) BUNDLE-INJECT(1)
+
+
+
+1mNAME0m
+ 1mbundle-inject 22m- Add named gem(s) with version requirements to Gemfile
+
+1mSYNOPSIS0m
+ 1mbundle inject 22m[GEM] [VERSION]
+
+1mDESCRIPTION0m
+ Adds the named gem(s) with their version requirements to the resolved
+ [1mGemfile(5)22m][Gemfile(5)].
+
+ This command will add the gem to both your [1mGemfile(5)22m][Gemfile(5)] and
+ Gemfile.lock if it isn't listed yet.
+
+ Example:
+
+
+
+ bundle install
+ bundle inject 'rack' '> 0'
+
+
+
+ This will inject the 'rack' gem with a version greater than 0 in your
+ [1mGemfile(5)22m][Gemfile(5)] and Gemfile.lock
+
+
+
+ June 2018 BUNDLE-INJECT(1)
diff --git a/man/bundle-inject.ronn b/man/bundle-inject.ronn
new file mode 100644
index 0000000000..f454341896
--- /dev/null
+++ b/man/bundle-inject.ronn
@@ -0,0 +1,22 @@
+bundle-inject(1) -- Add named gem(s) with version requirements to Gemfile
+=========================================================================
+
+## SYNOPSIS
+
+`bundle inject` [GEM] [VERSION]
+
+## DESCRIPTION
+
+Adds the named gem(s) with their version requirements to the resolved
+[`Gemfile(5)`][Gemfile(5)].
+
+This command will add the gem to both your [`Gemfile(5)`][Gemfile(5)] and Gemfile.lock if it
+isn't listed yet.
+
+Example:
+
+ bundle install
+ bundle inject 'rack' '> 0'
+
+This will inject the 'rack' gem with a version greater than 0 in your
+[`Gemfile(5)`][Gemfile(5)] and Gemfile.lock
diff --git a/man/bundle-install.1 b/man/bundle-install.1
new file mode 100644
index 0000000000..6026713af1
--- /dev/null
+++ b/man/bundle-install.1
@@ -0,0 +1,311 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-INSTALL" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
+.
+.SH "SYNOPSIS"
+\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-quiet] [\-\-redownload] [\-\-retry=NUMBER] [\-\-shebang] [\-\-standalone[=GROUP[ GROUP\.\.\.]]] [\-\-system] [\-\-trust\-policy=POLICY] [\-\-with=GROUP[ GROUP\.\.\.]] [\-\-without=GROUP[ GROUP\.\.\.]]
+.
+.SH "DESCRIPTION"
+Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\.
+.
+.P
+If a \fBGemfile\.lock\fR does exist, and you have not updated your Gemfile(5), Bundler will fetch all remote sources, but use the dependencies specified in the \fBGemfile\.lock\fR instead of resolving dependencies\.
+.
+.P
+If a \fBGemfile\.lock\fR does exist, and you have updated your Gemfile(5), Bundler will use the dependencies in the \fBGemfile\.lock\fR for all gems that you did not update, but will re\-resolve the dependencies of gems that you did update\. You can find more information about this update process below under \fICONSERVATIVE UPDATING\fR\.
+.
+.SH "OPTIONS"
+To apply any of \fB\-\-binstubs\fR, \fB\-\-deployment\fR, \fB\-\-path\fR, or \fB\-\-without\fR every time \fBbundle install\fR is run, use \fBbundle config\fR (see bundle\-config(1))\.
+.
+.TP
+\fB\-\-binstubs[=<directory>]\fR
+Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it in \fBbin/\fR\. This lets you link the binstub inside of an application to the exact gem version the application needs\.
+.
+.IP
+Creates a directory (defaults to \fB~/bin\fR) and places any executables from the gem there\. These executables run in Bundler\'s context\. If used, you might add this directory to your environment\'s \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\.
+.
+.TP
+\fB\-\-clean\fR
+On finishing the installation Bundler is going to remove any gems not present in the current Gemfile(5)\. Don\'t worry, gems currently in use will not be removed\.
+.
+.TP
+\fB\-\-deployment\fR
+In \fIdeployment mode\fR, Bundler will \'roll\-out\' the bundle for production or CI use\. Please check carefully if you want to have this option enabled in your development environment\.
+.
+.TP
+\fB\-\-redownload\fR
+Force download every gem, even if the required versions are already available locally\.
+.
+.TP
+\fB\-\-frozen\fR
+Do not allow the Gemfile\.lock to be updated after this install\. Exits non\-zero if there are going to be changes to the Gemfile\.lock\.
+.
+.TP
+\fB\-\-full\-index\fR
+Bundler will not call Rubygems\' API endpoint (default) but download and cache a (currently big) index file of all gems\. Performance can be improved for large bundles that seldom change by enabling this option\.
+.
+.TP
+\fB\-\-gemfile=<gemfile>\fR
+The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project\'s root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\.
+.
+.TP
+\fB\-\-jobs=[<number>]\fR, \fB\-j[<number>]\fR
+The maximum number of parallel download and install jobs\. The default is \fB1\fR\.
+.
+.TP
+\fB\-\-local\fR
+Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems\' cache or in \fBvendor/cache\fR\. Note that if a appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\.
+.
+.TP
+\fB\-\-no\-cache\fR
+Do not update the cache in \fBvendor/cache\fR with the newly bundled gems\. This does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install\.
+.
+.TP
+\fB\-\-no\-prune\fR
+Don\'t remove stale gems from the cache when the installation finishes\.
+.
+.TP
+\fB\-\-path=<path>\fR
+The location to install the specified gems to\. This defaults to Rubygems\' setting\. Bundler shares this location with Rubygems, \fBgem install \.\.\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \.\.\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\.
+.
+.TP
+\fB\-\-quiet\fR
+Do not print progress information to the standard output\. Instead, Bundler will exit using a status code (\fB$?\fR)\.
+.
+.TP
+\fB\-\-retry=[<number>]\fR
+Retry failed network or git requests for \fInumber\fR times\.
+.
+.TP
+\fB\-\-shebang=<ruby\-executable>\fR
+Uses the specified ruby executable (usually \fBruby\fR) to execute the scripts created with \fB\-\-binstubs\fR\. In addition, if you use \fB\-\-binstubs\fR together with \fB\-\-shebang jruby\fR these executables will be changed to execute \fBjruby\fR instead\.
+.
+.TP
+\fB\-\-standalone[=<list>]\fR
+Makes a bundle that can work without depending on Rubygems or Bundler at runtime\. A space separated list of groups to install has to be specified\. Bundler creates a directory named \fBbundle\fR and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler\'s own setup in the manner required\. Using this option implicitly sets \fBpath\fR, which is a [remembered option][REMEMBERED OPTIONS]\.
+.
+.TP
+\fB\-\-system\fR
+Installs the gems specified in the bundle to the system\'s Rubygems location\. This overrides any previous configuration of \fB\-\-path\fR\.
+.
+.TP
+\fB\-\-trust\-policy=[<policy>]\fR
+Apply the Rubygems security policy \fIpolicy\fR, where policy is one of \fBHighSecurity\fR, \fBMediumSecurity\fR, \fBLowSecurity\fR, \fBAlmostNoSecurity\fR, or \fBNoSecurity\fR\. For more details, please see the Rubygems signing documentation linked below in \fISEE ALSO\fR\.
+.
+.TP
+\fB\-\-with=<list>\fR
+A space\-separated list of groups referencing gems to install\. If an optional group is given it is installed\. If a group is given that is in the remembered list of groups given to \-\-without, it is removed from that list\.
+.
+.TP
+\fB\-\-without=<list>\fR
+A space\-separated list of groups referencing gems to skip during installation\. If a group is given that is in the remembered list of groups given to \-\-with, it is removed from that list\.
+.
+.SH "DEPLOYMENT MODE"
+Bundler\'s defaults are optimized for development\. To switch to defaults optimized for deployment and for CI, use the \fB\-\-deployment\fR flag\. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified\.
+.
+.IP "1." 4
+A \fBGemfile\.lock\fR is required\.
+.
+.IP
+To ensure that the same versions of the gems you developed with and tested with are also used in deployments, a \fBGemfile\.lock\fR is required\.
+.
+.IP
+This is mainly to ensure that you remember to check your \fBGemfile\.lock\fR into version control\.
+.
+.IP "2." 4
+The \fBGemfile\.lock\fR must be up to date
+.
+.IP
+In development, you can modify your Gemfile(5) and re\-run \fBbundle install\fR to \fIconservatively update\fR your \fBGemfile\.lock\fR snapshot\.
+.
+.IP
+In deployment, your \fBGemfile\.lock\fR should be up\-to\-date with changes made in your Gemfile(5)\.
+.
+.IP "3." 4
+Gems are installed to \fBvendor/bundle\fR not your default system location
+.
+.IP
+In development, it\'s convenient to share the gems used in your application with other applications and other scripts that run on the system\.
+.
+.IP
+In deployment, isolation is a more important default\. In addition, the user deploying the application may not have permission to install gems to the system, or the web server may not have permission to read them\.
+.
+.IP
+As a result, \fBbundle install \-\-deployment\fR installs gems to the \fBvendor/bundle\fR directory in the application\. This may be overridden using the \fB\-\-path\fR option\.
+.
+.IP "" 0
+.
+.SH "SUDO USAGE"
+By default, Bundler installs gems to the same location as \fBgem install\fR\.
+.
+.P
+In some cases, that location may not be writable by your Unix user\. In that case, Bundler will stage everything in a temporary directory, then ask you for your \fBsudo\fR password in order to copy the gems into their system location\.
+.
+.P
+From your perspective, this is identical to installing the gems directly into the system\.
+.
+.P
+You should never use \fBsudo bundle install\fR\. This is because several other steps in \fBbundle install\fR must be performed as the current user:
+.
+.IP "\(bu" 4
+Updating your \fBGemfile\.lock\fR
+.
+.IP "\(bu" 4
+Updating your \fBvendor/cache\fR, if necessary
+.
+.IP "\(bu" 4
+Checking out private git repositories using your user\'s SSH keys
+.
+.IP "" 0
+.
+.P
+Of these three, the first two could theoretically be performed by \fBchown\fRing the resulting files to \fB$SUDO_USER\fR\. The third, however, can only be performed by invoking the \fBgit\fR command as the current user\. Therefore, git gems are downloaded and installed into \fB~/\.bundle\fR rather than $GEM_HOME or $BUNDLE_PATH\.
+.
+.P
+As a result, you should run \fBbundle install\fR as the current user, and Bundler will ask for your password if it is needed to put the gems into their final location\.
+.
+.SH "INSTALLING GROUPS"
+By default, \fBbundle install\fR will install all gems in all groups in your Gemfile(5), except those declared for a different platform\.
+.
+.P
+However, you can explicitly tell Bundler to skip installing certain groups with the \fB\-\-without\fR option\. This option takes a space\-separated list of groups\.
+.
+.P
+While the \fB\-\-without\fR option will skip \fIinstalling\fR the gems in the specified groups, it will still \fIdownload\fR those gems and use them to resolve the dependencies of every gem in your Gemfile(5)\.
+.
+.P
+This is so that installing a different set of groups on another machine (such as a production server) will not change the gems and versions that you have already developed and tested against\.
+.
+.P
+\fBBundler offers a rock\-solid guarantee that the third\-party code you are running in development and testing is also the third\-party code you are running in production\. You can choose to exclude some of that code in different environments, but you will never be caught flat\-footed by different versions of third\-party code being used in different environments\.\fR
+.
+.P
+For a simple illustration, consider the following Gemfile(5):
+.
+.IP "" 4
+.
+.nf
+
+source \'https://rubygems\.org\'
+
+gem \'sinatra\'
+
+group :production do
+ gem \'rack\-perftools\-profiler\'
+end
+.
+.fi
+.
+.IP "" 0
+.
+.P
+In this case, \fBsinatra\fR depends on any version of Rack (\fB>= 1\.0\fR), while \fBrack\-perftools\-profiler\fR depends on 1\.x (\fB~> 1\.0\fR)\.
+.
+.P
+When you run \fBbundle install \-\-without production\fR in development, we look at the dependencies of \fBrack\-perftools\-profiler\fR as well\. That way, you do not spend all your time developing against Rack 2\.0, using new APIs unavailable in Rack 1\.x, only to have Bundler switch to Rack 1\.2 when the \fBproduction\fR group \fIis\fR used\.
+.
+.P
+This should not cause any problems in practice, because we do not attempt to \fBinstall\fR the gems in the excluded groups, and only evaluate as part of the dependency resolution process\.
+.
+.P
+This also means that you cannot include different versions of the same gem in different groups, because doing so would result in different sets of dependencies used in development and production\. Because of the vagaries of the dependency resolution process, this usually affects more than the gems you list in your Gemfile(5), and can (surprisingly) radically change the gems you are using\.
+.
+.SH "THE GEMFILE\.LOCK"
+When you run \fBbundle install\fR, Bundler will persist the full names and versions of all gems that you used (including dependencies of the gems specified in the Gemfile(5)) into a file called \fBGemfile\.lock\fR\.
+.
+.P
+Bundler uses this file in all subsequent calls to \fBbundle install\fR, which guarantees that you always use the same exact code, even as your application moves across machines\.
+.
+.P
+Because of the way dependency resolution works, even a seemingly small change (for instance, an update to a point\-release of a dependency of a gem in your Gemfile(5)) can result in radically different gems being needed to satisfy all dependencies\.
+.
+.P
+As a result, you \fBSHOULD\fR check your \fBGemfile\.lock\fR into version control, in both applications and gems\. If you do not, every machine that checks out your repository (including your production server) will resolve all dependencies again, which will result in different versions of third\-party code being used if \fBany\fR of the gems in the Gemfile(5) or any of their dependencies have been updated\.
+.
+.P
+When Bundler first shipped, the \fBGemfile\.lock\fR was included in the \fB\.gitignore\fR file included with generated gems\. Over time, however, it became clear that this practice forces the pain of broken dependencies onto new contributors, while leaving existing contributors potentially unaware of the problem\. Since \fBbundle install\fR is usually the first step towards a contribution, the pain of broken dependencies would discourage new contributors from contributing\. As a result, we have revised our guidance for gem authors to now recommend checking in the lock for gems\.
+.
+.SH "CONSERVATIVE UPDATING"
+When you make a change to the Gemfile(5) and then run \fBbundle install\fR, Bundler will update only the gems that you modified\.
+.
+.P
+In other words, if a gem that you \fBdid not modify\fR worked before you called \fBbundle install\fR, it will continue to use the exact same versions of all dependencies as it used before the update\.
+.
+.P
+Let\'s take a look at an example\. Here\'s your original Gemfile(5):
+.
+.IP "" 4
+.
+.nf
+
+source \'https://rubygems\.org\'
+
+gem \'actionpack\', \'2\.3\.8\'
+gem \'activemerchant\'
+.
+.fi
+.
+.IP "" 0
+.
+.P
+In this case, both \fBactionpack\fR and \fBactivemerchant\fR depend on \fBactivesupport\fR\. The \fBactionpack\fR gem depends on \fBactivesupport 2\.3\.8\fR and \fBrack ~> 1\.1\.0\fR, while the \fBactivemerchant\fR gem depends on \fBactivesupport >= 2\.3\.2\fR, \fBbraintree >= 2\.0\.0\fR, and \fBbuilder >= 2\.0\.0\fR\.
+.
+.P
+When the dependencies are first resolved, Bundler will select \fBactivesupport 2\.3\.8\fR, which satisfies the requirements of both gems in your Gemfile(5)\.
+.
+.P
+Next, you modify your Gemfile(5) to:
+.
+.IP "" 4
+.
+.nf
+
+source \'https://rubygems\.org\'
+
+gem \'actionpack\', \'3\.0\.0\.rc\'
+gem \'activemerchant\'
+.
+.fi
+.
+.IP "" 0
+.
+.P
+The \fBactionpack 3\.0\.0\.rc\fR gem has a number of new dependencies, and updates the \fBactivesupport\fR dependency to \fB= 3\.0\.0\.rc\fR and the \fBrack\fR dependency to \fB~> 1\.2\.1\fR\.
+.
+.P
+When you run \fBbundle install\fR, Bundler notices that you changed the \fBactionpack\fR gem, but not the \fBactivemerchant\fR gem\. It evaluates the gems currently being used to satisfy its requirements:
+.
+.TP
+\fBactivesupport 2\.3\.8\fR
+also used to satisfy a dependency in \fBactivemerchant\fR, which is not being updated
+.
+.TP
+\fBrack ~> 1\.1\.0\fR
+not currently being used to satisfy another dependency
+.
+.P
+Because you did not explicitly ask to update \fBactivemerchant\fR, you would not expect it to suddenly stop working after updating \fBactionpack\fR\. However, satisfying the new \fBactivesupport 3\.0\.0\.rc\fR dependency of actionpack requires updating one of its dependencies\.
+.
+.P
+Even though \fBactivemerchant\fR declares a very loose dependency that theoretically matches \fBactivesupport 3\.0\.0\.rc\fR, Bundler treats gems in your Gemfile(5) that have not changed as an atomic unit together with their dependencies\. In this case, the \fBactivemerchant\fR dependency is treated as \fBactivemerchant 1\.7\.1 + activesupport 2\.3\.8\fR, so \fBbundle install\fR will report that it cannot update \fBactionpack\fR\.
+.
+.P
+To explicitly update \fBactionpack\fR, including its dependencies which other gems in the Gemfile(5) still depend on, run \fBbundle update actionpack\fR (see \fBbundle update(1)\fR)\.
+.
+.P
+\fBSummary\fR: In general, after making a change to the Gemfile(5) , you should first try to run \fBbundle install\fR, which will guarantee that no other gem in the Gemfile(5) is impacted by the change\. If that does not work, run bundle update(1) \fIbundle\-update\.1\.html\fR\.
+.
+.SH "SEE ALSO"
+.
+.IP "\(bu" 4
+Gem install docs \fIhttp://guides\.rubygems\.org/rubygems\-basics/#installing\-gems\fR
+.
+.IP "\(bu" 4
+Rubygems signing docs \fIhttp://guides\.rubygems\.org/security/\fR
+.
+.IP "" 0
+
diff --git a/man/bundle-install.1.txt b/man/bundle-install.1.txt
new file mode 100644
index 0000000000..1b7f3adcdf
--- /dev/null
+++ b/man/bundle-install.1.txt
@@ -0,0 +1,401 @@
+BUNDLE-INSTALL(1) BUNDLE-INSTALL(1)
+
+
+
+1mNAME0m
+ 1mbundle-install 22m- Install the dependencies specified in your Gemfile
+
+1mSYNOPSIS0m
+ 1mbundle install 22m[--binstubs[=DIRECTORY]] [--clean] [--deployment]
+ [--frozen] [--full-index] [--gemfile=GEMFILE] [--jobs=NUMBER] [--local]
+ [--no-cache] [--no-prune] [--path PATH] [--quiet] [--redownload]
+ [--retry=NUMBER] [--shebang] [--standalone[=GROUP[ GROUP...]]] [--sys-
+ tem] [--trust-policy=POLICY] [--with=GROUP[ GROUP...]] [--with-
+ out=GROUP[ GROUP...]]
+
+1mDESCRIPTION0m
+ Install the gems specified in your Gemfile(5). If this is the first
+ time you run bundle install (and a 1mGemfile.lock 22mdoes not exist),
+ Bundler will fetch all remote sources, resolve dependencies and install
+ all needed gems.
+
+ If a 1mGemfile.lock 22mdoes exist, and you have not updated your Gemfile(5),
+ Bundler will fetch all remote sources, but use the dependencies speci-
+ fied in the 1mGemfile.lock 22minstead of resolving dependencies.
+
+ If a 1mGemfile.lock 22mdoes exist, and you have updated your Gemfile(5),
+ Bundler will use the dependencies in the 1mGemfile.lock 22mfor all gems that
+ you did not update, but will re-resolve the dependencies of gems that
+ you did update. You can find more information about this update process
+ below under 4mCONSERVATIVE24m 4mUPDATING24m.
+
+1mOPTIONS0m
+ To apply any of 1m--binstubs22m, 1m--deployment22m, 1m--path22m, or 1m--without 22mevery
+ time 1mbundle install 22mis run, use 1mbundle config 22m(see bundle-config(1)).
+
+ 1m--binstubs[=<directory>]0m
+ Binstubs are scripts that wrap around executables. Bundler cre-
+ ates a small Ruby file (a binstub) that loads Bundler, runs the
+ command, and puts it in 1mbin/22m. This lets you link the binstub
+ inside of an application to the exact gem version the applica-
+ tion needs.
+
+ Creates a directory (defaults to 1m~/bin22m) and places any executa-
+ bles from the gem there. These executables run in Bundler's con-
+ text. If used, you might add this directory to your environ-
+ ment's 1mPATH 22mvariable. For instance, if the 1mrails 22mgem comes with
+ a 1mrails 22mexecutable, this flag will create a 1mbin/rails 22mexecutable
+ that ensures that all referred dependencies will be resolved
+ using the bundled gems.
+
+ 1m--clean0m
+ On finishing the installation Bundler is going to remove any
+ gems not present in the current Gemfile(5). Don't worry, gems
+ currently in use will not be removed.
+
+ 1m--deployment0m
+ In 4mdeployment24m 4mmode24m, Bundler will 'roll-out' the bundle for pro-
+ duction or CI use. Please check carefully if you want to have
+ this option enabled in your development environment.
+
+ 1m--redownload0m
+ Force download every gem, even if the required versions are
+ already available locally.
+
+ 1m--frozen0m
+ Do not allow the Gemfile.lock to be updated after this install.
+ Exits non-zero if there are going to be changes to the Gem-
+ file.lock.
+
+ 1m--full-index0m
+ Bundler will not call Rubygems' API endpoint (default) but down-
+ load and cache a (currently big) index file of all gems. Perfor-
+ mance can be improved for large bundles that seldom change by
+ enabling this option.
+
+ 1m--gemfile=<gemfile>0m
+ The location of the Gemfile(5) which Bundler should use. This
+ defaults to a Gemfile(5) in the current working directory. In
+ general, Bundler will assume that the location of the Gemfile(5)
+ is also the project's root and will try to find 1mGemfile.lock 22mand
+ 1mvendor/cache 22mrelative to this location.
+
+ 1m--jobs=[<number>]22m, 1m-j[<number>]0m
+ The maximum number of parallel download and install jobs. The
+ default is 1m122m.
+
+ 1m--local0m
+ Do not attempt to connect to 1mrubygems.org22m. Instead, Bundler will
+ use the gems already present in Rubygems' cache or in 1mven-0m
+ 1mdor/cache22m. Note that if a appropriate platform-specific gem
+ exists on 1mrubygems.org 22mit will not be found.
+
+ 1m--no-cache0m
+ Do not update the cache in 1mvendor/cache 22mwith the newly bundled
+ gems. This does not remove any gems in the cache but keeps the
+ newly bundled gems from being cached during the install.
+
+ 1m--no-prune0m
+ Don't remove stale gems from the cache when the installation
+ finishes.
+
+ 1m--path=<path>0m
+ The location to install the specified gems to. This defaults to
+ Rubygems' setting. Bundler shares this location with Rubygems,
+ 1mgem install ... 22mwill have gem installed there, too. Therefore,
+ gems installed without a 1m--path ... 22msetting will show up by
+ calling 1mgem list22m. Accordingly, gems installed to other locations
+ will not get listed.
+
+ 1m--quiet0m
+ Do not print progress information to the standard output.
+ Instead, Bundler will exit using a status code (1m$?22m).
+
+ 1m--retry=[<number>]0m
+ Retry failed network or git requests for 4mnumber24m times.
+
+ 1m--shebang=<ruby-executable>0m
+ Uses the specified ruby executable (usually 1mruby22m) to execute the
+ scripts created with 1m--binstubs22m. In addition, if you use 1m--bin-0m
+ 1mstubs 22mtogether with 1m--shebang jruby 22mthese executables will be
+ changed to execute 1mjruby 22minstead.
+
+ 1m--standalone[=<list>]0m
+ Makes a bundle that can work without depending on Rubygems or
+ Bundler at runtime. A space separated list of groups to install
+ has to be specified. Bundler creates a directory named 1mbundle0m
+ and installs the bundle there. It also generates a 1mbun-0m
+ 1mdle/bundler/setup.rb 22mfile to replace Bundler's own setup in the
+ manner required. Using this option implicitly sets 1mpath22m, which
+ is a [remembered option][REMEMBERED OPTIONS].
+
+ 1m--system0m
+ Installs the gems specified in the bundle to the system's
+ Rubygems location. This overrides any previous configuration of
+ 1m--path22m.
+
+ 1m--trust-policy=[<policy>]0m
+ Apply the Rubygems security policy 4mpolicy24m, where policy is one
+ of 1mHighSecurity22m, 1mMediumSecurity22m, 1mLowSecurity22m, 1mAlmostNoSecurity22m,
+ or 1mNoSecurity22m. For more details, please see the Rubygems signing
+ documentation linked below in 4mSEE24m 4mALSO24m.
+
+ 1m--with=<list>0m
+ A space-separated list of groups referencing gems to install. If
+ an optional group is given it is installed. If a group is given
+ that is in the remembered list of groups given to --without, it
+ is removed from that list.
+
+ 1m--without=<list>0m
+ A space-separated list of groups referencing gems to skip during
+ installation. If a group is given that is in the remembered list
+ of groups given to --with, it is removed from that list.
+
+1mDEPLOYMENT MODE0m
+ Bundler's defaults are optimized for development. To switch to defaults
+ optimized for deployment and for CI, use the 1m--deployment 22mflag. Do not
+ activate deployment mode on development machines, as it will cause an
+ error when the Gemfile(5) is modified.
+
+ 1. A 1mGemfile.lock 22mis required.
+
+ To ensure that the same versions of the gems you developed with and
+ tested with are also used in deployments, a 1mGemfile.lock 22mis
+ required.
+
+ This is mainly to ensure that you remember to check your 1mGem-0m
+ 1mfile.lock 22minto version control.
+
+ 2. The 1mGemfile.lock 22mmust be up to date
+
+ In development, you can modify your Gemfile(5) and re-run 1mbundle0m
+ 1minstall 22mto 4mconservatively24m 4mupdate24m your 1mGemfile.lock 22msnapshot.
+
+ In deployment, your 1mGemfile.lock 22mshould be up-to-date with changes
+ made in your Gemfile(5).
+
+ 3. Gems are installed to 1mvendor/bundle 22mnot your default system loca-
+ tion
+
+ In development, it's convenient to share the gems used in your
+ application with other applications and other scripts that run on
+ the system.
+
+ In deployment, isolation is a more important default. In addition,
+ the user deploying the application may not have permission to
+ install gems to the system, or the web server may not have permis-
+ sion to read them.
+
+ As a result, 1mbundle install --deployment 22minstalls gems to the 1mven-0m
+ 1mdor/bundle 22mdirectory in the application. This may be overridden
+ using the 1m--path 22moption.
+
+
+
+1mSUDO USAGE0m
+ By default, Bundler installs gems to the same location as 1mgem install22m.
+
+ In some cases, that location may not be writable by your Unix user. In
+ that case, Bundler will stage everything in a temporary directory, then
+ ask you for your 1msudo 22mpassword in order to copy the gems into their
+ system location.
+
+ From your perspective, this is identical to installing the gems
+ directly into the system.
+
+ You should never use 1msudo bundle install22m. This is because several other
+ steps in 1mbundle install 22mmust be performed as the current user:
+
+ o Updating your 1mGemfile.lock0m
+
+ o Updating your 1mvendor/cache22m, if necessary
+
+ o Checking out private git repositories using your user's SSH keys
+
+
+
+ Of these three, the first two could theoretically be performed by
+ 1mchown22ming the resulting files to 1m$SUDO_USER22m. The third, however, can
+ only be performed by invoking the 1mgit 22mcommand as the current user.
+ Therefore, git gems are downloaded and installed into 1m~/.bundle 22mrather
+ than $GEM_HOME or $BUNDLE_PATH.
+
+ As a result, you should run 1mbundle install 22mas the current user, and
+ Bundler will ask for your password if it is needed to put the gems into
+ their final location.
+
+1mINSTALLING GROUPS0m
+ By default, 1mbundle install 22mwill install all gems in all groups in your
+ Gemfile(5), except those declared for a different platform.
+
+ However, you can explicitly tell Bundler to skip installing certain
+ groups with the 1m--without 22moption. This option takes a space-separated
+ list of groups.
+
+ While the 1m--without 22moption will skip 4minstalling24m the gems in the speci-
+ fied groups, it will still 4mdownload24m those gems and use them to resolve
+ the dependencies of every gem in your Gemfile(5).
+
+ This is so that installing a different set of groups on another machine
+ (such as a production server) will not change the gems and versions
+ that you have already developed and tested against.
+
+ 1mBundler offers a rock-solid guarantee that the third-party code you are0m
+ 1mrunning in development and testing is also the third-party code you are0m
+ 1mrunning in production. You can choose to exclude some of that code in0m
+ 1mdifferent environments, but you will never be caught flat-footed by0m
+ 1mdifferent versions of third-party code being used in different environ-0m
+ 1mments.0m
+
+ For a simple illustration, consider the following Gemfile(5):
+
+
+
+ source 'https://rubygems.org'
+
+ gem 'sinatra'
+
+ group :production do
+ gem 'rack-perftools-profiler'
+ end
+
+
+
+ In this case, 1msinatra 22mdepends on any version of Rack (1m>= 1.022m), while
+ 1mrack-perftools-profiler 22mdepends on 1.x (1m~> 1.022m).
+
+ When you run 1mbundle install --without production 22min development, we
+ look at the dependencies of 1mrack-perftools-profiler 22mas well. That way,
+ you do not spend all your time developing against Rack 2.0, using new
+ APIs unavailable in Rack 1.x, only to have Bundler switch to Rack 1.2
+ when the 1mproduction 22mgroup 4mis24m used.
+
+ This should not cause any problems in practice, because we do not
+ attempt to 1minstall 22mthe gems in the excluded groups, and only evaluate
+ as part of the dependency resolution process.
+
+ This also means that you cannot include different versions of the same
+ gem in different groups, because doing so would result in different
+ sets of dependencies used in development and production. Because of the
+ vagaries of the dependency resolution process, this usually affects
+ more than the gems you list in your Gemfile(5), and can (surprisingly)
+ radically change the gems you are using.
+
+1mTHE GEMFILE.LOCK0m
+ When you run 1mbundle install22m, Bundler will persist the full names and
+ versions of all gems that you used (including dependencies of the gems
+ specified in the Gemfile(5)) into a file called 1mGemfile.lock22m.
+
+ Bundler uses this file in all subsequent calls to 1mbundle install22m, which
+ guarantees that you always use the same exact code, even as your appli-
+ cation moves across machines.
+
+ Because of the way dependency resolution works, even a seemingly small
+ change (for instance, an update to a point-release of a dependency of a
+ gem in your Gemfile(5)) can result in radically different gems being
+ needed to satisfy all dependencies.
+
+ As a result, you 1mSHOULD 22mcheck your 1mGemfile.lock 22minto version control,
+ in both applications and gems. If you do not, every machine that checks
+ out your repository (including your production server) will resolve all
+ dependencies again, which will result in different versions of
+ third-party code being used if 1many 22mof the gems in the Gemfile(5) or any
+ of their dependencies have been updated.
+
+ When Bundler first shipped, the 1mGemfile.lock 22mwas included in the 1m.git-0m
+ 1mignore 22mfile included with generated gems. Over time, however, it became
+ clear that this practice forces the pain of broken dependencies onto
+ new contributors, while leaving existing contributors potentially
+ unaware of the problem. Since 1mbundle install 22mis usually the first step
+ towards a contribution, the pain of broken dependencies would discour-
+ age new contributors from contributing. As a result, we have revised
+ our guidance for gem authors to now recommend checking in the lock for
+ gems.
+
+1mCONSERVATIVE UPDATING0m
+ When you make a change to the Gemfile(5) and then run 1mbundle install22m,
+ Bundler will update only the gems that you modified.
+
+ In other words, if a gem that you 1mdid not modify 22mworked before you
+ called 1mbundle install22m, it will continue to use the exact same versions
+ of all dependencies as it used before the update.
+
+ Let's take a look at an example. Here's your original Gemfile(5):
+
+
+
+ source 'https://rubygems.org'
+
+ gem 'actionpack', '2.3.8'
+ gem 'activemerchant'
+
+
+
+ In this case, both 1mactionpack 22mand 1mactivemerchant 22mdepend on 1mactivesup-0m
+ 1mport22m. The 1mactionpack 22mgem depends on 1mactivesupport 2.3.8 22mand 1mrack ~>0m
+ 1m1.1.022m, while the 1mactivemerchant 22mgem depends on 1mactivesupport >= 2.3.222m,
+ 1mbraintree >= 2.0.022m, and 1mbuilder >= 2.0.022m.
+
+ When the dependencies are first resolved, Bundler will select
+ 1mactivesupport 2.3.822m, which satisfies the requirements of both gems in
+ your Gemfile(5).
+
+ Next, you modify your Gemfile(5) to:
+
+
+
+ source 'https://rubygems.org'
+
+ gem 'actionpack', '3.0.0.rc'
+ gem 'activemerchant'
+
+
+
+ The 1mactionpack 3.0.0.rc 22mgem has a number of new dependencies, and
+ updates the 1mactivesupport 22mdependency to 1m= 3.0.0.rc 22mand the 1mrack 22mdepen-
+ dency to 1m~> 1.2.122m.
+
+ When you run 1mbundle install22m, Bundler notices that you changed the
+ 1mactionpack 22mgem, but not the 1mactivemerchant 22mgem. It evaluates the gems
+ currently being used to satisfy its requirements:
+
+ 1mactivesupport 2.3.80m
+ also used to satisfy a dependency in 1mactivemerchant22m, which is
+ not being updated
+
+ 1mrack ~> 1.1.00m
+ not currently being used to satisfy another dependency
+
+ Because you did not explicitly ask to update 1mactivemerchant22m, you would
+ not expect it to suddenly stop working after updating 1mactionpack22m. How-
+ ever, satisfying the new 1mactivesupport 3.0.0.rc 22mdependency of action-
+ pack requires updating one of its dependencies.
+
+ Even though 1mactivemerchant 22mdeclares a very loose dependency that theo-
+ retically matches 1mactivesupport 3.0.0.rc22m, Bundler treats gems in your
+ Gemfile(5) that have not changed as an atomic unit together with their
+ dependencies. In this case, the 1mactivemerchant 22mdependency is treated as
+ 1mactivemerchant 1.7.1 + activesupport 2.3.822m, so 1mbundle install 22mwill
+ report that it cannot update 1mactionpack22m.
+
+ To explicitly update 1mactionpack22m, including its dependencies which other
+ gems in the Gemfile(5) still depend on, run 1mbundle update actionpack0m
+ (see 1mbundle update(1)22m).
+
+ 1mSummary22m: In general, after making a change to the Gemfile(5) , you
+ should first try to run 1mbundle install22m, which will guarantee that no
+ other gem in the Gemfile(5) is impacted by the change. If that does not
+ work, run bundle update(1) 4mbundle-update.1.html24m.
+
+1mSEE ALSO0m
+ o Gem install docs
+ 4mhttp://guides.rubygems.org/rubygems-basics/#installing-gems0m
+
+ o Rubygems signing docs 4mhttp://guides.rubygems.org/security/0m
+
+
+
+
+
+
+ October 2018 BUNDLE-INSTALL(1)
diff --git a/man/bundle-install.ronn b/man/bundle-install.ronn
new file mode 100644
index 0000000000..a9e375c87c
--- /dev/null
+++ b/man/bundle-install.ronn
@@ -0,0 +1,378 @@
+bundle-install(1) -- Install the dependencies specified in your Gemfile
+=======================================================================
+
+## SYNOPSIS
+
+`bundle install` [--binstubs[=DIRECTORY]]
+ [--clean]
+ [--deployment]
+ [--force]
+ [--frozen]
+ [--full-index]
+ [--gemfile=GEMFILE]
+ [--jobs=NUMBER]
+ [--local]
+ [--no-cache]
+ [--no-prune]
+ [--path PATH]
+ [--quiet]
+ [--retry=NUMBER]
+ [--shebang]
+ [--standalone[=GROUP[ GROUP...]]]
+ [--system]
+ [--trust-policy=POLICY]
+ [--with=GROUP[ GROUP...]]
+ [--without=GROUP[ GROUP...]]
+
+## DESCRIPTION
+
+Install the gems specified in your Gemfile(5). If this is the first
+time you run bundle install (and a `Gemfile.lock` does not exist),
+Bundler will fetch all remote sources, resolve dependencies and
+install all needed gems.
+
+If a `Gemfile.lock` does exist, and you have not updated your Gemfile(5),
+Bundler will fetch all remote sources, but use the dependencies
+specified in the `Gemfile.lock` instead of resolving dependencies.
+
+If a `Gemfile.lock` does exist, and you have updated your Gemfile(5),
+Bundler will use the dependencies in the `Gemfile.lock` for all gems
+that you did not update, but will re-resolve the dependencies of
+gems that you did update. You can find more information about this
+update process below under [CONSERVATIVE UPDATING][].
+
+## OPTIONS
+
+To apply any of `--binstubs`, `--deployment`, `--path`, or `--without` every
+time `bundle install` is run, use `bundle config` (see bundle-config(1)).
+
+* `--binstubs[=<directory>]`:
+ Creates a directory (defaults to `~/bin`) and place any executables from the
+ gem there. These executables run in Bundler's context. If used, you might add
+ this directory to your environment's `PATH` variable. For instance, if the
+ `rails` gem comes with a `rails` executable, this flag will create a
+ `bin/rails` executable that ensures that all referred dependencies will be
+ resolved using the bundled gems.
+
+* `--clean`:
+ On finishing the installation Bundler is going to remove any gems not present
+ in the current Gemfile(5). Don't worry, gems currently in use will not be
+ removed.
+
+* `--deployment`:
+ In [deployment mode][DEPLOYMENT MODE], Bundler will 'roll-out' the bundle for
+ production or CI use. Please check carefully if you want to have this option
+ enabled in your development environment.
+
+* `--force`:
+ Force download every gem, even if the required versions are already available
+ locally. `--redownload` is an alias of this option.
+
+* `--frozen`:
+ Do not allow the Gemfile.lock to be updated after this install. Exits
+ non-zero if there are going to be changes to the Gemfile.lock.
+
+* `--full-index`:
+ Bundler will not call Rubygems' API endpoint (default) but download and cache
+ a (currently big) index file of all gems. Performance can be improved for
+ large bundles that seldom change by enabling this option.
+
+* `--gemfile=<gemfile>`:
+ The location of the Gemfile(5) which Bundler should use. This defaults
+ to a Gemfile(5) in the current working directory. In general, Bundler
+ will assume that the location of the Gemfile(5) is also the project's
+ root and will try to find `Gemfile.lock` and `vendor/cache` relative
+ to this location.
+
+* `--jobs=[<number>]`, `-j[<number>]`:
+ The maximum number of parallel download and install jobs. The default
+ is `1`.
+
+* `--local`:
+ Do not attempt to connect to `rubygems.org`. Instead, Bundler will use the
+ gems already present in Rubygems' cache or in `vendor/cache`. Note that if a
+ appropriate platform-specific gem exists on `rubygems.org` it will not be
+ found.
+
+* `--no-cache`:
+ Do not update the cache in `vendor/cache` with the newly bundled gems. This
+ does not remove any gems in the cache but keeps the newly bundled gems from
+ being cached during the install.
+
+* `--no-prune`:
+ Don't remove stale gems from the cache when the installation finishes.
+
+* `--path=<path>`:
+ The location to install the specified gems to. This defaults to Rubygems'
+ setting. Bundler shares this location with Rubygems, `gem install ...` will
+ have gem installed there, too. Therefore, gems installed without a
+ `--path ...` setting will show up by calling `gem list`. Accordingly, gems
+ installed to other locations will not get listed.
+
+* `--quiet`:
+ Do not print progress information to the standard output. Instead, Bundler
+ will exit using a status code (`$?`).
+
+* `--retry=[<number>]`:
+ Retry failed network or git requests for <number> times.
+
+* `--shebang=<ruby-executable>`:
+ Uses the specified ruby executable (usually `ruby`) to execute the scripts
+ created with `--binstubs`. In addition, if you use `--binstubs` together with
+ `--shebang jruby` these executables will be changed to execute `jruby`
+ instead.
+
+* `--standalone[=<list>]`:
+ Makes a bundle that can work without depending on Rubygems or Bundler at
+ runtime. A space separated list of groups to install has to be specified.
+ Bundler creates a directory named `bundle` and installs the bundle there. It
+ also generates a `bundle/bundler/setup.rb` file to replace Bundler's own setup
+ in the manner required. Using this option implicitly sets `path`, which is a
+ [remembered option][REMEMBERED OPTIONS].
+
+* `--system`:
+ Installs the gems specified in the bundle to the system's Rubygems location.
+ This overrides any previous configuration of `--path`.
+
+* `--trust-policy=[<policy>]`:
+ Apply the Rubygems security policy <policy>, where policy is one of
+ `HighSecurity`, `MediumSecurity`, `LowSecurity`, `AlmostNoSecurity`, or
+ `NoSecurity`. For more details, please see the Rubygems signing documentation
+ linked below in [SEE ALSO][].
+
+* `--with=<list>`:
+ A space-separated list of groups referencing gems to install. If an
+ optional group is given it is installed. If a group is given that is
+ in the remembered list of groups given to --without, it is removed
+ from that list.
+
+* `--without=<list>`:
+ A space-separated list of groups referencing gems to skip during installation.
+ If a group is given that is in the remembered list of groups given
+ to --with, it is removed from that list.
+
+## DEPLOYMENT MODE
+
+Bundler's defaults are optimized for development. To switch to
+defaults optimized for deployment and for CI, use the `--deployment`
+flag. Do not activate deployment mode on development machines, as it
+will cause an error when the Gemfile(5) is modified.
+
+1. A `Gemfile.lock` is required.
+
+ To ensure that the same versions of the gems you developed with
+ and tested with are also used in deployments, a `Gemfile.lock`
+ is required.
+
+ This is mainly to ensure that you remember to check your
+ `Gemfile.lock` into version control.
+
+2. The `Gemfile.lock` must be up to date
+
+ In development, you can modify your Gemfile(5) and re-run
+ `bundle install` to [conservatively update][CONSERVATIVE UPDATING]
+ your `Gemfile.lock` snapshot.
+
+ In deployment, your `Gemfile.lock` should be up-to-date with
+ changes made in your Gemfile(5).
+
+3. Gems are installed to `vendor/bundle` not your default system location
+
+ In development, it's convenient to share the gems used in your
+ application with other applications and other scripts that run on
+ the system.
+
+ In deployment, isolation is a more important default. In addition,
+ the user deploying the application may not have permission to install
+ gems to the system, or the web server may not have permission to
+ read them.
+
+ As a result, `bundle install --deployment` installs gems to
+ the `vendor/bundle` directory in the application. This may be
+ overridden using the `--path` option.
+
+## SUDO USAGE
+
+By default, Bundler installs gems to the same location as `gem install`.
+
+In some cases, that location may not be writable by your Unix user. In
+that case, Bundler will stage everything in a temporary directory,
+then ask you for your `sudo` password in order to copy the gems into
+their system location.
+
+From your perspective, this is identical to installing the gems
+directly into the system.
+
+You should never use `sudo bundle install`. This is because several
+other steps in `bundle install` must be performed as the current user:
+
+* Updating your `Gemfile.lock`
+* Updating your `vendor/cache`, if necessary
+* Checking out private git repositories using your user's SSH keys
+
+Of these three, the first two could theoretically be performed by
+`chown`ing the resulting files to `$SUDO_USER`. The third, however,
+can only be performed by invoking the `git` command as
+the current user. Therefore, git gems are downloaded and installed
+into `~/.bundle` rather than $GEM_HOME or $BUNDLE_PATH.
+
+As a result, you should run `bundle install` as the current user,
+and Bundler will ask for your password if it is needed to put the
+gems into their final location.
+
+## INSTALLING GROUPS
+
+By default, `bundle install` will install all gems in all groups
+in your Gemfile(5), except those declared for a different platform.
+
+However, you can explicitly tell Bundler to skip installing
+certain groups with the `--without` option. This option takes
+a space-separated list of groups.
+
+While the `--without` option will skip _installing_ the gems in the
+specified groups, it will still _download_ those gems and use them to
+resolve the dependencies of every gem in your Gemfile(5).
+
+This is so that installing a different set of groups on another
+ machine (such as a production server) will not change the
+gems and versions that you have already developed and tested against.
+
+`Bundler offers a rock-solid guarantee that the third-party
+code you are running in development and testing is also the
+third-party code you are running in production. You can choose
+to exclude some of that code in different environments, but you
+will never be caught flat-footed by different versions of
+third-party code being used in different environments.`
+
+For a simple illustration, consider the following Gemfile(5):
+
+ source 'https://rubygems.org'
+
+ gem 'sinatra'
+
+ group :production do
+ gem 'rack-perftools-profiler'
+ end
+
+In this case, `sinatra` depends on any version of Rack (`>= 1.0`), while
+`rack-perftools-profiler` depends on 1.x (`~> 1.0`).
+
+When you run `bundle install --without production` in development, we
+look at the dependencies of `rack-perftools-profiler` as well. That way,
+you do not spend all your time developing against Rack 2.0, using new
+APIs unavailable in Rack 1.x, only to have Bundler switch to Rack 1.2
+when the `production` group _is_ used.
+
+This should not cause any problems in practice, because we do not
+attempt to `install` the gems in the excluded groups, and only evaluate
+as part of the dependency resolution process.
+
+This also means that you cannot include different versions of the same
+gem in different groups, because doing so would result in different
+sets of dependencies used in development and production. Because of
+the vagaries of the dependency resolution process, this usually
+affects more than the gems you list in your Gemfile(5), and can
+(surprisingly) radically change the gems you are using.
+
+## THE GEMFILE.LOCK
+
+When you run `bundle install`, Bundler will persist the full names
+and versions of all gems that you used (including dependencies of
+the gems specified in the Gemfile(5)) into a file called `Gemfile.lock`.
+
+Bundler uses this file in all subsequent calls to `bundle install`,
+which guarantees that you always use the same exact code, even
+as your application moves across machines.
+
+Because of the way dependency resolution works, even a
+seemingly small change (for instance, an update to a point-release
+of a dependency of a gem in your Gemfile(5)) can result in radically
+different gems being needed to satisfy all dependencies.
+
+As a result, you `SHOULD` check your `Gemfile.lock` into version
+control, in both applications and gems. If you do not, every machine that
+checks out your repository (including your production server) will resolve all
+dependencies again, which will result in different versions of
+third-party code being used if `any` of the gems in the Gemfile(5)
+or any of their dependencies have been updated.
+
+When Bundler first shipped, the `Gemfile.lock` was included in the `.gitignore`
+file included with generated gems. Over time, however, it became clear that
+this practice forces the pain of broken dependencies onto new contributors,
+while leaving existing contributors potentially unaware of the problem. Since
+`bundle install` is usually the first step towards a contribution, the pain of
+broken dependencies would discourage new contributors from contributing. As a
+result, we have revised our guidance for gem authors to now recommend checking
+in the lock for gems.
+
+## CONSERVATIVE UPDATING
+
+When you make a change to the Gemfile(5) and then run `bundle install`,
+Bundler will update only the gems that you modified.
+
+In other words, if a gem that you `did not modify` worked before
+you called `bundle install`, it will continue to use the exact
+same versions of all dependencies as it used before the update.
+
+Let's take a look at an example. Here's your original Gemfile(5):
+
+ source 'https://rubygems.org'
+
+ gem 'actionpack', '2.3.8'
+ gem 'activemerchant'
+
+In this case, both `actionpack` and `activemerchant` depend on
+`activesupport`. The `actionpack` gem depends on `activesupport 2.3.8`
+and `rack ~> 1.1.0`, while the `activemerchant` gem depends on
+`activesupport >= 2.3.2`, `braintree >= 2.0.0`, and `builder >= 2.0.0`.
+
+When the dependencies are first resolved, Bundler will select
+`activesupport 2.3.8`, which satisfies the requirements of both
+gems in your Gemfile(5).
+
+Next, you modify your Gemfile(5) to:
+
+ source 'https://rubygems.org'
+
+ gem 'actionpack', '3.0.0.rc'
+ gem 'activemerchant'
+
+The `actionpack 3.0.0.rc` gem has a number of new dependencies,
+and updates the `activesupport` dependency to `= 3.0.0.rc` and
+the `rack` dependency to `~> 1.2.1`.
+
+When you run `bundle install`, Bundler notices that you changed
+the `actionpack` gem, but not the `activemerchant` gem. It
+evaluates the gems currently being used to satisfy its requirements:
+
+ * `activesupport 2.3.8`:
+ also used to satisfy a dependency in `activemerchant`,
+ which is not being updated
+ * `rack ~> 1.1.0`:
+ not currently being used to satisfy another dependency
+
+Because you did not explicitly ask to update `activemerchant`,
+you would not expect it to suddenly stop working after updating
+`actionpack`. However, satisfying the new `activesupport 3.0.0.rc`
+dependency of actionpack requires updating one of its dependencies.
+
+Even though `activemerchant` declares a very loose dependency
+that theoretically matches `activesupport 3.0.0.rc`, Bundler treats
+gems in your Gemfile(5) that have not changed as an atomic unit
+together with their dependencies. In this case, the `activemerchant`
+dependency is treated as `activemerchant 1.7.1 + activesupport 2.3.8`,
+so `bundle install` will report that it cannot update `actionpack`.
+
+To explicitly update `actionpack`, including its dependencies
+which other gems in the Gemfile(5) still depend on, run
+`bundle update actionpack` (see `bundle update(1)`).
+
+`Summary`: In general, after making a change to the Gemfile(5) , you
+should first try to run `bundle install`, which will guarantee that no
+other gem in the Gemfile(5) is impacted by the change. If that
+does not work, run [bundle update(1)](bundle-update.1.html).
+
+## SEE ALSO
+
+* [Gem install docs](http://guides.rubygems.org/rubygems-basics/#installing-gems)
+* [Rubygems signing docs](http://guides.rubygems.org/security/)
diff --git a/man/bundle-list.1 b/man/bundle-list.1
new file mode 100644
index 0000000000..5a8281d16b
--- /dev/null
+++ b/man/bundle-list.1
@@ -0,0 +1,50 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-LIST" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-list\fR \- List all the gems in the bundle
+.
+.SH "SYNOPSIS"
+\fBbundle list\fR [\-\-name\-only] [\-\-paths] [\-\-without\-group=GROUP] [\-\-only\-group=GROUP]
+.
+.SH "DESCRIPTION"
+Prints a list of all the gems in the bundle including their version\.
+.
+.P
+Example:
+.
+.P
+bundle list \-\-name\-only
+.
+.P
+bundle list \-\-paths
+.
+.P
+bundle list \-\-without\-group test
+.
+.P
+bundle list \-\-only\-group dev
+.
+.P
+bundle list \-\-only\-group dev \-\-paths
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-name\-only\fR
+Print only the name of each gem\.
+.
+.TP
+\fB\-\-paths\fR
+Print the path to each gem in the bundle\.
+.
+.TP
+\fB\-\-without\-group\fR
+Print all gems expect from a group\.
+.
+.TP
+\fB\-\-only\-group\fR
+Print gems from a particular group\.
+
diff --git a/man/bundle-list.1.txt b/man/bundle-list.1.txt
new file mode 100644
index 0000000000..84a3cc811f
--- /dev/null
+++ b/man/bundle-list.1.txt
@@ -0,0 +1,43 @@
+BUNDLE-LIST(1) BUNDLE-LIST(1)
+
+
+
+1mNAME0m
+ 1mbundle-list 22m- List all the gems in the bundle
+
+1mSYNOPSIS0m
+ 1mbundle list 22m[--name-only] [--paths] [--without-group=GROUP]
+ [--only-group=GROUP]
+
+1mDESCRIPTION0m
+ Prints a list of all the gems in the bundle including their version.
+
+ Example:
+
+ bundle list --name-only
+
+ bundle list --paths
+
+ bundle list --without-group test
+
+ bundle list --only-group dev
+
+ bundle list --only-group dev --paths
+
+1mOPTIONS0m
+ 1m--name-only0m
+ Print only the name of each gem.
+
+ 1m--paths0m
+ Print the path to each gem in the bundle.
+
+ 1m--without-group0m
+ Print all gems expect from a group.
+
+ 1m--only-group0m
+ Print gems from a particular group.
+
+
+
+
+ October 2018 BUNDLE-LIST(1)
diff --git a/man/bundle-list.ronn b/man/bundle-list.ronn
new file mode 100644
index 0000000000..120cf5e307
--- /dev/null
+++ b/man/bundle-list.ronn
@@ -0,0 +1,33 @@
+bundle-list(1) -- List all the gems in the bundle
+=========================================================================
+
+## SYNOPSIS
+
+`bundle list` [--name-only] [--paths] [--without-group=GROUP] [--only-group=GROUP]
+
+## DESCRIPTION
+
+Prints a list of all the gems in the bundle including their version.
+
+Example:
+
+bundle list --name-only
+
+bundle list --paths
+
+bundle list --without-group test
+
+bundle list --only-group dev
+
+bundle list --only-group dev --paths
+
+## OPTIONS
+
+* `--name-only`:
+ Print only the name of each gem.
+* `--paths`:
+ Print the path to each gem in the bundle.
+* `--without-group`:
+ Print all gems expect from a group.
+* `--only-group`:
+ Print gems from a particular group.
diff --git a/man/bundle-lock.1 b/man/bundle-lock.1
new file mode 100644
index 0000000000..1a402074a7
--- /dev/null
+++ b/man/bundle-lock.1
@@ -0,0 +1,84 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-LOCK" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-lock\fR \- Creates / Updates a lockfile without installing
+.
+.SH "SYNOPSIS"
+\fBbundle lock\fR [\-\-update] [\-\-local] [\-\-print] [\-\-lockfile=PATH] [\-\-full\-index] [\-\-add\-platform] [\-\-remove\-platform] [\-\-patch] [\-\-minor] [\-\-major] [\-\-strict] [\-\-conservative]
+.
+.SH "DESCRIPTION"
+Lock the gems specified in Gemfile\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-update=<*gems>\fR
+Ignores the existing lockfile\. Resolve then updates lockfile\. Taking a list of gems or updating all gems if no list is given\.
+.
+.TP
+\fB\-\-local\fR
+Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems\' cache or in \fBvendor/cache\fR\. Note that if a appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\.
+.
+.TP
+\fB\-\-print\fR
+Prints the lockfile to STDOUT instead of writing to the file system\.
+.
+.TP
+\fB\-\-lockfile=<path>\fR
+The path where the lockfile should be written to\.
+.
+.TP
+\fB\-\-full\-index\fR
+Fall back to using the single\-file index of all gems\.
+.
+.TP
+\fB\-\-add\-platform\fR
+Add a new platform to the lockfile, re\-resolving for the addition of that platform\.
+.
+.TP
+\fB\-\-remove\-platform\fR
+Remove a platform from the lockfile\.
+.
+.TP
+\fB\-\-patch\fR
+If updating, prefer updating only to next patch version\.
+.
+.TP
+\fB\-\-minor\fR
+If updating, prefer updating only to next minor version\.
+.
+.TP
+\fB\-\-major\fR
+If updating, prefer updating to next major version (default)\.
+.
+.TP
+\fB\-\-strict\fR
+If updating, do not allow any gem to be updated past latest \-\-patch | \-\-minor | \-\-major\.
+.
+.TP
+\fB\-\-conservative\fR
+If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated\.
+.
+.SH "UPDATING ALL GEMS"
+If you run \fBbundle lock\fR with \fB\-\-update\fR option without list of gems, bundler will ignore any previously installed gems and resolve all dependencies again based on the latest versions of all gems available in the sources\.
+.
+.SH "UPDATING A LIST OF GEMS"
+Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of the gems that you specified locked to the versions in the \fBGemfile\.lock\fR\.
+.
+.P
+For instance, you only want to update \fBnokogiri\fR, run \fBbundle lock \-\-update nokogiri\fR\.
+.
+.P
+Bundler will update \fBnokogiri\fR and any of its dependencies, but leave the rest of the gems that you specified locked to the versions in the \fBGemfile\.lock\fR\.
+.
+.SH "SUPPORTING OTHER PLATFORMS"
+If you want your bundle to support platforms other than the one you\'re running locally, you can run \fBbundle lock \-\-add\-platform PLATFORM\fR to add PLATFORM to the lockfile, force bundler to re\-resolve and consider the new platform when picking gems, all without needing to have a machine that matches PLATFORM handy to install those platform\-specific gems on\.
+.
+.P
+For a full explanation of gem platforms, see \fBgem help platform\fR\.
+.
+.SH "PATCH LEVEL OPTIONS"
+See bundle update(1) \fIbundle\-update\.1\.html\fR for details\.
diff --git a/man/bundle-lock.1.txt b/man/bundle-lock.1.txt
new file mode 100644
index 0000000000..e921040ff4
--- /dev/null
+++ b/man/bundle-lock.1.txt
@@ -0,0 +1,93 @@
+BUNDLE-LOCK(1) BUNDLE-LOCK(1)
+
+
+
+1mNAME0m
+ 1mbundle-lock 22m- Creates / Updates a lockfile without installing
+
+1mSYNOPSIS0m
+ 1mbundle lock 22m[--update] [--local] [--print] [--lockfile=PATH]
+ [--full-index] [--add-platform] [--remove-platform] [--patch] [--minor]
+ [--major] [--strict] [--conservative]
+
+1mDESCRIPTION0m
+ Lock the gems specified in Gemfile.
+
+1mOPTIONS0m
+ 1m--update=<*gems>0m
+ Ignores the existing lockfile. Resolve then updates lockfile.
+ Taking a list of gems or updating all gems if no list is given.
+
+ 1m--local0m
+ Do not attempt to connect to 1mrubygems.org22m. Instead, Bundler will
+ use the gems already present in Rubygems' cache or in 1mven-0m
+ 1mdor/cache22m. Note that if a appropriate platform-specific gem
+ exists on 1mrubygems.org 22mit will not be found.
+
+ 1m--print0m
+ Prints the lockfile to STDOUT instead of writing to the file
+ system.
+
+ 1m--lockfile=<path>0m
+ The path where the lockfile should be written to.
+
+ 1m--full-index0m
+ Fall back to using the single-file index of all gems.
+
+ 1m--add-platform0m
+ Add a new platform to the lockfile, re-resolving for the addi-
+ tion of that platform.
+
+ 1m--remove-platform0m
+ Remove a platform from the lockfile.
+
+ 1m--patch0m
+ If updating, prefer updating only to next patch version.
+
+ 1m--minor0m
+ If updating, prefer updating only to next minor version.
+
+ 1m--major0m
+ If updating, prefer updating to next major version (default).
+
+ 1m--strict0m
+ If updating, do not allow any gem to be updated past latest
+ --patch | --minor | --major.
+
+ 1m--conservative0m
+ If updating, use bundle install conservative update behavior and
+ do not allow shared dependencies to be updated.
+
+1mUPDATING ALL GEMS0m
+ If you run 1mbundle lock 22mwith 1m--update 22moption without list of gems,
+ bundler will ignore any previously installed gems and resolve all
+ dependencies again based on the latest versions of all gems available
+ in the sources.
+
+1mUPDATING A LIST OF GEMS0m
+ Sometimes, you want to update a single gem in the Gemfile(5), and leave
+ the rest of the gems that you specified locked to the versions in the
+ 1mGemfile.lock22m.
+
+ For instance, you only want to update 1mnokogiri22m, run 1mbundle lock0m
+ 1m--update nokogiri22m.
+
+ Bundler will update 1mnokogiri 22mand any of its dependencies, but leave the
+ rest of the gems that you specified locked to the versions in the 1mGem-0m
+ 1mfile.lock22m.
+
+1mSUPPORTING OTHER PLATFORMS0m
+ If you want your bundle to support platforms other than the one you're
+ running locally, you can run 1mbundle lock --add-platform PLATFORM 22mto add
+ PLATFORM to the lockfile, force bundler to re-resolve and consider the
+ new platform when picking gems, all without needing to have a machine
+ that matches PLATFORM handy to install those platform-specific gems on.
+
+ For a full explanation of gem platforms, see 1mgem help platform22m.
+
+1mPATCH LEVEL OPTIONS0m
+ See bundle update(1) 4mbundle-update.1.html24m for details.
+
+
+
+ October 2018 BUNDLE-LOCK(1)
diff --git a/man/bundle-lock.ronn b/man/bundle-lock.ronn
new file mode 100644
index 0000000000..3aa5920f5a
--- /dev/null
+++ b/man/bundle-lock.ronn
@@ -0,0 +1,94 @@
+bundle-lock(1) -- Creates / Updates a lockfile without installing
+=================================================================
+
+## SYNOPSIS
+
+`bundle lock` [--update]
+ [--local]
+ [--print]
+ [--lockfile=PATH]
+ [--full-index]
+ [--add-platform]
+ [--remove-platform]
+ [--patch]
+ [--minor]
+ [--major]
+ [--strict]
+ [--conservative]
+
+## DESCRIPTION
+
+Lock the gems specified in Gemfile.
+
+## OPTIONS
+
+* `--update=<*gems>`:
+ Ignores the existing lockfile. Resolve then updates lockfile. Taking a list
+ of gems or updating all gems if no list is given.
+
+* `--local`:
+ Do not attempt to connect to `rubygems.org`. Instead, Bundler will use the
+ gems already present in Rubygems' cache or in `vendor/cache`. Note that if a
+ appropriate platform-specific gem exists on `rubygems.org` it will not be
+ found.
+
+* `--print`:
+ Prints the lockfile to STDOUT instead of writing to the file system.
+
+* `--lockfile=<path>`:
+ The path where the lockfile should be written to.
+
+* `--full-index`:
+ Fall back to using the single-file index of all gems.
+
+* `--add-platform`:
+ Add a new platform to the lockfile, re-resolving for the addition of that
+ platform.
+
+* `--remove-platform`:
+ Remove a platform from the lockfile.
+
+* `--patch`:
+ If updating, prefer updating only to next patch version.
+
+* `--minor`:
+ If updating, prefer updating only to next minor version.
+
+* `--major`:
+ If updating, prefer updating to next major version (default).
+
+* `--strict`:
+ If updating, do not allow any gem to be updated past latest --patch | --minor | --major.
+
+* `--conservative`:
+ If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated.
+
+## UPDATING ALL GEMS
+
+If you run `bundle lock` with `--update` option without list of gems, bundler will
+ignore any previously installed gems and resolve all dependencies again based
+on the latest versions of all gems available in the sources.
+
+## UPDATING A LIST OF GEMS
+
+Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of
+the gems that you specified locked to the versions in the `Gemfile.lock`.
+
+For instance, you only want to update `nokogiri`, run `bundle lock --update nokogiri`.
+
+Bundler will update `nokogiri` and any of its dependencies, but leave the rest of the
+gems that you specified locked to the versions in the `Gemfile.lock`.
+
+## SUPPORTING OTHER PLATFORMS
+
+If you want your bundle to support platforms other than the one you're running
+locally, you can run `bundle lock --add-platform PLATFORM` to add PLATFORM to
+the lockfile, force bundler to re-resolve and consider the new platform when
+picking gems, all without needing to have a machine that matches PLATFORM handy
+to install those platform-specific gems on.
+
+For a full explanation of gem platforms, see `gem help platform`.
+
+## PATCH LEVEL OPTIONS
+
+See [bundle update(1)](bundle-update.1.html) for details.
diff --git a/man/bundle-open.1 b/man/bundle-open.1
new file mode 100644
index 0000000000..392bd6d9f2
--- /dev/null
+++ b/man/bundle-open.1
@@ -0,0 +1,32 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-OPEN" "1" "May 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-open\fR \- Opens the source directory for a gem in your bundle
+.
+.SH "SYNOPSIS"
+\fBbundle open\fR [GEM]
+.
+.SH "DESCRIPTION"
+Opens the source directory of the provided GEM in your editor\.
+.
+.P
+For this to work the \fBEDITOR\fR or \fBBUNDLER_EDITOR\fR environment variable has to be set\.
+.
+.P
+Example:
+.
+.IP "" 4
+.
+.nf
+
+bundle open \'rack\'
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Will open the source directory for the \'rack\' gem in your bundle\.
diff --git a/man/bundle-open.1.txt b/man/bundle-open.1.txt
new file mode 100644
index 0000000000..3f067d8bde
--- /dev/null
+++ b/man/bundle-open.1.txt
@@ -0,0 +1,29 @@
+BUNDLE-OPEN(1) BUNDLE-OPEN(1)
+
+
+
+1mNAME0m
+ 1mbundle-open 22m- Opens the source directory for a gem in your bundle
+
+1mSYNOPSIS0m
+ 1mbundle open 22m[GEM]
+
+1mDESCRIPTION0m
+ Opens the source directory of the provided GEM in your editor.
+
+ For this to work the 1mEDITOR 22mor 1mBUNDLER_EDITOR 22menvironment variable has
+ to be set.
+
+ Example:
+
+
+
+ bundle open 'rack'
+
+
+
+ Will open the source directory for the 'rack' gem in your bundle.
+
+
+
+ May 2018 BUNDLE-OPEN(1)
diff --git a/man/bundle-open.ronn b/man/bundle-open.ronn
new file mode 100644
index 0000000000..497beac93f
--- /dev/null
+++ b/man/bundle-open.ronn
@@ -0,0 +1,19 @@
+bundle-open(1) -- Opens the source directory for a gem in your bundle
+=====================================================================
+
+## SYNOPSIS
+
+`bundle open` [GEM]
+
+## DESCRIPTION
+
+Opens the source directory of the provided GEM in your editor.
+
+For this to work the `EDITOR` or `BUNDLER_EDITOR` environment variable has to
+be set.
+
+Example:
+
+ bundle open 'rack'
+
+Will open the source directory for the 'rack' gem in your bundle.
diff --git a/man/bundle-outdated.1 b/man/bundle-outdated.1
new file mode 100644
index 0000000000..273e029db3
--- /dev/null
+++ b/man/bundle-outdated.1
@@ -0,0 +1,155 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-OUTDATED" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-outdated\fR \- List installed gems with newer versions available
+.
+.SH "SYNOPSIS"
+\fBbundle outdated\fR [GEM] [\-\-local] [\-\-pre] [\-\-source] [\-\-strict] [\-\-parseable | \-\-porcelain] [\-\-group=GROUP] [\-\-groups] [\-\-update\-strict] [\-\-patch|\-\-minor|\-\-major] [\-\-filter\-major] [\-\-filter\-minor] [\-\-filter\-patch] [\-\-only\-explicit]
+.
+.SH "DESCRIPTION"
+Outdated lists the names and versions of gems that have a newer version available in the given source\. Calling outdated with [GEM [GEM]] will only check for newer versions of the given gems\. Prerelease gems are ignored by default\. If your gems are up to date, Bundler will exit with a status of 0\. Otherwise, it will exit 1\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-local\fR
+Do not attempt to fetch gems remotely and use the gem cache instead\.
+.
+.TP
+\fB\-\-pre\fR
+Check for newer pre\-release gems\.
+.
+.TP
+\fB\-\-source\fR
+Check against a specific source\.
+.
+.TP
+\fB\-\-strict\fR
+Only list newer versions allowed by your Gemfile requirements\.
+.
+.TP
+\fB\-\-parseable\fR, \fB\-\-porcelain\fR
+Use minimal formatting for more parseable output\.
+.
+.TP
+\fB\-\-group\fR
+List gems from a specific group\.
+.
+.TP
+\fB\-\-groups\fR
+List gems organized by groups\.
+.
+.TP
+\fB\-\-update\-strict\fR
+Strict conservative resolution, do not allow any gem to be updated past latest \-\-patch | \-\-minor| \-\-major\.
+.
+.TP
+\fB\-\-minor\fR
+Prefer updating only to next minor version\.
+.
+.TP
+\fB\-\-major\fR
+Prefer updating to next major version (default)\.
+.
+.TP
+\fB\-\-patch\fR
+Prefer updating only to next patch version\.
+.
+.TP
+\fB\-\-filter\-major\fR
+Only list major newer versions\.
+.
+.TP
+\fB\-\-filter\-minor\fR
+Only list minor newer versions\.
+.
+.TP
+\fB\-\-filter\-patch\fR
+Only list patch newer versions\.
+.
+.TP
+\fB\-\-only\-explicit\fR
+Only list gems specified in your Gemfile, not their dependencies\.
+.
+.SH "PATCH LEVEL OPTIONS"
+See bundle update(1) \fIbundle\-update\.1\.html\fR for details\.
+.
+.P
+One difference between the patch level options in \fBbundle update\fR and here is the \fB\-\-strict\fR option\. \fB\-\-strict\fR was already an option on outdated before the patch level options were added\. \fB\-\-strict\fR wasn\'t altered, and the \fB\-\-update\-strict\fR option on \fBoutdated\fR reflects what \fB\-\-strict\fR does on \fBbundle update\fR\.
+.
+.SH "FILTERING OUTPUT"
+The 3 filtering options do not affect the resolution of versions, merely what versions are shown in the output\.
+.
+.P
+If the regular output shows the following:
+.
+.IP "" 4
+.
+.nf
+
+* faker (newest 1\.6\.6, installed 1\.6\.5, requested ~> 1\.4) in groups "development, test"
+* hashie (newest 3\.4\.6, installed 1\.2\.0, requested = 1\.2\.0) in groups "default"
+* headless (newest 2\.3\.1, installed 2\.2\.3) in groups "test"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+\fB\-\-filter\-major\fR would only show:
+.
+.IP "" 4
+.
+.nf
+
+* hashie (newest 3\.4\.6, installed 1\.2\.0, requested = 1\.2\.0) in groups "default"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+\fB\-\-filter\-minor\fR would only show:
+.
+.IP "" 4
+.
+.nf
+
+* headless (newest 2\.3\.1, installed 2\.2\.3) in groups "test"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+\fB\-\-filter\-patch\fR would only show:
+.
+.IP "" 4
+.
+.nf
+
+* faker (newest 1\.6\.6, installed 1\.6\.5, requested ~> 1\.4) in groups "development, test"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Filter options can be combined\. \fB\-\-filter\-minor\fR and \fB\-\-filter\-patch\fR would show:
+.
+.IP "" 4
+.
+.nf
+
+* faker (newest 1\.6\.6, installed 1\.6\.5, requested ~> 1\.4) in groups "development, test"
+* headless (newest 2\.3\.1, installed 2\.2\.3) in groups "test"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Combining all three \fBfilter\fR options would be the same result as providing none of them\.
diff --git a/man/bundle-outdated.1.txt b/man/bundle-outdated.1.txt
new file mode 100644
index 0000000000..3942f19b84
--- /dev/null
+++ b/man/bundle-outdated.1.txt
@@ -0,0 +1,131 @@
+BUNDLE-OUTDATED(1) BUNDLE-OUTDATED(1)
+
+
+
+1mNAME0m
+ 1mbundle-outdated 22m- List installed gems with newer versions available
+
+1mSYNOPSIS0m
+ 1mbundle outdated 22m[GEM] [--local] [--pre] [--source] [--strict]
+ [--parseable | --porcelain] [--group=GROUP] [--groups]
+ [--update-strict] [--patch|--minor|--major] [--filter-major] [--fil-
+ ter-minor] [--filter-patch] [--only-explicit]
+
+1mDESCRIPTION0m
+ Outdated lists the names and versions of gems that have a newer version
+ available in the given source. Calling outdated with [GEM [GEM]] will
+ only check for newer versions of the given gems. Prerelease gems are
+ ignored by default. If your gems are up to date, Bundler will exit with
+ a status of 0. Otherwise, it will exit 1.
+
+1mOPTIONS0m
+ 1m--local0m
+ Do not attempt to fetch gems remotely and use the gem cache
+ instead.
+
+ 1m--pre 22mCheck for newer pre-release gems.
+
+ 1m--source0m
+ Check against a specific source.
+
+ 1m--strict0m
+ Only list newer versions allowed by your Gemfile requirements.
+
+ 1m--parseable22m, 1m--porcelain0m
+ Use minimal formatting for more parseable output.
+
+ 1m--group0m
+ List gems from a specific group.
+
+ 1m--groups0m
+ List gems organized by groups.
+
+ 1m--update-strict0m
+ Strict conservative resolution, do not allow any gem to be
+ updated past latest --patch | --minor| --major.
+
+ 1m--minor0m
+ Prefer updating only to next minor version.
+
+ 1m--major0m
+ Prefer updating to next major version (default).
+
+ 1m--patch0m
+ Prefer updating only to next patch version.
+
+ 1m--filter-major0m
+ Only list major newer versions.
+
+ 1m--filter-minor0m
+ Only list minor newer versions.
+
+ 1m--filter-patch0m
+ Only list patch newer versions.
+
+ 1m--only-explicit0m
+ Only list gems specified in your Gemfile, not their dependen-
+ cies.
+
+1mPATCH LEVEL OPTIONS0m
+ See bundle update(1) 4mbundle-update.1.html24m for details.
+
+ One difference between the patch level options in 1mbundle update 22mand
+ here is the 1m--strict 22moption. 1m--strict 22mwas already an option on outdated
+ before the patch level options were added. 1m--strict 22mwasn't altered, and
+ the 1m--update-strict 22moption on 1moutdated 22mreflects what 1m--strict 22mdoes on
+ 1mbundle update22m.
+
+1mFILTERING OUTPUT0m
+ The 3 filtering options do not affect the resolution of versions,
+ merely what versions are shown in the output.
+
+ If the regular output shows the following:
+
+
+
+ * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test"
+ * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default"
+ * headless (newest 2.3.1, installed 2.2.3) in groups "test"
+
+
+
+ 1m--filter-major 22mwould only show:
+
+
+
+ * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default"
+
+
+
+ 1m--filter-minor 22mwould only show:
+
+
+
+ * headless (newest 2.3.1, installed 2.2.3) in groups "test"
+
+
+
+ 1m--filter-patch 22mwould only show:
+
+
+
+ * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test"
+
+
+
+ Filter options can be combined. 1m--filter-minor 22mand 1m--filter-patch 22mwould
+ show:
+
+
+
+ * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test"
+ * headless (newest 2.3.1, installed 2.2.3) in groups "test"
+
+
+
+ Combining all three 1mfilter 22moptions would be the same result as provid-
+ ing none of them.
+
+
+
+ October 2018 BUNDLE-OUTDATED(1)
diff --git a/man/bundle-outdated.ronn b/man/bundle-outdated.ronn
new file mode 100644
index 0000000000..a991d23789
--- /dev/null
+++ b/man/bundle-outdated.ronn
@@ -0,0 +1,111 @@
+bundle-outdated(1) -- List installed gems with newer versions available
+=======================================================================
+
+## SYNOPSIS
+
+`bundle outdated` [GEM] [--local]
+ [--pre]
+ [--source]
+ [--strict]
+ [--parseable | --porcelain]
+ [--group=GROUP]
+ [--groups]
+ [--update-strict]
+ [--patch|--minor|--major]
+ [--filter-major]
+ [--filter-minor]
+ [--filter-patch]
+ [--only-explicit]
+
+## DESCRIPTION
+
+Outdated lists the names and versions of gems that have a newer version available
+in the given source. Calling outdated with [GEM [GEM]] will only check for newer
+versions of the given gems. Prerelease gems are ignored by default. If your gems
+are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1.
+
+## OPTIONS
+
+* `--local`:
+ Do not attempt to fetch gems remotely and use the gem cache instead.
+
+* `--pre`:
+ Check for newer pre-release gems.
+
+* `--source`:
+ Check against a specific source.
+
+* `--strict`:
+ Only list newer versions allowed by your Gemfile requirements.
+
+* `--parseable`, `--porcelain`:
+ Use minimal formatting for more parseable output.
+
+* `--group`:
+ List gems from a specific group.
+
+* `--groups`:
+ List gems organized by groups.
+
+* `--update-strict`:
+ Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor| --major.
+
+* `--minor`:
+ Prefer updating only to next minor version.
+
+* `--major`:
+ Prefer updating to next major version (default).
+
+* `--patch`:
+ Prefer updating only to next patch version.
+
+* `--filter-major`:
+ Only list major newer versions.
+
+* `--filter-minor`:
+ Only list minor newer versions.
+
+* `--filter-patch`:
+ Only list patch newer versions.
+
+* `--only-explicit`:
+ Only list gems specified in your Gemfile, not their dependencies.
+
+## PATCH LEVEL OPTIONS
+
+See [bundle update(1)](bundle-update.1.html) for details.
+
+One difference between the patch level options in `bundle update` and here is the `--strict` option.
+`--strict` was already an option on outdated before the patch level options were added. `--strict`
+wasn't altered, and the `--update-strict` option on `outdated` reflects what `--strict` does on
+`bundle update`.
+
+## FILTERING OUTPUT
+
+The 3 filtering options do not affect the resolution of versions, merely what versions are shown
+in the output.
+
+If the regular output shows the following:
+
+ * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test"
+ * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default"
+ * headless (newest 2.3.1, installed 2.2.3) in groups "test"
+
+`--filter-major` would only show:
+
+ * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default"
+
+`--filter-minor` would only show:
+
+ * headless (newest 2.3.1, installed 2.2.3) in groups "test"
+
+`--filter-patch` would only show:
+
+ * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test"
+
+Filter options can be combined. `--filter-minor` and `--filter-patch` would show:
+
+ * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test"
+ * headless (newest 2.3.1, installed 2.2.3) in groups "test"
+
+Combining all three `filter` options would be the same result as providing none of them.
diff --git a/man/bundle-package.1 b/man/bundle-package.1
new file mode 100644
index 0000000000..edbf62c9f9
--- /dev/null
+++ b/man/bundle-package.1
@@ -0,0 +1,55 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-PACKAGE" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-package\fR \- Package your needed \fB\.gem\fR files into your application
+.
+.SH "SYNOPSIS"
+\fBbundle package\fR
+.
+.SH "DESCRIPTION"
+Copy all of the \fB\.gem\fR files needed to run the application into the \fBvendor/cache\fR directory\. In the future, when running [bundle install(1)][bundle\-install], use the gems in the cache in preference to the ones on \fBrubygems\.org\fR\.
+.
+.SH "GIT AND PATH GEMS"
+Since Bundler 1\.2, the \fBbundle package\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This needs to be explicitly enabled via the \fB\-\-all\fR option\. Once used, the \fB\-\-all\fR option will be remembered\.
+.
+.SH "SUPPORT FOR MULTIPLE PLATFORMS"
+When using gems that have different packages for different platforms, Bundler 1\.8 and newer support caching of gems for other platforms where the Gemfile has been resolved (i\.e\. present in the lockfile) in \fBvendor/cache\fR\. This needs to be enabled via the \fB\-\-all\-platforms\fR option\. This setting will be remembered in your local bundler configuration\.
+.
+.SH "REMOTE FETCHING"
+By default, if you run \fBbundle install(1)\fR](bundle\-install\.1\.html) after running bundle package(1) \fIbundle\-package\.1\.html\fR, bundler will still connect to \fBrubygems\.org\fR to check whether a platform\-specific gem exists for any of the gems in \fBvendor/cache\fR\.
+.
+.P
+For instance, consider this Gemfile(5):
+.
+.IP "" 4
+.
+.nf
+
+source "https://rubygems\.org"
+
+gem "nokogiri"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+If you run \fBbundle package\fR under C Ruby, bundler will retrieve the version of \fBnokogiri\fR for the \fB"ruby"\fR platform\. If you deploy to JRuby and run \fBbundle install\fR, bundler is forced to check to see whether a \fB"java"\fR platformed \fBnokogiri\fR exists\.
+.
+.P
+Even though the \fBnokogiri\fR gem for the Ruby platform is \fItechnically\fR acceptable on JRuby, it has a C extension that does not run on JRuby\. As a result, bundler will, by default, still connect to \fBrubygems\.org\fR to check whether it has a version of one of your gems more specific to your platform\.
+.
+.P
+This problem is also not limited to the \fB"java"\fR platform\. A similar (common) problem can happen when developing on Windows and deploying to Linux, or even when developing on OSX and deploying to Linux\.
+.
+.P
+If you know for sure that the gems packaged in \fBvendor/cache\fR are appropriate for the platform you are on, you can run \fBbundle install \-\-local\fR to skip checking for more appropriate gems, and use the ones in \fBvendor/cache\fR\.
+.
+.P
+One way to be sure that you have the right platformed versions of all your gems is to run \fBbundle package\fR on an identical machine and check in the gems\. For instance, you can run \fBbundle package\fR on an identical staging box during your staging process, and check in the \fBvendor/cache\fR before deploying to production\.
+.
+.P
+By default, bundle package(1) \fIbundle\-package\.1\.html\fR fetches and also installs the gems to the default location\. To package the dependencies to \fBvendor/cache\fR without installing them to the local install location, you can run \fBbundle package \-\-no\-install\fR\.
diff --git a/man/bundle-package.1.txt b/man/bundle-package.1.txt
new file mode 100644
index 0000000000..cbe957333a
--- /dev/null
+++ b/man/bundle-package.1.txt
@@ -0,0 +1,79 @@
+BUNDLE-PACKAGE(1) BUNDLE-PACKAGE(1)
+
+
+
+1mNAME0m
+ 1mbundle-package 22m- Package your needed 1m.gem 22mfiles into your application
+
+1mSYNOPSIS0m
+ 1mbundle package0m
+
+1mDESCRIPTION0m
+ Copy all of the 1m.gem 22mfiles needed to run the application into the 1mven-0m
+ 1mdor/cache 22mdirectory. In the future, when running [bundle
+ install(1)][bundle-install], use the gems in the cache in preference to
+ the ones on 1mrubygems.org22m.
+
+1mGIT AND PATH GEMS0m
+ Since Bundler 1.2, the 1mbundle package 22mcommand can also package 1m:git 22mand
+ 1m:path 22mdependencies besides .gem files. This needs to be explicitly
+ enabled via the 1m--all 22moption. Once used, the 1m--all 22moption will be
+ remembered.
+
+1mSUPPORT FOR MULTIPLE PLATFORMS0m
+ When using gems that have different packages for different platforms,
+ Bundler 1.8 and newer support caching of gems for other platforms where
+ the Gemfile has been resolved (i.e. present in the lockfile) in 1mven-0m
+ 1mdor/cache22m. This needs to be enabled via the 1m--all-platforms 22moption.
+ This setting will be remembered in your local bundler configuration.
+
+1mREMOTE FETCHING0m
+ By default, if you run 1mbundle install(1)22m](bundle-install.1.html) after
+ running bundle package(1) 4mbundle-package.1.html24m, bundler will still
+ connect to 1mrubygems.org 22mto check whether a platform-specific gem exists
+ for any of the gems in 1mvendor/cache22m.
+
+ For instance, consider this Gemfile(5):
+
+
+
+ source "https://rubygems.org"
+
+ gem "nokogiri"
+
+
+
+ If you run 1mbundle package 22munder C Ruby, bundler will retrieve the ver-
+ sion of 1mnokogiri 22mfor the 1m"ruby" 22mplatform. If you deploy to JRuby and
+ run 1mbundle install22m, bundler is forced to check to see whether a 1m"java"0m
+ platformed 1mnokogiri 22mexists.
+
+ Even though the 1mnokogiri 22mgem for the Ruby platform is 4mtechnically0m
+ acceptable on JRuby, it has a C extension that does not run on JRuby.
+ As a result, bundler will, by default, still connect to 1mrubygems.org 22mto
+ check whether it has a version of one of your gems more specific to
+ your platform.
+
+ This problem is also not limited to the 1m"java" 22mplatform. A similar
+ (common) problem can happen when developing on Windows and deploying to
+ Linux, or even when developing on OSX and deploying to Linux.
+
+ If you know for sure that the gems packaged in 1mvendor/cache 22mare appro-
+ priate for the platform you are on, you can run 1mbundle install --local0m
+ to skip checking for more appropriate gems, and use the ones in 1mven-0m
+ 1mdor/cache22m.
+
+ One way to be sure that you have the right platformed versions of all
+ your gems is to run 1mbundle package 22mon an identical machine and check in
+ the gems. For instance, you can run 1mbundle package 22mon an identical
+ staging box during your staging process, and check in the 1mvendor/cache0m
+ before deploying to production.
+
+ By default, bundle package(1) 4mbundle-package.1.html24m fetches and also
+ installs the gems to the default location. To package the dependencies
+ to 1mvendor/cache 22mwithout installing them to the local install location,
+ you can run 1mbundle package --no-install22m.
+
+
+
+ October 2018 BUNDLE-PACKAGE(1)
diff --git a/man/bundle-package.ronn b/man/bundle-package.ronn
new file mode 100644
index 0000000000..bc137374da
--- /dev/null
+++ b/man/bundle-package.ronn
@@ -0,0 +1,72 @@
+bundle-package(1) -- Package your needed `.gem` files into your application
+===========================================================================
+
+## SYNOPSIS
+
+`bundle package`
+
+## DESCRIPTION
+
+Copy all of the `.gem` files needed to run the application into the
+`vendor/cache` directory. In the future, when running [bundle install(1)][bundle-install],
+use the gems in the cache in preference to the ones on `rubygems.org`.
+
+## GIT AND PATH GEMS
+
+Since Bundler 1.2, the `bundle package` command can also package `:git` and
+`:path` dependencies besides .gem files. This needs to be explicitly enabled
+via the `--all` option. Once used, the `--all` option will be remembered.
+
+## SUPPORT FOR MULTIPLE PLATFORMS
+
+When using gems that have different packages for different platforms, Bundler
+1.8 and newer support caching of gems for other platforms where the Gemfile
+has been resolved (i.e. present in the lockfile) in `vendor/cache`. This needs
+to be enabled via the `--all-platforms` option. This setting will be remembered
+in your local bundler configuration.
+
+## REMOTE FETCHING
+
+By default, if you run `bundle install(1)`](bundle-install.1.html) after running
+[bundle package(1)](bundle-package.1.html), bundler will still connect to `rubygems.org`
+to check whether a platform-specific gem exists for any of the gems
+in `vendor/cache`.
+
+For instance, consider this Gemfile(5):
+
+ source "https://rubygems.org"
+
+ gem "nokogiri"
+
+If you run `bundle package` under C Ruby, bundler will retrieve
+the version of `nokogiri` for the `"ruby"` platform. If you deploy
+to JRuby and run `bundle install`, bundler is forced to check to
+see whether a `"java"` platformed `nokogiri` exists.
+
+Even though the `nokogiri` gem for the Ruby platform is
+_technically_ acceptable on JRuby, it has a C extension
+that does not run on JRuby. As a result, bundler will, by default,
+still connect to `rubygems.org` to check whether it has a version
+of one of your gems more specific to your platform.
+
+This problem is also not limited to the `"java"` platform.
+A similar (common) problem can happen when developing on Windows
+and deploying to Linux, or even when developing on OSX and
+deploying to Linux.
+
+If you know for sure that the gems packaged in `vendor/cache`
+are appropriate for the platform you are on, you can run
+`bundle install --local` to skip checking for more appropriate
+gems, and use the ones in `vendor/cache`.
+
+One way to be sure that you have the right platformed versions
+of all your gems is to run `bundle package` on an identical
+machine and check in the gems. For instance, you can run
+`bundle package` on an identical staging box during your
+staging process, and check in the `vendor/cache` before
+deploying to production.
+
+By default, [bundle package(1)](bundle-package.1.html) fetches and also
+installs the gems to the default location. To package the
+dependencies to `vendor/cache` without installing them to the
+local install location, you can run `bundle package --no-install`.
diff --git a/man/bundle-platform.1 b/man/bundle-platform.1
new file mode 100644
index 0000000000..5fa4ab2cb7
--- /dev/null
+++ b/man/bundle-platform.1
@@ -0,0 +1,61 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-PLATFORM" "1" "May 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-platform\fR \- Displays platform compatibility information
+.
+.SH "SYNOPSIS"
+\fBbundle platform\fR [\-\-ruby]
+.
+.SH "DESCRIPTION"
+\fBplatform\fR will display information from your Gemfile, Gemfile\.lock, and Ruby VM about your platform\.
+.
+.P
+For instance, using this Gemfile(5):
+.
+.IP "" 4
+.
+.nf
+
+source "https://rubygems\.org"
+
+ruby "1\.9\.3"
+
+gem "rack"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+If you run \fBbundle platform\fR on Ruby 1\.9\.3, it will display the following output:
+.
+.IP "" 4
+.
+.nf
+
+Your platform is: x86_64\-linux
+
+Your app has gems that work on these platforms:
+* ruby
+
+Your Gemfile specifies a Ruby version requirement:
+* ruby 1\.9\.3
+
+Your current platform satisfies the Ruby version requirement\.
+.
+.fi
+.
+.IP "" 0
+.
+.P
+\fBplatform\fR will list all the platforms in your \fBGemfile\.lock\fR as well as the \fBruby\fR directive if applicable from your Gemfile(5)\. It will also let you know if the \fBruby\fR directive requirement has been met\. If \fBruby\fR directive doesn\'t match the running Ruby VM, it will tell you what part does not\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-ruby\fR
+It will display the ruby directive information, so you don\'t have to parse it from the Gemfile(5)\.
+
diff --git a/man/bundle-platform.1.txt b/man/bundle-platform.1.txt
new file mode 100644
index 0000000000..ad3ba0eec0
--- /dev/null
+++ b/man/bundle-platform.1.txt
@@ -0,0 +1,57 @@
+BUNDLE-PLATFORM(1) BUNDLE-PLATFORM(1)
+
+
+
+1mNAME0m
+ 1mbundle-platform 22m- Displays platform compatibility information
+
+1mSYNOPSIS0m
+ 1mbundle platform 22m[--ruby]
+
+1mDESCRIPTION0m
+ 1mplatform 22mwill display information from your Gemfile, Gemfile.lock, and
+ Ruby VM about your platform.
+
+ For instance, using this Gemfile(5):
+
+
+
+ source "https://rubygems.org"
+
+ ruby "1.9.3"
+
+ gem "rack"
+
+
+
+ If you run 1mbundle platform 22mon Ruby 1.9.3, it will display the following
+ output:
+
+
+
+ Your platform is: x86_64-linux
+
+ Your app has gems that work on these platforms:
+ * ruby
+
+ Your Gemfile specifies a Ruby version requirement:
+ * ruby 1.9.3
+
+ Your current platform satisfies the Ruby version requirement.
+
+
+
+ 1mplatform 22mwill list all the platforms in your 1mGemfile.lock 22mas well as
+ the 1mruby 22mdirective if applicable from your Gemfile(5). It will also let
+ you know if the 1mruby 22mdirective requirement has been met. If 1mruby 22mdirec-
+ tive doesn't match the running Ruby VM, it will tell you what part does
+ not.
+
+1mOPTIONS0m
+ 1m--ruby 22mIt will display the ruby directive information, so you don't
+ have to parse it from the Gemfile(5).
+
+
+
+
+ May 2018 BUNDLE-PLATFORM(1)
diff --git a/man/bundle-platform.ronn b/man/bundle-platform.ronn
new file mode 100644
index 0000000000..b5d3283fb6
--- /dev/null
+++ b/man/bundle-platform.ronn
@@ -0,0 +1,42 @@
+bundle-platform(1) -- Displays platform compatibility information
+=================================================================
+
+## SYNOPSIS
+
+`bundle platform` [--ruby]
+
+## DESCRIPTION
+
+`platform` will display information from your Gemfile, Gemfile.lock, and Ruby
+VM about your platform.
+
+For instance, using this Gemfile(5):
+
+ source "https://rubygems.org"
+
+ ruby "1.9.3"
+
+ gem "rack"
+
+If you run `bundle platform` on Ruby 1.9.3, it will display the following output:
+
+ Your platform is: x86_64-linux
+
+ Your app has gems that work on these platforms:
+ * ruby
+
+ Your Gemfile specifies a Ruby version requirement:
+ * ruby 1.9.3
+
+ Your current platform satisfies the Ruby version requirement.
+
+`platform` will list all the platforms in your `Gemfile.lock` as well as the
+`ruby` directive if applicable from your Gemfile(5). It will also let you know
+if the `ruby` directive requirement has been met. If `ruby` directive doesn't
+match the running Ruby VM, it will tell you what part does not.
+
+## OPTIONS
+
+* `--ruby`:
+ It will display the ruby directive information, so you don't have to
+ parse it from the Gemfile(5).
diff --git a/man/bundle-pristine.1 b/man/bundle-pristine.1
new file mode 100644
index 0000000000..78a6e95fdc
--- /dev/null
+++ b/man/bundle-pristine.1
@@ -0,0 +1,34 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-PRISTINE" "1" "May 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-pristine\fR \- Restores installed gems to their pristine condition
+.
+.SH "SYNOPSIS"
+\fBbundle pristine\fR
+.
+.SH "DESCRIPTION"
+\fBpristine\fR restores the installed gems in the bundle to their pristine condition using the local gem cache from RubyGems\. For git gems, a forced checkout will be performed\.
+.
+.P
+For further explanation, \fBbundle pristine\fR ignores unpacked files on disk\. In other words, this command utilizes the local \fB\.gem\fR cache or the gem\'s git repository as if one were installing from scratch\.
+.
+.P
+Note: the Bundler gem cannot be restored to its original state with \fBpristine\fR\. One also cannot use \fBbundle pristine\fR on gems with a \'path\' option in the Gemfile, because bundler has no original copy it can restore from\.
+.
+.P
+When is it practical to use \fBbundle pristine\fR?
+.
+.P
+It comes in handy when a developer is debugging a gem\. \fBbundle pristine\fR is a great way to get rid of experimental changes to a gem that one may not want\.
+.
+.P
+Why use \fBbundle pristine\fR over \fBgem pristine \-\-all\fR?
+.
+.P
+Both commands are very similar\. For context: \fBbundle pristine\fR, without arguments, cleans all gems from the lockfile\. Meanwhile, \fBgem pristine \-\-all\fR cleans all installed gems for that Ruby version\.
+.
+.P
+If a developer forgets which gems in their project they might have been debugging, the Rubygems \fBgem pristine [GEMNAME]\fR command may be inconvenient\. One can avoid waiting for \fBgem pristine \-\-all\fR, and instead run \fBbundle pristine\fR\.
diff --git a/man/bundle-pristine.1.txt b/man/bundle-pristine.1.txt
new file mode 100644
index 0000000000..38d6dff1c5
--- /dev/null
+++ b/man/bundle-pristine.1.txt
@@ -0,0 +1,44 @@
+BUNDLE-PRISTINE(1) BUNDLE-PRISTINE(1)
+
+
+
+1mNAME0m
+ 1mbundle-pristine 22m- Restores installed gems to their pristine condition
+
+1mSYNOPSIS0m
+ 1mbundle pristine0m
+
+1mDESCRIPTION0m
+ 1mpristine 22mrestores the installed gems in the bundle to their pristine
+ condition using the local gem cache from RubyGems. For git gems, a
+ forced checkout will be performed.
+
+ For further explanation, 1mbundle pristine 22mignores unpacked files on
+ disk. In other words, this command utilizes the local 1m.gem 22mcache or the
+ gem's git repository as if one were installing from scratch.
+
+ Note: the Bundler gem cannot be restored to its original state with
+ 1mpristine22m. One also cannot use 1mbundle pristine 22mon gems with a 'path'
+ option in the Gemfile, because bundler has no original copy it can
+ restore from.
+
+ When is it practical to use 1mbundle pristine22m?
+
+ It comes in handy when a developer is debugging a gem. 1mbundle pristine0m
+ is a great way to get rid of experimental changes to a gem that one may
+ not want.
+
+ Why use 1mbundle pristine 22mover 1mgem pristine --all22m?
+
+ Both commands are very similar. For context: 1mbundle pristine22m, without
+ arguments, cleans all gems from the lockfile. Meanwhile, 1mgem pristine0m
+ 1m--all 22mcleans all installed gems for that Ruby version.
+
+ If a developer forgets which gems in their project they might have been
+ debugging, the Rubygems 1mgem pristine [GEMNAME] 22mcommand may be inconve-
+ nient. One can avoid waiting for 1mgem pristine --all22m, and instead run
+ 1mbundle pristine22m.
+
+
+
+ May 2018 BUNDLE-PRISTINE(1)
diff --git a/man/bundle-pristine.ronn b/man/bundle-pristine.ronn
new file mode 100644
index 0000000000..e2d6b6a348
--- /dev/null
+++ b/man/bundle-pristine.ronn
@@ -0,0 +1,34 @@
+bundle-pristine(1) -- Restores installed gems to their pristine condition
+===========================================================================
+
+## SYNOPSIS
+
+`bundle pristine`
+
+## DESCRIPTION
+
+`pristine` restores the installed gems in the bundle to their pristine condition
+using the local gem cache from RubyGems. For git gems, a forced checkout will be performed.
+
+For further explanation, `bundle pristine` ignores unpacked files on disk. In other
+words, this command utilizes the local `.gem` cache or the gem's git repository
+as if one were installing from scratch.
+
+Note: the Bundler gem cannot be restored to its original state with `pristine`.
+One also cannot use `bundle pristine` on gems with a 'path' option in the Gemfile,
+because bundler has no original copy it can restore from.
+
+When is it practical to use `bundle pristine`?
+
+It comes in handy when a developer is debugging a gem. `bundle pristine` is a
+great way to get rid of experimental changes to a gem that one may not want.
+
+Why use `bundle pristine` over `gem pristine --all`?
+
+Both commands are very similar.
+For context: `bundle pristine`, without arguments, cleans all gems from the lockfile.
+Meanwhile, `gem pristine --all` cleans all installed gems for that Ruby version.
+
+If a developer forgets which gems in their project they might
+have been debugging, the Rubygems `gem pristine [GEMNAME]` command may be inconvenient.
+One can avoid waiting for `gem pristine --all`, and instead run `bundle pristine`.
diff --git a/man/bundle-remove.1 b/man/bundle-remove.1
new file mode 100644
index 0000000000..bbc4a4c339
--- /dev/null
+++ b/man/bundle-remove.1
@@ -0,0 +1,31 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-REMOVE" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-remove\fR \- Removes gems from the Gemfile
+.
+.SH "SYNOPSIS"
+\fBbundle remove [GEM [GEM \.\.\.]] [\-\-install]\fR
+.
+.SH "DESCRIPTION"
+Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid\. If a gem cannot be removed, a warning is printed\. If a gem is already absent from the Gemfile, and error is raised\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-install\fR
+Runs \fBbundle install\fR after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s)\.
+.
+.P
+Example:
+.
+.P
+bundle remove rails
+.
+.P
+bundle remove rails rack
+.
+.P
+bundle remove rails rack \-\-install
diff --git a/man/bundle-remove.1.txt b/man/bundle-remove.1.txt
new file mode 100644
index 0000000000..1d1f74614d
--- /dev/null
+++ b/man/bundle-remove.1.txt
@@ -0,0 +1,34 @@
+BUNDLE-REMOVE(1) BUNDLE-REMOVE(1)
+
+
+
+1mNAME0m
+ 1mbundle-remove 22m- Removes gems from the Gemfile
+
+1mSYNOPSIS0m
+ 1mbundle remove [GEM [GEM ...]] [--install]0m
+
+1mDESCRIPTION0m
+ Removes the given gems from the Gemfile while ensuring that the result-
+ ing Gemfile is still valid. If a gem cannot be removed, a warning is
+ printed. If a gem is already absent from the Gemfile, and error is
+ raised.
+
+1mOPTIONS0m
+ 1m--install0m
+ Runs 1mbundle install 22mafter the given gems have been removed from
+ the Gemfile, which ensures that both the lockfile and the
+ installed gems on disk are also updated to remove the given
+ gem(s).
+
+ Example:
+
+ bundle remove rails
+
+ bundle remove rails rack
+
+ bundle remove rails rack --install
+
+
+
+ October 2018 BUNDLE-REMOVE(1)
diff --git a/man/bundle-remove.ronn b/man/bundle-remove.ronn
new file mode 100644
index 0000000000..40a239b4a2
--- /dev/null
+++ b/man/bundle-remove.ronn
@@ -0,0 +1,23 @@
+bundle-remove(1) -- Removes gems from the Gemfile
+===========================================================================
+
+## SYNOPSIS
+
+`bundle remove [GEM [GEM ...]] [--install]`
+
+## DESCRIPTION
+
+Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If a gem cannot be removed, a warning is printed. If a gem is already absent from the Gemfile, and error is raised.
+
+## OPTIONS
+
+* `--install`:
+ Runs `bundle install` after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s).
+
+Example:
+
+bundle remove rails
+
+bundle remove rails rack
+
+bundle remove rails rack --install
diff --git a/man/bundle-show.1 b/man/bundle-show.1
new file mode 100644
index 0000000000..d4a2a5c343
--- /dev/null
+++ b/man/bundle-show.1
@@ -0,0 +1,23 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-SHOW" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem
+.
+.SH "SYNOPSIS"
+\fBbundle show\fR [GEM] [\-\-paths]
+.
+.SH "DESCRIPTION"
+Without the [GEM] option, \fBshow\fR will print a list of the names and versions of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by name\.
+.
+.P
+Calling show with [GEM] will list the exact location of that gem on your machine\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-paths\fR
+List the paths of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by gem name\.
+
diff --git a/man/bundle-show.1.txt b/man/bundle-show.1.txt
new file mode 100644
index 0000000000..e14466d3a3
--- /dev/null
+++ b/man/bundle-show.1.txt
@@ -0,0 +1,27 @@
+BUNDLE-SHOW(1) BUNDLE-SHOW(1)
+
+
+
+1mNAME0m
+ 1mbundle-show 22m- Shows all the gems in your bundle, or the path to a gem
+
+1mSYNOPSIS0m
+ 1mbundle show 22m[GEM] [--paths]
+
+1mDESCRIPTION0m
+ Without the [GEM] option, 1mshow 22mwill print a list of the names and ver-
+ sions of all gems that are required by your [1mGemfile(5)22m][Gemfile(5)],
+ sorted by name.
+
+ Calling show with [GEM] will list the exact location of that gem on
+ your machine.
+
+1mOPTIONS0m
+ 1m--paths0m
+ List the paths of all gems that are required by your [1mGem-0m
+ 1mfile(5)22m][Gemfile(5)], sorted by gem name.
+
+
+
+
+ October 2018 BUNDLE-SHOW(1)
diff --git a/man/bundle-show.ronn b/man/bundle-show.ronn
new file mode 100644
index 0000000000..a6a59a1445
--- /dev/null
+++ b/man/bundle-show.ronn
@@ -0,0 +1,21 @@
+bundle-show(1) -- Shows all the gems in your bundle, or the path to a gem
+=========================================================================
+
+## SYNOPSIS
+
+`bundle show` [GEM]
+ [--paths]
+
+## DESCRIPTION
+
+Without the [GEM] option, `show` will print a list of the names and versions of
+all gems that are required by your [`Gemfile(5)`][Gemfile(5)], sorted by name.
+
+Calling show with [GEM] will list the exact location of that gem on your
+machine.
+
+## OPTIONS
+
+* `--paths`:
+ List the paths of all gems that are required by your [`Gemfile(5)`][Gemfile(5)],
+ sorted by gem name.
diff --git a/man/bundle-update.1 b/man/bundle-update.1
new file mode 100644
index 0000000000..49f51c09a8
--- /dev/null
+++ b/man/bundle-update.1
@@ -0,0 +1,394 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-UPDATE" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-update\fR \- Update your gems to the latest available versions
+.
+.SH "SYNOPSIS"
+\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-full\-index] [\-\-jobs=JOBS] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-redownload] [\-\-strict] [\-\-conservative]
+.
+.SH "DESCRIPTION"
+Update the gems specified (all gems, if \fB\-\-all\fR flag is used), ignoring the previously installed gems specified in the \fBGemfile\.lock\fR\. In general, you should use bundle install(1) \fIbundle\-install\.1\.html\fR to install the same exact gems and versions across machines\.
+.
+.P
+You would use \fBbundle update\fR to explicitly update the version of a gem\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-all\fR
+Update all gems specified in Gemfile\.
+.
+.TP
+\fB\-\-group=<name>\fR, \fB\-g=[<name>]\fR
+Only update the gems in the specified group\. For instance, you can update all gems in the development group with \fBbundle update \-\-group development\fR\. You can also call \fBbundle update rails \-\-group test\fR to update the rails gem and all gems in the test group, for example\.
+.
+.TP
+\fB\-\-source=<name>\fR
+The name of a \fB:git\fR or \fB:path\fR source used in the Gemfile(5)\. For instance, with a \fB:git\fR source of \fBhttp://github\.com/rails/rails\.git\fR, you would call \fBbundle update \-\-source rails\fR
+.
+.TP
+\fB\-\-local\fR
+Do not attempt to fetch gems remotely and use the gem cache instead\.
+.
+.TP
+\fB\-\-ruby\fR
+Update the locked version of Ruby to the current version of Ruby\.
+.
+.TP
+\fB\-\-bundler\fR
+Update the locked version of bundler to the invoked bundler version\.
+.
+.TP
+\fB\-\-full\-index\fR
+Fall back to using the single\-file index of all gems\.
+.
+.TP
+\fB\-\-jobs=[<number>]\fR, \fB\-j[<number>]\fR
+Specify the number of jobs to run in parallel\. The default is \fB1\fR\.
+.
+.TP
+\fB\-\-retry=[<number>]\fR
+Retry failed network or git requests for \fInumber\fR times\.
+.
+.TP
+\fB\-\-quiet\fR
+Only output warnings and errors\.
+.
+.TP
+\fB\-\-redownload\fR
+Force downloading every gem\.
+.
+.TP
+\fB\-\-patch\fR
+Prefer updating only to next patch version\.
+.
+.TP
+\fB\-\-minor\fR
+Prefer updating only to next minor version\.
+.
+.TP
+\fB\-\-major\fR
+Prefer updating to next major version (default)\.
+.
+.TP
+\fB\-\-strict\fR
+Do not allow any gem to be updated past latest \fB\-\-patch\fR | \fB\-\-minor\fR | \fB\-\-major\fR\.
+.
+.TP
+\fB\-\-conservative\fR
+Use bundle install conservative update behavior and do not allow shared dependencies to be updated\.
+.
+.SH "UPDATING ALL GEMS"
+If you run \fBbundle update \-\-all\fR, bundler will ignore any previously installed gems and resolve all dependencies again based on the latest versions of all gems available in the sources\.
+.
+.P
+Consider the following Gemfile(5):
+.
+.IP "" 4
+.
+.nf
+
+source "https://rubygems\.org"
+
+gem "rails", "3\.0\.0\.rc"
+gem "nokogiri"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+When you run bundle install(1) \fIbundle\-install\.1\.html\fR the first time, bundler will resolve all of the dependencies, all the way down, and install what you need:
+.
+.IP "" 4
+.
+.nf
+
+Fetching gem metadata from https://rubygems\.org/\.\.\.\.\.\.\.\.\.
+Resolving dependencies\.\.\.
+Installing builder 2\.1\.2
+Installing abstract 1\.0\.0
+Installing rack 1\.2\.8
+Using bundler 1\.7\.6
+Installing rake 10\.4\.0
+Installing polyglot 0\.3\.5
+Installing mime\-types 1\.25\.1
+Installing i18n 0\.4\.2
+Installing mini_portile 0\.6\.1
+Installing tzinfo 0\.3\.42
+Installing rack\-mount 0\.6\.14
+Installing rack\-test 0\.5\.7
+Installing treetop 1\.4\.15
+Installing thor 0\.14\.6
+Installing activesupport 3\.0\.0\.rc
+Installing erubis 2\.6\.6
+Installing activemodel 3\.0\.0\.rc
+Installing arel 0\.4\.0
+Installing mail 2\.2\.20
+Installing activeresource 3\.0\.0\.rc
+Installing actionpack 3\.0\.0\.rc
+Installing activerecord 3\.0\.0\.rc
+Installing actionmailer 3\.0\.0\.rc
+Installing railties 3\.0\.0\.rc
+Installing rails 3\.0\.0\.rc
+Installing nokogiri 1\.6\.5
+
+Bundle complete! 2 Gemfile dependencies, 26 gems total\.
+Use `bundle show [gemname]` to see where a bundled gem is installed\.
+.
+.fi
+.
+.IP "" 0
+.
+.P
+As you can see, even though you have two gems in the Gemfile(5), your application needs 26 different gems in order to run\. Bundler remembers the exact versions it installed in \fBGemfile\.lock\fR\. The next time you run bundle install(1) \fIbundle\-install\.1\.html\fR, bundler skips the dependency resolution and installs the same gems as it installed last time\.
+.
+.P
+After checking in the \fBGemfile\.lock\fR into version control and cloning it on another machine, running bundle install(1) \fIbundle\-install\.1\.html\fR will \fIstill\fR install the gems that you installed last time\. You don\'t need to worry that a new release of \fBerubis\fR or \fBmail\fR changes the gems you use\.
+.
+.P
+However, from time to time, you might want to update the gems you are using to the newest versions that still match the gems in your Gemfile(5)\.
+.
+.P
+To do this, run \fBbundle update \-\-all\fR, which will ignore the \fBGemfile\.lock\fR, and resolve all the dependencies again\. Keep in mind that this process can result in a significantly different set of the 25 gems, based on the requirements of new gems that the gem authors released since the last time you ran \fBbundle update \-\-all\fR\.
+.
+.SH "UPDATING A LIST OF GEMS"
+Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of the gems that you specified locked to the versions in the \fBGemfile\.lock\fR\.
+.
+.P
+For instance, in the scenario above, imagine that \fBnokogiri\fR releases version \fB1\.4\.4\fR, and you want to update it \fIwithout\fR updating Rails and all of its dependencies\. To do this, run \fBbundle update nokogiri\fR\.
+.
+.P
+Bundler will update \fBnokogiri\fR and any of its dependencies, but leave alone Rails and its dependencies\.
+.
+.SH "OVERLAPPING DEPENDENCIES"
+Sometimes, multiple gems declared in your Gemfile(5) are satisfied by the same second\-level dependency\. For instance, consider the case of \fBthin\fR and \fBrack\-perftools\-profiler\fR\.
+.
+.IP "" 4
+.
+.nf
+
+source "https://rubygems\.org"
+
+gem "thin"
+gem "rack\-perftools\-profiler"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+The \fBthin\fR gem depends on \fBrack >= 1\.0\fR, while \fBrack\-perftools\-profiler\fR depends on \fBrack ~> 1\.0\fR\. If you run bundle install, you get:
+.
+.IP "" 4
+.
+.nf
+
+Fetching source index for https://rubygems\.org/
+Installing daemons (1\.1\.0)
+Installing eventmachine (0\.12\.10) with native extensions
+Installing open4 (1\.0\.1)
+Installing perftools\.rb (0\.4\.7) with native extensions
+Installing rack (1\.2\.1)
+Installing rack\-perftools_profiler (0\.0\.2)
+Installing thin (1\.2\.7) with native extensions
+Using bundler (1\.0\.0\.rc\.3)
+.
+.fi
+.
+.IP "" 0
+.
+.P
+In this case, the two gems have their own set of dependencies, but they share \fBrack\fR in common\. If you run \fBbundle update thin\fR, bundler will update \fBdaemons\fR, \fBeventmachine\fR and \fBrack\fR, which are dependencies of \fBthin\fR, but not \fBopen4\fR or \fBperftools\.rb\fR, which are dependencies of \fBrack\-perftools_profiler\fR\. Note that \fBbundle update thin\fR will update \fBrack\fR even though it\'s \fIalso\fR a dependency of \fBrack\-perftools_profiler\fR\.
+.
+.P
+In short, by default, when you update a gem using \fBbundle update\fR, bundler will update all dependencies of that gem, including those that are also dependencies of another gem\.
+.
+.P
+To prevent updating shared dependencies, prior to version 1\.14 the only option was the \fBCONSERVATIVE UPDATING\fR behavior in bundle install(1) \fIbundle\-install\.1\.html\fR:
+.
+.P
+In this scenario, updating the \fBthin\fR version manually in the Gemfile(5), and then running bundle install(1) \fIbundle\-install\.1\.html\fR will only update \fBdaemons\fR and \fBeventmachine\fR, but not \fBrack\fR\. For more information, see the \fBCONSERVATIVE UPDATING\fR section of bundle install(1) \fIbundle\-install\.1\.html\fR\.
+.
+.P
+Starting with 1\.14, specifying the \fB\-\-conservative\fR option will also prevent shared dependencies from being updated\.
+.
+.SH "PATCH LEVEL OPTIONS"
+Version 1\.14 introduced 4 patch\-level options that will influence how gem versions are resolved\. One of the following options can be used: \fB\-\-patch\fR, \fB\-\-minor\fR or \fB\-\-major\fR\. \fB\-\-strict\fR can be added to further influence resolution\.
+.
+.TP
+\fB\-\-patch\fR
+Prefer updating only to next patch version\.
+.
+.TP
+\fB\-\-minor\fR
+Prefer updating only to next minor version\.
+.
+.TP
+\fB\-\-major\fR
+Prefer updating to next major version (default)\.
+.
+.TP
+\fB\-\-strict\fR
+Do not allow any gem to be updated past latest \fB\-\-patch\fR | \fB\-\-minor\fR | \fB\-\-major\fR\.
+.
+.P
+When Bundler is resolving what versions to use to satisfy declared requirements in the Gemfile or in parent gems, it looks up all available versions, filters out any versions that don\'t satisfy the requirement, and then, by default, sorts them from newest to oldest, considering them in that order\.
+.
+.P
+Providing one of the patch level options (e\.g\. \fB\-\-patch\fR) changes the sort order of the satisfying versions, causing Bundler to consider the latest \fB\-\-patch\fR or \fB\-\-minor\fR version available before other versions\. Note that versions outside the stated patch level could still be resolved to if necessary to find a suitable dependency graph\.
+.
+.P
+For example, if gem \'foo\' is locked at 1\.0\.2, with no gem requirement defined in the Gemfile, and versions 1\.0\.3, 1\.0\.4, 1\.1\.0, 1\.1\.1, 2\.0\.0 all exist, the default order of preference by default (\fB\-\-major\fR) will be "2\.0\.0, 1\.1\.1, 1\.1\.0, 1\.0\.4, 1\.0\.3, 1\.0\.2"\.
+.
+.P
+If the \fB\-\-patch\fR option is used, the order of preference will change to "1\.0\.4, 1\.0\.3, 1\.0\.2, 1\.1\.1, 1\.1\.0, 2\.0\.0"\.
+.
+.P
+If the \fB\-\-minor\fR option is used, the order of preference will change to "1\.1\.1, 1\.1\.0, 1\.0\.4, 1\.0\.3, 1\.0\.2, 2\.0\.0"\.
+.
+.P
+Combining the \fB\-\-strict\fR option with any of the patch level options will remove any versions beyond the scope of the patch level option, to ensure that no gem is updated that far\.
+.
+.P
+To continue the previous example, if both \fB\-\-patch\fR and \fB\-\-strict\fR options are used, the available versions for resolution would be "1\.0\.4, 1\.0\.3, 1\.0\.2"\. If \fB\-\-minor\fR and \fB\-\-strict\fR are used, it would be "1\.1\.1, 1\.1\.0, 1\.0\.4, 1\.0\.3, 1\.0\.2"\.
+.
+.P
+Gem requirements as defined in the Gemfile will still be the first determining factor for what versions are available\. If the gem requirement for \fBfoo\fR in the Gemfile is \'~> 1\.0\', that will accomplish the same thing as providing the \fB\-\-minor\fR and \fB\-\-strict\fR options\.
+.
+.SH "PATCH LEVEL EXAMPLES"
+Given the following gem specifications:
+.
+.IP "" 4
+.
+.nf
+
+foo 1\.4\.3, requires: ~> bar 2\.0
+foo 1\.4\.4, requires: ~> bar 2\.0
+foo 1\.4\.5, requires: ~> bar 2\.1
+foo 1\.5\.0, requires: ~> bar 2\.1
+foo 1\.5\.1, requires: ~> bar 3\.0
+bar with versions 2\.0\.3, 2\.0\.4, 2\.1\.0, 2\.1\.1, 3\.0\.0
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Gemfile:
+.
+.IP "" 4
+.
+.nf
+
+gem \'foo\'
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Gemfile\.lock:
+.
+.IP "" 4
+.
+.nf
+
+foo (1\.4\.3)
+ bar (~> 2\.0)
+bar (2\.0\.3)
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Cases:
+.
+.IP "" 4
+.
+.nf
+
+# Command Line Result
+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
+1 bundle update \-\-patch \'foo 1\.4\.5\', \'bar 2\.1\.1\'
+2 bundle update \-\-patch foo \'foo 1\.4\.5\', \'bar 2\.1\.1\'
+3 bundle update \-\-minor \'foo 1\.5\.1\', \'bar 3\.0\.0\'
+4 bundle update \-\-minor \-\-strict \'foo 1\.5\.0\', \'bar 2\.1\.1\'
+5 bundle update \-\-patch \-\-strict \'foo 1\.4\.4\', \'bar 2\.0\.4\'
+.
+.fi
+.
+.IP "" 0
+.
+.P
+In case 1, bar is upgraded to 2\.1\.1, a minor version increase, because the dependency from foo 1\.4\.5 required it\.
+.
+.P
+In case 2, only foo is requested to be unlocked, but bar is also allowed to move because it\'s not a declared dependency in the Gemfile\.
+.
+.P
+In case 3, bar goes up a whole major release, because a minor increase is preferred now for foo, and when it goes to 1\.5\.1, it requires 3\.0\.0 of bar\.
+.
+.P
+In case 4, foo is preferred up to a minor version, but 1\.5\.1 won\'t work because the \-\-strict flag removes bar 3\.0\.0 from consideration since it\'s a major increment\.
+.
+.P
+In case 5, both foo and bar have any minor or major increments removed from consideration because of the \-\-strict flag, so the most they can move is up to 1\.4\.4 and 2\.0\.4\.
+.
+.SH "RECOMMENDED WORKFLOW"
+In general, when working with an application managed with bundler, you should use the following workflow:
+.
+.IP "\(bu" 4
+After you create your Gemfile(5) for the first time, run
+.
+.IP
+$ bundle install
+.
+.IP "\(bu" 4
+Check the resulting \fBGemfile\.lock\fR into version control
+.
+.IP
+$ git add Gemfile\.lock
+.
+.IP "\(bu" 4
+When checking out this repository on another development machine, run
+.
+.IP
+$ bundle install
+.
+.IP "\(bu" 4
+When checking out this repository on a deployment machine, run
+.
+.IP
+$ bundle install \-\-deployment
+.
+.IP "\(bu" 4
+After changing the Gemfile(5) to reflect a new or update dependency, run
+.
+.IP
+$ bundle install
+.
+.IP "\(bu" 4
+Make sure to check the updated \fBGemfile\.lock\fR into version control
+.
+.IP
+$ git add Gemfile\.lock
+.
+.IP "\(bu" 4
+If bundle install(1) \fIbundle\-install\.1\.html\fR reports a conflict, manually update the specific gems that you changed in the Gemfile(5)
+.
+.IP
+$ bundle update rails thin
+.
+.IP "\(bu" 4
+If you want to update all the gems to the latest possible versions that still match the gems listed in the Gemfile(5), run
+.
+.IP
+$ bundle update \-\-all
+.
+.IP "" 0
+
diff --git a/man/bundle-update.1.txt b/man/bundle-update.1.txt
new file mode 100644
index 0000000000..d2ec46444b
--- /dev/null
+++ b/man/bundle-update.1.txt
@@ -0,0 +1,390 @@
+BUNDLE-UPDATE(1) BUNDLE-UPDATE(1)
+
+
+
+1mNAME0m
+ 1mbundle-update 22m- Update your gems to the latest available versions
+
+1mSYNOPSIS0m
+ 1mbundle update 4m22m*gems24m [--all] [--group=NAME] [--source=NAME] [--local]
+ [--ruby] [--bundler[=VERSION]] [--full-index] [--jobs=JOBS] [--quiet]
+ [--patch|--minor|--major] [--redownload] [--strict] [--conservative]
+
+1mDESCRIPTION0m
+ Update the gems specified (all gems, if 1m--all 22mflag is used), ignoring
+ the previously installed gems specified in the 1mGemfile.lock22m. In gen-
+ eral, you should use bundle install(1) 4mbundle-install.1.html24m to install
+ the same exact gems and versions across machines.
+
+ You would use 1mbundle update 22mto explicitly update the version of a gem.
+
+1mOPTIONS0m
+ 1m--all 22mUpdate all gems specified in Gemfile.
+
+ 1m--group=<name>22m, 1m-g=[<name>]0m
+ Only update the gems in the specified group. For instance, you
+ can update all gems in the development group with 1mbundle update0m
+ 1m--group development22m. You can also call 1mbundle update rails0m
+ 1m--group test 22mto update the rails gem and all gems in the test
+ group, for example.
+
+ 1m--source=<name>0m
+ The name of a 1m:git 22mor 1m:path 22msource used in the Gemfile(5). For
+ instance, with a 1m:git 22msource of
+ 1mhttp://github.com/rails/rails.git22m, you would call 1mbundle update0m
+ 1m--source rails0m
+
+ 1m--local0m
+ Do not attempt to fetch gems remotely and use the gem cache
+ instead.
+
+ 1m--ruby 22mUpdate the locked version of Ruby to the current version of
+ Ruby.
+
+ 1m--bundler0m
+ Update the locked version of bundler to the invoked bundler ver-
+ sion.
+
+ 1m--full-index0m
+ Fall back to using the single-file index of all gems.
+
+ 1m--jobs=[<number>]22m, 1m-j[<number>]0m
+ Specify the number of jobs to run in parallel. The default is 1m122m.
+
+ 1m--retry=[<number>]0m
+ Retry failed network or git requests for 4mnumber24m times.
+
+ 1m--quiet0m
+ Only output warnings and errors.
+
+ 1m--redownload0m
+ Force downloading every gem.
+
+ 1m--patch0m
+ Prefer updating only to next patch version.
+
+ 1m--minor0m
+ Prefer updating only to next minor version.
+
+ 1m--major0m
+ Prefer updating to next major version (default).
+
+ 1m--strict0m
+ Do not allow any gem to be updated past latest 1m--patch 22m| 1m--minor0m
+ | 1m--major22m.
+
+ 1m--conservative0m
+ Use bundle install conservative update behavior and do not allow
+ shared dependencies to be updated.
+
+1mUPDATING ALL GEMS0m
+ If you run 1mbundle update --all22m, bundler will ignore any previously
+ installed gems and resolve all dependencies again based on the latest
+ versions of all gems available in the sources.
+
+ Consider the following Gemfile(5):
+
+
+
+ source "https://rubygems.org"
+
+ gem "rails", "3.0.0.rc"
+ gem "nokogiri"
+
+
+
+ When you run bundle install(1) 4mbundle-install.1.html24m the first time,
+ bundler will resolve all of the dependencies, all the way down, and
+ install what you need:
+
+
+
+ Fetching gem metadata from https://rubygems.org/.........
+ Resolving dependencies...
+ Installing builder 2.1.2
+ Installing abstract 1.0.0
+ Installing rack 1.2.8
+ Using bundler 1.7.6
+ Installing rake 10.4.0
+ Installing polyglot 0.3.5
+ Installing mime-types 1.25.1
+ Installing i18n 0.4.2
+ Installing mini_portile 0.6.1
+ Installing tzinfo 0.3.42
+ Installing rack-mount 0.6.14
+ Installing rack-test 0.5.7
+ Installing treetop 1.4.15
+ Installing thor 0.14.6
+ Installing activesupport 3.0.0.rc
+ Installing erubis 2.6.6
+ Installing activemodel 3.0.0.rc
+ Installing arel 0.4.0
+ Installing mail 2.2.20
+ Installing activeresource 3.0.0.rc
+ Installing actionpack 3.0.0.rc
+ Installing activerecord 3.0.0.rc
+ Installing actionmailer 3.0.0.rc
+ Installing railties 3.0.0.rc
+ Installing rails 3.0.0.rc
+ Installing nokogiri 1.6.5
+
+ Bundle complete! 2 Gemfile dependencies, 26 gems total.
+ Use `bundle show [gemname]` to see where a bundled gem is installed.
+
+
+
+ As you can see, even though you have two gems in the Gemfile(5), your
+ application needs 26 different gems in order to run. Bundler remembers
+ the exact versions it installed in 1mGemfile.lock22m. The next time you run
+ bundle install(1) 4mbundle-install.1.html24m, bundler skips the dependency
+ resolution and installs the same gems as it installed last time.
+
+ After checking in the 1mGemfile.lock 22minto version control and cloning it
+ on another machine, running bundle install(1) 4mbundle-install.1.html0m
+ will 4mstill24m install the gems that you installed last time. You don't
+ need to worry that a new release of 1merubis 22mor 1mmail 22mchanges the gems you
+ use.
+
+ However, from time to time, you might want to update the gems you are
+ using to the newest versions that still match the gems in your Gem-
+ file(5).
+
+ To do this, run 1mbundle update --all22m, which will ignore the 1mGem-0m
+ 1mfile.lock22m, and resolve all the dependencies again. Keep in mind that
+ this process can result in a significantly different set of the 25
+ gems, based on the requirements of new gems that the gem authors
+ released since the last time you ran 1mbundle update --all22m.
+
+1mUPDATING A LIST OF GEMS0m
+ Sometimes, you want to update a single gem in the Gemfile(5), and leave
+ the rest of the gems that you specified locked to the versions in the
+ 1mGemfile.lock22m.
+
+ For instance, in the scenario above, imagine that 1mnokogiri 22mreleases
+ version 1m1.4.422m, and you want to update it 4mwithout24m updating Rails and all
+ of its dependencies. To do this, run 1mbundle update nokogiri22m.
+
+ Bundler will update 1mnokogiri 22mand any of its dependencies, but leave
+ alone Rails and its dependencies.
+
+1mOVERLAPPING DEPENDENCIES0m
+ Sometimes, multiple gems declared in your Gemfile(5) are satisfied by
+ the same second-level dependency. For instance, consider the case of
+ 1mthin 22mand 1mrack-perftools-profiler22m.
+
+
+
+ source "https://rubygems.org"
+
+ gem "thin"
+ gem "rack-perftools-profiler"
+
+
+
+ The 1mthin 22mgem depends on 1mrack >= 1.022m, while 1mrack-perftools-profiler0m
+ depends on 1mrack ~> 1.022m. If you run bundle install, you get:
+
+
+
+ Fetching source index for https://rubygems.org/
+ Installing daemons (1.1.0)
+ Installing eventmachine (0.12.10) with native extensions
+ Installing open4 (1.0.1)
+ Installing perftools.rb (0.4.7) with native extensions
+ Installing rack (1.2.1)
+ Installing rack-perftools_profiler (0.0.2)
+ Installing thin (1.2.7) with native extensions
+ Using bundler (1.0.0.rc.3)
+
+
+
+ In this case, the two gems have their own set of dependencies, but they
+ share 1mrack 22min common. If you run 1mbundle update thin22m, bundler will
+ update 1mdaemons22m, 1meventmachine 22mand 1mrack22m, which are dependencies of 1mthin22m,
+ but not 1mopen4 22mor 1mperftools.rb22m, which are dependencies of
+ 1mrack-perftools_profiler22m. Note that 1mbundle update thin 22mwill update 1mrack0m
+ even though it's 4malso24m a dependency of 1mrack-perftools_profiler22m.
+
+ In short, by default, when you update a gem using 1mbundle update22m,
+ bundler will update all dependencies of that gem, including those that
+ are also dependencies of another gem.
+
+ To prevent updating shared dependencies, prior to version 1.14 the only
+ option was the 1mCONSERVATIVE UPDATING 22mbehavior in bundle install(1) 4mbun-0m
+ 4mdle-install.1.html24m:
+
+ In this scenario, updating the 1mthin 22mversion manually in the Gemfile(5),
+ and then running bundle install(1) 4mbundle-install.1.html24m will only
+ update 1mdaemons 22mand 1meventmachine22m, but not 1mrack22m. For more information,
+ see the 1mCONSERVATIVE UPDATING 22msection of bundle install(1) 4mbun-0m
+ 4mdle-install.1.html24m.
+
+ Starting with 1.14, specifying the 1m--conservative 22moption will also pre-
+ vent shared dependencies from being updated.
+
+1mPATCH LEVEL OPTIONS0m
+ Version 1.14 introduced 4 patch-level options that will influence how
+ gem versions are resolved. One of the following options can be used:
+ 1m--patch22m, 1m--minor 22mor 1m--major22m. 1m--strict 22mcan be added to further influence
+ resolution.
+
+ 1m--patch0m
+ Prefer updating only to next patch version.
+
+ 1m--minor0m
+ Prefer updating only to next minor version.
+
+ 1m--major0m
+ Prefer updating to next major version (default).
+
+ 1m--strict0m
+ Do not allow any gem to be updated past latest 1m--patch 22m| 1m--minor0m
+ | 1m--major22m.
+
+ When Bundler is resolving what versions to use to satisfy declared
+ requirements in the Gemfile or in parent gems, it looks up all avail-
+ able versions, filters out any versions that don't satisfy the require-
+ ment, and then, by default, sorts them from newest to oldest, consider-
+ ing them in that order.
+
+ Providing one of the patch level options (e.g. 1m--patch22m) changes the
+ sort order of the satisfying versions, causing Bundler to consider the
+ latest 1m--patch 22mor 1m--minor 22mversion available before other versions. Note
+ that versions outside the stated patch level could still be resolved to
+ if necessary to find a suitable dependency graph.
+
+ For example, if gem 'foo' is locked at 1.0.2, with no gem requirement
+ defined in the Gemfile, and versions 1.0.3, 1.0.4, 1.1.0, 1.1.1, 2.0.0
+ all exist, the default order of preference by default (1m--major22m) will be
+ "2.0.0, 1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2".
+
+ If the 1m--patch 22moption is used, the order of preference will change to
+ "1.0.4, 1.0.3, 1.0.2, 1.1.1, 1.1.0, 2.0.0".
+
+ If the 1m--minor 22moption is used, the order of preference will change to
+ "1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2, 2.0.0".
+
+ Combining the 1m--strict 22moption with any of the patch level options will
+ remove any versions beyond the scope of the patch level option, to
+ ensure that no gem is updated that far.
+
+ To continue the previous example, if both 1m--patch 22mand 1m--strict 22moptions
+ are used, the available versions for resolution would be "1.0.4, 1.0.3,
+ 1.0.2". If 1m--minor 22mand 1m--strict 22mare used, it would be "1.1.1, 1.1.0,
+ 1.0.4, 1.0.3, 1.0.2".
+
+ Gem requirements as defined in the Gemfile will still be the first
+ determining factor for what versions are available. If the gem require-
+ ment for 1mfoo 22min the Gemfile is '~> 1.0', that will accomplish the same
+ thing as providing the 1m--minor 22mand 1m--strict 22moptions.
+
+1mPATCH LEVEL EXAMPLES0m
+ Given the following gem specifications:
+
+
+
+ foo 1.4.3, requires: ~> bar 2.0
+ foo 1.4.4, requires: ~> bar 2.0
+ foo 1.4.5, requires: ~> bar 2.1
+ foo 1.5.0, requires: ~> bar 2.1
+ foo 1.5.1, requires: ~> bar 3.0
+ bar with versions 2.0.3, 2.0.4, 2.1.0, 2.1.1, 3.0.0
+
+
+
+ Gemfile:
+
+
+
+ gem 'foo'
+
+
+
+ Gemfile.lock:
+
+
+
+ foo (1.4.3)
+ bar (~> 2.0)
+ bar (2.0.3)
+
+
+
+ Cases:
+
+
+
+ # Command Line Result
+ ------------------------------------------------------------
+ 1 bundle update --patch 'foo 1.4.5', 'bar 2.1.1'
+ 2 bundle update --patch foo 'foo 1.4.5', 'bar 2.1.1'
+ 3 bundle update --minor 'foo 1.5.1', 'bar 3.0.0'
+ 4 bundle update --minor --strict 'foo 1.5.0', 'bar 2.1.1'
+ 5 bundle update --patch --strict 'foo 1.4.4', 'bar 2.0.4'
+
+
+
+ In case 1, bar is upgraded to 2.1.1, a minor version increase, because
+ the dependency from foo 1.4.5 required it.
+
+ In case 2, only foo is requested to be unlocked, but bar is also
+ allowed to move because it's not a declared dependency in the Gemfile.
+
+ In case 3, bar goes up a whole major release, because a minor increase
+ is preferred now for foo, and when it goes to 1.5.1, it requires 3.0.0
+ of bar.
+
+ In case 4, foo is preferred up to a minor version, but 1.5.1 won't work
+ because the --strict flag removes bar 3.0.0 from consideration since
+ it's a major increment.
+
+ In case 5, both foo and bar have any minor or major increments removed
+ from consideration because of the --strict flag, so the most they can
+ move is up to 1.4.4 and 2.0.4.
+
+1mRECOMMENDED WORKFLOW0m
+ In general, when working with an application managed with bundler, you
+ should use the following workflow:
+
+ o After you create your Gemfile(5) for the first time, run
+
+ $ bundle install
+
+ o Check the resulting 1mGemfile.lock 22minto version control
+
+ $ git add Gemfile.lock
+
+ o When checking out this repository on another development machine,
+ run
+
+ $ bundle install
+
+ o When checking out this repository on a deployment machine, run
+
+ $ bundle install --deployment
+
+ o After changing the Gemfile(5) to reflect a new or update depen-
+ dency, run
+
+ $ bundle install
+
+ o Make sure to check the updated 1mGemfile.lock 22minto version control
+
+ $ git add Gemfile.lock
+
+ o If bundle install(1) 4mbundle-install.1.html24m reports a conflict, man-
+ ually update the specific gems that you changed in the Gemfile(5)
+
+ $ bundle update rails thin
+
+ o If you want to update all the gems to the latest possible versions
+ that still match the gems listed in the Gemfile(5), run
+
+ $ bundle update --all
+
+
+
+
+
+
+ October 2018 BUNDLE-UPDATE(1)
diff --git a/man/bundle-update.ronn b/man/bundle-update.ronn
new file mode 100644
index 0000000000..481bb5b14e
--- /dev/null
+++ b/man/bundle-update.ronn
@@ -0,0 +1,350 @@
+bundle-update(1) -- Update your gems to the latest available versions
+=====================================================================
+
+## SYNOPSIS
+
+`bundle update` <*gems> [--all]
+ [--group=NAME]
+ [--source=NAME]
+ [--local]
+ [--ruby]
+ [--bundler[=VERSION]]
+ [--full-index]
+ [--jobs=JOBS]
+ [--quiet]
+ [--force]
+ [--patch|--minor|--major]
+ [--strict]
+ [--conservative]
+
+## DESCRIPTION
+
+Update the gems specified (all gems, if `--all` flag is used), ignoring
+the previously installed gems specified in the `Gemfile.lock`. In
+general, you should use [bundle install(1)](bundle-install.1.html) to install the same exact
+gems and versions across machines.
+
+You would use `bundle update` to explicitly update the version of a
+gem.
+
+## OPTIONS
+
+* `--all`:
+ Update all gems specified in Gemfile.
+
+* `--group=<name>`, `-g=[<name>]`:
+ Only update the gems in the specified group. For instance, you can update all gems
+ in the development group with `bundle update --group development`. You can also
+ call `bundle update rails --group test` to update the rails gem and all gems in
+ the test group, for example.
+
+* `--source=<name>`:
+ The name of a `:git` or `:path` source used in the Gemfile(5). For
+ instance, with a `:git` source of `http://github.com/rails/rails.git`,
+ you would call `bundle update --source rails`
+
+* `--local`:
+ Do not attempt to fetch gems remotely and use the gem cache instead.
+
+* `--ruby`:
+ Update the locked version of Ruby to the current version of Ruby.
+
+* `--bundler`:
+ Update the locked version of bundler to the invoked bundler version.
+
+* `--full-index`:
+ Fall back to using the single-file index of all gems.
+
+* `--jobs=[<number>]`, `-j[<number>]`:
+ Specify the number of jobs to run in parallel. The default is `1`.
+
+* `--retry=[<number>]`:
+ Retry failed network or git requests for <number> times.
+
+* `--quiet`:
+ Only output warnings and errors.
+
+* `--force`:
+ Force downloading every gem. `--redownload` is an alias of this option.
+
+* `--patch`:
+ Prefer updating only to next patch version.
+
+* `--minor`:
+ Prefer updating only to next minor version.
+
+* `--major`:
+ Prefer updating to next major version (default).
+
+* `--strict`:
+ Do not allow any gem to be updated past latest `--patch` | `--minor` | `--major`.
+
+* `--conservative`:
+ Use bundle install conservative update behavior and do not allow shared dependencies to be updated.
+
+## UPDATING ALL GEMS
+
+If you run `bundle update --all`, bundler will ignore
+any previously installed gems and resolve all dependencies again
+based on the latest versions of all gems available in the sources.
+
+Consider the following Gemfile(5):
+
+ source "https://rubygems.org"
+
+ gem "rails", "3.0.0.rc"
+ gem "nokogiri"
+
+When you run [bundle install(1)](bundle-install.1.html) the first time, bundler will resolve
+all of the dependencies, all the way down, and install what you need:
+
+ Fetching gem metadata from https://rubygems.org/.........
+ Resolving dependencies...
+ Installing builder 2.1.2
+ Installing abstract 1.0.0
+ Installing rack 1.2.8
+ Using bundler 1.7.6
+ Installing rake 10.4.0
+ Installing polyglot 0.3.5
+ Installing mime-types 1.25.1
+ Installing i18n 0.4.2
+ Installing mini_portile 0.6.1
+ Installing tzinfo 0.3.42
+ Installing rack-mount 0.6.14
+ Installing rack-test 0.5.7
+ Installing treetop 1.4.15
+ Installing thor 0.14.6
+ Installing activesupport 3.0.0.rc
+ Installing erubis 2.6.6
+ Installing activemodel 3.0.0.rc
+ Installing arel 0.4.0
+ Installing mail 2.2.20
+ Installing activeresource 3.0.0.rc
+ Installing actionpack 3.0.0.rc
+ Installing activerecord 3.0.0.rc
+ Installing actionmailer 3.0.0.rc
+ Installing railties 3.0.0.rc
+ Installing rails 3.0.0.rc
+ Installing nokogiri 1.6.5
+
+ Bundle complete! 2 Gemfile dependencies, 26 gems total.
+ Use `bundle show [gemname]` to see where a bundled gem is installed.
+
+As you can see, even though you have two gems in the Gemfile(5), your application
+needs 26 different gems in order to run. Bundler remembers the exact versions
+it installed in `Gemfile.lock`. The next time you run [bundle install(1)](bundle-install.1.html), bundler skips
+the dependency resolution and installs the same gems as it installed last time.
+
+After checking in the `Gemfile.lock` into version control and cloning it on another
+machine, running [bundle install(1)](bundle-install.1.html) will _still_ install the gems that you installed
+last time. You don't need to worry that a new release of `erubis` or `mail` changes
+the gems you use.
+
+However, from time to time, you might want to update the gems you are using to the
+newest versions that still match the gems in your Gemfile(5).
+
+To do this, run `bundle update --all`, which will ignore the `Gemfile.lock`, and resolve
+all the dependencies again. Keep in mind that this process can result in a significantly
+different set of the 25 gems, based on the requirements of new gems that the gem
+authors released since the last time you ran `bundle update --all`.
+
+## UPDATING A LIST OF GEMS
+
+Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of the
+gems that you specified locked to the versions in the `Gemfile.lock`.
+
+For instance, in the scenario above, imagine that `nokogiri` releases version `1.4.4`, and
+you want to update it _without_ updating Rails and all of its dependencies. To do this,
+run `bundle update nokogiri`.
+
+Bundler will update `nokogiri` and any of its dependencies, but leave alone Rails and
+its dependencies.
+
+## OVERLAPPING DEPENDENCIES
+
+Sometimes, multiple gems declared in your Gemfile(5) are satisfied by the same
+second-level dependency. For instance, consider the case of `thin` and
+`rack-perftools-profiler`.
+
+ source "https://rubygems.org"
+
+ gem "thin"
+ gem "rack-perftools-profiler"
+
+The `thin` gem depends on `rack >= 1.0`, while `rack-perftools-profiler` depends
+on `rack ~> 1.0`. If you run bundle install, you get:
+
+ Fetching source index for https://rubygems.org/
+ Installing daemons (1.1.0)
+ Installing eventmachine (0.12.10) with native extensions
+ Installing open4 (1.0.1)
+ Installing perftools.rb (0.4.7) with native extensions
+ Installing rack (1.2.1)
+ Installing rack-perftools_profiler (0.0.2)
+ Installing thin (1.2.7) with native extensions
+ Using bundler (1.0.0.rc.3)
+
+In this case, the two gems have their own set of dependencies, but they share
+`rack` in common. If you run `bundle update thin`, bundler will update `daemons`,
+`eventmachine` and `rack`, which are dependencies of `thin`, but not `open4` or
+`perftools.rb`, which are dependencies of `rack-perftools_profiler`. Note that
+`bundle update thin` will update `rack` even though it's _also_ a dependency of
+`rack-perftools_profiler`.
+
+In short, by default, when you update a gem using `bundle update`, bundler will
+update all dependencies of that gem, including those that are also dependencies
+of another gem.
+
+To prevent updating shared dependencies, prior to version 1.14 the only option
+was the `CONSERVATIVE UPDATING` behavior in [bundle install(1)](bundle-install.1.html):
+
+In this scenario, updating the `thin` version manually in the Gemfile(5),
+and then running [bundle install(1)](bundle-install.1.html) will only update `daemons` and `eventmachine`,
+but not `rack`. For more information, see the `CONSERVATIVE UPDATING` section
+of [bundle install(1)](bundle-install.1.html).
+
+Starting with 1.14, specifying the `--conservative` option will also prevent shared
+dependencies from being updated.
+
+## PATCH LEVEL OPTIONS
+
+Version 1.14 introduced 4 patch-level options that will influence how gem
+versions are resolved. One of the following options can be used: `--patch`,
+`--minor` or `--major`. `--strict` can be added to further influence resolution.
+
+* `--patch`:
+ Prefer updating only to next patch version.
+
+* `--minor`:
+ Prefer updating only to next minor version.
+
+* `--major`:
+ Prefer updating to next major version (default).
+
+* `--strict`:
+ Do not allow any gem to be updated past latest `--patch` | `--minor` | `--major`.
+
+When Bundler is resolving what versions to use to satisfy declared
+requirements in the Gemfile or in parent gems, it looks up all
+available versions, filters out any versions that don't satisfy
+the requirement, and then, by default, sorts them from newest to
+oldest, considering them in that order.
+
+Providing one of the patch level options (e.g. `--patch`) changes the
+sort order of the satisfying versions, causing Bundler to consider the
+latest `--patch` or `--minor` version available before other versions.
+Note that versions outside the stated patch level could still be
+resolved to if necessary to find a suitable dependency graph.
+
+For example, if gem 'foo' is locked at 1.0.2, with no gem requirement
+defined in the Gemfile, and versions 1.0.3, 1.0.4, 1.1.0, 1.1.1, 2.0.0
+all exist, the default order of preference by default (`--major`) will
+be "2.0.0, 1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2".
+
+If the `--patch` option is used, the order of preference will change to
+"1.0.4, 1.0.3, 1.0.2, 1.1.1, 1.1.0, 2.0.0".
+
+If the `--minor` option is used, the order of preference will change to
+"1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2, 2.0.0".
+
+Combining the `--strict` option with any of the patch level options
+will remove any versions beyond the scope of the patch level option,
+to ensure that no gem is updated that far.
+
+To continue the previous example, if both `--patch` and `--strict`
+options are used, the available versions for resolution would be
+"1.0.4, 1.0.3, 1.0.2". If `--minor` and `--strict` are used, it would
+be "1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2".
+
+Gem requirements as defined in the Gemfile will still be the first
+determining factor for what versions are available. If the gem
+requirement for `foo` in the Gemfile is '~> 1.0', that will accomplish
+the same thing as providing the `--minor` and `--strict` options.
+
+## PATCH LEVEL EXAMPLES
+
+Given the following gem specifications:
+
+ foo 1.4.3, requires: ~> bar 2.0
+ foo 1.4.4, requires: ~> bar 2.0
+ foo 1.4.5, requires: ~> bar 2.1
+ foo 1.5.0, requires: ~> bar 2.1
+ foo 1.5.1, requires: ~> bar 3.0
+ bar with versions 2.0.3, 2.0.4, 2.1.0, 2.1.1, 3.0.0
+
+Gemfile:
+
+ gem 'foo'
+
+Gemfile.lock:
+
+ foo (1.4.3)
+ bar (~> 2.0)
+ bar (2.0.3)
+
+Cases:
+
+ # Command Line Result
+ ------------------------------------------------------------
+ 1 bundle update --patch 'foo 1.4.5', 'bar 2.1.1'
+ 2 bundle update --patch foo 'foo 1.4.5', 'bar 2.1.1'
+ 3 bundle update --minor 'foo 1.5.1', 'bar 3.0.0'
+ 4 bundle update --minor --strict 'foo 1.5.0', 'bar 2.1.1'
+ 5 bundle update --patch --strict 'foo 1.4.4', 'bar 2.0.4'
+
+In case 1, bar is upgraded to 2.1.1, a minor version increase, because
+the dependency from foo 1.4.5 required it.
+
+In case 2, only foo is requested to be unlocked, but bar is also
+allowed to move because it's not a declared dependency in the Gemfile.
+
+In case 3, bar goes up a whole major release, because a minor increase
+is preferred now for foo, and when it goes to 1.5.1, it requires 3.0.0
+of bar.
+
+In case 4, foo is preferred up to a minor version, but 1.5.1 won't work
+because the --strict flag removes bar 3.0.0 from consideration since
+it's a major increment.
+
+In case 5, both foo and bar have any minor or major increments removed
+from consideration because of the --strict flag, so the most they can
+move is up to 1.4.4 and 2.0.4.
+
+## RECOMMENDED WORKFLOW
+
+In general, when working with an application managed with bundler, you should
+use the following workflow:
+
+* After you create your Gemfile(5) for the first time, run
+
+ $ bundle install
+
+* Check the resulting `Gemfile.lock` into version control
+
+ $ git add Gemfile.lock
+
+* When checking out this repository on another development machine, run
+
+ $ bundle install
+
+* When checking out this repository on a deployment machine, run
+
+ $ bundle install --deployment
+
+* After changing the Gemfile(5) to reflect a new or update dependency, run
+
+ $ bundle install
+
+* Make sure to check the updated `Gemfile.lock` into version control
+
+ $ git add Gemfile.lock
+
+* If [bundle install(1)](bundle-install.1.html) reports a conflict, manually update the specific
+ gems that you changed in the Gemfile(5)
+
+ $ bundle update rails thin
+
+* If you want to update all the gems to the latest possible versions that
+ still match the gems listed in the Gemfile(5), run
+
+ $ bundle update --all
diff --git a/man/bundle-viz.1 b/man/bundle-viz.1
new file mode 100644
index 0000000000..662c91991e
--- /dev/null
+++ b/man/bundle-viz.1
@@ -0,0 +1,39 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE\-VIZ" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile
+.
+.SH "SYNOPSIS"
+\fBbundle viz\fR [\-\-file=FILE] [\-\-format=FORMAT] [\-\-requirements] [\-\-version] [\-\-without=GROUP GROUP]
+.
+.SH "DESCRIPTION"
+\fBviz\fR generates a PNG file of the current \fBGemfile(5)\fR as a dependency graph\. \fBviz\fR requires the ruby\-graphviz gem (and its dependencies)\.
+.
+.P
+The associated gems must also be installed via \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-file\fR, \fB\-f\fR
+The name to use for the generated file\. See \fB\-\-format\fR option
+.
+.TP
+\fB\-\-format\fR, \fB\-F\fR
+This is output format option\. Supported format is png, jpg, svg, dot \.\.\.
+.
+.TP
+\fB\-\-requirements\fR, \fB\-R\fR
+Set to show the version of each required dependency\.
+.
+.TP
+\fB\-\-version\fR, \fB\-v\fR
+Set to show each gem version\.
+.
+.TP
+\fB\-\-without\fR, \fB\-W\fR
+Exclude gems that are part of the specified named group\.
+
diff --git a/man/bundle-viz.1.txt b/man/bundle-viz.1.txt
new file mode 100644
index 0000000000..30a7d68d09
--- /dev/null
+++ b/man/bundle-viz.1.txt
@@ -0,0 +1,39 @@
+BUNDLE-VIZ(1) BUNDLE-VIZ(1)
+
+
+
+1mNAME0m
+ 1mbundle-viz 22m- Generates a visual dependency graph for your Gemfile
+
+1mSYNOPSIS0m
+ 1mbundle viz 22m[--file=FILE] [--format=FORMAT] [--requirements] [--version]
+ [--without=GROUP GROUP]
+
+1mDESCRIPTION0m
+ 1mviz 22mgenerates a PNG file of the current 1mGemfile(5) 22mas a dependency
+ graph. 1mviz 22mrequires the ruby-graphviz gem (and its dependencies).
+
+ The associated gems must also be installed via 1mbundle install(1) 4m22mbun-0m
+ 4mdle-install.1.html24m.
+
+1mOPTIONS0m
+ 1m--file22m, 1m-f0m
+ The name to use for the generated file. See 1m--format 22moption
+
+ 1m--format22m, 1m-F0m
+ This is output format option. Supported format is png, jpg, svg,
+ dot ...
+
+ 1m--requirements22m, 1m-R0m
+ Set to show the version of each required dependency.
+
+ 1m--version22m, 1m-v0m
+ Set to show each gem version.
+
+ 1m--without22m, 1m-W0m
+ Exclude gems that are part of the specified named group.
+
+
+
+
+ October 2018 BUNDLE-VIZ(1)
diff --git a/man/bundle-viz.ronn b/man/bundle-viz.ronn
new file mode 100644
index 0000000000..701df5415e
--- /dev/null
+++ b/man/bundle-viz.ronn
@@ -0,0 +1,30 @@
+bundle-viz(1) -- Generates a visual dependency graph for your Gemfile
+=====================================================================
+
+## SYNOPSIS
+
+`bundle viz` [--file=FILE]
+ [--format=FORMAT]
+ [--requirements]
+ [--version]
+ [--without=GROUP GROUP]
+
+## DESCRIPTION
+
+`viz` generates a PNG file of the current `Gemfile(5)` as a dependency graph.
+`viz` requires the ruby-graphviz gem (and its dependencies).
+
+The associated gems must also be installed via [`bundle install(1)`](bundle-install.1.html).
+
+## OPTIONS
+
+* `--file`, `-f`:
+ The name to use for the generated file. See `--format` option
+* `--format`, `-F`:
+ This is output format option. Supported format is png, jpg, svg, dot ...
+* `--requirements`, `-R`:
+ Set to show the version of each required dependency.
+* `--version`, `-v`:
+ Set to show each gem version.
+* `--without`, `-W`:
+ Exclude gems that are part of the specified named group.
diff --git a/man/bundle.1 b/man/bundle.1
new file mode 100644
index 0000000000..341c6ac71b
--- /dev/null
+++ b/man/bundle.1
@@ -0,0 +1,132 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "BUNDLE" "1" "October 2018" "" ""
+.
+.SH "NAME"
+\fBbundle\fR \- Ruby Dependency Management
+.
+.SH "SYNOPSIS"
+\fBbundle\fR COMMAND [\-\-no\-color] [\-\-verbose] [ARGS]
+.
+.SH "DESCRIPTION"
+Bundler manages an \fBapplication\'s dependencies\fR through its entire life across many machines systematically and repeatably\.
+.
+.P
+See the bundler website \fIhttp://bundler\.io\fR for information on getting started, and Gemfile(5) for more information on the \fBGemfile\fR format\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-\-no\-color\fR
+Print all output without color
+.
+.TP
+\fB\-\-retry\fR, \fB\-r\fR
+Specify the number of times you wish to attempt network commands
+.
+.TP
+\fB\-\-verbose\fR, \fB\-V\fR
+Print out additional logging information
+.
+.SH "BUNDLE COMMANDS"
+We divide \fBbundle\fR subcommands into primary commands and utilities:
+.
+.SH "PRIMARY COMMANDS"
+.
+.TP
+\fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR
+Install the gems specified by the \fBGemfile\fR or \fBGemfile\.lock\fR
+.
+.TP
+\fBbundle update(1)\fR \fIbundle\-update\.1\.html\fR
+Update dependencies to their latest versions
+.
+.TP
+\fBbundle package(1)\fR \fIbundle\-package\.1\.html\fR
+Package the \.gem files required by your application into the \fBvendor/cache\fR directory
+.
+.TP
+\fBbundle exec(1)\fR \fIbundle\-exec\.1\.html\fR
+Execute a script in the current bundle
+.
+.TP
+\fBbundle config(1)\fR \fIbundle\-config\.1\.html\fR
+Specify and read configuration options for Bundler
+.
+.TP
+\fBbundle help(1)\fR
+Display detailed help for each subcommand
+.
+.SH "UTILITIES"
+.
+.TP
+\fBbundle add(1)\fR \fIbundle\-add\.1\.html\fR
+Add the named gem to the Gemfile and run \fBbundle install\fR
+.
+.TP
+\fBbundle binstubs(1)\fR \fIbundle\-binstubs\.1\.html\fR
+Generate binstubs for executables in a gem
+.
+.TP
+\fBbundle check(1)\fR \fIbundle\-check\.1\.html\fR
+Determine whether the requirements for your application are installed and available to Bundler
+.
+.TP
+\fBbundle show(1)\fR \fIbundle\-show\.1\.html\fR
+Show the source location of a particular gem in the bundle
+.
+.TP
+\fBbundle outdated(1)\fR \fIbundle\-outdated\.1\.html\fR
+Show all of the outdated gems in the current bundle
+.
+.TP
+\fBbundle console(1)\fR
+Start an IRB session in the current bundle
+.
+.TP
+\fBbundle open(1)\fR \fIbundle\-open\.1\.html\fR
+Open an installed gem in the editor
+.
+.TP
+\fBbundle lock(1)\fR \fIbundle\-lock\.1\.hmtl\fR
+Generate a lockfile for your dependencies
+.
+.TP
+\fBbundle viz(1)\fR \fIbundle\-viz\.1\.html\fR
+Generate a visual representation of your dependencies
+.
+.TP
+\fBbundle init(1)\fR \fIbundle\-init\.1\.html\fR
+Generate a simple \fBGemfile\fR, placed in the current directory
+.
+.TP
+\fBbundle gem(1)\fR \fIbundle\-gem\.1\.html\fR
+Create a simple gem, suitable for development with Bundler
+.
+.TP
+\fBbundle platform(1)\fR \fIbundle\-platform\.1\.html\fR
+Display platform compatibility information
+.
+.TP
+\fBbundle clean(1)\fR \fIbundle\-clean\.1\.html\fR
+Clean up unused gems in your Bundler directory
+.
+.TP
+\fBbundle doctor(1)\fR \fIbundle\-doctor\.1\.html\fR
+Display warnings about common problems
+.
+.SH "PLUGINS"
+When running a command that isn\'t listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named \fBbundler\-<command>\fR and execute it, passing down any extra arguments to it\.
+.
+.SH "OBSOLETE"
+These commands are obsolete and should no longer be used:
+.
+.IP "\(bu" 4
+\fBbundle cache(1)\fR
+.
+.IP "\(bu" 4
+\fBbundle show(1)\fR
+.
+.IP "" 0
+
diff --git a/man/bundle.1.txt b/man/bundle.1.txt
new file mode 100644
index 0000000000..cf03adcf82
--- /dev/null
+++ b/man/bundle.1.txt
@@ -0,0 +1,113 @@
+BUNDLE(1) BUNDLE(1)
+
+
+
+1mNAME0m
+ 1mbundle 22m- Ruby Dependency Management
+
+1mSYNOPSIS0m
+ 1mbundle 22mCOMMAND [--no-color] [--verbose] [ARGS]
+
+1mDESCRIPTION0m
+ Bundler manages an 1mapplication's dependencies 22mthrough its entire life
+ across many machines systematically and repeatably.
+
+ See the bundler website 4mhttp://bundler.io24m for information on getting
+ started, and Gemfile(5) for more information on the 1mGemfile 22mformat.
+
+1mOPTIONS0m
+ 1m--no-color0m
+ Print all output without color
+
+ 1m--retry22m, 1m-r0m
+ Specify the number of times you wish to attempt network commands
+
+ 1m--verbose22m, 1m-V0m
+ Print out additional logging information
+
+1mBUNDLE COMMANDS0m
+ We divide 1mbundle 22msubcommands into primary commands and utilities:
+
+1mPRIMARY COMMANDS0m
+ 1mbundle install(1) 4m22mbundle-install.1.html0m
+ Install the gems specified by the 1mGemfile 22mor 1mGemfile.lock0m
+
+ 1mbundle update(1) 4m22mbundle-update.1.html0m
+ Update dependencies to their latest versions
+
+ 1mbundle package(1) 4m22mbundle-package.1.html0m
+ Package the .gem files required by your application into the
+ 1mvendor/cache 22mdirectory
+
+ 1mbundle exec(1) 4m22mbundle-exec.1.html0m
+ Execute a script in the current bundle
+
+ 1mbundle config(1) 4m22mbundle-config.1.html0m
+ Specify and read configuration options for Bundler
+
+ 1mbundle help(1)0m
+ Display detailed help for each subcommand
+
+1mUTILITIES0m
+ 1mbundle add(1) 4m22mbundle-add.1.html0m
+ Add the named gem to the Gemfile and run 1mbundle install0m
+
+ 1mbundle binstubs(1) 4m22mbundle-binstubs.1.html0m
+ Generate binstubs for executables in a gem
+
+ 1mbundle check(1) 4m22mbundle-check.1.html0m
+ Determine whether the requirements for your application are
+ installed and available to Bundler
+
+ 1mbundle show(1) 4m22mbundle-show.1.html0m
+ Show the source location of a particular gem in the bundle
+
+ 1mbundle outdated(1) 4m22mbundle-outdated.1.html0m
+ Show all of the outdated gems in the current bundle
+
+ 1mbundle console(1)0m
+ Start an IRB session in the current bundle
+
+ 1mbundle open(1) 4m22mbundle-open.1.html0m
+ Open an installed gem in the editor
+
+ 1mbundle lock(1) 4m22mbundle-lock.1.hmtl0m
+ Generate a lockfile for your dependencies
+
+ 1mbundle viz(1) 4m22mbundle-viz.1.html0m
+ Generate a visual representation of your dependencies
+
+ 1mbundle init(1) 4m22mbundle-init.1.html0m
+ Generate a simple 1mGemfile22m, placed in the current directory
+
+ 1mbundle gem(1) 4m22mbundle-gem.1.html0m
+ Create a simple gem, suitable for development with Bundler
+
+ 1mbundle platform(1) 4m22mbundle-platform.1.html0m
+ Display platform compatibility information
+
+ 1mbundle clean(1) 4m22mbundle-clean.1.html0m
+ Clean up unused gems in your Bundler directory
+
+ 1mbundle doctor(1) 4m22mbundle-doctor.1.html0m
+ Display warnings about common problems
+
+1mPLUGINS0m
+ When running a command that isn't listed in PRIMARY COMMANDS or UTILI-
+ TIES, Bundler will try to find an executable on your path named
+ 1mbundler-<command> 22mand execute it, passing down any extra arguments to
+ it.
+
+1mOBSOLETE0m
+ These commands are obsolete and should no longer be used:
+
+ o 1mbundle cache(1)0m
+
+ o 1mbundle show(1)0m
+
+
+
+
+
+
+ October 2018 BUNDLE(1)
diff --git a/man/bundle.ronn b/man/bundle.ronn
new file mode 100644
index 0000000000..c03201a30c
--- /dev/null
+++ b/man/bundle.ronn
@@ -0,0 +1,108 @@
+bundle(1) -- Ruby Dependency Management
+=======================================
+
+## SYNOPSIS
+
+`bundle` COMMAND [--no-color] [--verbose] [ARGS]
+
+## DESCRIPTION
+
+Bundler manages an `application's dependencies` through its entire life
+across many machines systematically and repeatably.
+
+See [the bundler website](http://bundler.io) for information on getting
+started, and Gemfile(5) for more information on the `Gemfile` format.
+
+## OPTIONS
+
+* `--no-color`:
+ Print all output without color
+
+* `--retry`, `-r`:
+ Specify the number of times you wish to attempt network commands
+
+* `--verbose`, `-V`:
+ Print out additional logging information
+
+## BUNDLE COMMANDS
+
+We divide `bundle` subcommands into primary commands and utilities:
+
+## PRIMARY COMMANDS
+
+* [`bundle install(1)`](bundle-install.1.html):
+ Install the gems specified by the `Gemfile` or `Gemfile.lock`
+
+* [`bundle update(1)`](bundle-update.1.html):
+ Update dependencies to their latest versions
+
+* [`bundle package(1)`](bundle-package.1.html):
+ Package the .gem files required by your application into the
+ `vendor/cache` directory
+
+* [`bundle exec(1)`](bundle-exec.1.html):
+ Execute a script in the current bundle
+
+* [`bundle config(1)`](bundle-config.1.html):
+ Specify and read configuration options for Bundler
+
+* `bundle help(1)`:
+ Display detailed help for each subcommand
+
+## UTILITIES
+
+* [`bundle add(1)`](bundle-add.1.html):
+ Add the named gem to the Gemfile and run `bundle install`
+
+* [`bundle binstubs(1)`](bundle-binstubs.1.html):
+ Generate binstubs for executables in a gem
+
+* [`bundle check(1)`](bundle-check.1.html):
+ Determine whether the requirements for your application are installed
+ and available to Bundler
+
+* [`bundle show(1)`](bundle-show.1.html):
+ Show the source location of a particular gem in the bundle
+
+* [`bundle outdated(1)`](bundle-outdated.1.html):
+ Show all of the outdated gems in the current bundle
+
+* `bundle console(1)`:
+ Start an IRB session in the current bundle
+
+* [`bundle open(1)`](bundle-open.1.html):
+ Open an installed gem in the editor
+
+* [`bundle lock(1)`](bundle-lock.1.hmtl):
+ Generate a lockfile for your dependencies
+
+* [`bundle viz(1)`](bundle-viz.1.html):
+ Generate a visual representation of your dependencies
+
+* [`bundle init(1)`](bundle-init.1.html):
+ Generate a simple `Gemfile`, placed in the current directory
+
+* [`bundle gem(1)`](bundle-gem.1.html):
+ Create a simple gem, suitable for development with Bundler
+
+* [`bundle platform(1)`](bundle-platform.1.html):
+ Display platform compatibility information
+
+* [`bundle clean(1)`](bundle-clean.1.html):
+ Clean up unused gems in your Bundler directory
+
+* [`bundle doctor(1)`](bundle-doctor.1.html):
+ Display warnings about common problems
+
+## PLUGINS
+
+When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES,
+Bundler will try to find an executable on your path named `bundler-<command>`
+and execute it, passing down any extra arguments to it.
+
+## OBSOLETE
+
+These commands are obsolete and should no longer be used:
+
+* `bundle cache(1)`
+* `bundle show(1)`
diff --git a/man/gemfile.5 b/man/gemfile.5
new file mode 100644
index 0000000000..db89219d6c
--- /dev/null
+++ b/man/gemfile.5
@@ -0,0 +1,689 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "GEMFILE" "5" "October 2018" "" ""
+.
+.SH "NAME"
+\fBGemfile\fR \- A format for describing gem dependencies for Ruby programs
+.
+.SH "SYNOPSIS"
+A \fBGemfile\fR describes the gem dependencies required to execute associated Ruby code\.
+.
+.P
+Place the \fBGemfile\fR in the root of the directory containing the associated code\. For instance, in a Rails application, place the \fBGemfile\fR in the same directory as the \fBRakefile\fR\.
+.
+.SH "SYNTAX"
+A \fBGemfile\fR is evaluated as Ruby code, in a context which makes available a number of methods used to describe the gem requirements\.
+.
+.SH "GLOBAL SOURCES"
+At the top of the \fBGemfile\fR, add a line for the \fBRubygems\fR source that contains the gems listed in the \fBGemfile\fR\.
+.
+.IP "" 4
+.
+.nf
+
+source "https://rubygems\.org"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+It is possible, but not recommended as of Bundler 1\.7, to add multiple global \fBsource\fR lines\. Each of these \fBsource\fRs \fBMUST\fR be a valid Rubygems repository\.
+.
+.P
+Sources are checked for gems following the heuristics described in \fISOURCE PRIORITY\fR\. If a gem is found in more than one global source, Bundler will print a warning after installing the gem indicating which source was used, and listing the other sources where the gem is available\. A specific source can be selected for gems that need to use a non\-standard repository, suppressing this warning, by using the \fI\fB:source\fR option\fR or a \fI\fBsource\fR block\fR\.
+.
+.SS "CREDENTIALS"
+Some gem sources require a username and password\. Use bundle config(1) \fIbundle\-config\.1\.html\fR to set the username and password for any of the sources that need it\. The command must be run once on each computer that will install the Gemfile, but this keeps the credentials from being stored in plain text in version control\.
+.
+.IP "" 4
+.
+.nf
+
+bundle config gems\.example\.com user:password
+.
+.fi
+.
+.IP "" 0
+.
+.P
+For some sources, like a company Gemfury account, it may be easier to include the credentials in the Gemfile as part of the source URL\.
+.
+.IP "" 4
+.
+.nf
+
+source "https://user:password@gems\.example\.com"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Credentials in the source URL will take precedence over credentials set using \fBconfig\fR\.
+.
+.SH "RUBY"
+If your application requires a specific Ruby version or engine, specify your requirements using the \fBruby\fR method, with the following arguments\. All parameters are \fBOPTIONAL\fR unless otherwise specified\.
+.
+.SS "VERSION (required)"
+The version of Ruby that your application requires\. If your application requires an alternate Ruby engine, such as JRuby, Rubinius or TruffleRuby, this should be the Ruby version that the engine is compatible with\.
+.
+.IP "" 4
+.
+.nf
+
+ruby "1\.9\.3"
+.
+.fi
+.
+.IP "" 0
+.
+.SS "ENGINE"
+Each application \fImay\fR specify a Ruby engine\. If an engine is specified, an engine version \fImust\fR also be specified\.
+.
+.P
+What exactly is an Engine? \- A Ruby engine is an implementation of the Ruby language\.
+.
+.IP "\(bu" 4
+For background: the reference or original implementation of the Ruby programming language is called Matz\'s Ruby Interpreter \fIhttps://en\.wikipedia\.org/wiki/Ruby_MRI\fR, or MRI for short\. This is named after Ruby creator Yukihiro Matsumoto, also known as Matz\. MRI is also known as CRuby, because it is written in C\. MRI is the most widely used Ruby engine\.
+.
+.IP "\(bu" 4
+Other implementations \fIhttps://www\.ruby\-lang\.org/en/about/\fR of Ruby exist\. Some of the more well\-known implementations include Rubinius \fIhttps://rubinius\.com/\fR, and JRuby \fIhttp://jruby\.org/\fR\. Rubinius is an alternative implementation of Ruby written in Ruby\. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine\.
+.
+.IP "" 0
+.
+.SS "ENGINE VERSION"
+Each application \fImay\fR specify a Ruby engine version\. If an engine version is specified, an engine \fImust\fR also be specified\. If the engine is "ruby" the engine version specified \fImust\fR match the Ruby version\.
+.
+.IP "" 4
+.
+.nf
+
+ruby "1\.8\.7", :engine => "jruby", :engine_version => "1\.6\.7"
+.
+.fi
+.
+.IP "" 0
+.
+.SS "PATCHLEVEL"
+Each application \fImay\fR specify a Ruby patchlevel\.
+.
+.IP "" 4
+.
+.nf
+
+ruby "2\.0\.0", :patchlevel => "247"
+.
+.fi
+.
+.IP "" 0
+.
+.SH "GEMS"
+Specify gem requirements using the \fBgem\fR method, with the following arguments\. All parameters are \fBOPTIONAL\fR unless otherwise specified\.
+.
+.SS "NAME (required)"
+For each gem requirement, list a single \fIgem\fR line\.
+.
+.IP "" 4
+.
+.nf
+
+gem "nokogiri"
+.
+.fi
+.
+.IP "" 0
+.
+.SS "VERSION"
+Each \fIgem\fR \fBMAY\fR have one or more version specifiers\.
+.
+.IP "" 4
+.
+.nf
+
+gem "nokogiri", ">= 1\.4\.2"
+gem "RedCloth", ">= 4\.1\.0", "< 4\.2\.0"
+.
+.fi
+.
+.IP "" 0
+.
+.SS "REQUIRE AS"
+Each \fIgem\fR \fBMAY\fR specify files that should be used when autorequiring via \fBBundler\.require\fR\. You may pass an array with multiple files or \fBtrue\fR if file you want \fBrequired\fR has same name as \fIgem\fR or \fBfalse\fR to prevent any file from being autorequired\.
+.
+.IP "" 4
+.
+.nf
+
+gem "redis", :require => ["redis/connection/hiredis", "redis"]
+gem "webmock", :require => false
+gem "debugger", :require => true
+.
+.fi
+.
+.IP "" 0
+.
+.P
+The argument defaults to the name of the gem\. For example, these are identical:
+.
+.IP "" 4
+.
+.nf
+
+gem "nokogiri"
+gem "nokogiri", :require => "nokogiri"
+gem "nokogiri", :require => true
+.
+.fi
+.
+.IP "" 0
+.
+.SS "GROUPS"
+Each \fIgem\fR \fBMAY\fR specify membership in one or more groups\. Any \fIgem\fR that does not specify membership in any group is placed in the \fBdefault\fR group\.
+.
+.IP "" 4
+.
+.nf
+
+gem "rspec", :group => :test
+gem "wirble", :groups => [:development, :test]
+.
+.fi
+.
+.IP "" 0
+.
+.P
+The Bundler runtime allows its two main methods, \fBBundler\.setup\fR and \fBBundler\.require\fR, to limit their impact to particular groups\.
+.
+.IP "" 4
+.
+.nf
+
+# setup adds gems to Ruby\'s load path
+Bundler\.setup # defaults to all groups
+require "bundler/setup" # same as Bundler\.setup
+Bundler\.setup(:default) # only set up the _default_ group
+Bundler\.setup(:test) # only set up the _test_ group (but `not` _default_)
+Bundler\.setup(:default, :test) # set up the _default_ and _test_ groups, but no others
+
+# require requires all of the gems in the specified groups
+Bundler\.require # defaults to the _default_ group
+Bundler\.require(:default) # identical
+Bundler\.require(:default, :test) # requires the _default_ and _test_ groups
+Bundler\.require(:test) # requires the _test_ group
+.
+.fi
+.
+.IP "" 0
+.
+.P
+The Bundler CLI allows you to specify a list of groups whose gems \fBbundle install\fR should not install with the \fB\-\-without\fR option\. To specify multiple groups to ignore, specify a list of groups separated by spaces\.
+.
+.IP "" 4
+.
+.nf
+
+bundle install \-\-without test
+bundle install \-\-without development test
+.
+.fi
+.
+.IP "" 0
+.
+.P
+After running \fBbundle install \-\-without test\fR, bundler will remember that you excluded the test group in the last installation\. The next time you run \fBbundle install\fR, without any \fB\-\-without option\fR, bundler will recall it\.
+.
+.P
+Also, calling \fBBundler\.setup\fR with no parameters, or calling \fBrequire "bundler/setup"\fR will setup all groups except for the ones you excluded via \fB\-\-without\fR (since they are not available)\.
+.
+.P
+Note that on \fBbundle install\fR, bundler downloads and evaluates all gems, in order to create a single canonical list of all of the required gems and their dependencies\. This means that you cannot list different versions of the same gems in different groups\. For more details, see Understanding Bundler \fIhttp://bundler\.io/rationale\.html\fR\.
+.
+.SS "PLATFORMS"
+If a gem should only be used in a particular platform or set of platforms, you can specify them\. Platforms are essentially identical to groups, except that you do not need to use the \fB\-\-without\fR install\-time flag to exclude groups of gems for other platforms\.
+.
+.P
+There are a number of \fBGemfile\fR platforms:
+.
+.TP
+\fBruby\fR
+C Ruby (MRI), Rubinius or TruffleRuby, but \fBNOT\fR Windows
+.
+.TP
+\fBmri\fR
+Same as \fIruby\fR, but only C Ruby (MRI)
+.
+.TP
+\fBmingw\fR
+Windows 32 bit \'mingw32\' platform (aka RubyInstaller)
+.
+.TP
+\fBx64_mingw\fR
+Windows 64 bit \'mingw32\' platform (aka RubyInstaller x64)
+.
+.TP
+\fBrbx\fR
+Rubinius
+.
+.TP
+\fBjruby\fR
+JRuby
+.
+.TP
+\fBtruffleruby\fR
+TruffleRuby
+.
+.TP
+\fBmswin\fR
+Windows
+.
+.P
+You can restrict further by platform and version for all platforms \fIexcept\fR for \fBrbx\fR, \fBjruby\fR, \fBtruffleruby\fR and \fBmswin\fR\.
+.
+.P
+To specify a version in addition to a platform, append the version number without the delimiter to the platform\. For example, to specify that a gem should only be used on platforms with Ruby 2\.3, use:
+.
+.IP "" 4
+.
+.nf
+
+ruby_23
+.
+.fi
+.
+.IP "" 0
+.
+.P
+The full list of platforms and supported versions includes:
+.
+.TP
+\fBruby\fR
+1\.8, 1\.9, 2\.0, 2\.1, 2\.2, 2\.3, 2\.4, 2\.5
+.
+.TP
+\fBmri\fR
+1\.8, 1\.9, 2\.0, 2\.1, 2\.2, 2\.3, 2\.4, 2\.5
+.
+.TP
+\fBmingw\fR
+1\.8, 1\.9, 2\.0, 2\.1, 2\.2, 2\.3, 2\.4, 2\.5
+.
+.TP
+\fBx64_mingw\fR
+2\.0, 2\.1, 2\.2, 2\.3, 2\.4, 2\.5
+.
+.P
+As with groups, you can specify one or more platforms:
+.
+.IP "" 4
+.
+.nf
+
+gem "weakling", :platforms => :jruby
+gem "ruby\-debug", :platforms => :mri_18
+gem "nokogiri", :platforms => [:mri_18, :jruby]
+.
+.fi
+.
+.IP "" 0
+.
+.P
+All operations involving groups (\fBbundle install\fR \fIbundle\-install\.1\.html\fR, \fBBundler\.setup\fR, \fBBundler\.require\fR) behave exactly the same as if any groups not matching the current platform were explicitly excluded\.
+.
+.SS "SOURCE"
+You can select an alternate Rubygems repository for a gem using the \':source\' option\.
+.
+.IP "" 4
+.
+.nf
+
+gem "some_internal_gem", :source => "https://gems\.example\.com"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+This forces the gem to be loaded from this source and ignores any global sources declared at the top level of the file\. If the gem does not exist in this source, it will not be installed\.
+.
+.P
+Bundler will search for child dependencies of this gem by first looking in the source selected for the parent, but if they are not found there, it will fall back on global sources using the ordering described in \fISOURCE PRIORITY\fR\.
+.
+.P
+Selecting a specific source repository this way also suppresses the ambiguous gem warning described above in \fIGLOBAL SOURCES (#source)\fR\.
+.
+.P
+Using the \fB:source\fR option for an individual gem will also make that source available as a possible global source for any other gems which do not specify explicit sources\. Thus, when adding gems with explicit sources, it is recommended that you also ensure all other gems in the Gemfile are using explicit sources\.
+.
+.SS "GIT"
+If necessary, you can specify that a gem is located at a particular git repository using the \fB:git\fR parameter\. The repository can be accessed via several protocols:
+.
+.TP
+\fBHTTP(S)\fR
+gem "rails", :git => "https://github\.com/rails/rails\.git"
+.
+.TP
+\fBSSH\fR
+gem "rails", :git => "git@github\.com:rails/rails\.git"
+.
+.TP
+\fBgit\fR
+gem "rails", :git => "git://github\.com/rails/rails\.git"
+.
+.P
+If using SSH, the user that you use to run \fBbundle install\fR \fBMUST\fR have the appropriate keys available in their \fB$HOME/\.ssh\fR\.
+.
+.P
+\fBNOTE\fR: \fBhttp://\fR and \fBgit://\fR URLs should be avoided if at all possible\. These protocols are unauthenticated, so a man\-in\-the\-middle attacker can deliver malicious code and compromise your system\. HTTPS and SSH are strongly preferred\.
+.
+.P
+The \fBgroup\fR, \fBplatforms\fR, and \fBrequire\fR options are available and behave exactly the same as they would for a normal gem\.
+.
+.P
+A git repository \fBSHOULD\fR have at least one file, at the root of the directory containing the gem, with the extension \fB\.gemspec\fR\. This file \fBMUST\fR contain a valid gem specification, as expected by the \fBgem build\fR command\.
+.
+.P
+If a git repository does not have a \fB\.gemspec\fR, bundler will attempt to create one, but it will not contain any dependencies, executables, or C extension compilation instructions\. As a result, it may fail to properly integrate into your application\.
+.
+.P
+If a git repository does have a \fB\.gemspec\fR for the gem you attached it to, a version specifier, if provided, means that the git repository is only valid if the \fB\.gemspec\fR specifies a version matching the version specifier\. If not, bundler will print a warning\.
+.
+.IP "" 4
+.
+.nf
+
+gem "rails", "2\.3\.8", :git => "https://github\.com/rails/rails\.git"
+# bundle install will fail, because the \.gemspec in the rails
+# repository\'s master branch specifies version 3\.0\.0
+.
+.fi
+.
+.IP "" 0
+.
+.P
+If a git repository does \fBnot\fR have a \fB\.gemspec\fR for the gem you attached it to, a version specifier \fBMUST\fR be provided\. Bundler will use this version in the simple \fB\.gemspec\fR it creates\.
+.
+.P
+Git repositories support a number of additional options\.
+.
+.TP
+\fBbranch\fR, \fBtag\fR, and \fBref\fR
+You \fBMUST\fR only specify at most one of these options\. The default is \fB:branch => "master"\fR
+.
+.TP
+For example:
+.
+.IP
+git "https://github\.com/rails/rails\.git", :branch => "5\-0\-stable" do
+.
+.IP
+git "https://github\.com/rails/rails\.git", :tag => "v5\.0\.0" do
+.
+.IP
+git "https://github\.com/rails/rails\.git", :ref => "4aded" do
+.
+.TP
+\fBsubmodules\fR
+For reference, a git submodule \fIhttps://git\-scm\.com/book/en/v2/Git\-Tools\-Submodules\fR lets you have another git repository within a subfolder of your repository\. Specify \fB:submodules => true\fR to cause bundler to expand any submodules included in the git repository
+.
+.P
+If a git repository contains multiple \fB\.gemspecs\fR, each \fB\.gemspec\fR represents a gem located at the same place in the file system as the \fB\.gemspec\fR\.
+.
+.IP "" 4
+.
+.nf
+
+|~rails [git root]
+| |\-rails\.gemspec [rails gem located here]
+|~actionpack
+| |\-actionpack\.gemspec [actionpack gem located here]
+|~activesupport
+| |\-activesupport\.gemspec [activesupport gem located here]
+|\.\.\.
+.
+.fi
+.
+.IP "" 0
+.
+.P
+To install a gem located in a git repository, bundler changes to the directory containing the gemspec, runs \fBgem build name\.gemspec\fR and then installs the resulting gem\. The \fBgem build\fR command, which comes standard with Rubygems, evaluates the \fB\.gemspec\fR in the context of the directory in which it is located\.
+.
+.SS "GIT SOURCE"
+A custom git source can be defined via the \fBgit_source\fR method\. Provide the source\'s name as an argument, and a block which receives a single argument and interpolates it into a string to return the full repo address:
+.
+.IP "" 4
+.
+.nf
+
+git_source(:stash){ |repo_name| "https://stash\.corp\.acme\.pl/#{repo_name}\.git" }
+gem \'rails\', :stash => \'forks/rails\'
+.
+.fi
+.
+.IP "" 0
+.
+.P
+In addition, if you wish to choose a specific branch:
+.
+.IP "" 4
+.
+.nf
+
+gem "rails", :stash => "forks/rails", :branch => "branch_name"
+.
+.fi
+.
+.IP "" 0
+.
+.SS "GITHUB"
+\fBNOTE\fR: This shorthand should be avoided until Bundler 2\.0, since it currently expands to an insecure \fBgit://\fR URL\. This allows a man\-in\-the\-middle attacker to compromise your system\.
+.
+.P
+If the git repository you want to use is hosted on GitHub and is public, you can use the :github shorthand to specify the github username and repository name (without the trailing "\.git"), separated by a slash\. If both the username and repository name are the same, you can omit one\.
+.
+.IP "" 4
+.
+.nf
+
+gem "rails", :github => "rails/rails"
+gem "rails", :github => "rails"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Are both equivalent to
+.
+.IP "" 4
+.
+.nf
+
+gem "rails", :git => "git://github\.com/rails/rails\.git"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Since the \fBgithub\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\.
+.
+.SS "GIST"
+If the git repository you want to use is hosted as a Github Gist and is public, you can use the :gist shorthand to specify the gist identifier (without the trailing "\.git")\.
+.
+.IP "" 4
+.
+.nf
+
+gem "the_hatch", :gist => "4815162342"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Is equivalent to:
+.
+.IP "" 4
+.
+.nf
+
+gem "the_hatch", :git => "https://gist\.github\.com/4815162342\.git"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Since the \fBgist\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\.
+.
+.SS "BITBUCKET"
+If the git repository you want to use is hosted on Bitbucket and is public, you can use the :bitbucket shorthand to specify the bitbucket username and repository name (without the trailing "\.git"), separated by a slash\. If both the username and repository name are the same, you can omit one\.
+.
+.IP "" 4
+.
+.nf
+
+gem "rails", :bitbucket => "rails/rails"
+gem "rails", :bitbucket => "rails"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Are both equivalent to
+.
+.IP "" 4
+.
+.nf
+
+gem "rails", :git => "https://rails@bitbucket\.org/rails/rails\.git"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Since the \fBbitbucket\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\.
+.
+.SS "PATH"
+You can specify that a gem is located in a particular location on the file system\. Relative paths are resolved relative to the directory containing the \fBGemfile\fR\.
+.
+.P
+Similar to the semantics of the \fB:git\fR option, the \fB:path\fR option requires that the directory in question either contains a \fB\.gemspec\fR for the gem, or that you specify an explicit version that bundler should use\.
+.
+.P
+Unlike \fB:git\fR, bundler does not compile C extensions for gems specified as paths\.
+.
+.IP "" 4
+.
+.nf
+
+gem "rails", :path => "vendor/rails"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+If you would like to use multiple local gems directly from the filesystem, you can set a global \fBpath\fR option to the path containing the gem\'s files\. This will automatically load gemspec files from subdirectories\.
+.
+.IP "" 4
+.
+.nf
+
+path \'components\' do
+ gem \'admin_ui\'
+ gem \'public_ui\'
+end
+.
+.fi
+.
+.IP "" 0
+.
+.SH "BLOCK FORM OF SOURCE, GIT, PATH, GROUP and PLATFORMS"
+The \fB:source\fR, \fB:git\fR, \fB:path\fR, \fB:group\fR, and \fB:platforms\fR options may be applied to a group of gems by using block form\.
+.
+.IP "" 4
+.
+.nf
+
+source "https://gems\.example\.com" do
+ gem "some_internal_gem"
+ gem "another_internal_gem"
+end
+
+git "https://github\.com/rails/rails\.git" do
+ gem "activesupport"
+ gem "actionpack"
+end
+
+platforms :ruby do
+ gem "ruby\-debug"
+ gem "sqlite3"
+end
+
+group :development, :optional => true do
+ gem "wirble"
+ gem "faker"
+end
+.
+.fi
+.
+.IP "" 0
+.
+.P
+In the case of the group block form the :optional option can be given to prevent a group from being installed unless listed in the \fB\-\-with\fR option given to the \fBbundle install\fR command\.
+.
+.P
+In the case of the \fBgit\fR block form, the \fB:ref\fR, \fB:branch\fR, \fB:tag\fR, and \fB:submodules\fR options may be passed to the \fBgit\fR method, and all gems in the block will inherit those options\.
+.
+.P
+The presence of a \fBsource\fR block in a Gemfile also makes that source available as a possible global source for any other gems which do not specify explicit sources\. Thus, when defining source blocks, it is recommended that you also ensure all other gems in the Gemfile are using explicit sources, either via source blocks or \fB:source\fR directives on individual gems\.
+.
+.SH "INSTALL_IF"
+The \fBinstall_if\fR method allows gems to be installed based on a proc or lambda\. This is especially useful for optional gems that can only be used if certain software is installed or some other conditions are met\.
+.
+.IP "" 4
+.
+.nf
+
+install_if \-> { RUBY_PLATFORM =~ /darwin/ } do
+ gem "pasteboard"
+end
+.
+.fi
+.
+.IP "" 0
+.
+.SH "GEMSPEC"
+The \fB\.gemspec\fR \fIhttp://guides\.rubygems\.org/specification\-reference/\fR file is where you provide metadata about your gem to Rubygems\. Some required Gemspec attributes include the name, description, and homepage of your gem\. This is also where you specify the dependencies your gem needs to run\.
+.
+.P
+If you wish to use Bundler to help install dependencies for a gem while it is being developed, use the \fBgemspec\fR method to pull in the dependencies listed in the \fB\.gemspec\fR file\.
+.
+.P
+The \fBgemspec\fR method adds any runtime dependencies as gem requirements in the default group\. It also adds development dependencies as gem requirements in the \fBdevelopment\fR group\. Finally, it adds a gem requirement on your project (\fB:path => \'\.\'\fR)\. In conjunction with \fBBundler\.setup\fR, this allows you to require project files in your test code as you would if the project were installed as a gem; you need not manipulate the load path manually or require project files via relative paths\.
+.
+.P
+The \fBgemspec\fR method supports optional \fB:path\fR, \fB:glob\fR, \fB:name\fR, and \fB:development_group\fR options, which control where bundler looks for the \fB\.gemspec\fR, the glob it uses to look for the gemspec (defaults to: "{,\fI,\fR/*}\.gemspec"), what named \fB\.gemspec\fR it uses (if more than one is present), and which group development dependencies are included in\.
+.
+.P
+When a \fBgemspec\fR dependency encounters version conflicts during resolution, the local version under development will always be selected \-\- even if there are remote versions that better match other requirements for the \fBgemspec\fR gem\.
+.
+.SH "SOURCE PRIORITY"
+When attempting to locate a gem to satisfy a gem requirement, bundler uses the following priority order:
+.
+.IP "1." 4
+The source explicitly attached to the gem (using \fB:source\fR, \fB:path\fR, or \fB:git\fR)
+.
+.IP "2." 4
+For implicit gems (dependencies of explicit gems), any source, git, or path repository declared on the parent\. This results in bundler prioritizing the ActiveSupport gem from the Rails git repository over ones from \fBrubygems\.org\fR
+.
+.IP "3." 4
+The sources specified via global \fBsource\fR lines, searching each source in your \fBGemfile\fR from last added to first added\.
+.
+.IP "" 0
+
diff --git a/man/gemfile.5.ronn b/man/gemfile.5.ronn
new file mode 100644
index 0000000000..f4772f6d8d
--- /dev/null
+++ b/man/gemfile.5.ronn
@@ -0,0 +1,521 @@
+Gemfile(5) -- A format for describing gem dependencies for Ruby programs
+========================================================================
+
+## SYNOPSIS
+
+A `Gemfile` describes the gem dependencies required to execute associated
+Ruby code.
+
+Place the `Gemfile` in the root of the directory containing the associated
+code. For instance, in a Rails application, place the `Gemfile` in the same
+directory as the `Rakefile`.
+
+## SYNTAX
+
+A `Gemfile` is evaluated as Ruby code, in a context which makes available
+a number of methods used to describe the gem requirements.
+
+## GLOBAL SOURCES
+
+At the top of the `Gemfile`, add a line for the `Rubygems` source that contains
+the gems listed in the `Gemfile`.
+
+ source "https://rubygems.org"
+
+It is possible, but not recommended as of Bundler 1.7, to add multiple global
+`source` lines. Each of these `source`s `MUST` be a valid Rubygems repository.
+
+Sources are checked for gems following the heuristics described in
+[SOURCE PRIORITY][]. If a gem is found in more than one global source, Bundler
+will print a warning after installing the gem indicating which source was used,
+and listing the other sources where the gem is available. A specific source can
+be selected for gems that need to use a non-standard repository, suppressing
+this warning, by using the [`:source` option](#SOURCE) or a
+[`source` block](#BLOCK-FORM-OF-SOURCE-GIT-PATH-GROUP-and-PLATFORMS).
+
+### CREDENTIALS
+
+Some gem sources require a username and password. Use [bundle config(1)](bundle-config.1.html) to set
+the username and password for any of the sources that need it. The command must
+be run once on each computer that will install the Gemfile, but this keeps the
+credentials from being stored in plain text in version control.
+
+ bundle config gems.example.com user:password
+
+For some sources, like a company Gemfury account, it may be easier to
+include the credentials in the Gemfile as part of the source URL.
+
+ source "https://user:password@gems.example.com"
+
+Credentials in the source URL will take precedence over credentials set using
+`config`.
+
+## RUBY
+
+If your application requires a specific Ruby version or engine, specify your
+requirements using the `ruby` method, with the following arguments.
+All parameters are `OPTIONAL` unless otherwise specified.
+
+### VERSION (required)
+
+The version of Ruby that your application requires. If your application
+requires an alternate Ruby engine, such as JRuby, Rubinius or TruffleRuby, this
+should be the Ruby version that the engine is compatible with.
+
+ ruby "1.9.3"
+
+### ENGINE
+
+Each application _may_ specify a Ruby engine. If an engine is specified, an
+engine version _must_ also be specified.
+
+What exactly is an Engine?
+ - A Ruby engine is an implementation of the Ruby language.
+
+ - For background: the reference or original implementation of the Ruby
+ programming language is called
+ [Matz's Ruby Interpreter](https://en.wikipedia.org/wiki/Ruby_MRI), or MRI
+ for short. This is named after Ruby creator Yukihiro Matsumoto,
+ also known as Matz. MRI is also known as CRuby, because it is written in C.
+ MRI is the most widely used Ruby engine.
+
+ - [Other implementations](https://www.ruby-lang.org/en/about/) of Ruby exist.
+ Some of the more well-known implementations include
+ [Rubinius](https://rubinius.com/), and [JRuby](http://jruby.org/).
+ Rubinius is an alternative implementation of Ruby written in Ruby.
+ JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine.
+
+### ENGINE VERSION
+
+Each application _may_ specify a Ruby engine version. If an engine version is
+specified, an engine _must_ also be specified. If the engine is "ruby" the
+engine version specified _must_ match the Ruby version.
+
+ ruby "1.8.7", :engine => "jruby", :engine_version => "1.6.7"
+
+### PATCHLEVEL
+
+Each application _may_ specify a Ruby patchlevel.
+
+ ruby "2.0.0", :patchlevel => "247"
+
+## GEMS
+
+Specify gem requirements using the `gem` method, with the following arguments.
+All parameters are `OPTIONAL` unless otherwise specified.
+
+### NAME (required)
+
+For each gem requirement, list a single _gem_ line.
+
+ gem "nokogiri"
+
+### VERSION
+
+Each _gem_ `MAY` have one or more version specifiers.
+
+ gem "nokogiri", ">= 1.4.2"
+ gem "RedCloth", ">= 4.1.0", "< 4.2.0"
+
+### REQUIRE AS
+
+Each _gem_ `MAY` specify files that should be used when autorequiring via
+`Bundler.require`. You may pass an array with multiple files or `true` if file
+you want `required` has same name as _gem_ or `false` to
+prevent any file from being autorequired.
+
+ gem "redis", :require => ["redis/connection/hiredis", "redis"]
+ gem "webmock", :require => false
+ gem "debugger", :require => true
+
+The argument defaults to the name of the gem. For example, these are identical:
+
+ gem "nokogiri"
+ gem "nokogiri", :require => "nokogiri"
+ gem "nokogiri", :require => true
+
+### GROUPS
+
+Each _gem_ `MAY` specify membership in one or more groups. Any _gem_ that does
+not specify membership in any group is placed in the `default` group.
+
+ gem "rspec", :group => :test
+ gem "wirble", :groups => [:development, :test]
+
+The Bundler runtime allows its two main methods, `Bundler.setup` and
+`Bundler.require`, to limit their impact to particular groups.
+
+ # setup adds gems to Ruby's load path
+ Bundler.setup # defaults to all groups
+ require "bundler/setup" # same as Bundler.setup
+ Bundler.setup(:default) # only set up the _default_ group
+ Bundler.setup(:test) # only set up the _test_ group (but `not` _default_)
+ Bundler.setup(:default, :test) # set up the _default_ and _test_ groups, but no others
+
+ # require requires all of the gems in the specified groups
+ Bundler.require # defaults to the _default_ group
+ Bundler.require(:default) # identical
+ Bundler.require(:default, :test) # requires the _default_ and _test_ groups
+ Bundler.require(:test) # requires the _test_ group
+
+The Bundler CLI allows you to specify a list of groups whose gems `bundle install` should
+not install with the `--without` option. To specify multiple groups to ignore, specify a
+list of groups separated by spaces.
+
+ bundle install --without test
+ bundle install --without development test
+
+After running `bundle install --without test`, bundler will remember that you excluded
+the test group in the last installation. The next time you run `bundle install`,
+without any `--without option`, bundler will recall it.
+
+Also, calling `Bundler.setup` with no parameters, or calling `require "bundler/setup"`
+will setup all groups except for the ones you excluded via `--without` (since they
+are not available).
+
+Note that on `bundle install`, bundler downloads and evaluates all gems, in order to
+create a single canonical list of all of the required gems and their dependencies.
+This means that you cannot list different versions of the same gems in different
+groups. For more details, see [Understanding Bundler](http://bundler.io/rationale.html).
+
+### PLATFORMS
+
+If a gem should only be used in a particular platform or set of platforms, you can
+specify them. Platforms are essentially identical to groups, except that you do not
+need to use the `--without` install-time flag to exclude groups of gems for other
+platforms.
+
+There are a number of `Gemfile` platforms:
+
+ * `ruby`:
+ C Ruby (MRI), Rubinius or TruffleRuby, but `NOT` Windows
+ * `mri`:
+ Same as _ruby_, but only C Ruby (MRI)
+ * `mingw`:
+ Windows 32 bit 'mingw32' platform (aka RubyInstaller)
+ * `x64_mingw`:
+ Windows 64 bit 'mingw32' platform (aka RubyInstaller x64)
+ * `rbx`:
+ Rubinius
+ * `jruby`:
+ JRuby
+ * `truffleruby`:
+ TruffleRuby
+ * `mswin`:
+ Windows
+
+You can restrict further by platform and version for all platforms *except* for
+`rbx`, `jruby`, `truffleruby` and `mswin`.
+
+To specify a version in addition to a platform, append the version number without
+the delimiter to the platform. For example, to specify that a gem should only be
+used on platforms with Ruby 2.3, use:
+
+ ruby_23
+
+The full list of platforms and supported versions includes:
+
+ * `ruby`:
+ 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5
+ * `mri`:
+ 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5
+ * `mingw`:
+ 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5
+ * `x64_mingw`:
+ 2.0, 2.1, 2.2, 2.3, 2.4, 2.5
+
+As with groups, you can specify one or more platforms:
+
+ gem "weakling", :platforms => :jruby
+ gem "ruby-debug", :platforms => :mri_18
+ gem "nokogiri", :platforms => [:mri_18, :jruby]
+
+All operations involving groups ([`bundle install`](bundle-install.1.html), `Bundler.setup`,
+`Bundler.require`) behave exactly the same as if any groups not
+matching the current platform were explicitly excluded.
+
+### SOURCE
+
+You can select an alternate Rubygems repository for a gem using the ':source'
+option.
+
+ gem "some_internal_gem", :source => "https://gems.example.com"
+
+This forces the gem to be loaded from this source and ignores any global sources
+declared at the top level of the file. If the gem does not exist in this source,
+it will not be installed.
+
+Bundler will search for child dependencies of this gem by first looking in the
+source selected for the parent, but if they are not found there, it will fall
+back on global sources using the ordering described in [SOURCE PRIORITY][].
+
+Selecting a specific source repository this way also suppresses the ambiguous
+gem warning described above in
+[GLOBAL SOURCES (#source)](#GLOBAL-SOURCES).
+
+Using the `:source` option for an individual gem will also make that source
+available as a possible global source for any other gems which do not specify
+explicit sources. Thus, when adding gems with explicit sources, it is
+recommended that you also ensure all other gems in the Gemfile are using
+explicit sources.
+
+### GIT
+
+If necessary, you can specify that a gem is located at a particular
+git repository using the `:git` parameter. The repository can be accessed via
+several protocols:
+
+ * `HTTP(S)`:
+ gem "rails", :git => "https://github.com/rails/rails.git"
+ * `SSH`:
+ gem "rails", :git => "git@github.com:rails/rails.git"
+ * `git`:
+ gem "rails", :git => "git://github.com/rails/rails.git"
+
+If using SSH, the user that you use to run `bundle install` `MUST` have the
+appropriate keys available in their `$HOME/.ssh`.
+
+`NOTE`: `http://` and `git://` URLs should be avoided if at all possible. These
+protocols are unauthenticated, so a man-in-the-middle attacker can deliver
+malicious code and compromise your system. HTTPS and SSH are strongly
+preferred.
+
+The `group`, `platforms`, and `require` options are available and behave
+exactly the same as they would for a normal gem.
+
+A git repository `SHOULD` have at least one file, at the root of the
+directory containing the gem, with the extension `.gemspec`. This file
+`MUST` contain a valid gem specification, as expected by the `gem build`
+command.
+
+If a git repository does not have a `.gemspec`, bundler will attempt to
+create one, but it will not contain any dependencies, executables, or
+C extension compilation instructions. As a result, it may fail to properly
+integrate into your application.
+
+If a git repository does have a `.gemspec` for the gem you attached it
+to, a version specifier, if provided, means that the git repository is
+only valid if the `.gemspec` specifies a version matching the version
+specifier. If not, bundler will print a warning.
+
+ gem "rails", "2.3.8", :git => "https://github.com/rails/rails.git"
+ # bundle install will fail, because the .gemspec in the rails
+ # repository's master branch specifies version 3.0.0
+
+If a git repository does `not` have a `.gemspec` for the gem you attached
+it to, a version specifier `MUST` be provided. Bundler will use this
+version in the simple `.gemspec` it creates.
+
+Git repositories support a number of additional options.
+
+ * `branch`, `tag`, and `ref`:
+ You `MUST` only specify at most one of these options. The default
+ is `:branch => "master"`
+ * For example:
+
+ git "https://github.com/rails/rails.git", :branch => "5-0-stable" do
+
+ git "https://github.com/rails/rails.git", :tag => "v5.0.0" do
+
+ git "https://github.com/rails/rails.git", :ref => "4aded" do
+
+ * `submodules`:
+ For reference, a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules)
+ lets you have another git repository within a subfolder of your repository.
+ Specify `:submodules => true` to cause bundler to expand any
+ submodules included in the git repository
+
+If a git repository contains multiple `.gemspecs`, each `.gemspec`
+represents a gem located at the same place in the file system as
+the `.gemspec`.
+
+ |~rails [git root]
+ | |-rails.gemspec [rails gem located here]
+ |~actionpack
+ | |-actionpack.gemspec [actionpack gem located here]
+ |~activesupport
+ | |-activesupport.gemspec [activesupport gem located here]
+ |...
+
+To install a gem located in a git repository, bundler changes to
+the directory containing the gemspec, runs `gem build name.gemspec`
+and then installs the resulting gem. The `gem build` command,
+which comes standard with Rubygems, evaluates the `.gemspec` in
+the context of the directory in which it is located.
+
+### GIT SOURCE
+
+A custom git source can be defined via the `git_source` method. Provide the source's name
+as an argument, and a block which receives a single argument and interpolates it into a
+string to return the full repo address:
+
+ git_source(:stash){ |repo_name| "https://stash.corp.acme.pl/#{repo_name}.git" }
+ gem 'rails', :stash => 'forks/rails'
+
+In addition, if you wish to choose a specific branch:
+
+ gem "rails", :stash => "forks/rails", :branch => "branch_name"
+
+### GITHUB
+
+`NOTE`: This shorthand should be avoided until Bundler 2.0, since it
+currently expands to an insecure `git://` URL. This allows a
+man-in-the-middle attacker to compromise your system.
+
+If the git repository you want to use is hosted on GitHub and is public, you can use the
+:github shorthand to specify the github username and repository name (without the
+trailing ".git"), separated by a slash. If both the username and repository name are the
+same, you can omit one.
+
+ gem "rails", :github => "rails/rails"
+ gem "rails", :github => "rails"
+
+Are both equivalent to
+
+ gem "rails", :git => "git://github.com/rails/rails.git"
+
+Since the `github` method is a specialization of `git_source`, it accepts a `:branch` named argument.
+
+### GIST
+
+If the git repository you want to use is hosted as a Github Gist and is public, you can use
+the :gist shorthand to specify the gist identifier (without the trailing ".git").
+
+ gem "the_hatch", :gist => "4815162342"
+
+Is equivalent to:
+
+ gem "the_hatch", :git => "https://gist.github.com/4815162342.git"
+
+Since the `gist` method is a specialization of `git_source`, it accepts a `:branch` named argument.
+
+### BITBUCKET
+
+If the git repository you want to use is hosted on Bitbucket and is public, you can use the
+:bitbucket shorthand to specify the bitbucket username and repository name (without the
+trailing ".git"), separated by a slash. If both the username and repository name are the
+same, you can omit one.
+
+ gem "rails", :bitbucket => "rails/rails"
+ gem "rails", :bitbucket => "rails"
+
+Are both equivalent to
+
+ gem "rails", :git => "https://rails@bitbucket.org/rails/rails.git"
+
+Since the `bitbucket` method is a specialization of `git_source`, it accepts a `:branch` named argument.
+
+### PATH
+
+You can specify that a gem is located in a particular location
+on the file system. Relative paths are resolved relative to the
+directory containing the `Gemfile`.
+
+Similar to the semantics of the `:git` option, the `:path`
+option requires that the directory in question either contains
+a `.gemspec` for the gem, or that you specify an explicit
+version that bundler should use.
+
+Unlike `:git`, bundler does not compile C extensions for
+gems specified as paths.
+
+ gem "rails", :path => "vendor/rails"
+
+If you would like to use multiple local gems directly from the filesystem, you can set a global `path` option to the path containing the gem's files. This will automatically load gemspec files from subdirectories.
+
+ path 'components' do
+ gem 'admin_ui'
+ gem 'public_ui'
+ end
+
+## BLOCK FORM OF SOURCE, GIT, PATH, GROUP and PLATFORMS
+
+The `:source`, `:git`, `:path`, `:group`, and `:platforms` options may be
+applied to a group of gems by using block form.
+
+ source "https://gems.example.com" do
+ gem "some_internal_gem"
+ gem "another_internal_gem"
+ end
+
+ git "https://github.com/rails/rails.git" do
+ gem "activesupport"
+ gem "actionpack"
+ end
+
+ platforms :ruby do
+ gem "ruby-debug"
+ gem "sqlite3"
+ end
+
+ group :development, :optional => true do
+ gem "wirble"
+ gem "faker"
+ end
+
+In the case of the group block form the :optional option can be given
+to prevent a group from being installed unless listed in the `--with`
+option given to the `bundle install` command.
+
+In the case of the `git` block form, the `:ref`, `:branch`, `:tag`,
+and `:submodules` options may be passed to the `git` method, and
+all gems in the block will inherit those options.
+
+The presence of a `source` block in a Gemfile also makes that source
+available as a possible global source for any other gems which do not specify
+explicit sources. Thus, when defining source blocks, it is
+recommended that you also ensure all other gems in the Gemfile are using
+explicit sources, either via source blocks or `:source` directives on
+individual gems.
+
+## INSTALL_IF
+
+The `install_if` method allows gems to be installed based on a proc or lambda.
+This is especially useful for optional gems that can only be used if certain
+software is installed or some other conditions are met.
+
+ install_if -> { RUBY_PLATFORM =~ /darwin/ } do
+ gem "pasteboard"
+ end
+
+## GEMSPEC
+
+The [`.gemspec`](http://guides.rubygems.org/specification-reference/) file is where
+ you provide metadata about your gem to Rubygems. Some required Gemspec
+ attributes include the name, description, and homepage of your gem. This is
+ also where you specify the dependencies your gem needs to run.
+
+If you wish to use Bundler to help install dependencies for a gem while it is
+being developed, use the `gemspec` method to pull in the dependencies listed in
+the `.gemspec` file.
+
+The `gemspec` method adds any runtime dependencies as gem requirements in the
+default group. It also adds development dependencies as gem requirements in the
+`development` group. Finally, it adds a gem requirement on your project (`:path
+=> '.'`). In conjunction with `Bundler.setup`, this allows you to require project
+files in your test code as you would if the project were installed as a gem; you
+need not manipulate the load path manually or require project files via relative
+paths.
+
+The `gemspec` method supports optional `:path`, `:glob`, `:name`, and `:development_group`
+options, which control where bundler looks for the `.gemspec`, the glob it uses to look
+for the gemspec (defaults to: "{,*,*/*}.gemspec"), what named `.gemspec` it uses
+(if more than one is present), and which group development dependencies are included in.
+
+When a `gemspec` dependency encounters version conflicts during resolution, the
+local version under development will always be selected -- even if there are
+remote versions that better match other requirements for the `gemspec` gem.
+
+## SOURCE PRIORITY
+
+When attempting to locate a gem to satisfy a gem requirement,
+bundler uses the following priority order:
+
+ 1. The source explicitly attached to the gem (using `:source`, `:path`, or
+ `:git`)
+ 2. For implicit gems (dependencies of explicit gems), any source, git, or path
+ repository declared on the parent. This results in bundler prioritizing the
+ ActiveSupport gem from the Rails git repository over ones from
+ `rubygems.org`
+ 3. The sources specified via global `source` lines, searching each source in
+ your `Gemfile` from last added to first added.
diff --git a/man/gemfile.5.txt b/man/gemfile.5.txt
new file mode 100644
index 0000000000..0ff77795ad
--- /dev/null
+++ b/man/gemfile.5.txt
@@ -0,0 +1,653 @@
+GEMFILE(5) GEMFILE(5)
+
+
+
+1mNAME0m
+ 1mGemfile 22m- A format for describing gem dependencies for Ruby programs
+
+1mSYNOPSIS0m
+ A 1mGemfile 22mdescribes the gem dependencies required to execute associated
+ Ruby code.
+
+ Place the 1mGemfile 22min the root of the directory containing the associ-
+ ated code. For instance, in a Rails application, place the 1mGemfile 22min
+ the same directory as the 1mRakefile22m.
+
+1mSYNTAX0m
+ A 1mGemfile 22mis evaluated as Ruby code, in a context which makes available
+ a number of methods used to describe the gem requirements.
+
+1mGLOBAL SOURCES0m
+ At the top of the 1mGemfile22m, add a line for the 1mRubygems 22msource that con-
+ tains the gems listed in the 1mGemfile22m.
+
+
+
+ source "https://rubygems.org"
+
+
+
+ It is possible, but not recommended as of Bundler 1.7, to add multiple
+ global 1msource 22mlines. Each of these 1msource22ms 1mMUST 22mbe a valid Rubygems
+ repository.
+
+ Sources are checked for gems following the heuristics described in
+ 4mSOURCE24m 4mPRIORITY24m. If a gem is found in more than one global source,
+ Bundler will print a warning after installing the gem indicating which
+ source was used, and listing the other sources where the gem is avail-
+ able. A specific source can be selected for gems that need to use a
+ non-standard repository, suppressing this warning, by using the 1m:source0m
+ option or a 1msource 22mblock.
+
+ 1mCREDENTIALS0m
+ Some gem sources require a username and password. Use bundle config(1)
+ 4mbundle-config.1.html24m to set the username and password for any of the
+ sources that need it. The command must be run once on each computer
+ that will install the Gemfile, but this keeps the credentials from
+ being stored in plain text in version control.
+
+
+
+ bundle config gems.example.com user:password
+
+
+
+ For some sources, like a company Gemfury account, it may be easier to
+ include the credentials in the Gemfile as part of the source URL.
+
+
+
+ source "https://user:password@gems.example.com"
+
+
+
+ Credentials in the source URL will take precedence over credentials set
+ using 1mconfig22m.
+
+1mRUBY0m
+ If your application requires a specific Ruby version or engine, specify
+ your requirements using the 1mruby 22mmethod, with the following arguments.
+ All parameters are 1mOPTIONAL 22munless otherwise specified.
+
+ 1mVERSION (required)0m
+ The version of Ruby that your application requires. If your application
+ requires an alternate Ruby engine, such as JRuby, Rubinius or Truf-
+ fleRuby, this should be the Ruby version that the engine is compatible
+ with.
+
+
+
+ ruby "1.9.3"
+
+
+
+ 1mENGINE0m
+ Each application 4mmay24m specify a Ruby engine. If an engine is specified,
+ an engine version 4mmust24m also be specified.
+
+ What exactly is an Engine? - A Ruby engine is an implementation of the
+ Ruby language.
+
+ o For background: the reference or original implementation of the
+ Ruby programming language is called Matz's Ruby Interpreter
+ 4mhttps://en.wikipedia.org/wiki/Ruby_MRI24m, or MRI for short. This is
+ named after Ruby creator Yukihiro Matsumoto, also known as Matz.
+ MRI is also known as CRuby, because it is written in C. MRI is the
+ most widely used Ruby engine.
+
+ o Other implementations 4mhttps://www.ruby-lang.org/en/about/24m of Ruby
+ exist. Some of the more well-known implementations include Rubinius
+ 4mhttps://rubinius.com/24m, and JRuby 4mhttp://jruby.org/24m. Rubinius is an
+ alternative implementation of Ruby written in Ruby. JRuby is an
+ implementation of Ruby on the JVM, short for Java Virtual Machine.
+
+
+
+ 1mENGINE VERSION0m
+ Each application 4mmay24m specify a Ruby engine version. If an engine ver-
+ sion is specified, an engine 4mmust24m also be specified. If the engine is
+ "ruby" the engine version specified 4mmust24m match the Ruby version.
+
+
+
+ ruby "1.8.7", :engine => "jruby", :engine_version => "1.6.7"
+
+
+
+ 1mPATCHLEVEL0m
+ Each application 4mmay24m specify a Ruby patchlevel.
+
+
+
+ ruby "2.0.0", :patchlevel => "247"
+
+
+
+1mGEMS0m
+ Specify gem requirements using the 1mgem 22mmethod, with the following argu-
+ ments. All parameters are 1mOPTIONAL 22munless otherwise specified.
+
+ 1mNAME (required)0m
+ For each gem requirement, list a single 4mgem24m line.
+
+
+
+ gem "nokogiri"
+
+
+
+ 1mVERSION0m
+ Each 4mgem24m 1mMAY 22mhave one or more version specifiers.
+
+
+
+ gem "nokogiri", ">= 1.4.2"
+ gem "RedCloth", ">= 4.1.0", "< 4.2.0"
+
+
+
+ 1mREQUIRE AS0m
+ Each 4mgem24m 1mMAY 22mspecify files that should be used when autorequiring via
+ 1mBundler.require22m. You may pass an array with multiple files or 1mtrue 22mif
+ file you want 1mrequired 22mhas same name as 4mgem24m or 1mfalse 22mto prevent any
+ file from being autorequired.
+
+
+
+ gem "redis", :require => ["redis/connection/hiredis", "redis"]
+ gem "webmock", :require => false
+ gem "debugger", :require => true
+
+
+
+ The argument defaults to the name of the gem. For example, these are
+ identical:
+
+
+
+ gem "nokogiri"
+ gem "nokogiri", :require => "nokogiri"
+ gem "nokogiri", :require => true
+
+
+
+ 1mGROUPS0m
+ Each 4mgem24m 1mMAY 22mspecify membership in one or more groups. Any 4mgem24m that
+ does not specify membership in any group is placed in the 1mdefault0m
+ group.
+
+
+
+ gem "rspec", :group => :test
+ gem "wirble", :groups => [:development, :test]
+
+
+
+ The Bundler runtime allows its two main methods, 1mBundler.setup 22mand
+ 1mBundler.require22m, to limit their impact to particular groups.
+
+
+
+ # setup adds gems to Ruby's load path
+ Bundler.setup # defaults to all groups
+ require "bundler/setup" # same as Bundler.setup
+ Bundler.setup(:default) # only set up the _default_ group
+ Bundler.setup(:test) # only set up the _test_ group (but `not` _default_)
+ Bundler.setup(:default, :test) # set up the _default_ and _test_ groups, but no others
+
+ # require requires all of the gems in the specified groups
+ Bundler.require # defaults to the _default_ group
+ Bundler.require(:default) # identical
+ Bundler.require(:default, :test) # requires the _default_ and _test_ groups
+ Bundler.require(:test) # requires the _test_ group
+
+
+
+ The Bundler CLI allows you to specify a list of groups whose gems 1mbun-0m
+ 1mdle install 22mshould not install with the 1m--without 22moption. To specify
+ multiple groups to ignore, specify a list of groups separated by spa-
+ ces.
+
+
+
+ bundle install --without test
+ bundle install --without development test
+
+
+
+ After running 1mbundle install --without test22m, bundler will remember that
+ you excluded the test group in the last installation. The next time you
+ run 1mbundle install22m, without any 1m--without option22m, bundler will recall
+ it.
+
+ Also, calling 1mBundler.setup 22mwith no parameters, or calling 1mrequire0m
+ 1m"bundler/setup" 22mwill setup all groups except for the ones you excluded
+ via 1m--without 22m(since they are not available).
+
+ Note that on 1mbundle install22m, bundler downloads and evaluates all gems,
+ in order to create a single canonical list of all of the required gems
+ and their dependencies. This means that you cannot list different ver-
+ sions of the same gems in different groups. For more details, see
+ Understanding Bundler 4mhttp://bundler.io/rationale.html24m.
+
+ 1mPLATFORMS0m
+ If a gem should only be used in a particular platform or set of plat-
+ forms, you can specify them. Platforms are essentially identical to
+ groups, except that you do not need to use the 1m--without 22minstall-time
+ flag to exclude groups of gems for other platforms.
+
+ There are a number of 1mGemfile 22mplatforms:
+
+ 1mruby 22mC Ruby (MRI), Rubinius or TruffleRuby, but 1mNOT 22mWindows
+
+ 1mmri 22mSame as 4mruby24m, but only C Ruby (MRI)
+
+ 1mmingw 22mWindows 32 bit 'mingw32' platform (aka RubyInstaller)
+
+ 1mx64_mingw0m
+ Windows 64 bit 'mingw32' platform (aka RubyInstaller x64)
+
+ 1mrbx 22mRubinius
+
+ 1mjruby 22mJRuby
+
+ 1mtruffleruby0m
+ TruffleRuby
+
+ 1mmswin 22mWindows
+
+ You can restrict further by platform and version for all platforms
+ 4mexcept24m for 1mrbx22m, 1mjruby22m, 1mtruffleruby 22mand 1mmswin22m.
+
+ To specify a version in addition to a platform, append the version num-
+ ber without the delimiter to the platform. For example, to specify that
+ a gem should only be used on platforms with Ruby 2.3, use:
+
+
+
+ ruby_23
+
+
+
+ The full list of platforms and supported versions includes:
+
+ 1mruby 22m1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5
+
+ 1mmri 22m1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5
+
+ 1mmingw 22m1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5
+
+ 1mx64_mingw0m
+ 2.0, 2.1, 2.2, 2.3, 2.4, 2.5
+
+ As with groups, you can specify one or more platforms:
+
+
+
+ gem "weakling", :platforms => :jruby
+ gem "ruby-debug", :platforms => :mri_18
+ gem "nokogiri", :platforms => [:mri_18, :jruby]
+
+
+
+ All operations involving groups (1mbundle install 4m22mbundle-install.1.html24m,
+ 1mBundler.setup22m, 1mBundler.require22m) behave exactly the same as if any
+ groups not matching the current platform were explicitly excluded.
+
+ 1mSOURCE0m
+ You can select an alternate Rubygems repository for a gem using the
+ ':source' option.
+
+
+
+ gem "some_internal_gem", :source => "https://gems.example.com"
+
+
+
+ This forces the gem to be loaded from this source and ignores any
+ global sources declared at the top level of the file. If the gem does
+ not exist in this source, it will not be installed.
+
+ Bundler will search for child dependencies of this gem by first looking
+ in the source selected for the parent, but if they are not found there,
+ it will fall back on global sources using the ordering described in
+ 4mSOURCE24m 4mPRIORITY24m.
+
+ Selecting a specific source repository this way also suppresses the
+ ambiguous gem warning described above in 4mGLOBAL24m 4mSOURCES24m 4m(#source)24m.
+
+ Using the 1m:source 22moption for an individual gem will also make that
+ source available as a possible global source for any other gems which
+ do not specify explicit sources. Thus, when adding gems with explicit
+ sources, it is recommended that you also ensure all other gems in the
+ Gemfile are using explicit sources.
+
+ 1mGIT0m
+ If necessary, you can specify that a gem is located at a particular git
+ repository using the 1m:git 22mparameter. The repository can be accessed via
+ several protocols:
+
+ 1mHTTP(S)0m
+ gem "rails", :git => "https://github.com/rails/rails.git"
+
+ 1mSSH 22mgem "rails", :git => "git@github.com:rails/rails.git"
+
+ 1mgit 22mgem "rails", :git => "git://github.com/rails/rails.git"
+
+ If using SSH, the user that you use to run 1mbundle install MUST 22mhave the
+ appropriate keys available in their 1m$HOME/.ssh22m.
+
+ 1mNOTE22m: 1mhttp:// 22mand 1mgit:// 22mURLs should be avoided if at all possible.
+ These protocols are unauthenticated, so a man-in-the-middle attacker
+ can deliver malicious code and compromise your system. HTTPS and SSH
+ are strongly preferred.
+
+ The 1mgroup22m, 1mplatforms22m, and 1mrequire 22moptions are available and behave
+ exactly the same as they would for a normal gem.
+
+ A git repository 1mSHOULD 22mhave at least one file, at the root of the
+ directory containing the gem, with the extension 1m.gemspec22m. This file
+ 1mMUST 22mcontain a valid gem specification, as expected by the 1mgem build0m
+ command.
+
+ If a git repository does not have a 1m.gemspec22m, bundler will attempt to
+ create one, but it will not contain any dependencies, executables, or C
+ extension compilation instructions. As a result, it may fail to prop-
+ erly integrate into your application.
+
+ If a git repository does have a 1m.gemspec 22mfor the gem you attached it
+ to, a version specifier, if provided, means that the git repository is
+ only valid if the 1m.gemspec 22mspecifies a version matching the version
+ specifier. If not, bundler will print a warning.
+
+
+
+ gem "rails", "2.3.8", :git => "https://github.com/rails/rails.git"
+ # bundle install will fail, because the .gemspec in the rails
+ # repository's master branch specifies version 3.0.0
+
+
+
+ If a git repository does 1mnot 22mhave a 1m.gemspec 22mfor the gem you attached
+ it to, a version specifier 1mMUST 22mbe provided. Bundler will use this ver-
+ sion in the simple 1m.gemspec 22mit creates.
+
+ Git repositories support a number of additional options.
+
+ 1mbranch22m, 1mtag22m, and 1mref0m
+ You 1mMUST 22monly specify at most one of these options. The default
+ is 1m:branch => "master"0m
+
+ For example:
+
+ git "https://github.com/rails/rails.git", :branch => "5-0-sta-
+ ble" do
+
+ git "https://github.com/rails/rails.git", :tag => "v5.0.0" do
+
+ git "https://github.com/rails/rails.git", :ref => "4aded" do
+
+ 1msubmodules0m
+ For reference, a git submodule
+ 4mhttps://git-scm.com/book/en/v2/Git-Tools-Submodules24m lets you
+ have another git repository within a subfolder of your reposi-
+ tory. Specify 1m:submodules => true 22mto cause bundler to expand any
+ submodules included in the git repository
+
+ If a git repository contains multiple 1m.gemspecs22m, each 1m.gemspec 22mrepre-
+ sents a gem located at the same place in the file system as the 1m.gem-0m
+ 1mspec22m.
+
+
+
+ |~rails [git root]
+ | |-rails.gemspec [rails gem located here]
+ |~actionpack
+ | |-actionpack.gemspec [actionpack gem located here]
+ |~activesupport
+ | |-activesupport.gemspec [activesupport gem located here]
+ |...
+
+
+
+ To install a gem located in a git repository, bundler changes to the
+ directory containing the gemspec, runs 1mgem build name.gemspec 22mand then
+ installs the resulting gem. The 1mgem build 22mcommand, which comes standard
+ with Rubygems, evaluates the 1m.gemspec 22min the context of the directory
+ in which it is located.
+
+ 1mGIT SOURCE0m
+ A custom git source can be defined via the 1mgit_source 22mmethod. Provide
+ the source's name as an argument, and a block which receives a single
+ argument and interpolates it into a string to return the full repo
+ address:
+
+
+
+ git_source(:stash){ |repo_name| "https://stash.corp.acme.pl/#{repo_name}.git" }
+ gem 'rails', :stash => 'forks/rails'
+
+
+
+ In addition, if you wish to choose a specific branch:
+
+
+
+ gem "rails", :stash => "forks/rails", :branch => "branch_name"
+
+
+
+ 1mGITHUB0m
+ 1mNOTE22m: This shorthand should be avoided until Bundler 2.0, since it cur-
+ rently expands to an insecure 1mgit:// 22mURL. This allows a man-in-the-mid-
+ dle attacker to compromise your system.
+
+ If the git repository you want to use is hosted on GitHub and is pub-
+ lic, you can use the :github shorthand to specify the github username
+ and repository name (without the trailing ".git"), separated by a
+ slash. If both the username and repository name are the same, you can
+ omit one.
+
+
+
+ gem "rails", :github => "rails/rails"
+ gem "rails", :github => "rails"
+
+
+
+ Are both equivalent to
+
+
+
+ gem "rails", :git => "git://github.com/rails/rails.git"
+
+
+
+ Since the 1mgithub 22mmethod is a specialization of 1mgit_source22m, it accepts a
+ 1m:branch 22mnamed argument.
+
+ 1mGIST0m
+ If the git repository you want to use is hosted as a Github Gist and is
+ public, you can use the :gist shorthand to specify the gist identifier
+ (without the trailing ".git").
+
+
+
+ gem "the_hatch", :gist => "4815162342"
+
+
+
+ Is equivalent to:
+
+
+
+ gem "the_hatch", :git => "https://gist.github.com/4815162342.git"
+
+
+
+ Since the 1mgist 22mmethod is a specialization of 1mgit_source22m, it accepts a
+ 1m:branch 22mnamed argument.
+
+ 1mBITBUCKET0m
+ If the git repository you want to use is hosted on Bitbucket and is
+ public, you can use the :bitbucket shorthand to specify the bitbucket
+ username and repository name (without the trailing ".git"), separated
+ by a slash. If both the username and repository name are the same, you
+ can omit one.
+
+
+
+ gem "rails", :bitbucket => "rails/rails"
+ gem "rails", :bitbucket => "rails"
+
+
+
+ Are both equivalent to
+
+
+
+ gem "rails", :git => "https://rails@bitbucket.org/rails/rails.git"
+
+
+
+ Since the 1mbitbucket 22mmethod is a specialization of 1mgit_source22m, it
+ accepts a 1m:branch 22mnamed argument.
+
+ 1mPATH0m
+ You can specify that a gem is located in a particular location on the
+ file system. Relative paths are resolved relative to the directory con-
+ taining the 1mGemfile22m.
+
+ Similar to the semantics of the 1m:git 22moption, the 1m:path 22moption requires
+ that the directory in question either contains a 1m.gemspec 22mfor the gem,
+ or that you specify an explicit version that bundler should use.
+
+ Unlike 1m:git22m, bundler does not compile C extensions for gems specified
+ as paths.
+
+
+
+ gem "rails", :path => "vendor/rails"
+
+
+
+ If you would like to use multiple local gems directly from the filesys-
+ tem, you can set a global 1mpath 22moption to the path containing the gem's
+ files. This will automatically load gemspec files from subdirectories.
+
+
+
+ path 'components' do
+ gem 'admin_ui'
+ gem 'public_ui'
+ end
+
+
+
+1mBLOCK FORM OF SOURCE, GIT, PATH, GROUP and PLATFORMS0m
+ The 1m:source22m, 1m:git22m, 1m:path22m, 1m:group22m, and 1m:platforms 22moptions may be applied
+ to a group of gems by using block form.
+
+
+
+ source "https://gems.example.com" do
+ gem "some_internal_gem"
+ gem "another_internal_gem"
+ end
+
+ git "https://github.com/rails/rails.git" do
+ gem "activesupport"
+ gem "actionpack"
+ end
+
+ platforms :ruby do
+ gem "ruby-debug"
+ gem "sqlite3"
+ end
+
+ group :development, :optional => true do
+ gem "wirble"
+ gem "faker"
+ end
+
+
+
+ In the case of the group block form the :optional option can be given
+ to prevent a group from being installed unless listed in the 1m--with0m
+ option given to the 1mbundle install 22mcommand.
+
+ In the case of the 1mgit 22mblock form, the 1m:ref22m, 1m:branch22m, 1m:tag22m, and 1m:sub-0m
+ 1mmodules 22moptions may be passed to the 1mgit 22mmethod, and all gems in the
+ block will inherit those options.
+
+ The presence of a 1msource 22mblock in a Gemfile also makes that source
+ available as a possible global source for any other gems which do not
+ specify explicit sources. Thus, when defining source blocks, it is rec-
+ ommended that you also ensure all other gems in the Gemfile are using
+ explicit sources, either via source blocks or 1m:source 22mdirectives on
+ individual gems.
+
+1mINSTALL_IF0m
+ The 1minstall_if 22mmethod allows gems to be installed based on a proc or
+ lambda. This is especially useful for optional gems that can only be
+ used if certain software is installed or some other conditions are met.
+
+
+
+ install_if -> { RUBY_PLATFORM =~ /darwin/ } do
+ gem "pasteboard"
+ end
+
+
+
+1mGEMSPEC0m
+ The 1m.gemspec 4m22mhttp://guides.rubygems.org/specification-reference/24m file
+ is where you provide metadata about your gem to Rubygems. Some required
+ Gemspec attributes include the name, description, and homepage of your
+ gem. This is also where you specify the dependencies your gem needs to
+ run.
+
+ If you wish to use Bundler to help install dependencies for a gem while
+ it is being developed, use the 1mgemspec 22mmethod to pull in the dependen-
+ cies listed in the 1m.gemspec 22mfile.
+
+ The 1mgemspec 22mmethod adds any runtime dependencies as gem requirements in
+ the default group. It also adds development dependencies as gem
+ requirements in the 1mdevelopment 22mgroup. Finally, it adds a gem require-
+ ment on your project (1m:path => '.'22m). In conjunction with 1mBundler.setup22m,
+ this allows you to require project files in your test code as you would
+ if the project were installed as a gem; you need not manipulate the
+ load path manually or require project files via relative paths.
+
+ The 1mgemspec 22mmethod supports optional 1m:path22m, 1m:glob22m, 1m:name22m, and 1m:develop-0m
+ 1mment_group 22moptions, which control where bundler looks for the 1m.gemspec22m,
+ the glob it uses to look for the gemspec (defaults to: "{,4m,24m/*}.gem-
+ spec"), what named 1m.gemspec 22mit uses (if more than one is present), and
+ which group development dependencies are included in.
+
+ When a 1mgemspec 22mdependency encounters version conflicts during resolu-
+ tion, the local version under development will always be selected --
+ even if there are remote versions that better match other requirements
+ for the 1mgemspec 22mgem.
+
+1mSOURCE PRIORITY0m
+ When attempting to locate a gem to satisfy a gem requirement, bundler
+ uses the following priority order:
+
+ 1. The source explicitly attached to the gem (using 1m:source22m, 1m:path22m, or
+ 1m:git22m)
+
+ 2. For implicit gems (dependencies of explicit gems), any source, git,
+ or path repository declared on the parent. This results in bundler
+ prioritizing the ActiveSupport gem from the Rails git repository
+ over ones from 1mrubygems.org0m
+
+ 3. The sources specified via global 1msource 22mlines, searching each
+ source in your 1mGemfile 22mfrom last added to first added.
+
+
+
+
+
+
+ October 2018 GEMFILE(5)
diff --git a/spec/README.md b/spec/README.md
index 7f2b827a1f..59c2c605c5 100644
--- a/spec/README.md
+++ b/spec/README.md
@@ -1,3 +1,14 @@
+# spec/bundler
+
+spec/bundler is rspec examples for bundler library(lib/bundler.rb, lib/bundler/*).
+
+## Running spec/bundler
+
+To run rspec for bundler:
+```bash
+make test-bundler
+```
+
# spec/ruby
ruby/spec (https://github.com/ruby/spec/) is
diff --git a/spec/bundler/bundler/bundler_spec.rb b/spec/bundler/bundler/bundler_spec.rb
new file mode 100644
index 0000000000..194d6752b2
--- /dev/null
+++ b/spec/bundler/bundler/bundler_spec.rb
@@ -0,0 +1,490 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+require "bundler"
+require "tmpdir"
+
+RSpec.describe Bundler do
+ describe "#load_gemspec_uncached" do
+ let(:app_gemspec_path) { tmp("test.gemspec") }
+ subject { Bundler.load_gemspec_uncached(app_gemspec_path) }
+
+ context "with incorrect YAML file" do
+ before do
+ File.open(app_gemspec_path, "wb") do |f|
+ f.write strip_whitespace(<<-GEMSPEC)
+ ---
+ {:!00 ao=gu\g1= 7~f
+ GEMSPEC
+ end
+ end
+
+ it "catches YAML syntax errors" do
+ expect { subject }.to raise_error(Bundler::GemspecError, /error while loading `test.gemspec`/)
+ end
+
+ context "on Rubies with a settable YAML engine", :if => defined?(YAML::ENGINE) do
+ context "with Syck as YAML::Engine" do
+ it "raises a GemspecError after YAML load throws ArgumentError" do
+ orig_yamler = YAML::ENGINE.yamler
+ YAML::ENGINE.yamler = "syck"
+
+ expect { subject }.to raise_error(Bundler::GemspecError)
+
+ YAML::ENGINE.yamler = orig_yamler
+ end
+ end
+
+ context "with Psych as YAML::Engine" do
+ it "raises a GemspecError after YAML load throws Psych::SyntaxError" do
+ orig_yamler = YAML::ENGINE.yamler
+ YAML::ENGINE.yamler = "psych"
+
+ expect { subject }.to raise_error(Bundler::GemspecError)
+
+ YAML::ENGINE.yamler = orig_yamler
+ end
+ end
+ end
+ end
+
+ context "with correct YAML file", :if => defined?(Encoding) do
+ it "can load a gemspec with unicode characters with default ruby encoding" do
+ # spec_helper forces the external encoding to UTF-8 but that's not the
+ # default until Ruby 2.0
+ verbose = $VERBOSE
+ $VERBOSE = false
+ encoding = Encoding.default_external
+ Encoding.default_external = "ASCII"
+ $VERBOSE = verbose
+
+ File.open(app_gemspec_path, "wb") do |file|
+ file.puts <<-GEMSPEC.gsub(/^\s+/, "")
+ # -*- encoding: utf-8 -*-
+ Gem::Specification.new do |gem|
+ gem.author = "André the Giant"
+ end
+ GEMSPEC
+ end
+
+ expect(subject.author).to eq("André the Giant")
+
+ verbose = $VERBOSE
+ $VERBOSE = false
+ Encoding.default_external = encoding
+ $VERBOSE = verbose
+ end
+ end
+
+ it "sets loaded_from" do
+ app_gemspec_path.open("w") do |f|
+ f.puts <<-GEMSPEC
+ Gem::Specification.new do |gem|
+ gem.name = "validated"
+ end
+ GEMSPEC
+ end
+
+ expect(subject.loaded_from).to eq(app_gemspec_path.expand_path.to_s)
+ end
+
+ context "validate is true" do
+ subject { Bundler.load_gemspec_uncached(app_gemspec_path, true) }
+
+ it "validates the specification" do
+ app_gemspec_path.open("w") do |f|
+ f.puts <<-GEMSPEC
+ Gem::Specification.new do |gem|
+ gem.name = "validated"
+ end
+ GEMSPEC
+ end
+ expect(Bundler.rubygems).to receive(:validate).with have_attributes(:name => "validated")
+ subject
+ end
+ end
+
+ context "with gemspec containing local variables" do
+ before do
+ File.open(app_gemspec_path, "wb") do |f|
+ f.write strip_whitespace(<<-GEMSPEC)
+ must_not_leak = true
+ Gem::Specification.new do |gem|
+ gem.name = "leak check"
+ end
+ GEMSPEC
+ end
+ end
+
+ it "should not pollute the TOPLEVEL_BINDING" do
+ subject
+ expect(TOPLEVEL_BINDING.eval("local_variables")).to_not include(:must_not_leak)
+ end
+ end
+ end
+
+ describe "#which" do
+ let(:executable) { "executable" }
+ let(:path) { %w[/a /b c ../d /e] }
+ let(:expected) { "executable" }
+
+ before do
+ ENV["PATH"] = path.join(File::PATH_SEPARATOR)
+
+ allow(File).to receive(:file?).and_return(false)
+ allow(File).to receive(:executable?).and_return(false)
+ if expected
+ expect(File).to receive(:file?).with(expected).and_return(true)
+ expect(File).to receive(:executable?).with(expected).and_return(true)
+ end
+ end
+
+ subject { described_class.which(executable) }
+
+ shared_examples_for "it returns the correct executable" do
+ it "returns the expected file" do
+ expect(subject).to eq(expected)
+ end
+ end
+
+ it_behaves_like "it returns the correct executable"
+
+ context "when the executable in inside a quoted path" do
+ let(:expected) { "/e/executable" }
+ it_behaves_like "it returns the correct executable"
+ end
+
+ context "when the executable is not found" do
+ let(:expected) { nil }
+ it_behaves_like "it returns the correct executable"
+ end
+ end
+
+ describe "configuration" do
+ context "disable_shared_gems" do
+ it "should unset GEM_PATH with empty string" do
+ env = {}
+ expect(Bundler).to receive(:use_system_gems?).and_return(false)
+ Bundler.send(:configure_gem_path, env)
+ expect(env.keys).to include("GEM_PATH")
+ expect(env["GEM_PATH"]).to eq ""
+ end
+ end
+ end
+
+ describe "#rm_rf" do
+ context "the directory is world writable" do
+ let(:bundler_ui) { Bundler.ui }
+ it "should raise a friendly error" do
+ allow(File).to receive(:exist?).and_return(true)
+ allow(bundler_fileutils).to receive(:remove_entry_secure).and_raise(ArgumentError)
+ allow(File).to receive(:world_writable?).and_return(true)
+ message = <<EOF
+It is a security vulnerability to allow your home directory to be world-writable, and bundler can not continue.
+You should probably consider fixing this issue by running `chmod o-w ~` on *nix.
+Please refer to http://ruby-doc.org/stdlib-2.1.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure for details.
+EOF
+ expect(bundler_ui).to receive(:warn).with(message)
+ expect { Bundler.send(:rm_rf, bundled_app) }.to raise_error(Bundler::PathError)
+ end
+ end
+ end
+
+ describe "#mkdir_p" do
+ it "creates a folder at the given path" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ Bundler.mkdir_p(bundled_app.join("foo", "bar"))
+ expect(bundled_app.join("foo", "bar")).to exist
+ end
+
+ context "when mkdir_p requires sudo" do
+ it "creates a new folder using sudo" do
+ expect(Bundler).to receive(:requires_sudo?).and_return(true)
+ expect(Bundler).to receive(:sudo).and_return true
+ Bundler.mkdir_p(bundled_app.join("foo"))
+ end
+ end
+
+ context "with :no_sudo option" do
+ it "forces mkdir_p to not use sudo" do
+ expect(Bundler).to receive(:requires_sudo?).and_return(true)
+ expect(Bundler).to_not receive(:sudo)
+ Bundler.mkdir_p(bundled_app.join("foo"), :no_sudo => true)
+ end
+ end
+ end
+
+ describe "#user_home" do
+ context "home directory is set" do
+ it "should return the user home" do
+ path = "/home/oggy"
+ allow(Bundler.rubygems).to receive(:user_home).and_return(path)
+ allow(File).to receive(:directory?).with(path).and_return true
+ allow(File).to receive(:writable?).with(path).and_return true
+ expect(Bundler.user_home).to eq(Pathname(path))
+ end
+
+ context "is not a directory" do
+ it "should issue a warning and return a temporary user home" do
+ path = "/home/oggy"
+ allow(Bundler.rubygems).to receive(:user_home).and_return(path)
+ allow(File).to receive(:directory?).with(path).and_return false
+ allow(Etc).to receive(:getlogin).and_return("USER")
+ allow(Dir).to receive(:tmpdir).and_return("/TMP")
+ allow(FileTest).to receive(:exist?).with("/TMP/bundler/home").and_return(true)
+ expect(FileUtils).to receive(:mkpath).with("/TMP/bundler/home/USER")
+ message = <<EOF
+`/home/oggy` is not a directory.
+Bundler will use `/TMP/bundler/home/USER' as your home directory temporarily.
+EOF
+ expect(Bundler.ui).to receive(:warn).with(message)
+ expect(Bundler.user_home).to eq(Pathname("/TMP/bundler/home/USER"))
+ end
+ end
+
+ context "is not writable" do
+ let(:path) { "/home/oggy" }
+ let(:dotbundle) { "/home/oggy/.bundle" }
+
+ it "should issue a warning and return a temporary user home" do
+ allow(Bundler.rubygems).to receive(:user_home).and_return(path)
+ allow(File).to receive(:directory?).with(path).and_return true
+ allow(File).to receive(:writable?).with(path).and_return false
+ allow(File).to receive(:directory?).with(dotbundle).and_return false
+ allow(Etc).to receive(:getlogin).and_return("USER")
+ allow(Dir).to receive(:tmpdir).and_return("/TMP")
+ allow(FileTest).to receive(:exist?).with("/TMP/bundler/home").and_return(true)
+ expect(FileUtils).to receive(:mkpath).with("/TMP/bundler/home/USER")
+ message = <<EOF
+`/home/oggy` is not writable.
+Bundler will use `/TMP/bundler/home/USER' as your home directory temporarily.
+EOF
+ expect(Bundler.ui).to receive(:warn).with(message)
+ expect(Bundler.user_home).to eq(Pathname("/TMP/bundler/home/USER"))
+ end
+
+ context ".bundle exists and have correct permissions" do
+ it "should return the user home" do
+ allow(Bundler.rubygems).to receive(:user_home).and_return(path)
+ allow(File).to receive(:directory?).with(path).and_return true
+ allow(File).to receive(:writable?).with(path).and_return false
+ allow(File).to receive(:directory?).with(dotbundle).and_return true
+ allow(File).to receive(:writable?).with(dotbundle).and_return true
+ expect(Bundler.user_home).to eq(Pathname(path))
+ end
+ end
+ end
+ end
+
+ context "home directory is not set" do
+ it "should issue warning and return a temporary user home" do
+ allow(Bundler.rubygems).to receive(:user_home).and_return(nil)
+ allow(Etc).to receive(:getlogin).and_return("USER")
+ allow(Dir).to receive(:tmpdir).and_return("/TMP")
+ allow(FileTest).to receive(:exist?).with("/TMP/bundler/home").and_return(true)
+ expect(FileUtils).to receive(:mkpath).with("/TMP/bundler/home/USER")
+ message = <<EOF
+Your home directory is not set.
+Bundler will use `/TMP/bundler/home/USER' as your home directory temporarily.
+EOF
+ expect(Bundler.ui).to receive(:warn).with(message)
+ expect(Bundler.user_home).to eq(Pathname("/TMP/bundler/home/USER"))
+ end
+ end
+ end
+
+ describe "#tmp_home_path" do
+ it "should create temporary user home" do
+ allow(Dir).to receive(:tmpdir).and_return("/TMP")
+ allow(FileTest).to receive(:exist?).with("/TMP/bundler/home").and_return(false)
+ expect(FileUtils).to receive(:mkpath).once.ordered.with("/TMP/bundler/home")
+ expect(FileUtils).to receive(:mkpath).once.ordered.with("/TMP/bundler/home/USER")
+ expect(File).to receive(:chmod).with(0o777, "/TMP/bundler/home")
+ expect(Bundler.tmp_home_path("USER", "")).to eq(Pathname("/TMP/bundler/home/USER"))
+ end
+ end
+
+ describe "#requires_sudo?" do
+ let!(:tmpdir) { Dir.mktmpdir }
+ let(:bundle_path) { Pathname("#{tmpdir}/bundle") }
+
+ def clear_cached_requires_sudo
+ # Private in ruby 1.8.7
+ return unless Bundler.instance_variable_defined?(:@requires_sudo_ran)
+ Bundler.send(:remove_instance_variable, :@requires_sudo_ran)
+ Bundler.send(:remove_instance_variable, :@requires_sudo)
+ end
+
+ before do
+ clear_cached_requires_sudo
+ allow(Bundler).to receive(:which).with("sudo").and_return("/usr/bin/sudo")
+ allow(Bundler).to receive(:bundle_path).and_return(bundle_path)
+ end
+
+ after do
+ FileUtils.rm_rf(tmpdir)
+ clear_cached_requires_sudo
+ end
+
+ subject { Bundler.requires_sudo? }
+
+ context "bundle_path doesn't exist" do
+ it { should be false }
+
+ context "and parent dir can't be written" do
+ before do
+ FileUtils.chmod(0o500, tmpdir)
+ end
+
+ it { should be true }
+ end
+
+ context "with unwritable files in a parent dir" do
+ # Regression test for https://github.com/bundler/bundler/pull/6316
+ # It doesn't matter if there are other unwritable files so long as
+ # bundle_path can be created
+ before do
+ file = File.join(tmpdir, "unrelated_file")
+ FileUtils.touch(file)
+ FileUtils.chmod(0o400, file)
+ end
+
+ it { should be false }
+ end
+ end
+
+ context "bundle_path exists" do
+ before do
+ FileUtils.mkdir_p(bundle_path)
+ end
+
+ it { should be false }
+
+ context "and is unwritable" do
+ before do
+ FileUtils.chmod(0o500, bundle_path)
+ end
+
+ it { should be true }
+ end
+ end
+ end
+
+ describe "#requires_sudo?" do
+ before do
+ allow(Bundler).to receive(:which).with("sudo").and_return("/usr/bin/sudo")
+ FileUtils.mkdir_p("tmp/vendor/bundle")
+ FileUtils.mkdir_p("tmp/vendor/bin_dir")
+ end
+ after do
+ FileUtils.rm_rf("tmp/vendor/bundle")
+ FileUtils.rm_rf("tmp/vendor/bin_dir")
+ if Bundler.respond_to?(:remove_instance_variable)
+ Bundler.remove_instance_variable(:@requires_sudo_ran)
+ Bundler.remove_instance_variable(:@requires_sudo)
+ else
+ # TODO: Remove these code when Bundler drops Ruby 1.8.7 support
+ Bundler.send(:remove_instance_variable, :@requires_sudo_ran)
+ Bundler.send(:remove_instance_variable, :@requires_sudo)
+ end
+ end
+ context "writable paths" do
+ it "should return false and display nothing" do
+ allow(Bundler).to receive(:bundle_path).and_return(Pathname("tmp/vendor/bundle"))
+ expect(Bundler.ui).to_not receive(:warn)
+ expect(Bundler.requires_sudo?).to eq(false)
+ end
+ end
+ context "unwritable paths" do
+ before do
+ FileUtils.touch("tmp/vendor/bundle/unwritable1.txt")
+ FileUtils.touch("tmp/vendor/bundle/unwritable2.txt")
+ FileUtils.touch("tmp/vendor/bin_dir/unwritable3.txt")
+ FileUtils.chmod(0o400, "tmp/vendor/bundle/unwritable1.txt")
+ FileUtils.chmod(0o400, "tmp/vendor/bundle/unwritable2.txt")
+ FileUtils.chmod(0o400, "tmp/vendor/bin_dir/unwritable3.txt")
+ end
+ it "should return true and display warn message" do
+ allow(Bundler).to receive(:bundle_path).and_return(Pathname("tmp/vendor/bundle"))
+ bin_dir = Pathname("tmp/vendor/bin_dir/")
+
+ # allow File#writable? to be called with args other than the stubbed on below
+ allow(File).to receive(:writable?).and_call_original
+
+ # fake make the directory unwritable
+ allow(File).to receive(:writable?).with(bin_dir).and_return(false)
+ allow(Bundler).to receive(:system_bindir).and_return(Pathname("tmp/vendor/bin_dir/"))
+ message = <<-MESSAGE.chomp
+Following files may not be writable, so sudo is needed:
+ tmp/vendor/bin_dir/
+ tmp/vendor/bundle/unwritable1.txt
+ tmp/vendor/bundle/unwritable2.txt
+MESSAGE
+ expect(Bundler.ui).to receive(:warn).with(message)
+ expect(Bundler.requires_sudo?).to eq(true)
+ end
+ end
+ end
+
+ context "user cache dir" do
+ let(:home_path) { Pathname.new(ENV["HOME"]) }
+
+ let(:xdg_data_home) { home_path.join(".local") }
+ let(:xdg_cache_home) { home_path.join(".cache") }
+ let(:xdg_config_home) { home_path.join(".config") }
+
+ let(:bundle_user_home_default) { home_path.join(".bundle") }
+ let(:bundle_user_home_custom) { xdg_data_home.join("bundle") }
+
+ let(:bundle_user_cache_default) { bundle_user_home_default.join("cache") }
+ let(:bundle_user_cache_custom) { xdg_cache_home.join("bundle") }
+
+ let(:bundle_user_config_default) { bundle_user_home_default.join("config") }
+ let(:bundle_user_config_custom) { xdg_config_home.join("bundle") }
+
+ let(:bundle_user_plugin_default) { bundle_user_home_default.join("plugin") }
+ let(:bundle_user_plugin_custom) { xdg_data_home.join("bundle").join("plugin") }
+
+ describe "#user_bundle_path" do
+ before do
+ allow(Bundler.rubygems).to receive(:user_home).and_return(home_path)
+ end
+
+ it "should use the default home path" do
+ expect(Bundler.user_bundle_path).to eq(bundle_user_home_default)
+ expect(Bundler.user_bundle_path("home")).to eq(bundle_user_home_default)
+ expect(Bundler.user_bundle_path("cache")).to eq(bundle_user_cache_default)
+ expect(Bundler.user_cache).to eq(bundle_user_cache_default)
+ expect(Bundler.user_bundle_path("config")).to eq(bundle_user_config_default)
+ expect(Bundler.user_bundle_path("plugin")).to eq(bundle_user_plugin_default)
+ end
+
+ it "should use custom home path as root for other paths" do
+ ENV["BUNDLE_USER_HOME"] = bundle_user_home_custom.to_s
+ expect(Bundler.user_bundle_path).to eq(bundle_user_home_custom)
+ expect(Bundler.user_bundle_path("home")).to eq(bundle_user_home_custom)
+ expect(Bundler.user_bundle_path("cache")).to eq(bundle_user_home_custom.join("cache"))
+ expect(Bundler.user_cache).to eq(bundle_user_home_custom.join("cache"))
+ expect(Bundler.user_bundle_path("config")).to eq(bundle_user_home_custom.join("config"))
+ expect(Bundler.user_bundle_path("plugin")).to eq(bundle_user_home_custom.join("plugin"))
+ end
+
+ it "should use all custom paths, except home" do
+ ENV.delete("BUNDLE_USER_HOME")
+ ENV["BUNDLE_USER_CACHE"] = bundle_user_cache_custom.to_s
+ ENV["BUNDLE_USER_CONFIG"] = bundle_user_config_custom.to_s
+ ENV["BUNDLE_USER_PLUGIN"] = bundle_user_plugin_custom.to_s
+ expect(Bundler.user_bundle_path).to eq(bundle_user_home_default)
+ expect(Bundler.user_bundle_path("home")).to eq(bundle_user_home_default)
+ expect(Bundler.user_bundle_path("cache")).to eq(bundle_user_cache_custom)
+ expect(Bundler.user_cache).to eq(bundle_user_cache_custom)
+ expect(Bundler.user_bundle_path("config")).to eq(bundle_user_config_custom)
+ expect(Bundler.user_bundle_path("plugin")).to eq(bundle_user_plugin_custom)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb
new file mode 100644
index 0000000000..c82d46587e
--- /dev/null
+++ b/spec/bundler/bundler/cli_spec.rb
@@ -0,0 +1,173 @@
+# frozen_string_literal: true
+
+require "bundler/cli"
+
+RSpec.describe "bundle executable" do
+ it "returns non-zero exit status when passed unrecognized options" do
+ bundle "--invalid_argument"
+ expect(exitstatus).to_not be_zero if exitstatus
+ end
+
+ it "returns non-zero exit status when passed unrecognized task" do
+ bundle "unrecognized-task"
+ expect(exitstatus).to_not be_zero if exitstatus
+ end
+
+ it "looks for a binary and executes it if it's named bundler-<task>" do
+ File.open(tmp("bundler-testtasks"), "w", 0o755) do |f|
+ ruby = ENV["BUNDLE_RUBY"] || "/usr/bin/env ruby"
+ f.puts "#!#{ruby}\nputs 'Hello, world'\n"
+ end
+
+ with_path_added(tmp) do
+ bundle "testtasks"
+ end
+
+ expect(exitstatus).to be_zero if exitstatus
+ expect(out).to eq("Hello, world")
+ end
+
+ context "with no arguments" do
+ it "prints a concise help message", :bundler => "2" do
+ bundle! ""
+ expect(last_command.stderr).to be_empty
+ expect(last_command.stdout).to include("Bundler version #{Bundler::VERSION}").
+ and include("\n\nBundler commands:\n\n").
+ and include("\n\n Primary commands:\n").
+ and include("\n\n Utilities:\n").
+ and include("\n\nOptions:\n")
+ end
+ end
+
+ context "when ENV['BUNDLE_GEMFILE'] is set to an empty string" do
+ it "ignores it" do
+ gemfile bundled_app("Gemfile"), <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ bundle :install, :env => { "BUNDLE_GEMFILE" => "" }
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "when ENV['RUBYGEMS_GEMDEPS'] is set" do
+ it "displays a warning" do
+ gemfile bundled_app("Gemfile"), <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ bundle :install, :env => { "RUBYGEMS_GEMDEPS" => "foo" }
+ expect(out).to include("RUBYGEMS_GEMDEPS")
+ expect(out).to include("conflict with Bundler")
+
+ bundle :install, :env => { "RUBYGEMS_GEMDEPS" => "" }
+ expect(out).not_to include("RUBYGEMS_GEMDEPS")
+ end
+ end
+
+ context "with --verbose" do
+ it "prints the running command" do
+ gemfile ""
+ bundle! "info bundler", :verbose => true
+ expect(last_command.stdout).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}")
+ end
+
+ it "doesn't print defaults" do
+ install_gemfile! "", :verbose => true
+ expect(last_command.stdout).to start_with("Running `bundle install --retry 0 --verbose` with bundler #{Bundler::VERSION}")
+ end
+
+ it "doesn't print defaults" do
+ install_gemfile! "", :verbose => true
+ expect(last_command.stdout).to start_with("Running `bundle install --retry 0 --verbose` with bundler #{Bundler::VERSION}")
+ end
+ end
+
+ describe "printing the outdated warning" do
+ shared_examples_for "no warning" do
+ it "prints no warning" do
+ bundle "fail"
+ expect(last_command.stdboth).to eq("Could not find command \"fail\".")
+ end
+ end
+
+ let(:bundler_version) { "1.1" }
+ let(:latest_version) { nil }
+ before do
+ bundle! "config --global disable_version_check false"
+
+ simulate_bundler_version(bundler_version)
+ if latest_version
+ info_path = home(".bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/bundler")
+ info_path.parent.mkpath
+ info_path.open("w") {|f| f.write "#{latest_version}\n" }
+ end
+ end
+
+ context "when there is no latest version" do
+ include_examples "no warning"
+ end
+
+ context "when the latest version is equal to the current version" do
+ let(:latest_version) { bundler_version }
+ include_examples "no warning"
+ end
+
+ context "when the latest version is less than the current version" do
+ let(:latest_version) { "0.9" }
+ include_examples "no warning"
+ end
+
+ context "when the latest version is greater than the current version" do
+ let(:latest_version) { "222.0" }
+ it "prints the version warning" do
+ bundle "fail"
+ expect(last_command.stdout).to start_with(<<-EOS.strip)
+The latest bundler is #{latest_version}, but you are currently running #{bundler_version}.
+To install the latest version, run `gem install bundler`
+ EOS
+ end
+
+ context "and disable_version_check is set" do
+ before { bundle! "config disable_version_check true" }
+ include_examples "no warning"
+ end
+
+ context "running a parseable command" do
+ it "prints no warning" do
+ bundle! "config --parseable foo"
+ expect(last_command.stdboth).to eq ""
+
+ bundle "platform --ruby"
+ expect(last_command.stdboth).to eq "Could not locate Gemfile"
+ end
+ end
+
+ context "and is a pre-release" do
+ let(:latest_version) { "222.0.0.pre.4" }
+ it "prints the version warning" do
+ bundle "fail"
+ expect(last_command.stdout).to start_with(<<-EOS.strip)
+The latest bundler is #{latest_version}, but you are currently running #{bundler_version}.
+To install the latest version, run `gem install bundler --pre`
+ EOS
+ end
+ end
+ end
+ end
+end
+
+RSpec.describe "bundler executable" do
+ it "shows the bundler version just as the `bundle` executable does", :bundler => "< 2" do
+ bundler "--version"
+ expect(out).to eq("Bundler version #{Bundler::VERSION}")
+ end
+
+ it "shows the bundler version just as the `bundle` executable does", :bundler => "2" do
+ bundler "--version"
+ expect(out).to eq(Bundler::VERSION)
+ end
+end
diff --git a/spec/bundler/bundler/compact_index_client/updater_spec.rb b/spec/bundler/bundler/compact_index_client/updater_spec.rb
new file mode 100644
index 0000000000..fd554a7b0d
--- /dev/null
+++ b/spec/bundler/bundler/compact_index_client/updater_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require "net/http"
+require "bundler/compact_index_client"
+require "bundler/compact_index_client/updater"
+
+RSpec.describe Bundler::CompactIndexClient::Updater do
+ let(:fetcher) { double(:fetcher) }
+ let(:local_path) { Pathname("/tmp/localpath") }
+ let(:remote_path) { double(:remote_path) }
+
+ subject(:updater) { described_class.new(fetcher) }
+
+ context "when the ETag header is missing" do
+ # Regression test for https://github.com/bundler/bundler/issues/5463
+
+ let(:response) { double(:response, :body => "") }
+
+ it "MisMatchedChecksumError is raised" do
+ # Twice: #update retries on failure
+ expect(response).to receive(:[]).with("Content-Encoding").twice { "" }
+ expect(response).to receive(:[]).with("ETag").twice { nil }
+ expect(fetcher).to receive(:call).twice { response }
+
+ expect do
+ updater.update(local_path, remote_path)
+ end.to raise_error(Bundler::CompactIndexClient::Updater::MisMatchedChecksumError)
+ end
+ end
+
+ context "when the download is corrupt" do
+ let(:response) { double(:response, :body => "") }
+
+ it "raises HTTPError" do
+ expect(response).to receive(:[]).with("Content-Encoding") { "gzip" }
+ expect(fetcher).to receive(:call) { response }
+
+ expect do
+ updater.update(local_path, remote_path)
+ end.to raise_error(Bundler::HTTPError)
+ end
+ end
+
+ context "when bundler doesn't have permissions on Dir.tmpdir" do
+ let(:response) { double(:response, :body => "") }
+
+ it "Errno::EACCES is raised" do
+ allow(Dir).to receive(:mktmpdir) { raise Errno::EACCES }
+
+ expect do
+ updater.update(local_path, remote_path)
+ end.to raise_error(Bundler::PermissionError)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb
new file mode 100644
index 0000000000..2ed87ec81d
--- /dev/null
+++ b/spec/bundler/bundler/definition_spec.rb
@@ -0,0 +1,358 @@
+# frozen_string_literal: true
+
+require "bundler/definition"
+
+RSpec.describe Bundler::Definition do
+ describe "#lock" do
+ before do
+ allow(Bundler).to receive(:settings) { Bundler::Settings.new(".") }
+ allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") }
+ allow(Bundler).to receive(:ui) { double("UI", :info => "", :debug => "") }
+ end
+ context "when it's not possible to write to the file" do
+ subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) }
+
+ it "raises an PermissionError with explanation" do
+ expect(File).to receive(:open).with("Gemfile.lock", "wb").
+ and_raise(Errno::EACCES)
+ expect { subject.lock("Gemfile.lock") }.
+ to raise_error(Bundler::PermissionError, /Gemfile\.lock/)
+ end
+ end
+ context "when a temporary resource access issue occurs" do
+ subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) }
+
+ it "raises a TemporaryResourceError with explanation" do
+ expect(File).to receive(:open).with("Gemfile.lock", "wb").
+ and_raise(Errno::EAGAIN)
+ expect { subject.lock("Gemfile.lock") }.
+ to raise_error(Bundler::TemporaryResourceError, /temporarily unavailable/)
+ end
+ end
+ end
+
+ describe "detects changes" do
+ it "for a path gem with changes", :bundler => "< 2" do
+ build_lib "foo", "1.0", :path => lib_path("foo")
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ end
+
+ bundle :install, :env => { "DEBUG" => 1 }
+
+ expect(out).to match(/re-resolving dependencies/)
+ lockfile_should_be <<-G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack (= 1.0)
+
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "for a path gem with changes", :bundler => "2" do
+ build_lib "foo", "1.0", :path => lib_path("foo")
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ end
+
+ bundle :install, :env => { "DEBUG" => 1 }
+
+ expect(out).to match(/re-resolving dependencies/)
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack (= 1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "for a path gem with deps and no changes", :bundler => "< 2" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ s.add_development_dependency "net-ssh", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ bundle :check, :env => { "DEBUG" => 1 }
+
+ expect(out).to match(/using resolution from the lockfile/)
+ lockfile_should_be <<-G
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack (= 1.0)
+
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "for a path gem with deps and no changes", :bundler => "2" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ s.add_development_dependency "net-ssh", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ bundle :check, :env => { "DEBUG" => 1 }
+
+ expect(out).to match(/using resolution from the lockfile/)
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PATH
+ remote: #{lib_path("foo")}
+ specs:
+ foo (1.0)
+ rack (= 1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "for a rubygems gem" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "foo"
+ G
+
+ bundle :check, :env => { "DEBUG" => 1 }
+
+ expect(out).to match(/using resolution from the lockfile/)
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+ end
+
+ describe "initialize" do
+ context "gem version promoter" do
+ context "with lockfile" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo"
+ G
+ end
+
+ it "should get a locked specs list when updating all" do
+ definition = Bundler::Definition.new(bundled_app("Gemfile.lock"), [], Bundler::SourceList.new, true)
+ locked_specs = definition.gem_version_promoter.locked_specs
+ expect(locked_specs.to_a.map(&:name)).to eq ["foo"]
+ expect(definition.instance_variable_get("@locked_specs").empty?).to eq true
+ end
+ end
+
+ context "without gemfile or lockfile" do
+ it "should not attempt to parse empty lockfile contents" do
+ definition = Bundler::Definition.new(nil, [], mock_source_list, true)
+ expect(definition.gem_version_promoter.locked_specs.to_a).to eq []
+ end
+ end
+
+ context "eager unlock" do
+ let(:source_list) do
+ Bundler::SourceList.new.tap do |source_list|
+ source_list.global_rubygems_source = "file://#{gem_repo4}"
+ end
+ end
+
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'isolated_owner'
+
+ gem 'shared_owner_a'
+ gem 'shared_owner_b'
+ G
+
+ lockfile <<-L
+ GEM
+ remote: file://#{gem_repo4}
+ specs:
+ isolated_dep (2.0.1)
+ isolated_owner (1.0.1)
+ isolated_dep (~> 2.0)
+ shared_dep (5.0.1)
+ shared_owner_a (3.0.1)
+ shared_dep (~> 5.0)
+ shared_owner_b (4.0.1)
+ shared_dep (~> 5.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ shared_owner_a
+ shared_owner_b
+ isolated_owner
+
+ BUNDLED WITH
+ 1.13.0
+ L
+ end
+
+ it "should not eagerly unlock shared dependency with bundle install conservative updating behavior" do
+ updated_deps_in_gemfile = [Bundler::Dependency.new("isolated_owner", ">= 0"),
+ Bundler::Dependency.new("shared_owner_a", "3.0.2"),
+ Bundler::Dependency.new("shared_owner_b", ">= 0")]
+ unlock_hash_for_bundle_install = {}
+ definition = Bundler::Definition.new(
+ bundled_app("Gemfile.lock"),
+ updated_deps_in_gemfile,
+ source_list,
+ unlock_hash_for_bundle_install
+ )
+ locked = definition.send(:converge_locked_specs).map(&:name)
+ expect(locked).to include "shared_dep"
+ end
+
+ it "should not eagerly unlock shared dependency with bundle update conservative updating behavior" do
+ updated_deps_in_gemfile = [Bundler::Dependency.new("isolated_owner", ">= 0"),
+ Bundler::Dependency.new("shared_owner_a", ">= 0"),
+ Bundler::Dependency.new("shared_owner_b", ">= 0")]
+ definition = Bundler::Definition.new(
+ bundled_app("Gemfile.lock"),
+ updated_deps_in_gemfile,
+ source_list,
+ :gems => ["shared_owner_a"], :lock_shared_dependencies => true
+ )
+ locked = definition.send(:converge_locked_specs).map(&:name)
+ expect(locked).to eq %w[isolated_dep isolated_owner shared_dep shared_owner_b]
+ expect(locked.include?("shared_dep")).to be_truthy
+ end
+ end
+ end
+ end
+
+ describe "find_resolved_spec" do
+ it "with no platform set in SpecSet" do
+ ss = Bundler::SpecSet.new([build_stub_spec("a", "1.0"), build_stub_spec("b", "1.0")])
+ dfn = Bundler::Definition.new(nil, [], mock_source_list, true)
+ dfn.instance_variable_set("@specs", ss)
+ found = dfn.find_resolved_spec(build_spec("a", "0.9", "ruby").first)
+ expect(found.name).to eq "a"
+ expect(found.version.to_s).to eq "1.0"
+ end
+ end
+
+ describe "find_indexed_specs" do
+ it "with no platform set in indexed specs" do
+ index = Bundler::Index.new
+ %w[1.0.0 1.0.1 1.1.0].each {|v| index << build_stub_spec("foo", v) }
+
+ dfn = Bundler::Definition.new(nil, [], mock_source_list, true)
+ dfn.instance_variable_set("@index", index)
+ found = dfn.find_indexed_specs(build_spec("foo", "0.9", "ruby").first)
+ expect(found.length).to eq 3
+ end
+ end
+
+ def build_stub_spec(name, version)
+ Bundler::StubSpecification.new(name, version, nil, nil)
+ end
+
+ def mock_source_list
+ Class.new do
+ def all_sources
+ []
+ end
+
+ def path_sources
+ []
+ end
+
+ def rubygems_remotes
+ []
+ end
+
+ def replace_sources!(arg)
+ nil
+ end
+ end.new
+ end
+end
diff --git a/spec/bundler/bundler/dep_proxy_spec.rb b/spec/bundler/bundler/dep_proxy_spec.rb
new file mode 100644
index 0000000000..0f8d6b1076
--- /dev/null
+++ b/spec/bundler/bundler/dep_proxy_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::DepProxy do
+ let(:dep) { Bundler::Dependency.new("rake", ">= 0") }
+ subject { described_class.new(dep, Gem::Platform::RUBY) }
+ let(:same) { subject }
+ let(:other) { subject.dup }
+ let(:different) { described_class.new(dep, Gem::Platform::JAVA) }
+
+ describe "#eql?" do
+ it { expect(subject.eql?(same)).to be true }
+ it { expect(subject.eql?(other)).to be true }
+ it { expect(subject.eql?(different)).to be false }
+ it { expect(subject.eql?(nil)).to be false }
+ it { expect(subject.eql?("foobar")).to be false }
+ end
+
+ describe "#hash" do
+ it { expect(subject.hash).to eq(same.hash) }
+ it { expect(subject.hash).to eq(other.hash) }
+ end
+end
diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb
new file mode 100644
index 0000000000..bffe4f1608
--- /dev/null
+++ b/spec/bundler/bundler/dsl_spec.rb
@@ -0,0 +1,289 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Dsl do
+ before do
+ @rubygems = double("rubygems")
+ allow(Bundler::Source::Rubygems).to receive(:new) { @rubygems }
+ end
+
+ describe "#git_source" do
+ it "registers custom hosts" do
+ subject.git_source(:example) {|repo_name| "git@git.example.com:#{repo_name}.git" }
+ subject.git_source(:foobar) {|repo_name| "git@foobar.com:#{repo_name}.git" }
+ subject.gem("dobry-pies", :example => "strzalek/dobry-pies")
+ example_uri = "git@git.example.com:strzalek/dobry-pies.git"
+ expect(subject.dependencies.first.source.uri).to eq(example_uri)
+ end
+
+ it "raises exception on invalid hostname" do
+ expect do
+ subject.git_source(:group) {|repo_name| "git@git.example.com:#{repo_name}.git" }
+ end.to raise_error(Bundler::InvalidOption)
+ end
+
+ it "expects block passed" do
+ expect { subject.git_source(:example) }.to raise_error(Bundler::InvalidOption)
+ end
+
+ context "default hosts (git, gist)", :bundler => "< 2" do
+ it "converts :github to :git" do
+ subject.gem("sparks", :github => "indirect/sparks")
+ github_uri = "git://github.com/indirect/sparks.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ end
+
+ it "converts numeric :gist to :git" do
+ subject.gem("not-really-a-gem", :gist => 2_859_988)
+ github_uri = "https://gist.github.com/2859988.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ end
+
+ it "converts :gist to :git" do
+ subject.gem("not-really-a-gem", :gist => "2859988")
+ github_uri = "https://gist.github.com/2859988.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ end
+
+ it "converts 'rails' to 'rails/rails'" do
+ subject.gem("rails", :github => "rails")
+ github_uri = "git://github.com/rails/rails.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ end
+
+ it "converts :bitbucket to :git" do
+ subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails")
+ bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/flatlab-rails.git"
+ expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri)
+ end
+
+ it "converts 'mcorp' to 'mcorp/mcorp'" do
+ subject.gem("not-really-a-gem", :bitbucket => "mcorp")
+ bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/mcorp.git"
+ expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri)
+ end
+ end
+
+ context "default git sources", :bundler => "2" do
+ it "has none" do
+ expect(subject.instance_variable_get(:@git_sources)).to eq({})
+ end
+ end
+ end
+
+ describe "#method_missing" do
+ it "raises an error for unknown DSL methods" do
+ expect(Bundler).to receive(:read_file).with(bundled_app("Gemfile").to_s).
+ and_return("unknown")
+
+ error_msg = "There was an error parsing `Gemfile`: Undefined local variable or method `unknown' for Gemfile. Bundler cannot continue."
+ expect { subject.eval_gemfile("Gemfile") }.
+ to raise_error(Bundler::GemfileError, Regexp.new(error_msg))
+ end
+ end
+
+ describe "#eval_gemfile" do
+ it "handles syntax errors with a useful message" do
+ expect(Bundler).to receive(:read_file).with(bundled_app("Gemfile").to_s).and_return("}")
+ expect { subject.eval_gemfile("Gemfile") }.
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`: (syntax error, unexpected tSTRING_DEND|(compile error - )?syntax error, unexpected '\}'). Bundler cannot continue./)
+ end
+
+ it "distinguishes syntax errors from evaluation errors" do
+ expect(Bundler).to receive(:read_file).with(bundled_app("Gemfile").to_s).and_return(
+ "ruby '2.1.5', :engine => 'ruby', :engine_version => '1.2.4'"
+ )
+ expect { subject.eval_gemfile("Gemfile") }.
+ to raise_error(Bundler::GemfileError, /There was an error evaluating `Gemfile`: ruby_version must match the :engine_version for MRI/)
+ end
+ end
+
+ describe "#gem" do
+ [:ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24, :ruby_25, :mri, :mri_18, :mri_19,
+ :mri_20, :mri_21, :mri_22, :mri_23, :mri_24, :mri_25, :jruby, :rbx, :truffleruby].each do |platform|
+ it "allows #{platform} as a valid platform" do
+ subject.gem("foo", :platform => platform)
+ end
+ end
+
+ it "rejects invalid platforms" do
+ expect { subject.gem("foo", :platform => :bogus) }.
+ to raise_error(Bundler::GemfileError, /is not a valid platform/)
+ end
+
+ it "rejects empty gem name" do
+ expect { subject.gem("") }.
+ to raise_error(Bundler::GemfileError, /an empty gem name is not valid/)
+ end
+
+ it "rejects with a leading space in the name" do
+ expect { subject.gem(" foo") }.
+ to raise_error(Bundler::GemfileError, /' foo' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a trailing space in the name" do
+ expect { subject.gem("foo ") }.
+ to raise_error(Bundler::GemfileError, /'foo ' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a space in the gem name" do
+ expect { subject.gem("fo o") }.
+ to raise_error(Bundler::GemfileError, /'fo o' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a tab in the gem name" do
+ expect { subject.gem("fo\to") }.
+ to raise_error(Bundler::GemfileError, /'fo\to' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a newline in the gem name" do
+ expect { subject.gem("fo\no") }.
+ to raise_error(Bundler::GemfileError, /'fo\no' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a carriage return in the gem name" do
+ expect { subject.gem("fo\ro") }.
+ to raise_error(Bundler::GemfileError, /'fo\ro' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects with a form feed in the gem name" do
+ expect { subject.gem("fo\fo") }.
+ to raise_error(Bundler::GemfileError, /'fo\fo' is not a valid gem name because it contains whitespace/)
+ end
+
+ it "rejects symbols as gem name" do
+ expect { subject.gem(:foo) }.
+ to raise_error(Bundler::GemfileError, /You need to specify gem names as Strings. Use 'gem "foo"' instead/)
+ end
+
+ it "rejects branch option on non-git gems" do
+ expect { subject.gem("foo", :branch => "test") }.
+ to raise_error(Bundler::GemfileError, /The `branch` option for `gem 'foo'` is not allowed. Only gems with a git source can specify a branch/)
+ end
+
+ it "allows specifying a branch on git gems" do
+ subject.gem("foo", :branch => "test", :git => "http://mytestrepo")
+ dep = subject.dependencies.last
+ expect(dep.name).to eq "foo"
+ end
+
+ it "allows specifying a branch on git gems with a git_source" do
+ subject.git_source(:test_source) {|n| "https://github.com/#{n}" }
+ subject.gem("foo", :branch => "test", :test_source => "bundler/bundler")
+ dep = subject.dependencies.last
+ expect(dep.name).to eq "foo"
+ end
+ end
+
+ describe "#gemspec" do
+ let(:spec) do
+ Gem::Specification.new do |gem|
+ gem.name = "example"
+ gem.platform = platform
+ end
+ end
+
+ before do
+ allow(Dir).to receive(:[]).and_return(["spec_path"])
+ allow(Bundler).to receive(:load_gemspec).with("spec_path").and_return(spec)
+ allow(Bundler).to receive(:default_gemfile).and_return(Pathname.new("./Gemfile"))
+ end
+
+ context "with a ruby platform" do
+ let(:platform) { "ruby" }
+
+ it "keeps track of the ruby platforms in the dependency" do
+ subject.gemspec
+ expect(subject.dependencies.last.platforms).to eq(Bundler::Dependency::REVERSE_PLATFORM_MAP[Gem::Platform::RUBY])
+ end
+ end
+
+ context "with a jruby platform" do
+ let(:platform) { "java" }
+
+ it "keeps track of the jruby platforms in the dependency" do
+ allow(Gem::Platform).to receive(:local).and_return(java)
+ subject.gemspec
+ expect(subject.dependencies.last.platforms).to eq(Bundler::Dependency::REVERSE_PLATFORM_MAP[Gem::Platform::JAVA])
+ end
+ end
+ end
+
+ context "can bundle groups of gems with" do
+ # git "https://github.com/rails/rails.git" do
+ # gem "railties"
+ # gem "action_pack"
+ # gem "active_model"
+ # end
+ describe "#git" do
+ it "from a single repo" do
+ rails_gems = %w[railties action_pack active_model]
+ subject.git "https://github.com/rails/rails.git" do
+ rails_gems.each {|rails_gem| subject.send :gem, rails_gem }
+ end
+ expect(subject.dependencies.map(&:name)).to match_array rails_gems
+ end
+ end
+
+ # github 'spree' do
+ # gem 'spree_core'
+ # gem 'spree_api'
+ # gem 'spree_backend'
+ # end
+ describe "#github", :bundler => "< 2" do
+ it "from github" do
+ spree_gems = %w[spree_core spree_api spree_backend]
+ subject.github "spree" do
+ spree_gems.each {|spree_gem| subject.send :gem, spree_gem }
+ end
+
+ subject.dependencies.each do |d|
+ expect(d.source.uri).to eq("git://github.com/spree/spree.git")
+ end
+ end
+ end
+
+ describe "#github", :bundler => "2" do
+ it "from github" do
+ expect do
+ spree_gems = %w[spree_core spree_api spree_backend]
+ subject.github "spree" do
+ spree_gems.each {|spree_gem| subject.send :gem, spree_gem }
+ end
+ end.to raise_error(Bundler::DeprecatedError, /github method has been removed/)
+ end
+ end
+ end
+
+ describe "syntax errors" do
+ it "will raise a Bundler::GemfileError" do
+ gemfile "gem 'foo', :path => /unquoted/string/syntax/error"
+ expect { Bundler::Dsl.evaluate(bundled_app("Gemfile"), nil, true) }.
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`:( compile error -)? unknown regexp options - trg. Bundler cannot continue./)
+ end
+ end
+
+ describe "Runtime errors", :unless => Bundler.current_ruby.on_18? do
+ it "will raise a Bundler::GemfileError" do
+ gemfile "s = 'foo'.freeze; s.strip!"
+ expect { Bundler::Dsl.evaluate(bundled_app("Gemfile"), nil, true) }.
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`: can't modify frozen String. Bundler cannot continue./i)
+ end
+ end
+
+ describe "#with_source" do
+ context "if there was a rubygem source already defined" do
+ it "restores it after it's done" do
+ other_source = double("other-source")
+ allow(Bundler::Source::Rubygems).to receive(:new).and_return(other_source)
+ allow(Bundler).to receive(:default_gemfile).and_return(Pathname.new("./Gemfile"))
+
+ subject.source("https://other-source.org") do
+ subject.gem("dobry-pies", :path => "foo")
+ subject.gem("foo")
+ end
+
+ expect(subject.dependencies.last.source).to eq(other_source)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/endpoint_specification_spec.rb b/spec/bundler/bundler/endpoint_specification_spec.rb
new file mode 100644
index 0000000000..a9371f6617
--- /dev/null
+++ b/spec/bundler/bundler/endpoint_specification_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::EndpointSpecification do
+ let(:name) { "foo" }
+ let(:version) { "1.0.0" }
+ let(:platform) { Gem::Platform::RUBY }
+ let(:dependencies) { [] }
+ let(:metadata) { nil }
+
+ subject(:spec) { described_class.new(name, version, platform, dependencies, metadata) }
+
+ describe "#build_dependency" do
+ let(:name) { "foo" }
+ let(:requirement1) { "~> 1.1" }
+ let(:requirement2) { ">= 1.1.7" }
+
+ it "should return a Gem::Dependency" do
+ expect(subject.send(:build_dependency, name, [requirement1, requirement2])).
+ to eq(Gem::Dependency.new(name, requirement1, requirement2))
+ end
+
+ context "when an ArgumentError occurs" do
+ before do
+ allow(Gem::Dependency).to receive(:new).with(name, [requirement1, requirement2]) {
+ raise ArgumentError.new("Some error occurred")
+ }
+ end
+
+ it "should raise the original error" do
+ expect { subject.send(:build_dependency, name, [requirement1, requirement2]) }.to raise_error(
+ ArgumentError, "Some error occurred"
+ )
+ end
+ end
+
+ context "when there is an ill formed requirement" do
+ before do
+ allow(Gem::Dependency).to receive(:new).with(name, [requirement1, requirement2]) {
+ raise ArgumentError.new("Ill-formed requirement [\"#<YAML::Syck::DefaultKey")
+ }
+ # Eliminate extra line break in rspec output due to `puts` in `#build_dependency`
+ allow(subject).to receive(:puts) {}
+ end
+
+ it "should raise a Bundler::GemspecError with invalid gemspec message" do
+ expect { subject.send(:build_dependency, name, [requirement1, requirement2]) }.to raise_error(
+ Bundler::GemspecError, /Unfortunately, the gem foo \(1\.0\.0\) has an invalid gemspec/
+ )
+ end
+ end
+ end
+
+ describe "#parse_metadata" do
+ context "when the metadata has malformed requirements" do
+ let(:metadata) { { "rubygems" => ">\n" } }
+ it "raises a helpful error message" do
+ expect { subject }.to raise_error(
+ Bundler::GemspecError,
+ a_string_including("There was an error parsing the metadata for the gem foo (1.0.0)").
+ and(a_string_including('The metadata was {"rubygems"=>">\n"}'))
+ )
+ end
+ end
+ end
+
+ it "supports equality comparison" do
+ other_spec = described_class.new("bar", version, platform, dependencies, metadata)
+ expect(spec).to eql(spec)
+ expect(spec).to_not eql(other_spec)
+ end
+end
diff --git a/spec/bundler/bundler/env_spec.rb b/spec/bundler/bundler/env_spec.rb
new file mode 100644
index 0000000000..20bd38b021
--- /dev/null
+++ b/spec/bundler/bundler/env_spec.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require "bundler/settings"
+
+RSpec.describe Bundler::Env do
+ let(:git_proxy_stub) { Bundler::Source::Git::GitProxy.new(nil, nil, nil) }
+
+ describe "#report" do
+ it "prints the environment" do
+ out = described_class.report
+
+ expect(out).to include("Environment")
+ expect(out).to include(Bundler::VERSION)
+ expect(out).to include(Gem::VERSION)
+ expect(out).to include(described_class.send(:ruby_version))
+ expect(out).to include(described_class.send(:git_version))
+ expect(out).to include(OpenSSL::OPENSSL_VERSION)
+ end
+
+ context "when there is a Gemfile and a lockfile and print_gemfile is true" do
+ before do
+ gemfile "gem 'rack', '1.0.0'"
+
+ lockfile <<-L
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 1.10.0
+ L
+ end
+
+ let(:output) { described_class.report(:print_gemfile => true) }
+
+ it "prints the Gemfile" do
+ expect(output).to include("Gemfile")
+ expect(output).to include("'rack', '1.0.0'")
+ end
+
+ it "prints the lockfile" do
+ expect(output).to include("Gemfile.lock")
+ expect(output).to include("rack (1.0.0)")
+ end
+ end
+
+ context "when there no Gemfile and print_gemfile is true" do
+ let(:output) { described_class.report(:print_gemfile => true) }
+
+ it "prints the environment" do
+ expect(output).to start_with("## Environment")
+ end
+ end
+
+ context "when Gemfile contains a gemspec and print_gemspecs is true" do
+ let(:gemspec) do
+ strip_whitespace(<<-GEMSPEC)
+ Gem::Specification.new do |gem|
+ gem.name = "foo"
+ gem.author = "Fumofu"
+ end
+ GEMSPEC
+ end
+
+ before do
+ gemfile("gemspec")
+
+ File.open(bundled_app.join("foo.gemspec"), "wb") do |f|
+ f.write(gemspec)
+ end
+ end
+
+ it "prints the gemspec" do
+ output = described_class.report(:print_gemspecs => true)
+
+ expect(output).to include("foo.gemspec")
+ expect(output).to include(gemspec)
+ end
+ end
+
+ context "when eval_gemfile is used" do
+ it "prints all gemfiles" do
+ create_file "other/Gemfile-other", "gem 'rack'"
+ create_file "other/Gemfile", "eval_gemfile 'Gemfile-other'"
+ create_file "Gemfile-alt", <<-G
+ source "file:#{gem_repo1}"
+ eval_gemfile "other/Gemfile"
+ G
+ gemfile "eval_gemfile #{File.expand_path("Gemfile-alt").dump}"
+
+ output = described_class.report(:print_gemspecs => true)
+ expect(output).to include(strip_whitespace(<<-ENV))
+ ## Gemfile
+
+ ### Gemfile
+
+ ```ruby
+ eval_gemfile #{File.expand_path("Gemfile-alt").dump}
+ ```
+
+ ### Gemfile-alt
+
+ ```ruby
+ source "file:#{gem_repo1}"
+ eval_gemfile "other/Gemfile"
+ ```
+
+ ### other/Gemfile
+
+ ```ruby
+ eval_gemfile 'Gemfile-other'
+ ```
+
+ ### other/Gemfile-other
+
+ ```ruby
+ gem 'rack'
+ ```
+
+ ### Gemfile.lock
+
+ ```
+ <No #{bundled_app("Gemfile.lock")} found>
+ ```
+ ENV
+ end
+ end
+
+ context "when the git version is OS specific" do
+ it "includes OS specific information with the version number" do
+ expect(git_proxy_stub).to receive(:git).with("--version").
+ and_return("git version 1.2.3 (Apple Git-BS)")
+ expect(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub)
+
+ expect(described_class.report).to include("Git 1.2.3 (Apple Git-BS)")
+ end
+ end
+ end
+
+ describe ".version_of", :ruby_repo do
+ let(:parsed_version) { described_class.send(:version_of, "ruby") }
+
+ it "strips version of new line characters" do
+ expect(parsed_version).to_not include("\n")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/environment_preserver_spec.rb b/spec/bundler/bundler/environment_preserver_spec.rb
new file mode 100644
index 0000000000..530ca6f835
--- /dev/null
+++ b/spec/bundler/bundler/environment_preserver_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::EnvironmentPreserver do
+ let(:preserver) { described_class.new(env, ["foo"]) }
+
+ describe "#backup" do
+ let(:env) { { "foo" => "my-foo", "bar" => "my-bar" } }
+ subject { preserver.backup }
+
+ it "should create backup entries" do
+ expect(subject["BUNDLER_ORIG_foo"]).to eq("my-foo")
+ end
+
+ it "should keep the original entry" do
+ expect(subject["foo"]).to eq("my-foo")
+ end
+
+ it "should not create backup entries for unspecified keys" do
+ expect(subject.key?("BUNDLER_ORIG_bar")).to eq(false)
+ end
+
+ it "should not affect the original env" do
+ subject
+ expect(env.keys.sort).to eq(%w[bar foo])
+ end
+
+ context "when a key is empty" do
+ let(:env) { { "foo" => "" } }
+
+ it "should not create backup entries" do
+ expect(subject).not_to have_key "BUNDLER_ORIG_foo"
+ end
+ end
+
+ context "when an original key is set" do
+ let(:env) { { "foo" => "my-foo", "BUNDLER_ORIG_foo" => "orig-foo" } }
+
+ it "should keep the original value in the BUNDLER_ORIG_ variable" do
+ expect(subject["BUNDLER_ORIG_foo"]).to eq("orig-foo")
+ end
+
+ it "should keep the variable" do
+ expect(subject["foo"]).to eq("my-foo")
+ end
+ end
+ end
+
+ describe "#restore" do
+ subject { preserver.restore }
+
+ context "when an original key is set" do
+ let(:env) { { "foo" => "my-foo", "BUNDLER_ORIG_foo" => "orig-foo" } }
+
+ it "should restore the original value" do
+ expect(subject["foo"]).to eq("orig-foo")
+ end
+
+ it "should delete the backup value" do
+ expect(subject.key?("BUNDLER_ORIG_foo")).to eq(false)
+ end
+ end
+
+ context "when no original key is set" do
+ let(:env) { { "foo" => "my-foo" } }
+
+ it "should keep the current value" do
+ expect(subject["foo"]).to eq("my-foo")
+ end
+ end
+
+ context "when the original key is empty" do
+ let(:env) { { "foo" => "my-foo", "BUNDLER_ORIG_foo" => "" } }
+
+ it "should keep the current value" do
+ expect(subject["foo"]).to eq("my-foo")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/base_spec.rb b/spec/bundler/bundler/fetcher/base_spec.rb
new file mode 100644
index 0000000000..df1245d44d
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/base_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Fetcher::Base do
+ let(:downloader) { double(:downloader) }
+ let(:remote) { double(:remote) }
+ let(:display_uri) { "http://sample_uri.com" }
+
+ class TestClass < described_class; end
+
+ subject { TestClass.new(downloader, remote, display_uri) }
+
+ describe "#initialize" do
+ context "with the abstract Base class" do
+ it "should raise an error" do
+ expect { described_class.new(downloader, remote, display_uri) }.to raise_error(RuntimeError, "Abstract class")
+ end
+ end
+
+ context "with a class that inherits the Base class" do
+ it "should set the passed attributes" do
+ expect(subject.downloader).to eq(downloader)
+ expect(subject.remote).to eq(remote)
+ expect(subject.display_uri).to eq("http://sample_uri.com")
+ end
+ end
+ end
+
+ describe "#remote_uri" do
+ let(:remote_uri_obj) { double(:remote_uri_obj) }
+
+ before { allow(remote).to receive(:uri).and_return(remote_uri_obj) }
+
+ it "should return the remote's uri" do
+ expect(subject.remote_uri).to eq(remote_uri_obj)
+ end
+ end
+
+ describe "#fetch_uri" do
+ let(:remote_uri_obj) { URI("http://rubygems.org") }
+
+ before { allow(subject).to receive(:remote_uri).and_return(remote_uri_obj) }
+
+ context "when the remote uri's host is rubygems.org" do
+ it "should create a copy of the remote uri with index.rubygems.org as the host" do
+ fetched_uri = subject.fetch_uri
+ expect(fetched_uri.host).to eq("index.rubygems.org")
+ expect(fetched_uri).to_not be(remote_uri_obj)
+ end
+ end
+
+ context "when the remote uri's host is not rubygems.org" do
+ let(:remote_uri_obj) { URI("http://otherhost.org") }
+
+ it "should return the remote uri" do
+ expect(subject.fetch_uri).to eq(URI("http://otherhost.org"))
+ end
+ end
+
+ it "memoizes the fetched uri" do
+ expect(remote_uri_obj).to receive(:host).once
+ 2.times { subject.fetch_uri }
+ end
+ end
+
+ describe "#available?" do
+ it "should return whether the api is available" do
+ expect(subject.available?).to be_truthy
+ end
+ end
+
+ describe "#api_fetcher?" do
+ it "should return false" do
+ expect(subject.api_fetcher?).to be_falsey
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/compact_index_spec.rb b/spec/bundler/bundler/fetcher/compact_index_spec.rb
new file mode 100644
index 0000000000..e0f58766ea
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/compact_index_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Fetcher::CompactIndex do
+ let(:downloader) { double(:downloader) }
+ let(:display_uri) { URI("http://sampleuri.com") }
+ let(:remote) { double(:remote, :cache_slug => "lsjdf", :uri => display_uri) }
+ let(:compact_index) { described_class.new(downloader, remote, display_uri) }
+
+ before do
+ allow(compact_index).to receive(:log_specs) {}
+ end
+
+ describe "#specs_for_names" do
+ it "has only one thread open at the end of the run" do
+ compact_index.specs_for_names(["lskdjf"])
+
+ thread_count = Thread.list.count {|thread| thread.status == "run" }
+ expect(thread_count).to eq 1
+ end
+
+ it "calls worker#stop during the run" do
+ expect_any_instance_of(Bundler::Worker).to receive(:stop).at_least(:once)
+
+ compact_index.specs_for_names(["lskdjf"])
+ end
+
+ describe "#available?" do
+ before do
+ allow(compact_index).to receive(:compact_index_client).
+ and_return(double(:compact_index_client, :update_and_parse_checksums! => true))
+ end
+
+ it "returns true" do
+ expect(compact_index).to be_available
+ end
+
+ context "when OpenSSL is not available" do
+ before do
+ allow(compact_index).to receive(:require).with("openssl").and_raise(LoadError)
+ end
+
+ it "returns true" do
+ expect(compact_index).to be_available
+ end
+ end
+
+ context "when OpenSSL is FIPS-enabled", :ruby => ">= 2.0.0" do
+ def remove_cached_md5_availability
+ return unless Bundler::SharedHelpers.instance_variable_defined?(:@md5_available)
+ Bundler::SharedHelpers.remove_instance_variable(:@md5_available)
+ end
+
+ before do
+ remove_cached_md5_availability
+ stub_const("OpenSSL::OPENSSL_FIPS", true)
+ end
+
+ after { remove_cached_md5_availability }
+
+ context "when FIPS-mode is active" do
+ before do
+ allow(OpenSSL::Digest::MD5).to receive(:digest).
+ and_raise(OpenSSL::Digest::DigestError)
+ end
+
+ it "returns false" do
+ expect(compact_index).to_not be_available
+ end
+ end
+
+ it "returns true" do
+ expect(compact_index).to be_available
+ end
+ end
+ end
+
+ context "logging" do
+ before { allow(compact_index).to receive(:log_specs).and_call_original }
+
+ context "with debug on" do
+ before do
+ allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(true)
+ end
+
+ it "should log at info level" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with('Looking up gems ["lskdjf"]')
+ compact_index.specs_for_names(["lskdjf"])
+ end
+ end
+
+ context "with debug off" do
+ before do
+ allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(false)
+ end
+
+ it "should log at info level" do
+ expect(Bundler).to receive_message_chain(:ui, :info).with(".", false)
+ compact_index.specs_for_names(["lskdjf"])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/dependency_spec.rb b/spec/bundler/bundler/fetcher/dependency_spec.rb
new file mode 100644
index 0000000000..081fdff34d
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/dependency_spec.rb
@@ -0,0 +1,287 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Fetcher::Dependency do
+ let(:downloader) { double(:downloader) }
+ let(:remote) { double(:remote, :uri => URI("http://localhost:5000")) }
+ let(:display_uri) { "http://sample_uri.com" }
+
+ subject { described_class.new(downloader, remote, display_uri) }
+
+ describe "#available?" do
+ let(:dependency_api_uri) { double(:dependency_api_uri) }
+ let(:fetched_spec) { double(:fetched_spec) }
+
+ before do
+ allow(subject).to receive(:dependency_api_uri).and_return(dependency_api_uri)
+ allow(downloader).to receive(:fetch).with(dependency_api_uri).and_return(fetched_spec)
+ end
+
+ it "should be truthy" do
+ expect(subject.available?).to be_truthy
+ end
+
+ context "when there is no network access" do
+ before do
+ allow(downloader).to receive(:fetch).with(dependency_api_uri) {
+ raise Bundler::Fetcher::NetworkDownError.new("Network Down Message")
+ }
+ end
+
+ it "should raise an HTTPError with the original message" do
+ expect { subject.available? }.to raise_error(Bundler::HTTPError, "Network Down Message")
+ end
+ end
+
+ context "when authentication is required" do
+ let(:remote_uri) { "http://remote_uri.org" }
+
+ before do
+ allow(downloader).to receive(:fetch).with(dependency_api_uri) {
+ raise Bundler::Fetcher::AuthenticationRequiredError.new(remote_uri)
+ }
+ end
+
+ it "should raise the original error" do
+ expect { subject.available? }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError,
+ %r{Authentication is required for http://remote_uri.org})
+ end
+ end
+
+ context "when there is an http error" do
+ before { allow(downloader).to receive(:fetch).with(dependency_api_uri) { raise Bundler::HTTPError.new } }
+
+ it "should be falsey" do
+ expect(subject.available?).to be_falsey
+ end
+ end
+ end
+
+ describe "#api_fetcher?" do
+ it "should return true" do
+ expect(subject.api_fetcher?).to be_truthy
+ end
+ end
+
+ describe "#specs" do
+ let(:gem_names) { %w[foo bar] }
+ let(:full_dependency_list) { ["bar"] }
+ let(:last_spec_list) { [["boulder", gem_version1, "ruby", resque]] }
+ let(:fail_errors) { double(:fail_errors) }
+ let(:bundler_retry) { double(:bundler_retry) }
+ let(:gem_version1) { double(:gem_version1) }
+ let(:resque) { double(:resque) }
+ let(:remote_uri) { "http://remote-uri.org" }
+
+ before do
+ stub_const("Bundler::Fetcher::FAIL_ERRORS", fail_errors)
+ allow(Bundler::Retry).to receive(:new).with("dependency api", fail_errors).and_return(bundler_retry)
+ allow(bundler_retry).to receive(:attempts) {|&block| block.call }
+ allow(subject).to receive(:log_specs) {}
+ allow(subject).to receive(:remote_uri).and_return(remote_uri)
+ allow(Bundler).to receive_message_chain(:ui, :debug?)
+ allow(Bundler).to receive_message_chain(:ui, :info)
+ allow(Bundler).to receive_message_chain(:ui, :debug)
+ end
+
+ context "when there are given gem names that are not in the full dependency list" do
+ let(:spec_list) { [["top", gem_version2, "ruby", faraday]] }
+ let(:deps_list) { [] }
+ let(:dependency_specs) { [spec_list, deps_list] }
+ let(:gem_version2) { double(:gem_version2) }
+ let(:faraday) { double(:faraday) }
+
+ before { allow(subject).to receive(:dependency_specs).with(["foo"]).and_return(dependency_specs) }
+
+ it "should return a hash with the remote_uri and the list of specs" do
+ expect(subject.specs(gem_names, full_dependency_list, last_spec_list)).to eq([
+ ["top", gem_version2, "ruby", faraday],
+ ["boulder", gem_version1, "ruby", resque],
+ ])
+ end
+ end
+
+ context "when all given gem names are in the full dependency list" do
+ let(:gem_names) { ["foo"] }
+ let(:full_dependency_list) { %w[foo bar] }
+ let(:last_spec_list) { ["boulder"] }
+
+ it "should return a hash with the remote_uri and the last spec list" do
+ expect(subject.specs(gem_names, full_dependency_list, last_spec_list)).to eq(["boulder"])
+ end
+ end
+
+ context "logging" do
+ before { allow(subject).to receive(:log_specs).and_call_original }
+
+ context "with debug on" do
+ before do
+ allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(true)
+ allow(subject).to receive(:dependency_specs).with(["foo"]).and_return([[], []])
+ end
+
+ it "should log the query list at debug level" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("Query List: [\"foo\"]")
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("Query List: []")
+ subject.specs(gem_names, full_dependency_list, last_spec_list)
+ end
+ end
+
+ context "with debug off" do
+ before do
+ allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(false)
+ allow(subject).to receive(:dependency_specs).with(["foo"]).and_return([[], []])
+ end
+
+ it "should log at info level" do
+ expect(Bundler).to receive_message_chain(:ui, :info).with(".", false)
+ expect(Bundler).to receive_message_chain(:ui, :info).with(".", false)
+ subject.specs(gem_names, full_dependency_list, last_spec_list)
+ end
+ end
+ end
+
+ shared_examples_for "the error is properly handled" do
+ it "should return nil" do
+ expect(subject.specs(gem_names, full_dependency_list, last_spec_list)).to be_nil
+ end
+
+ context "debug logging is not on" do
+ before { allow(Bundler).to receive_message_chain(:ui, :debug?).and_return(false) }
+
+ it "should log a new line to info" do
+ expect(Bundler).to receive_message_chain(:ui, :info).with("")
+ subject.specs(gem_names, full_dependency_list, last_spec_list)
+ end
+ end
+ end
+
+ shared_examples_for "the error suggests retrying with the full index" do
+ it "should log the inability to fetch from API at debug level" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API\nit's suggested to retry using the full index via `bundle install --full-index`")
+ subject.specs(gem_names, full_dependency_list, last_spec_list)
+ end
+ end
+
+ context "when an HTTPError occurs" do
+ before { allow(subject).to receive(:dependency_specs) { raise Bundler::HTTPError.new } }
+
+ it_behaves_like "the error is properly handled"
+ it_behaves_like "the error suggests retrying with the full index"
+ end
+
+ context "when a GemspecError occurs" do
+ before { allow(subject).to receive(:dependency_specs) { raise Bundler::GemspecError.new } }
+
+ it_behaves_like "the error is properly handled"
+ it_behaves_like "the error suggests retrying with the full index"
+ end
+
+ context "when a MarshalError occurs" do
+ before { allow(subject).to receive(:dependency_specs) { raise Bundler::MarshalError.new } }
+
+ it_behaves_like "the error is properly handled"
+
+ it "should log the inability to fetch from API and mention retrying" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API, trying the full index")
+ subject.specs(gem_names, full_dependency_list, last_spec_list)
+ end
+ end
+ end
+
+ describe "#dependency_specs" do
+ let(:gem_names) { [%w[foo bar], %w[bundler rubocop]] }
+ let(:gem_list) { double(:gem_list) }
+ let(:formatted_specs_and_deps) { double(:formatted_specs_and_deps) }
+
+ before do
+ allow(subject).to receive(:unmarshalled_dep_gems).with(gem_names).and_return(gem_list)
+ allow(subject).to receive(:get_formatted_specs_and_deps).with(gem_list).and_return(formatted_specs_and_deps)
+ end
+
+ it "should log the query list at debug level" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with(
+ "Query Gemcutter Dependency Endpoint API: foo,bar,bundler,rubocop"
+ )
+ subject.dependency_specs(gem_names)
+ end
+
+ it "should return formatted specs and a unique list of dependencies" do
+ expect(subject.dependency_specs(gem_names)).to eq(formatted_specs_and_deps)
+ end
+ end
+
+ describe "#unmarshalled_dep_gems" do
+ let(:gem_names) { [%w[foo bar], %w[bundler rubocop]] }
+ let(:dep_api_uri) { double(:dep_api_uri) }
+ let(:unmarshalled_gems) { double(:unmarshalled_gems) }
+ let(:fetch_response) { double(:fetch_response, :body => double(:body)) }
+ let(:rubygems_limit) { 50 }
+
+ before { allow(subject).to receive(:dependency_api_uri).with(gem_names).and_return(dep_api_uri) }
+
+ it "should fetch dependencies from RubyGems and unmarshal them" do
+ expect(gem_names).to receive(:each_slice).with(rubygems_limit).and_call_original
+ expect(downloader).to receive(:fetch).with(dep_api_uri).and_return(fetch_response)
+ expect(Bundler).to receive(:load_marshal).with(fetch_response.body).and_return([unmarshalled_gems])
+ expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems])
+ end
+ end
+
+ describe "#get_formatted_specs_and_deps" do
+ let(:gem_list) do
+ [
+ {
+ :dependencies => {
+ "resque" => "req3,req4",
+ },
+ :name => "typhoeus",
+ :number => "1.0.1",
+ :platform => "ruby",
+ },
+ {
+ :dependencies => {
+ "faraday" => "req1,req2",
+ },
+ :name => "grape",
+ :number => "2.0.2",
+ :platform => "jruby",
+ },
+ ]
+ end
+
+ it "should return formatted specs and a unique list of dependencies" do
+ spec_list, deps_list = subject.get_formatted_specs_and_deps(gem_list)
+ expect(spec_list).to eq([["typhoeus", "1.0.1", "ruby", [["resque", ["req3,req4"]]]],
+ ["grape", "2.0.2", "jruby", [["faraday", ["req1,req2"]]]]])
+ expect(deps_list).to eq(%w[resque faraday])
+ end
+ end
+
+ describe "#dependency_api_uri" do
+ let(:uri) { URI("http://gem-api.com") }
+
+ context "with gem names" do
+ let(:gem_names) { %w[foo bar bundler rubocop] }
+
+ before { allow(subject).to receive(:fetch_uri).and_return(uri) }
+
+ it "should return an api calling uri with the gems in the query" do
+ expect(subject.dependency_api_uri(gem_names).to_s).to eq(
+ "http://gem-api.com/api/v1/dependencies?gems=bar%2Cbundler%2Cfoo%2Crubocop"
+ )
+ end
+ end
+
+ context "with no gem names" do
+ let(:gem_names) { [] }
+
+ before { allow(subject).to receive(:fetch_uri).and_return(uri) }
+
+ it "should return an api calling uri with no query" do
+ expect(subject.dependency_api_uri(gem_names).to_s).to eq(
+ "http://gem-api.com/api/v1/dependencies"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb
new file mode 100644
index 0000000000..c9b4fa662a
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/downloader_spec.rb
@@ -0,0 +1,250 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Fetcher::Downloader do
+ let(:connection) { double(:connection) }
+ let(:redirect_limit) { 5 }
+ let(:uri) { URI("http://www.uri-to-fetch.com/api/v2/endpoint") }
+ let(:options) { double(:options) }
+
+ subject { described_class.new(connection, redirect_limit) }
+
+ describe "fetch" do
+ let(:counter) { 0 }
+ let(:httpv) { "1.1" }
+ let(:http_response) { double(:response) }
+
+ before do
+ allow(subject).to receive(:request).with(uri, options).and_return(http_response)
+ allow(http_response).to receive(:body).and_return("Body with info")
+ end
+
+ context "when the # requests counter is greater than the redirect limit" do
+ let(:counter) { redirect_limit + 1 }
+
+ it "should raise a Bundler::HTTPError specifying too many redirects" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, "Too many redirects")
+ end
+ end
+
+ context "logging" do
+ let(:http_response) { Net::HTTPSuccess.new("1.1", 200, "Success") }
+
+ it "should log the HTTP response code and message to debug" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP 200 Success #{uri}")
+ subject.fetch(uri, options, counter)
+ end
+ end
+
+ context "when the request response is a Net::HTTPRedirection" do
+ let(:http_response) { Net::HTTPRedirection.new(httpv, 308, "Moved") }
+
+ before { http_response["location"] = "http://www.redirect-uri.com/api/v2/endpoint" }
+
+ it "should try to fetch the redirect uri and iterate the # requests counter" do
+ expect(subject).to receive(:fetch).with(URI("http://www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original
+ expect(subject).to receive(:fetch).with(URI("http://www.redirect-uri.com/api/v2/endpoint"), options, 1)
+ subject.fetch(uri, options, counter)
+ end
+
+ context "when the redirect uri and original uri are the same" do
+ let(:uri) { URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint") }
+
+ before { http_response["location"] = "ssh://www.uri-to-fetch.com/api/v1/endpoint" }
+
+ it "should set the same user and password for the redirect uri" do
+ expect(subject).to receive(:fetch).with(URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original
+ expect(subject).to receive(:fetch).with(URI("ssh://username:password@www.uri-to-fetch.com/api/v1/endpoint"), options, 1)
+ subject.fetch(uri, options, counter)
+ end
+ end
+ end
+
+ context "when the request response is a Net::HTTPSuccess" do
+ let(:http_response) { Net::HTTPSuccess.new("1.1", 200, "Success") }
+
+ it "should return the response body" do
+ expect(subject.fetch(uri, options, counter)).to eq(http_response)
+ end
+ end
+
+ context "when the request response is a Net::HTTPRequestEntityTooLarge" do
+ let(:http_response) { Net::HTTPRequestEntityTooLarge.new("1.1", 413, "Too Big") }
+
+ it "should raise a Bundler::Fetcher::FallbackError with the response body" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::FallbackError, "Body with info")
+ end
+ end
+
+ context "when the request response is a Net::HTTPUnauthorized" do
+ let(:http_response) { Net::HTTPUnauthorized.new("1.1", 401, "Unauthorized") }
+
+ it "should raise a Bundler::Fetcher::AuthenticationRequiredError with the uri host" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError,
+ /Authentication is required for www.uri-to-fetch.com/)
+ end
+ end
+
+ context "when the request response is a Net::HTTPNotFound" do
+ let(:http_response) { Net::HTTPNotFound.new("1.1", 404, "Not Found") }
+
+ it "should raise a Bundler::Fetcher::FallbackError with Net::HTTPNotFound" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::FallbackError, "Net::HTTPNotFound")
+ end
+ end
+
+ context "when the request response is some other type" do
+ let(:http_response) { Net::HTTPBadGateway.new("1.1", 500, "Fatal Error") }
+
+ it "should raise a Bundler::HTTPError with the response class and body" do
+ expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, "Net::HTTPBadGateway: Body with info")
+ end
+ end
+ end
+
+ describe "request" do
+ let(:net_http_get) { double(:net_http_get) }
+ let(:response) { double(:response) }
+
+ before do
+ allow(Net::HTTP::Get).to receive(:new).with("/api/v2/endpoint", options).and_return(net_http_get)
+ allow(connection).to receive(:request).with(uri, net_http_get).and_return(response)
+ end
+
+ it "should log the HTTP GET request to debug" do
+ expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP GET http://www.uri-to-fetch.com/api/v2/endpoint")
+ subject.request(uri, options)
+ end
+
+ context "when there is a user provided in the request" do
+ context "and there is also a password provided" do
+ context "that contains cgi escaped characters" do
+ let(:uri) { URI("http://username:password%24@www.uri-to-fetch.com/api/v2/endpoint") }
+
+ it "should request basic authentication with the username and password" do
+ expect(net_http_get).to receive(:basic_auth).with("username", "password$")
+ subject.request(uri, options)
+ end
+ end
+
+ context "that is all unescaped characters" do
+ let(:uri) { URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") }
+ it "should request basic authentication with the username and proper cgi compliant password" do
+ expect(net_http_get).to receive(:basic_auth).with("username", "password")
+ subject.request(uri, options)
+ end
+ end
+ end
+
+ context "and there is no password provided" do
+ let(:uri) { URI("http://username@www.uri-to-fetch.com/api/v2/endpoint") }
+
+ it "should request basic authentication with just the user" do
+ expect(net_http_get).to receive(:basic_auth).with("username", nil)
+ subject.request(uri, options)
+ end
+ end
+
+ context "that contains cgi escaped characters" do
+ let(:uri) { URI("http://username%24@www.uri-to-fetch.com/api/v2/endpoint") }
+
+ it "should request basic authentication with the proper cgi compliant password user" do
+ expect(net_http_get).to receive(:basic_auth).with("username$", nil)
+ subject.request(uri, options)
+ end
+ end
+ end
+
+ context "when the request response causes a NoMethodError" do
+ before { allow(connection).to receive(:request).with(uri, net_http_get) { raise NoMethodError.new(message) } }
+
+ context "and the error message is about use_ssl=" do
+ let(:message) { "undefined method 'use_ssl='" }
+
+ it "should raise a LoadError about openssl" do
+ expect { subject.request(uri, options) }.to raise_error(LoadError, "cannot load such file -- openssl")
+ end
+ end
+
+ context "and the error message is not about use_ssl=" do
+ let(:message) { "undefined method 'undefined_method_call'" }
+
+ it "should raise the original NoMethodError" do
+ expect { subject.request(uri, options) }.to raise_error(NoMethodError, "undefined method 'undefined_method_call'")
+ end
+ end
+ end
+
+ context "when the request response causes a OpenSSL::SSL::SSLError" do
+ before { allow(connection).to receive(:request).with(uri, net_http_get) { raise OpenSSL::SSL::SSLError.new } }
+
+ it "should raise a LoadError about openssl" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::CertificateFailureError,
+ %r{Could not verify the SSL certificate for http://www.uri-to-fetch.com/api/v2/endpoint})
+ end
+ end
+
+ context "when the request response causes an error included in HTTP_ERRORS" do
+ let(:message) { nil }
+ let(:error) { RuntimeError.new(message) }
+
+ before do
+ stub_const("Bundler::Fetcher::HTTP_ERRORS", [RuntimeError])
+ allow(connection).to receive(:request).with(uri, net_http_get) { raise error }
+ end
+
+ it "should trace log the error" do
+ allow(Bundler).to receive_message_chain(:ui, :debug)
+ expect(Bundler).to receive_message_chain(:ui, :trace).with(error)
+ expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError)
+ end
+
+ context "when error message is about the host being down" do
+ let(:message) { "host down: http://www.uri-to-fetch.com" }
+
+ it "should raise a Bundler::Fetcher::NetworkDownError" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError,
+ /Could not reach host www.uri-to-fetch.com/)
+ end
+ end
+
+ context "when error message is about getaddrinfo issues" do
+ let(:message) { "getaddrinfo: nodename nor servname provided for http://www.uri-to-fetch.com" }
+
+ it "should raise a Bundler::Fetcher::NetworkDownError" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError,
+ /Could not reach host www.uri-to-fetch.com/)
+ end
+ end
+
+ context "when error message is about neither host down or getaddrinfo" do
+ let(:message) { "other error about network" }
+
+ it "should raise a Bundler::HTTPError" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError,
+ "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (other error about network)")
+ end
+
+ context "when the there are credentials provided in the request" do
+ let(:uri) { URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") }
+ before do
+ allow(net_http_get).to receive(:basic_auth).with("username", "password")
+ end
+
+ it "should raise a Bundler::HTTPError that doesn't contain the password" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError,
+ "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (other error about network)")
+ end
+ end
+ end
+
+ context "when error message is about no route to host" do
+ let(:message) { "Failed to open TCP connection to www.uri-to-fetch.com:443 " }
+
+ it "should raise a Bundler::Fetcher::HTTPError" do
+ expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError,
+ "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (#{message})")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher/index_spec.rb b/spec/bundler/bundler/fetcher/index_spec.rb
new file mode 100644
index 0000000000..0cf0ae764e
--- /dev/null
+++ b/spec/bundler/bundler/fetcher/index_spec.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Fetcher::Index do
+ let(:downloader) { nil }
+ let(:remote) { nil }
+ let(:display_uri) { "http://sample_uri.com" }
+ let(:rubygems) { double(:rubygems) }
+ let(:gem_names) { %w[foo bar] }
+
+ subject { described_class.new(downloader, remote, display_uri) }
+
+ before { allow(Bundler).to receive(:rubygems).and_return(rubygems) }
+
+ it "fetches and returns the list of remote specs" do
+ expect(rubygems).to receive(:fetch_all_remote_specs) { nil }
+ subject.specs(gem_names)
+ end
+
+ context "error handling" do
+ shared_examples_for "the error is properly handled" do
+ let(:remote_uri) { URI("http://remote-uri.org") }
+ before do
+ allow(subject).to receive(:remote_uri).and_return(remote_uri)
+ end
+
+ context "when certificate verify failed" do
+ let(:error_message) { "certificate verify failed" }
+
+ it "should raise a Bundler::Fetcher::CertificateFailureError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::CertificateFailureError,
+ %r{Could not verify the SSL certificate for http://sample_uri.com})
+ end
+ end
+
+ context "when a 401 response occurs" do
+ let(:error_message) { "401" }
+
+ it "should raise a Bundler::Fetcher::AuthenticationRequiredError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError,
+ %r{Authentication is required for http://remote-uri.org})
+ end
+ end
+
+ context "when a 403 response occurs" do
+ let(:error_message) { "403" }
+
+ before do
+ allow(remote_uri).to receive(:userinfo).and_return(userinfo)
+ end
+
+ context "and there was userinfo" do
+ let(:userinfo) { double(:userinfo) }
+
+ it "should raise a Bundler::Fetcher::BadAuthenticationError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::BadAuthenticationError,
+ %r{Bad username or password for http://remote-uri.org})
+ end
+ end
+
+ context "and there was no userinfo" do
+ let(:userinfo) { nil }
+
+ it "should raise a Bundler::Fetcher::AuthenticationRequiredError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError,
+ %r{Authentication is required for http://remote-uri.org})
+ end
+ end
+ end
+
+ context "any other message is returned" do
+ let(:error_message) { "You get an error, you get an error!" }
+
+ before { allow(Bundler).to receive(:ui).and_return(double(:trace => nil)) }
+
+ it "should raise a Bundler::HTTPError" do
+ expect { subject.specs(gem_names) }.to raise_error(Bundler::HTTPError, "Could not fetch specs from http://sample_uri.com")
+ end
+ end
+ end
+
+ context "when a Gem::RemoteFetcher::FetchError occurs" do
+ before { allow(rubygems).to receive(:fetch_all_remote_specs) { raise Gem::RemoteFetcher::FetchError.new(error_message, nil) } }
+
+ it_behaves_like "the error is properly handled"
+ end
+
+ context "when a OpenSSL::SSL::SSLError occurs" do
+ before { allow(rubygems).to receive(:fetch_all_remote_specs) { raise OpenSSL::SSL::SSLError.new(error_message) } }
+
+ it_behaves_like "the error is properly handled"
+ end
+
+ context "when a Net::HTTPFatalError occurs" do
+ before { allow(rubygems).to receive(:fetch_all_remote_specs) { raise Net::HTTPFatalError.new(error_message, 404) } }
+
+ it_behaves_like "the error is properly handled"
+ end
+ end
+end
diff --git a/spec/bundler/bundler/fetcher_spec.rb b/spec/bundler/bundler/fetcher_spec.rb
new file mode 100644
index 0000000000..184b9efa64
--- /dev/null
+++ b/spec/bundler/bundler/fetcher_spec.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+require "bundler/fetcher"
+
+RSpec.describe Bundler::Fetcher do
+ let(:uri) { URI("https://example.com") }
+ let(:remote) { double("remote", :uri => uri, :original_uri => nil) }
+
+ subject(:fetcher) { Bundler::Fetcher.new(remote) }
+
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new("root") }
+ end
+
+ describe "#connection" do
+ context "when Gem.configuration doesn't specify http_proxy" do
+ it "specify no http_proxy" do
+ expect(fetcher.http_proxy).to be_nil
+ end
+ it "consider environment vars when determine proxy" do
+ with_env_vars("HTTP_PROXY" => "http://proxy-example.com") do
+ expect(fetcher.http_proxy).to match("http://proxy-example.com")
+ end
+ end
+ end
+ context "when Gem.configuration specifies http_proxy " do
+ let(:proxy) { "http://proxy-example2.com" }
+ before do
+ allow(Bundler.rubygems.configuration).to receive(:[]).with(:http_proxy).and_return(proxy)
+ end
+ it "consider Gem.configuration when determine proxy" do
+ expect(fetcher.http_proxy).to match("http://proxy-example2.com")
+ end
+ it "consider Gem.configuration when determine proxy" do
+ with_env_vars("HTTP_PROXY" => "http://proxy-example.com") do
+ expect(fetcher.http_proxy).to match("http://proxy-example2.com")
+ end
+ end
+ context "when the proxy is :no_proxy" do
+ let(:proxy) { :no_proxy }
+ it "does not set a proxy" do
+ expect(fetcher.http_proxy).to be_nil
+ end
+ end
+ end
+
+ context "when a rubygems source mirror is set" do
+ let(:orig_uri) { URI("http://zombo.com") }
+ let(:remote_with_mirror) do
+ double("remote", :uri => uri, :original_uri => orig_uri, :anonymized_uri => uri)
+ end
+
+ let(:fetcher) { Bundler::Fetcher.new(remote_with_mirror) }
+
+ it "sets the 'X-Gemfile-Source' header containing the original source" do
+ expect(
+ fetcher.send(:connection).override_headers["X-Gemfile-Source"]
+ ).to eq("http://zombo.com")
+ end
+ end
+
+ context "when there is no rubygems source mirror set" do
+ let(:remote_no_mirror) do
+ double("remote", :uri => uri, :original_uri => nil, :anonymized_uri => uri)
+ end
+
+ let(:fetcher) { Bundler::Fetcher.new(remote_no_mirror) }
+
+ it "does not set the 'X-Gemfile-Source' header" do
+ expect(fetcher.send(:connection).override_headers["X-Gemfile-Source"]).to be_nil
+ end
+ end
+
+ context "when there are proxy environment variable(s) set" do
+ it "consider http_proxy" do
+ with_env_vars("HTTP_PROXY" => "http://proxy-example3.com") do
+ expect(fetcher.http_proxy).to match("http://proxy-example3.com")
+ end
+ end
+ it "consider no_proxy" do
+ with_env_vars("HTTP_PROXY" => "http://proxy-example4.com", "NO_PROXY" => ".example.com,.example.net") do
+ expect(
+ fetcher.send(:connection).no_proxy
+ ).to eq([".example.com", ".example.net"])
+ end
+ end
+ end
+
+ context "when no ssl configuration is set" do
+ it "no cert" do
+ expect(fetcher.send(:connection).cert).to be_nil
+ expect(fetcher.send(:connection).key).to be_nil
+ end
+ end
+
+ context "when bunder ssl ssl configuration is set" do
+ before do
+ cert = File.join(Spec::Path.tmpdir, "cert")
+ File.open(cert, "w") {|f| f.write "PEM" }
+ allow(Bundler.settings).to receive(:[]).and_return(nil)
+ allow(Bundler.settings).to receive(:[]).with(:ssl_client_cert).and_return(cert)
+ expect(OpenSSL::X509::Certificate).to receive(:new).with("PEM").and_return("cert")
+ expect(OpenSSL::PKey::RSA).to receive(:new).with("PEM").and_return("key")
+ end
+ after do
+ FileUtils.rm File.join(Spec::Path.tmpdir, "cert")
+ end
+ it "use bundler configuration" do
+ expect(fetcher.send(:connection).cert).to eq("cert")
+ expect(fetcher.send(:connection).key).to eq("key")
+ end
+ end
+
+ context "when gem ssl configuration is set" do
+ before do
+ allow(Bundler.rubygems.configuration).to receive_messages(
+ :http_proxy => nil,
+ :ssl_client_cert => "cert",
+ :ssl_ca_cert => "ca"
+ )
+ expect(File).to receive(:read).and_return("")
+ expect(OpenSSL::X509::Certificate).to receive(:new).and_return("cert")
+ expect(OpenSSL::PKey::RSA).to receive(:new).and_return("key")
+ store = double("ca store")
+ expect(store).to receive(:add_file)
+ expect(OpenSSL::X509::Store).to receive(:new).and_return(store)
+ end
+ it "use gem configuration" do
+ expect(fetcher.send(:connection).cert).to eq("cert")
+ expect(fetcher.send(:connection).key).to eq("key")
+ end
+ end
+ end
+
+ describe "#user_agent" do
+ it "builds user_agent with current ruby version and Bundler settings" do
+ allow(Bundler.settings).to receive(:all).and_return(%w[foo bar])
+ expect(fetcher.user_agent).to match(%r{bundler/(\d.)})
+ expect(fetcher.user_agent).to match(%r{rubygems/(\d.)})
+ expect(fetcher.user_agent).to match(%r{ruby/(\d.)})
+ expect(fetcher.user_agent).to match(%r{options/foo,bar})
+ end
+
+ describe "include CI information" do
+ it "from one CI" do
+ with_env_vars("JENKINS_URL" => "foo") do
+ ci_part = fetcher.user_agent.split(" ").find {|x| x.match(%r{\Aci/}) }
+ expect(ci_part).to match("jenkins")
+ end
+ end
+
+ it "from many CI" do
+ with_env_vars("TRAVIS" => "foo", "CI_NAME" => "my_ci") do
+ ci_part = fetcher.user_agent.split(" ").find {|x| x.match(%r{\Aci/}) }
+ expect(ci_part).to match("travis")
+ expect(ci_part).to match("my_ci")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb
new file mode 100644
index 0000000000..2a1be491ef
--- /dev/null
+++ b/spec/bundler/bundler/friendly_errors_spec.rb
@@ -0,0 +1,270 @@
+# frozen_string_literal: true
+
+require "bundler"
+require "bundler/friendly_errors"
+require "cgi"
+
+RSpec.describe Bundler, "friendly errors" do
+ context "with invalid YAML in .gemrc" do
+ before do
+ File.open(Gem.configuration.config_file_name, "w") do |f|
+ f.write "invalid: yaml: hah"
+ end
+ end
+
+ after do
+ FileUtils.rm(Gem.configuration.config_file_name)
+ end
+
+ it "reports a relevant friendly error message", :ruby => ">= 1.9", :rubygems => "< 2.5.0" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle :install, :env => { "DEBUG" => true }
+
+ expect(out).to include("Your RubyGems configuration")
+ expect(out).to include("invalid YAML syntax")
+ expect(out).to include("Psych::SyntaxError")
+ expect(out).not_to include("ERROR REPORT TEMPLATE")
+ expect(exitstatus).to eq(25) if exitstatus
+ end
+
+ it "reports a relevant friendly error message", :ruby => ">= 1.9", :rubygems => ">= 2.5.0" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle :install, :env => { "DEBUG" => true }
+
+ expect(last_command.stderr).to include("Failed to load #{home(".gemrc")}")
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+ end
+
+ it "calls log_error in case of exception" do
+ exception = Exception.new
+ expect(Bundler::FriendlyErrors).to receive(:exit_status).with(exception).and_return(1)
+ expect do
+ Bundler.with_friendly_errors do
+ raise exception
+ end
+ end.to raise_error(SystemExit)
+ end
+
+ it "calls exit_status on exception" do
+ exception = Exception.new
+ expect(Bundler::FriendlyErrors).to receive(:log_error).with(exception)
+ expect do
+ Bundler.with_friendly_errors do
+ raise exception
+ end
+ end.to raise_error(SystemExit)
+ end
+
+ describe "#log_error" do
+ shared_examples "Bundler.ui receive error" do |error, message|
+ it "" do
+ expect(Bundler.ui).to receive(:error).with(message || error.message)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ shared_examples "Bundler.ui receive trace" do |error|
+ it "" do
+ expect(Bundler.ui).to receive(:trace).with(error)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "YamlSyntaxError" do
+ it_behaves_like "Bundler.ui receive error", Bundler::YamlSyntaxError.new(StandardError.new, "sample_message")
+
+ it "Bundler.ui receive trace" do
+ std_error = StandardError.new
+ exception = Bundler::YamlSyntaxError.new(std_error, "sample_message")
+ expect(Bundler.ui).to receive(:trace).with(std_error)
+ Bundler::FriendlyErrors.log_error(exception)
+ end
+ end
+
+ context "Dsl::DSLError, GemspecError" do
+ it_behaves_like "Bundler.ui receive error", Bundler::Dsl::DSLError.new("description", "dsl_path", "backtrace")
+ it_behaves_like "Bundler.ui receive error", Bundler::GemspecError.new
+ end
+
+ context "GemRequireError" do
+ let(:orig_error) { StandardError.new }
+ let(:error) { Bundler::GemRequireError.new(orig_error, "sample_message") }
+
+ before do
+ allow(orig_error).to receive(:backtrace).and_return([])
+ end
+
+ it "Bundler.ui receive error" do
+ expect(Bundler.ui).to receive(:error).with(error.message)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+
+ it "writes to Bundler.ui.trace" do
+ expect(Bundler.ui).to receive(:trace).with(orig_error, nil, true)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "BundlerError" do
+ it "Bundler.ui receive error" do
+ error = Bundler::BundlerError.new
+ expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ it_behaves_like "Bundler.ui receive trace", Bundler::BundlerError.new
+ end
+
+ context "Thor::Error" do
+ it_behaves_like "Bundler.ui receive error", Bundler::Thor::Error.new
+ end
+
+ context "LoadError" do
+ let(:error) { LoadError.new("cannot load such file -- openssl") }
+
+ it "Bundler.ui receive error" do
+ expect(Bundler.ui).to receive(:error).with("\nCould not load OpenSSL.")
+ Bundler::FriendlyErrors.log_error(error)
+ end
+
+ it "Bundler.ui receive warn" do
+ expect(Bundler.ui).to receive(:warn).with(any_args, :wrap => true)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+
+ it "Bundler.ui receive trace" do
+ expect(Bundler.ui).to receive(:trace).with(error)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "Interrupt" do
+ it "Bundler.ui receive error" do
+ expect(Bundler.ui).to receive(:error).with("\nQuitting...")
+ Bundler::FriendlyErrors.log_error(Interrupt.new)
+ end
+ it_behaves_like "Bundler.ui receive trace", Interrupt.new
+ end
+
+ context "Gem::InvalidSpecificationException" do
+ it "Bundler.ui receive error" do
+ error = Gem::InvalidSpecificationException.new
+ expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "SystemExit" do
+ # Does nothing
+ end
+
+ context "Java::JavaLang::OutOfMemoryError" do
+ module Java
+ module JavaLang
+ class OutOfMemoryError < StandardError; end
+ end
+ end
+
+ it "Bundler.ui receive error" do
+ error = Java::JavaLang::OutOfMemoryError.new
+ expect(Bundler.ui).to receive(:error).with(/JVM has run out of memory/)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+
+ context "unexpected error" do
+ it "calls request_issue_report_for with error" do
+ error = StandardError.new
+ expect(Bundler::FriendlyErrors).to receive(:request_issue_report_for).with(error)
+ Bundler::FriendlyErrors.log_error(error)
+ end
+ end
+ end
+
+ describe "#exit_status" do
+ it "calls status_code for BundlerError" do
+ error = Bundler::BundlerError.new
+ expect(error).to receive(:status_code).and_return("sample_status_code")
+ expect(Bundler::FriendlyErrors.exit_status(error)).to eq("sample_status_code")
+ end
+
+ it "returns 15 for Thor::Error" do
+ error = Bundler::Thor::Error.new
+ expect(Bundler::FriendlyErrors.exit_status(error)).to eq(15)
+ end
+
+ it "calls status for SystemExit" do
+ error = SystemExit.new
+ expect(error).to receive(:status).and_return("sample_status")
+ expect(Bundler::FriendlyErrors.exit_status(error)).to eq("sample_status")
+ end
+
+ it "returns 1 in other cases" do
+ error = StandardError.new
+ expect(Bundler::FriendlyErrors.exit_status(error)).to eq(1)
+ end
+ end
+
+ describe "#request_issue_report_for" do
+ it "calls relevant methods for Bundler.ui" do
+ expect(Bundler.ui).to receive(:info)
+ expect(Bundler.ui).to receive(:error)
+ expect(Bundler.ui).to receive(:warn)
+ Bundler::FriendlyErrors.request_issue_report_for(StandardError.new)
+ end
+
+ it "includes error class, message and backlog" do
+ error = StandardError.new
+ allow(Bundler::FriendlyErrors).to receive(:issues_url).and_return("")
+
+ expect(error).to receive(:class).at_least(:once)
+ expect(error).to receive(:message).at_least(:once)
+ expect(error).to receive(:backtrace).at_least(:once)
+ Bundler::FriendlyErrors.request_issue_report_for(error)
+ end
+ end
+
+ describe "#issues_url" do
+ it "generates a search URL for the exception message" do
+ exception = Exception.new("Exception message")
+
+ expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/bundler/bundler/search?q=Exception+message&type=Issues")
+ end
+
+ it "generates a search URL for only the first line of a multi-line exception message" do
+ exception = Exception.new(<<END)
+First line of the exception message
+Second line of the exception message
+END
+
+ expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/bundler/bundler/search?q=First+line+of+the+exception+message&type=Issues")
+ end
+
+ it "generates the url without colons" do
+ exception = Exception.new(<<END)
+Exception ::: with ::: colons :::
+END
+ issues_url = Bundler::FriendlyErrors.issues_url(exception)
+ expect(issues_url).not_to include("%3A")
+ expect(issues_url).to eq("https://github.com/bundler/bundler/search?q=#{CGI.escape("Exception with colons ")}&type=Issues")
+ end
+
+ it "removes information after - for Errono::EACCES" do
+ exception = Exception.new(<<END)
+Errno::EACCES: Permission denied @ dir_s_mkdir - /Users/foo/bar/
+END
+ allow(exception).to receive(:is_a?).with(Errno).and_return(true)
+ issues_url = Bundler::FriendlyErrors.issues_url(exception)
+ expect(issues_url).not_to include("/Users/foo/bar")
+ expect(issues_url).to eq("https://github.com/bundler/bundler/search?q=#{CGI.escape("Errno EACCES Permission denied @ dir_s_mkdir ")}&type=Issues")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/gem_helper_spec.rb b/spec/bundler/bundler/gem_helper_spec.rb
new file mode 100644
index 0000000000..a627129fe3
--- /dev/null
+++ b/spec/bundler/bundler/gem_helper_spec.rb
@@ -0,0 +1,351 @@
+# frozen_string_literal: true
+
+require "rake"
+require "bundler/gem_helper"
+
+RSpec.describe Bundler::GemHelper do
+ let(:app_name) { "lorem__ipsum" }
+ let(:app_path) { bundled_app app_name }
+ let(:app_gemspec_path) { app_path.join("#{app_name}.gemspec") }
+
+ before(:each) do
+ global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false"
+ bundle "gem #{app_name}"
+ end
+
+ context "determining gemspec" do
+ subject { Bundler::GemHelper.new(app_path) }
+
+ context "fails" do
+ it "when there is no gemspec" do
+ FileUtils.rm app_gemspec_path
+ expect { subject }.to raise_error(/Unable to determine name/)
+ end
+
+ it "when there are two gemspecs and the name isn't specified" do
+ FileUtils.touch app_path.join("#{app_name}-2.gemspec")
+ expect { subject }.to raise_error(/Unable to determine name/)
+ end
+ end
+
+ context "interpolates the name" do
+ before do
+ # Remove exception that prevents public pushes on older RubyGems versions
+ if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0")
+ content = File.read(app_gemspec_path)
+ content.sub!(/raise "RubyGems 2\.0 or newer.*/, "")
+ File.open(app_gemspec_path, "w") {|f| f.write(content) }
+ end
+ end
+
+ it "when there is only one gemspec" do
+ expect(subject.gemspec.name).to eq(app_name)
+ end
+
+ it "for a hidden gemspec" do
+ FileUtils.mv app_gemspec_path, app_path.join(".gemspec")
+ expect(subject.gemspec.name).to eq(app_name)
+ end
+ end
+
+ it "handles namespaces and converts them to CamelCase" do
+ bundle "gem #{app_name}-foo_bar"
+ underscore_path = bundled_app "#{app_name}-foo_bar"
+
+ lib = underscore_path.join("lib/#{app_name}/foo_bar.rb").read
+ expect(lib).to include("module LoremIpsum")
+ expect(lib).to include("module FooBar")
+ end
+ end
+
+ context "gem management" do
+ def mock_confirm_message(message)
+ expect(Bundler.ui).to receive(:confirm).with(message)
+ end
+
+ def mock_build_message(name, version)
+ message = "#{name} #{version} built to pkg/#{name}-#{version}.gem."
+ mock_confirm_message message
+ end
+
+ subject! { Bundler::GemHelper.new(app_path) }
+ let(:app_version) { "0.1.0" }
+ let(:app_gem_dir) { app_path.join("pkg") }
+ let(:app_gem_path) { app_gem_dir.join("#{app_name}-#{app_version}.gem") }
+ let(:app_gemspec_content) { remove_push_guard(File.read(app_gemspec_path)) }
+
+ before(:each) do
+ content = app_gemspec_content.gsub("TODO: ", "")
+ content.sub!(/homepage\s+= ".*"/, 'homepage = ""')
+ content.gsub!(/spec\.metadata.+\n/, "")
+ File.open(app_gemspec_path, "w") {|file| file << content }
+ end
+
+ def remove_push_guard(gemspec_content)
+ # Remove exception that prevents public pushes on older RubyGems versions
+ if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0")
+ gemspec_content.sub!(/raise "RubyGems 2\.0 or newer.*/, "")
+ end
+ gemspec_content
+ end
+
+ it "uses a shell UI for output" do
+ expect(Bundler.ui).to be_a(Bundler::UI::Shell)
+ end
+
+ describe "#install" do
+ let!(:rake_application) { Rake.application }
+
+ before(:each) do
+ Rake.application = Rake::Application.new
+ end
+
+ after(:each) do
+ Rake.application = rake_application
+ end
+
+ context "defines Rake tasks" do
+ let(:task_names) do
+ %w[build install release release:guard_clean
+ release:source_control_push release:rubygem_push]
+ end
+
+ context "before installation" do
+ it "raises an error with appropriate message" do
+ task_names.each do |name|
+ expect { Rake.application[name] }.
+ to raise_error(/^Don't know how to build task '#{name}'/)
+ end
+ end
+ end
+
+ context "after installation" do
+ before do
+ subject.install
+ end
+
+ it "adds Rake tasks successfully" do
+ task_names.each do |name|
+ expect { Rake.application[name] }.not_to raise_error
+ expect(Rake.application[name]).to be_instance_of Rake::Task
+ end
+ end
+
+ it "provides a way to access the gemspec object" do
+ expect(subject.gemspec.name).to eq(app_name)
+ end
+ end
+ end
+ end
+
+ describe "#build_gem" do
+ context "when build failed" do
+ it "raises an error with appropriate message" do
+ # break the gemspec by adding back the TODOs
+ File.open(app_gemspec_path, "w") {|file| file << app_gemspec_content }
+ expect { subject.build_gem }.to raise_error(/TODO/)
+ end
+ end
+
+ context "when build was successful" do
+ it "creates .gem file" do
+ mock_build_message app_name, app_version
+ subject.build_gem
+ expect(app_gem_path).to exist
+ end
+ end
+ end
+
+ describe "#install_gem" do
+ context "when installation was successful" do
+ it "gem is installed" do
+ mock_build_message app_name, app_version
+ mock_confirm_message "#{app_name} (#{app_version}) installed."
+ subject.install_gem(nil, :local)
+ expect(app_gem_path).to exist
+ gem_command! :list
+ expect(out).to include("#{app_name} (#{app_version})")
+ end
+ end
+
+ context "when installation fails" do
+ it "raises an error with appropriate message" do
+ # create empty gem file in order to simulate install failure
+ allow(subject).to receive(:build_gem) do
+ FileUtils.mkdir_p(app_gem_dir)
+ FileUtils.touch app_gem_path
+ app_gem_path
+ end
+ expect { subject.install_gem }.to raise_error(/Couldn't install gem/)
+ end
+ end
+ end
+
+ describe "rake release" do
+ let!(:rake_application) { Rake.application }
+
+ before(:each) do
+ Rake.application = Rake::Application.new
+ subject.install
+ end
+
+ after(:each) do
+ Rake.application = rake_application
+ end
+
+ before do
+ Dir.chdir(app_path) do
+ `git init`
+ `git config user.email "you@example.com"`
+ `git config user.name "name"`
+ `git config push.default simple`
+ end
+
+ # silence messages
+ allow(Bundler.ui).to receive(:confirm)
+ allow(Bundler.ui).to receive(:error)
+ end
+
+ context "fails" do
+ it "when there are unstaged files" do
+ expect { Rake.application["release"].invoke }.
+ to raise_error("There are files that need to be committed first.")
+ end
+
+ it "when there are uncommitted files" do
+ Dir.chdir(app_path) { `git add .` }
+ expect { Rake.application["release"].invoke }.
+ to raise_error("There are files that need to be committed first.")
+ end
+
+ it "when there is no git remote" do
+ Dir.chdir(app_path) { `git commit -a -m "initial commit"` }
+ expect { Rake.application["release"].invoke }.to raise_error(RuntimeError)
+ end
+ end
+
+ context "succeeds" do
+ before do
+ Dir.chdir(gem_repo1) { `git init --bare` }
+ Dir.chdir(app_path) do
+ `git remote add origin file://#{gem_repo1}`
+ `git commit -a -m "initial commit"`
+ end
+ end
+
+ it "on releasing" do
+ mock_build_message app_name, app_version
+ mock_confirm_message "Tagged v#{app_version}."
+ mock_confirm_message "Pushed git commits and tags."
+ expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+
+ Dir.chdir(app_path) { sys_exec("git push -u origin master") }
+
+ Rake.application["release"].invoke
+ end
+
+ it "even if tag already exists" do
+ mock_build_message app_name, app_version
+ mock_confirm_message "Tag v#{app_version} has already been created."
+ expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s)
+
+ Dir.chdir(app_path) do
+ `git tag -a -m \"Version #{app_version}\" v#{app_version}`
+ end
+
+ Rake.application["release"].invoke
+ end
+ end
+ end
+
+ describe "release:rubygem_push" do
+ let!(:rake_application) { Rake.application }
+
+ before(:each) do
+ Rake.application = Rake::Application.new
+ subject.install
+ allow(subject).to receive(:sh)
+ end
+
+ after(:each) do
+ Rake.application = rake_application
+ end
+
+ before do
+ Dir.chdir(app_path) do
+ `git init`
+ `git config user.email "you@example.com"`
+ `git config user.name "name"`
+ `git config push.default simple`
+ end
+
+ # silence messages
+ allow(Bundler.ui).to receive(:confirm)
+ allow(Bundler.ui).to receive(:error)
+
+ credentials = double("credentials", "file?" => true)
+ allow(Bundler.user_home).to receive(:join).
+ with(".gem/credentials").and_return(credentials)
+ end
+
+ describe "success messaging" do
+ context "No allowed_push_host set" do
+ before do
+ allow(subject).to receive(:allowed_push_host).and_return(nil)
+ end
+
+ around do |example|
+ orig_host = ENV["RUBYGEMS_HOST"]
+ ENV["RUBYGEMS_HOST"] = rubygems_host_env
+
+ example.run
+
+ ENV["RUBYGEMS_HOST"] = orig_host
+ end
+
+ context "RUBYGEMS_HOST env var is set" do
+ let(:rubygems_host_env) { "https://custom.env.gemhost.com" }
+
+ it "should report successful push to the host from the environment" do
+ mock_confirm_message "Pushed #{app_name} #{app_version} to #{rubygems_host_env}"
+
+ Rake.application["release:rubygem_push"].invoke
+ end
+ end
+
+ context "RUBYGEMS_HOST env var is not set" do
+ let(:rubygems_host_env) { nil }
+
+ it "should report successful push to rubygems.org" do
+ mock_confirm_message "Pushed #{app_name} #{app_version} to rubygems.org"
+
+ Rake.application["release:rubygem_push"].invoke
+ end
+ end
+
+ context "RUBYGEMS_HOST env var is an empty string" do
+ let(:rubygems_host_env) { "" }
+
+ it "should report successful push to rubygems.org" do
+ mock_confirm_message "Pushed #{app_name} #{app_version} to rubygems.org"
+
+ Rake.application["release:rubygem_push"].invoke
+ end
+ end
+ end
+
+ context "allowed_push_host set in gemspec" do
+ before do
+ allow(subject).to receive(:allowed_push_host).and_return("https://my.gemhost.com")
+ end
+
+ it "should report successful push to the allowed gem host" do
+ mock_confirm_message "Pushed #{app_name} #{app_version} to https://my.gemhost.com"
+
+ Rake.application["release:rubygem_push"].invoke
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/gem_version_promoter_spec.rb b/spec/bundler/bundler/gem_version_promoter_spec.rb
new file mode 100644
index 0000000000..01e0232fba
--- /dev/null
+++ b/spec/bundler/bundler/gem_version_promoter_spec.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::GemVersionPromoter do
+ context "conservative resolver" do
+ def versions(result)
+ result.flatten.map(&:version).map(&:to_s)
+ end
+
+ def make_instance(*args)
+ @gvp = Bundler::GemVersionPromoter.new(*args).tap do |gvp|
+ gvp.class.class_eval { public :filter_dep_specs, :sort_dep_specs }
+ end
+ end
+
+ def unlocking(options)
+ make_instance(Bundler::SpecSet.new([]), ["foo"]).tap do |p|
+ p.level = options[:level] if options[:level]
+ p.strict = options[:strict] if options[:strict]
+ end
+ end
+
+ def keep_locked(options)
+ make_instance(Bundler::SpecSet.new([]), ["bar"]).tap do |p|
+ p.level = options[:level] if options[:level]
+ p.strict = options[:strict] if options[:strict]
+ end
+ end
+
+ def build_spec_groups(name, versions)
+ versions.map do |v|
+ Bundler::Resolver::SpecGroup.new(build_spec(name, v))
+ end
+ end
+
+ # Rightmost (highest array index) in result is most preferred.
+ # Leftmost (lowest array index) in result is least preferred.
+ # `build_spec_groups` has all versions of gem in index.
+ # `build_spec` is the version currently in the .lock file.
+ #
+ # In default (not strict) mode, all versions in the index will
+ # be returned, allowing Bundler the best chance to resolve all
+ # dependencies, but sometimes resulting in upgrades that some
+ # would not consider conservative.
+ context "filter specs (strict) level patch" do
+ it "when keeping build_spec, keep current, next release" do
+ keep_locked(:level => :patch)
+ res = @gvp.filter_dep_specs(
+ build_spec_groups("foo", %w[1.7.8 1.7.9 1.8.0]),
+ build_spec("foo", "1.7.8").first
+ )
+ expect(versions(res)).to eq %w[1.7.9 1.7.8]
+ end
+
+ it "when unlocking prefer next release first" do
+ unlocking(:level => :patch)
+ res = @gvp.filter_dep_specs(
+ build_spec_groups("foo", %w[1.7.8 1.7.9 1.8.0]),
+ build_spec("foo", "1.7.8").first
+ )
+ expect(versions(res)).to eq %w[1.7.8 1.7.9]
+ end
+
+ it "when unlocking keep current when already at latest release" do
+ unlocking(:level => :patch)
+ res = @gvp.filter_dep_specs(
+ build_spec_groups("foo", %w[1.7.9 1.8.0 2.0.0]),
+ build_spec("foo", "1.7.9").first
+ )
+ expect(versions(res)).to eq %w[1.7.9]
+ end
+ end
+
+ context "filter specs (strict) level minor" do
+ it "when unlocking favor next releases, remove minor and major increases" do
+ unlocking(:level => :minor)
+ res = @gvp.filter_dep_specs(
+ build_spec_groups("foo", %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1]),
+ build_spec("foo", "0.2.0").first
+ )
+ expect(versions(res)).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0]
+ end
+
+ it "when keep locked, keep current, then favor next release, remove minor and major increases" do
+ keep_locked(:level => :minor)
+ res = @gvp.filter_dep_specs(
+ build_spec_groups("foo", %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1]),
+ build_spec("foo", "0.2.0").first
+ )
+ expect(versions(res)).to eq %w[0.3.0 0.3.1 0.9.0 0.2.0]
+ end
+ end
+
+ context "sort specs (not strict) level patch" do
+ it "when not unlocking, same order but make sure build_spec version is most preferred to stay put" do
+ keep_locked(:level => :patch)
+ res = @gvp.sort_dep_specs(
+ build_spec_groups("foo", %w[1.5.4 1.6.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 2.0.0 2.0.1]),
+ build_spec("foo", "1.7.7").first
+ )
+ expect(versions(res)).to eq %w[1.5.4 1.6.5 1.7.6 2.0.0 2.0.1 1.8.0 1.8.1 1.7.8 1.7.9 1.7.7]
+ end
+
+ it "when unlocking favor next release, then current over minor increase" do
+ unlocking(:level => :patch)
+ res = @gvp.sort_dep_specs(
+ build_spec_groups("foo", %w[1.7.7 1.7.8 1.7.9 1.8.0]),
+ build_spec("foo", "1.7.8").first
+ )
+ expect(versions(res)).to eq %w[1.7.7 1.8.0 1.7.8 1.7.9]
+ end
+
+ it "when unlocking do proper integer comparison, not string" do
+ unlocking(:level => :patch)
+ res = @gvp.sort_dep_specs(
+ build_spec_groups("foo", %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0]),
+ build_spec("foo", "1.7.8").first
+ )
+ expect(versions(res)).to eq %w[1.7.7 1.8.0 1.7.8 1.7.9 1.7.15]
+ end
+
+ it "leave current when unlocking but already at latest release" do
+ unlocking(:level => :patch)
+ res = @gvp.sort_dep_specs(
+ build_spec_groups("foo", %w[1.7.9 1.8.0 2.0.0]),
+ build_spec("foo", "1.7.9").first
+ )
+ expect(versions(res)).to eq %w[2.0.0 1.8.0 1.7.9]
+ end
+ end
+
+ context "sort specs (not strict) level minor" do
+ it "when unlocking favor next release, then minor increase over current" do
+ unlocking(:level => :minor)
+ res = @gvp.sort_dep_specs(
+ build_spec_groups("foo", %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1]),
+ build_spec("foo", "0.2.0").first
+ )
+ expect(versions(res)).to eq %w[2.0.0 2.0.1 1.0.0 0.2.0 0.3.0 0.3.1 0.9.0]
+ end
+ end
+
+ context "level error handling" do
+ subject { Bundler::GemVersionPromoter.new }
+
+ it "should raise if not major, minor or patch is passed" do
+ expect { subject.level = :minjor }.to raise_error ArgumentError
+ end
+
+ it "should raise if invalid classes passed" do
+ [123, nil].each do |value|
+ expect { subject.level = value }.to raise_error ArgumentError
+ end
+ end
+
+ it "should accept major, minor patch symbols" do
+ [:major, :minor, :patch].each do |value|
+ subject.level = value
+ expect(subject.level).to eq value
+ end
+ end
+
+ it "should accept major, minor patch strings" do
+ %w[major minor patch].each do |value|
+ subject.level = value
+ expect(subject.level).to eq value.to_sym
+ end
+ end
+ end
+
+ context "debug output" do
+ it "should not kerblooie on its own debug output" do
+ gvp = unlocking(:level => :patch)
+ dep = Bundler::DepProxy.new(dep("foo", "1.2.0").first, "ruby")
+ result = gvp.send(:debug_format_result, dep, build_spec_groups("foo", %w[1.2.0 1.3.0]))
+ expect(result.class).to eq Array
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/index_spec.rb b/spec/bundler/bundler/index_spec.rb
new file mode 100644
index 0000000000..0f3f6e4944
--- /dev/null
+++ b/spec/bundler/bundler/index_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Index do
+ let(:specs) { [] }
+ subject { described_class.build {|i| i.use(specs) } }
+
+ context "specs with a nil platform" do
+ let(:spec) do
+ Gem::Specification.new do |s|
+ s.name = "json"
+ s.version = "1.8.3"
+ allow(s).to receive(:platform).and_return(nil)
+ end
+ end
+ let(:specs) { [spec] }
+
+ describe "#search_by_spec" do
+ it "finds the spec when a nil platform is specified" do
+ expect(subject.search(spec)).to eq([spec])
+ end
+
+ it "finds the spec when a ruby platform is specified" do
+ query = spec.dup.tap {|s| s.platform = "ruby" }
+ expect(subject.search(query)).to eq([spec])
+ end
+ end
+ end
+
+ context "with specs that include development dependencies" do
+ let(:specs) { [*build_spec("a", "1.0.0") {|s| s.development("b", "~> 1.0") }] }
+
+ it "does not include b in #dependency_names" do
+ expect(subject.dependency_names).not_to include("b")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/installer/gem_installer_spec.rb b/spec/bundler/bundler/installer/gem_installer_spec.rb
new file mode 100644
index 0000000000..7340a3acc0
--- /dev/null
+++ b/spec/bundler/bundler/installer/gem_installer_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require "bundler/installer/gem_installer"
+
+RSpec.describe Bundler::GemInstaller do
+ let(:installer) { instance_double("Installer") }
+ let(:spec_source) { instance_double("SpecSource") }
+ let(:spec) { instance_double("Specification", :name => "dummy", :version => "0.0.1", :loaded_from => "dummy", :source => spec_source) }
+
+ subject { described_class.new(spec, installer) }
+
+ context "spec_settings is nil" do
+ it "invokes install method with empty build_args", :rubygems => ">= 2" do
+ allow(spec_source).to receive(:install).with(spec, :force => false, :ensure_builtin_gems_cached => false, :build_args => [])
+ subject.install_from_spec
+ end
+ end
+
+ context "spec_settings is build option" do
+ it "invokes install method with build_args", :rubygems => ">= 2" do
+ allow(Bundler.settings).to receive(:[]).with(:bin)
+ allow(Bundler.settings).to receive(:[]).with(:inline)
+ allow(Bundler.settings).to receive(:[]).with(:forget_cli_options)
+ allow(Bundler.settings).to receive(:[]).with("build.dummy").and_return("--with-dummy-config=dummy")
+ expect(spec_source).to receive(:install).with(spec, :force => false, :ensure_builtin_gems_cached => false, :build_args => ["--with-dummy-config=dummy"])
+ subject.install_from_spec
+ end
+ end
+end
diff --git a/spec/bundler/bundler/installer/parallel_installer_spec.rb b/spec/bundler/bundler/installer/parallel_installer_spec.rb
new file mode 100644
index 0000000000..ace5c1a23a
--- /dev/null
+++ b/spec/bundler/bundler/installer/parallel_installer_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require "bundler/installer/parallel_installer"
+
+RSpec.describe Bundler::ParallelInstaller do
+ let(:installer) { instance_double("Installer") }
+ let(:all_specs) { [] }
+ let(:size) { 1 }
+ let(:standalone) { false }
+ let(:force) { false }
+
+ subject { described_class.new(installer, all_specs, size, standalone, force) }
+
+ context "when dependencies that are not on the overall installation list are the only ones not installed" do
+ let(:all_specs) do
+ [
+ build_spec("alpha", "1.0") {|s| s.runtime "a", "1" },
+ ].flatten
+ end
+
+ it "prints a warning" do
+ expect(Bundler.ui).to receive(:warn).with(<<-W.strip)
+Your lockfile was created by an old Bundler that left some things out.
+You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile.
+The missing gems are:
+* a depended upon by alpha
+ W
+ subject.check_for_corrupt_lockfile
+ end
+
+ context "when size > 1" do
+ let(:size) { 500 }
+
+ it "prints a warning and sets size to 1" do
+ expect(Bundler.ui).to receive(:warn).with(<<-W.strip)
+Your lockfile was created by an old Bundler that left some things out.
+Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing 500 at a time.
+You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile.
+The missing gems are:
+* a depended upon by alpha
+ W
+ subject.check_for_corrupt_lockfile
+ expect(subject.size).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/installer/spec_installation_spec.rb b/spec/bundler/bundler/installer/spec_installation_spec.rb
new file mode 100644
index 0000000000..a9cf09a372
--- /dev/null
+++ b/spec/bundler/bundler/installer/spec_installation_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require "bundler/installer/parallel_installer"
+
+RSpec.describe Bundler::ParallelInstaller::SpecInstallation do
+ let!(:dep) do
+ a_spec = Object.new
+ def a_spec.name
+ "I like tests"
+ end
+ a_spec
+ end
+
+ describe "#ready_to_enqueue?" do
+ context "when in enqueued state" do
+ it "is falsey" do
+ spec = described_class.new(dep)
+ spec.state = :enqueued
+ expect(spec.ready_to_enqueue?).to be_falsey
+ end
+ end
+
+ context "when in installed state" do
+ it "returns falsey" do
+ spec = described_class.new(dep)
+ spec.state = :installed
+ expect(spec.ready_to_enqueue?).to be_falsey
+ end
+ end
+
+ it "returns truthy" do
+ spec = described_class.new(dep)
+ expect(spec.ready_to_enqueue?).to be_truthy
+ end
+ end
+
+ describe "#dependencies_installed?" do
+ context "when all dependencies are installed" do
+ it "returns true" do
+ dependencies = []
+ dependencies << instance_double("SpecInstallation", :spec => "alpha", :name => "alpha", :installed? => true, :all_dependencies => [], :type => :production)
+ dependencies << instance_double("SpecInstallation", :spec => "beta", :name => "beta", :installed? => true, :all_dependencies => [], :type => :production)
+ all_specs = dependencies + [instance_double("SpecInstallation", :spec => "gamma", :name => "gamma", :installed? => false, :all_dependencies => [], :type => :production)]
+ spec = described_class.new(dep)
+ allow(spec).to receive(:all_dependencies).and_return(dependencies)
+ expect(spec.dependencies_installed?(all_specs)).to be_truthy
+ end
+ end
+
+ context "when all dependencies are not installed" do
+ it "returns false" do
+ dependencies = []
+ dependencies << instance_double("SpecInstallation", :spec => "alpha", :name => "alpha", :installed? => false, :all_dependencies => [], :type => :production)
+ dependencies << instance_double("SpecInstallation", :spec => "beta", :name => "beta", :installed? => true, :all_dependencies => [], :type => :production)
+ all_specs = dependencies + [instance_double("SpecInstallation", :spec => "gamma", :name => "gamma", :installed? => false, :all_dependencies => [], :type => :production)]
+ spec = described_class.new(dep)
+ allow(spec).to receive(:all_dependencies).and_return(dependencies)
+ expect(spec.dependencies_installed?(all_specs)).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/lockfile_parser_spec.rb b/spec/bundler/bundler/lockfile_parser_spec.rb
new file mode 100644
index 0000000000..3a6d61336f
--- /dev/null
+++ b/spec/bundler/bundler/lockfile_parser_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require "bundler/lockfile_parser"
+
+RSpec.describe Bundler::LockfileParser do
+ let(:lockfile_contents) { strip_whitespace(<<-L) }
+ GIT
+ remote: https://github.com/alloy/peiji-san.git
+ revision: eca485d8dc95f12aaec1a434b49d295c7e91844b
+ specs:
+ peiji-san (1.2.0)
+
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ rake (10.3.2)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ peiji-san!
+ rake
+
+ RUBY VERSION
+ ruby 2.1.3p242
+
+ BUNDLED WITH
+ 1.12.0.rc.2
+ L
+
+ describe ".sections_in_lockfile" do
+ it "returns the attributes" do
+ attributes = described_class.sections_in_lockfile(lockfile_contents)
+ expect(attributes).to contain_exactly(
+ "BUNDLED WITH", "DEPENDENCIES", "GEM", "GIT", "PLATFORMS", "RUBY VERSION"
+ )
+ end
+ end
+
+ describe ".unknown_sections_in_lockfile" do
+ let(:lockfile_contents) { strip_whitespace(<<-L) }
+ UNKNOWN ATTR
+
+ UNKNOWN ATTR 2
+ random contents
+ L
+
+ it "returns the unknown attributes" do
+ attributes = described_class.unknown_sections_in_lockfile(lockfile_contents)
+ expect(attributes).to contain_exactly("UNKNOWN ATTR", "UNKNOWN ATTR 2")
+ end
+ end
+
+ describe ".sections_to_ignore" do
+ subject { described_class.sections_to_ignore(base_version) }
+
+ context "with a nil base version" do
+ let(:base_version) { nil }
+
+ it "returns the same as > 1.0" do
+ expect(subject).to contain_exactly(
+ described_class::BUNDLED, described_class::RUBY, described_class::PLUGIN
+ )
+ end
+ end
+
+ context "with a prerelease base version" do
+ let(:base_version) { Gem::Version.create("1.11.0.rc.1") }
+
+ it "returns the same as for the release version" do
+ expect(subject).to contain_exactly(
+ described_class::RUBY, described_class::PLUGIN
+ )
+ end
+ end
+
+ context "with a current version" do
+ let(:base_version) { Gem::Version.create(Bundler::VERSION) }
+
+ it "returns an empty array" do
+ expect(subject).to eq([])
+ end
+ end
+
+ context "with a future version" do
+ let(:base_version) { Gem::Version.create("5.5.5") }
+
+ it "returns an empty array" do
+ expect(subject).to eq([])
+ end
+ end
+ end
+
+ describe "#initialize" do
+ before { allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app("gems.rb")) }
+ subject { described_class.new(lockfile_contents) }
+
+ let(:sources) do
+ [Bundler::Source::Git.new("uri" => "https://github.com/alloy/peiji-san.git", "revision" => "eca485d8dc95f12aaec1a434b49d295c7e91844b"),
+ Bundler::Source::Rubygems.new("remotes" => ["https://rubygems.org"])]
+ end
+ let(:dependencies) do
+ {
+ "peiji-san" => Bundler::Dependency.new("peiji-san", ">= 0"),
+ "rake" => Bundler::Dependency.new("rake", ">= 0"),
+ }
+ end
+ let(:specs) do
+ [
+ Bundler::LazySpecification.new("peiji-san", v("1.2.0"), rb),
+ Bundler::LazySpecification.new("rake", v("10.3.2"), rb),
+ ]
+ end
+ let(:platforms) { [rb] }
+ let(:bundler_version) { Gem::Version.new("1.12.0.rc.2") }
+ let(:ruby_version) { "ruby 2.1.3p242" }
+
+ shared_examples_for "parsing" do
+ it "parses correctly" do
+ expect(subject.sources).to eq sources
+ expect(subject.dependencies).to eq dependencies
+ expect(subject.specs).to eq specs
+ expect(Hash[subject.specs.map {|s| [s, s.dependencies] }]).to eq Hash[subject.specs.map {|s| [s, s.dependencies] }]
+ expect(subject.platforms).to eq platforms
+ expect(subject.bundler_version).to eq bundler_version
+ expect(subject.ruby_version).to eq ruby_version
+ end
+ end
+
+ include_examples "parsing"
+
+ context "when an extra section is at the end" do
+ let(:lockfile_contents) { super() + "\n\nFOO BAR\n baz\n baa\n qux\n" }
+ include_examples "parsing"
+ end
+
+ context "when an extra section is at the start" do
+ let(:lockfile_contents) { "FOO BAR\n baz\n baa\n qux\n\n" + super() }
+ include_examples "parsing"
+ end
+
+ context "when an extra section is in the middle" do
+ let(:lockfile_contents) { super().split(/(?=GEM)/).insert(1, "FOO BAR\n baz\n baa\n qux\n\n").join }
+ include_examples "parsing"
+ end
+
+ context "when a dependency has options" do
+ let(:lockfile_contents) { super().sub("peiji-san!", "peiji-san!\n foo: bar") }
+ include_examples "parsing"
+ end
+ end
+end
diff --git a/spec/bundler/bundler/mirror_spec.rb b/spec/bundler/bundler/mirror_spec.rb
new file mode 100644
index 0000000000..acd0895f2f
--- /dev/null
+++ b/spec/bundler/bundler/mirror_spec.rb
@@ -0,0 +1,329 @@
+# frozen_string_literal: true
+
+require "bundler/mirror"
+
+RSpec.describe Bundler::Settings::Mirror do
+ let(:mirror) { Bundler::Settings::Mirror.new }
+
+ it "returns zero when fallback_timeout is not set" do
+ expect(mirror.fallback_timeout).to eq(0)
+ end
+
+ it "takes a number as a fallback_timeout" do
+ mirror.fallback_timeout = 1
+ expect(mirror.fallback_timeout).to eq(1)
+ end
+
+ it "takes truthy as a default fallback timeout" do
+ mirror.fallback_timeout = true
+ expect(mirror.fallback_timeout).to eq(0.1)
+ end
+
+ it "takes falsey as a zero fallback timeout" do
+ mirror.fallback_timeout = false
+ expect(mirror.fallback_timeout).to eq(0)
+ end
+
+ it "takes a string with 'true' as a default fallback timeout" do
+ mirror.fallback_timeout = "true"
+ expect(mirror.fallback_timeout).to eq(0.1)
+ end
+
+ it "takes a string with 'false' as a zero fallback timeout" do
+ mirror.fallback_timeout = "false"
+ expect(mirror.fallback_timeout).to eq(0)
+ end
+
+ it "takes a string for the uri but returns an uri object" do
+ mirror.uri = "http://localhost:9292"
+ expect(mirror.uri).to eq(URI("http://localhost:9292"))
+ end
+
+ it "takes an uri object for the uri" do
+ mirror.uri = URI("http://localhost:9293")
+ expect(mirror.uri).to eq(URI("http://localhost:9293"))
+ end
+
+ context "without a uri" do
+ it "invalidates the mirror" do
+ mirror.validate!
+ expect(mirror.valid?).to be_falsey
+ end
+ end
+
+ context "with an uri" do
+ before { mirror.uri = "http://localhost:9292" }
+
+ context "without a fallback timeout" do
+ it "is not valid by default" do
+ expect(mirror.valid?).to be_falsey
+ end
+
+ context "when probed" do
+ let(:probe) { double }
+
+ context "with a replying mirror" do
+ before do
+ allow(probe).to receive(:replies?).and_return(true)
+ mirror.validate!(probe)
+ end
+
+ it "is valid" do
+ expect(mirror.valid?).to be_truthy
+ end
+ end
+
+ context "with a non replying mirror" do
+ before do
+ allow(probe).to receive(:replies?).and_return(false)
+ mirror.validate!(probe)
+ end
+
+ it "is still valid" do
+ expect(mirror.valid?).to be_truthy
+ end
+ end
+ end
+ end
+
+ context "with a fallback timeout" do
+ before { mirror.fallback_timeout = 1 }
+
+ it "is not valid by default" do
+ expect(mirror.valid?).to be_falsey
+ end
+
+ context "when probed" do
+ let(:probe) { double }
+
+ context "with a replying mirror" do
+ before do
+ allow(probe).to receive(:replies?).and_return(true)
+ mirror.validate!(probe)
+ end
+
+ it "is valid" do
+ expect(mirror.valid?).to be_truthy
+ end
+
+ it "is validated only once" do
+ allow(probe).to receive(:replies?).and_raise("Only once!")
+ mirror.validate!(probe)
+ expect(mirror.valid?).to be_truthy
+ end
+ end
+
+ context "with a non replying mirror" do
+ before do
+ allow(probe).to receive(:replies?).and_return(false)
+ mirror.validate!(probe)
+ end
+
+ it "is not valid" do
+ expect(mirror.valid?).to be_falsey
+ end
+
+ it "is validated only once" do
+ allow(probe).to receive(:replies?).and_raise("Only once!")
+ mirror.validate!(probe)
+ expect(mirror.valid?).to be_falsey
+ end
+ end
+ end
+ end
+
+ describe "#==" do
+ it "returns true if uri and fallback timeout are the same" do
+ uri = "https://ruby.taobao.org"
+ mirror = Bundler::Settings::Mirror.new(uri, 1)
+ another_mirror = Bundler::Settings::Mirror.new(uri, 1)
+
+ expect(mirror == another_mirror).to be true
+ end
+ end
+ end
+end
+
+RSpec.describe Bundler::Settings::Mirrors do
+ let(:localhost_uri) { URI("http://localhost:9292") }
+
+ context "with a just created mirror" do
+ let(:mirrors) do
+ probe = double
+ allow(probe).to receive(:replies?).and_return(true)
+ Bundler::Settings::Mirrors.new(probe)
+ end
+
+ it "returns a mirror that contains the source uri for an unknown uri" do
+ mirror = mirrors.for("http://rubygems.org/")
+ expect(mirror).to eq(Bundler::Settings::Mirror.new("http://rubygems.org/"))
+ end
+
+ it "parses a mirror key and returns a mirror for the parsed uri" do
+ mirrors.parse("mirror.http://rubygems.org/", localhost_uri)
+ expect(mirrors.for("http://rubygems.org/").uri).to eq(localhost_uri)
+ end
+
+ it "parses a relative mirror key and returns a mirror for the parsed http uri" do
+ mirrors.parse("mirror.rubygems.org", localhost_uri)
+ expect(mirrors.for("http://rubygems.org/").uri).to eq(localhost_uri)
+ end
+
+ it "parses a relative mirror key and returns a mirror for the parsed https uri" do
+ mirrors.parse("mirror.rubygems.org", localhost_uri)
+ expect(mirrors.for("https://rubygems.org/").uri).to eq(localhost_uri)
+ end
+
+ context "with a uri parsed already" do
+ before { mirrors.parse("mirror.http://rubygems.org/", localhost_uri) }
+
+ it "takes a mirror fallback_timeout and assigns the timeout" do
+ mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "2")
+ expect(mirrors.for("http://rubygems.org/").fallback_timeout).to eq(2)
+ end
+
+ it "parses a 'true' fallback timeout and sets the default timeout" do
+ mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "true")
+ expect(mirrors.for("http://rubygems.org/").fallback_timeout).to eq(0.1)
+ end
+
+ it "parses a 'false' fallback timeout and sets it to zero" do
+ mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "false")
+ expect(mirrors.for("http://rubygems.org/").fallback_timeout).to eq(0)
+ end
+ end
+ end
+
+ context "with a mirror prober that replies on time" do
+ let(:mirrors) do
+ probe = double
+ allow(probe).to receive(:replies?).and_return(true)
+ Bundler::Settings::Mirrors.new(probe)
+ end
+
+ context "with a default fallback_timeout for rubygems.org" do
+ before do
+ mirrors.parse("mirror.http://rubygems.org/", localhost_uri)
+ mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "true")
+ end
+
+ it "returns localhost" do
+ expect(mirrors.for("http://rubygems.org").uri).to eq(localhost_uri)
+ end
+ end
+
+ context "with a mirror for all" do
+ before do
+ mirrors.parse("mirror.all", localhost_uri)
+ end
+
+ context "without a fallback timeout" do
+ it "returns localhost uri for rubygems" do
+ expect(mirrors.for("http://rubygems.org").uri).to eq(localhost_uri)
+ end
+
+ it "returns localhost for any other url" do
+ expect(mirrors.for("http://whatever.com/").uri).to eq(localhost_uri)
+ end
+ end
+ context "with a fallback timeout" do
+ before { mirrors.parse("mirror.all.fallback_timeout", "1") }
+
+ it "returns localhost uri for rubygems" do
+ expect(mirrors.for("http://rubygems.org").uri).to eq(localhost_uri)
+ end
+
+ it "returns localhost for any other url" do
+ expect(mirrors.for("http://whatever.com/").uri).to eq(localhost_uri)
+ end
+ end
+ end
+ end
+
+ context "with a mirror prober that does not reply on time" do
+ let(:mirrors) do
+ probe = double
+ allow(probe).to receive(:replies?).and_return(false)
+ Bundler::Settings::Mirrors.new(probe)
+ end
+
+ context "with a localhost mirror for all" do
+ before { mirrors.parse("mirror.all", localhost_uri) }
+
+ context "without a fallback timeout" do
+ it "returns localhost" do
+ expect(mirrors.for("http://whatever.com").uri).to eq(localhost_uri)
+ end
+ end
+
+ context "with a fallback timeout" do
+ before { mirrors.parse("mirror.all.fallback_timeout", "true") }
+
+ it "returns the source uri, not localhost" do
+ expect(mirrors.for("http://whatever.com").uri).to eq(URI("http://whatever.com/"))
+ end
+ end
+ end
+
+ context "with localhost as a mirror for rubygems.org" do
+ before { mirrors.parse("mirror.http://rubygems.org/", localhost_uri) }
+
+ context "without a fallback timeout" do
+ it "returns the uri that is not mirrored" do
+ expect(mirrors.for("http://whatever.com").uri).to eq(URI("http://whatever.com/"))
+ end
+
+ it "returns localhost for rubygems.org" do
+ expect(mirrors.for("http://rubygems.org/").uri).to eq(localhost_uri)
+ end
+ end
+
+ context "with a fallback timeout" do
+ before { mirrors.parse("mirror.http://rubygems.org/.fallback_timeout", "true") }
+
+ it "returns the uri that is not mirrored" do
+ expect(mirrors.for("http://whatever.com").uri).to eq(URI("http://whatever.com/"))
+ end
+
+ it "returns rubygems.org for rubygems.org" do
+ expect(mirrors.for("http://rubygems.org/").uri).to eq(URI("http://rubygems.org/"))
+ end
+ end
+ end
+ end
+end
+
+RSpec.describe Bundler::Settings::TCPSocketProbe do
+ let(:probe) { Bundler::Settings::TCPSocketProbe.new }
+
+ context "with a listening TCP Server" do
+ def with_server_and_mirror
+ server = TCPServer.new("127.0.0.1", 0)
+ mirror = Bundler::Settings::Mirror.new("http://localhost:#{server.addr[1]}", 1)
+ yield server, mirror
+ server.close unless server.closed?
+ end
+
+ it "probes the server correctly", :ruby_repo do
+ with_server_and_mirror do |server, mirror|
+ expect(server.closed?).to be_falsey
+ expect(probe.replies?(mirror)).to be_truthy
+ end
+ end
+
+ it "probes falsey when the server is down" do
+ with_server_and_mirror do |server, mirror|
+ server.close
+ expect(probe.replies?(mirror)).to be_falsey
+ end
+ end
+ end
+
+ context "with an invalid mirror" do
+ let(:mirror) { Bundler::Settings::Mirror.new("http://127.0.0.127:9292", true) }
+
+ it "fails with a timeout when there is nothing to tcp handshake" do
+ expect(probe.replies?(mirror)).to be_falsey
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/api/source_spec.rb b/spec/bundler/bundler/plugin/api/source_spec.rb
new file mode 100644
index 0000000000..2c50ff56a4
--- /dev/null
+++ b/spec/bundler/bundler/plugin/api/source_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::API::Source do
+ let(:uri) { "uri://to/test" }
+ let(:type) { "spec_type" }
+
+ subject(:source) do
+ klass = Class.new
+ klass.send :include, Bundler::Plugin::API::Source
+ klass.new("uri" => uri, "type" => type)
+ end
+
+ describe "attributes" do
+ it "allows access to uri" do
+ expect(source.uri).to eq("uri://to/test")
+ end
+
+ it "allows access to name" do
+ expect(source.name).to eq("spec_type at uri://to/test")
+ end
+ end
+
+ context "post_install" do
+ let(:installer) { double(:installer) }
+
+ before do
+ allow(Bundler::Source::Path::Installer).to receive(:new) { installer }
+ end
+
+ it "calls Path::Installer's post_install" do
+ expect(installer).to receive(:post_install).once
+
+ source.post_install(double(:spec))
+ end
+ end
+
+ context "install_path" do
+ let(:uri) { "uri://to/a/repository-name" }
+ let(:hash) { Digest(:SHA1).hexdigest(uri) }
+ let(:install_path) { Pathname.new "/bundler/install/path" }
+
+ before do
+ allow(Bundler).to receive(:install_path) { install_path }
+ end
+
+ it "returns basename with uri_hash" do
+ expected = Pathname.new "#{install_path}/repository-name-#{hash[0..11]}"
+ expect(source.install_path).to eq(expected)
+ end
+ end
+
+ context "to_lock" do
+ it "returns the string with remote and type" do
+ expected = strip_whitespace <<-L
+ PLUGIN SOURCE
+ remote: #{uri}
+ type: #{type}
+ specs:
+ L
+
+ expect(source.to_lock).to eq(expected)
+ end
+
+ context "with additional options to lock" do
+ before do
+ allow(source).to receive(:options_to_lock) { { "first" => "option" } }
+ end
+
+ it "includes them" do
+ expected = strip_whitespace <<-L
+ PLUGIN SOURCE
+ remote: #{uri}
+ type: #{type}
+ first: option
+ specs:
+ L
+
+ expect(source.to_lock).to eq(expected)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/api_spec.rb b/spec/bundler/bundler/plugin/api_spec.rb
new file mode 100644
index 0000000000..58fb908572
--- /dev/null
+++ b/spec/bundler/bundler/plugin/api_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::API do
+ context "plugin declarations" do
+ before do
+ stub_const "UserPluginClass", Class.new(Bundler::Plugin::API)
+ end
+
+ describe "#command" do
+ it "declares a command plugin with same class as handler" do
+ expect(Bundler::Plugin).
+ to receive(:add_command).with("meh", UserPluginClass).once
+
+ UserPluginClass.command "meh"
+ end
+
+ it "accepts another class as argument that handles the command" do
+ stub_const "NewClass", Class.new
+ expect(Bundler::Plugin).to receive(:add_command).with("meh", NewClass).once
+
+ UserPluginClass.command "meh", NewClass
+ end
+ end
+
+ describe "#source" do
+ it "declares a source plugin with same class as handler" do
+ expect(Bundler::Plugin).
+ to receive(:add_source).with("a_source", UserPluginClass).once
+
+ UserPluginClass.source "a_source"
+ end
+
+ it "accepts another class as argument that handles the command" do
+ stub_const "NewClass", Class.new
+ expect(Bundler::Plugin).to receive(:add_source).with("a_source", NewClass).once
+
+ UserPluginClass.source "a_source", NewClass
+ end
+ end
+
+ describe "#hook" do
+ it "accepts a block and passes it to Plugin module" do
+ foo = double("tester")
+ expect(foo).to receive(:called)
+
+ expect(Bundler::Plugin).to receive(:add_hook).with("post-foo").and_yield
+
+ Bundler::Plugin::API.hook("post-foo") { foo.called }
+ end
+ end
+ end
+
+ context "bundler interfaces provided" do
+ before do
+ stub_const "UserPluginClass", Class.new(Bundler::Plugin::API)
+ end
+
+ subject(:api) { UserPluginClass.new }
+
+ # A test of delegation
+ it "provides the Bundler's functions" do
+ expect(Bundler).to receive(:an_unknown_function).once
+
+ api.an_unknown_function
+ end
+
+ it "includes Bundler::SharedHelpers' functions" do
+ expect(Bundler::SharedHelpers).to receive(:an_unknown_helper).once
+
+ api.an_unknown_helper
+ end
+
+ context "#tmp" do
+ it "provides a tmp dir" do
+ expect(api.tmp("mytmp")).to be_directory
+ end
+
+ it "accepts multiple names for suffix" do
+ expect(api.tmp("myplugin", "download")).to be_directory
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/dsl_spec.rb b/spec/bundler/bundler/plugin/dsl_spec.rb
new file mode 100644
index 0000000000..be23db3bba
--- /dev/null
+++ b/spec/bundler/bundler/plugin/dsl_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::DSL do
+ DSL = Bundler::Plugin::DSL
+
+ subject(:dsl) { Bundler::Plugin::DSL.new }
+
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new "/" }
+ end
+
+ describe "it ignores only the methods defined in Bundler::Dsl" do
+ it "doesn't raises error for Dsl methods" do
+ expect { dsl.install_if }.not_to raise_error
+ end
+
+ it "raises error for other methods" do
+ expect { dsl.no_method }.to raise_error(DSL::PluginGemfileError)
+ end
+ end
+
+ describe "source block" do
+ it "adds #source with :type to list and also inferred_plugins list" do
+ expect(dsl).to receive(:plugin).with("bundler-source-news").once
+
+ dsl.source("some_random_url", :type => "news") {}
+
+ expect(dsl.inferred_plugins).to eq(["bundler-source-news"])
+ end
+
+ it "registers a source type plugin only once for multiple declataions" do
+ expect(dsl).to receive(:plugin).with("bundler-source-news").and_call_original.once
+
+ dsl.source("some_random_url", :type => "news") {}
+ dsl.source("another_random_url", :type => "news") {}
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/events_spec.rb b/spec/bundler/bundler/plugin/events_spec.rb
new file mode 100644
index 0000000000..b09e915682
--- /dev/null
+++ b/spec/bundler/bundler/plugin/events_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::Events do
+ context "plugin events" do
+ describe "#define" do
+ it "raises when redefining a constant" do
+ expect do
+ Bundler::Plugin::Events.send(:define, :GEM_BEFORE_INSTALL_ALL, "another-value")
+ end.to raise_error(ArgumentError)
+ end
+
+ it "can define a new constant" do
+ Bundler::Plugin::Events.send(:define, :NEW_CONSTANT, "value")
+ expect(Bundler::Plugin::Events::NEW_CONSTANT).to eq("value")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/index_spec.rb b/spec/bundler/bundler/plugin/index_spec.rb
new file mode 100644
index 0000000000..ca3476ea2a
--- /dev/null
+++ b/spec/bundler/bundler/plugin/index_spec.rb
@@ -0,0 +1,186 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::Index do
+ Index = Bundler::Plugin::Index
+
+ before do
+ gemfile ""
+ path = lib_path(plugin_name)
+ index.register_plugin("new-plugin", path.to_s, [path.join("lib").to_s], commands, sources, hooks)
+ end
+
+ let(:plugin_name) { "new-plugin" }
+ let(:commands) { [] }
+ let(:sources) { [] }
+ let(:hooks) { [] }
+
+ subject(:index) { Index.new }
+
+ describe "#register plugin" do
+ it "is available for retrieval" do
+ expect(index.plugin_path(plugin_name)).to eq(lib_path(plugin_name))
+ end
+
+ it "load_paths is available for retrival" do
+ expect(index.load_paths(plugin_name)).to eq([lib_path(plugin_name).join("lib").to_s])
+ end
+
+ it "is persistent" do
+ new_index = Index.new
+ expect(new_index.plugin_path(plugin_name)).to eq(lib_path(plugin_name))
+ end
+
+ it "load_paths are persistent" do
+ new_index = Index.new
+ expect(new_index.load_paths(plugin_name)).to eq([lib_path(plugin_name).join("lib").to_s])
+ end
+ end
+
+ describe "commands" do
+ let(:commands) { ["newco"] }
+
+ it "returns the plugins name on query" do
+ expect(index.command_plugin("newco")).to eq(plugin_name)
+ end
+
+ it "raises error on conflict" do
+ expect do
+ index.register_plugin("aplugin", lib_path("aplugin").to_s, lib_path("aplugin").join("lib").to_s, ["newco"], [], [])
+ end.to raise_error(Index::CommandConflict)
+ end
+
+ it "is persistent" do
+ new_index = Index.new
+ expect(new_index.command_plugin("newco")).to eq(plugin_name)
+ end
+ end
+
+ describe "source" do
+ let(:sources) { ["new_source"] }
+
+ it "returns the plugins name on query" do
+ expect(index.source_plugin("new_source")).to eq(plugin_name)
+ end
+
+ it "raises error on conflict" do
+ expect do
+ index.register_plugin("aplugin", lib_path("aplugin").to_s, lib_path("aplugin").join("lib").to_s, [], ["new_source"], [])
+ end.to raise_error(Index::SourceConflict)
+ end
+
+ it "is persistent" do
+ new_index = Index.new
+ expect(new_index.source_plugin("new_source")).to eq(plugin_name)
+ end
+ end
+
+ describe "hook" do
+ let(:hooks) { ["after-bar"] }
+
+ it "returns the plugins name on query" do
+ expect(index.hook_plugins("after-bar")).to include(plugin_name)
+ end
+
+ it "is persistent" do
+ new_index = Index.new
+ expect(new_index.hook_plugins("after-bar")).to eq([plugin_name])
+ end
+
+ context "that are not registered", :focused do
+ let(:file) { double("index-file") }
+
+ before do
+ index.hook_plugins("not-there")
+ allow(File).to receive(:open).and_yield(file)
+ end
+
+ it "should not save it with next registered hook" do
+ expect(file).to receive(:puts) do |content|
+ expect(content).not_to include("not-there")
+ end
+
+ index.register_plugin("aplugin", lib_path("aplugin").to_s, lib_path("aplugin").join("lib").to_s, [], [], [])
+ end
+ end
+ end
+
+ describe "global index" do
+ before do
+ Dir.chdir(tmp) do
+ Bundler::Plugin.reset!
+ path = lib_path("gplugin")
+ index.register_plugin("gplugin", path.to_s, [path.join("lib").to_s], [], ["glb_source"], [])
+ end
+ end
+
+ it "skips sources" do
+ new_index = Index.new
+ expect(new_index.source_plugin("glb_source")).to be_falsy
+ end
+ end
+
+ describe "after conflict" do
+ let(:commands) { ["foo"] }
+ let(:sources) { ["bar"] }
+ let(:hooks) { ["hoook"] }
+
+ shared_examples "it cleans up" do
+ it "the path" do
+ expect(index.installed?("cplugin")).to be_falsy
+ end
+
+ it "the command" do
+ expect(index.command_plugin("xfoo")).to be_falsy
+ end
+
+ it "the source" do
+ expect(index.source_plugin("xbar")).to be_falsy
+ end
+
+ it "the hook" do
+ expect(index.hook_plugins("xhoook")).to be_empty
+ end
+ end
+
+ context "on command conflict it cleans up" do
+ before do
+ expect do
+ path = lib_path("cplugin")
+ index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["xbar"], ["xhoook"])
+ end.to raise_error(Index::CommandConflict)
+ end
+
+ include_examples "it cleans up"
+ end
+
+ context "on source conflict it cleans up" do
+ before do
+ expect do
+ path = lib_path("cplugin")
+ index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["xfoo"], ["bar"], ["xhoook"])
+ end.to raise_error(Index::SourceConflict)
+ end
+
+ include_examples "it cleans up"
+ end
+
+ context "on command and source conflict it cleans up" do
+ before do
+ expect do
+ path = lib_path("cplugin")
+ index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["bar"], ["xhoook"])
+ end.to raise_error(Index::CommandConflict)
+ end
+
+ include_examples "it cleans up"
+ end
+ end
+
+ describe "readonly disk without home" do
+ it "ignores being unable to create temp home dir" do
+ expect_any_instance_of(Bundler::Plugin::Index).to receive(:global_index_file).
+ and_raise(Bundler::GenericSystemCallError.new("foo", "bar"))
+ Bundler::Plugin::Index.new
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/installer_spec.rb b/spec/bundler/bundler/plugin/installer_spec.rb
new file mode 100644
index 0000000000..f8bf8450c9
--- /dev/null
+++ b/spec/bundler/bundler/plugin/installer_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::Installer do
+ subject(:installer) { Bundler::Plugin::Installer.new }
+
+ before do
+ # allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(Pathname.new("/Gemfile"))
+ end
+
+ describe "cli install" do
+ it "uses Gem.sources when non of the source is provided" do
+ sources = double(:sources)
+ Bundler.settings # initialize it before we have to touch rubygems.ext_lock
+ allow(Bundler).to receive_message_chain("rubygems.sources") { sources }
+
+ allow(installer).to receive(:install_rubygems).
+ with("new-plugin", [">= 0"], sources).once
+
+ installer.install("new-plugin", {})
+ end
+
+ describe "with mocked installers" do
+ let(:spec) { double(:spec) }
+ it "returns the installed spec after installing git plugins" do
+ allow(installer).to receive(:install_git).
+ and_return("new-plugin" => spec)
+
+ expect(installer.install(["new-plugin"], :git => "https://some.ran/dom")).
+ to eq("new-plugin" => spec)
+ end
+
+ it "returns the installed spec after installing rubygems plugins" do
+ allow(installer).to receive(:install_rubygems).
+ and_return("new-plugin" => spec)
+
+ expect(installer.install(["new-plugin"], :source => "https://some.ran/dom")).
+ to eq("new-plugin" => spec)
+ end
+ end
+
+ describe "with actual installers" do
+ before do
+ build_repo2 do
+ build_plugin "re-plugin"
+ build_plugin "ma-plugin"
+ end
+ end
+
+ context "git plugins" do
+ before do
+ build_git "ga-plugin", :path => lib_path("ga-plugin") do |s|
+ s.write "plugins.rb"
+ end
+ end
+
+ let(:result) do
+ installer.install(["ga-plugin"], :git => "file://#{lib_path("ga-plugin")}")
+ end
+
+ it "returns the installed spec after installing" do
+ spec = result["ga-plugin"]
+ expect(spec.full_name).to eq "ga-plugin-1.0"
+ end
+
+ it "has expected full gem path" do
+ rev = revision_for(lib_path("ga-plugin"))
+ expect(result["ga-plugin"].full_gem_path).
+ to eq(Bundler::Plugin.root.join("bundler", "gems", "ga-plugin-#{rev[0..11]}").to_s)
+ end
+ end
+
+ context "rubygems plugins" do
+ let(:result) do
+ installer.install(["re-plugin"], :source => "file://#{gem_repo2}")
+ end
+
+ it "returns the installed spec after installing " do
+ expect(result["re-plugin"]).to be_kind_of(Bundler::RemoteSpecification)
+ end
+
+ it "has expected full_gem)path" do
+ expect(result["re-plugin"].full_gem_path).
+ to eq(global_plugin_gem("re-plugin-1.0").to_s)
+ end
+ end
+
+ context "multiple plugins" do
+ let(:result) do
+ installer.install(["re-plugin", "ma-plugin"], :source => "file://#{gem_repo2}")
+ end
+
+ it "returns the installed spec after installing " do
+ expect(result["re-plugin"]).to be_kind_of(Bundler::RemoteSpecification)
+ expect(result["ma-plugin"]).to be_kind_of(Bundler::RemoteSpecification)
+ end
+
+ it "has expected full_gem)path" do
+ expect(result["re-plugin"].full_gem_path).to eq(global_plugin_gem("re-plugin-1.0").to_s)
+ expect(result["ma-plugin"].full_gem_path).to eq(global_plugin_gem("ma-plugin-1.0").to_s)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin/source_list_spec.rb b/spec/bundler/bundler/plugin/source_list_spec.rb
new file mode 100644
index 0000000000..64a1233dd1
--- /dev/null
+++ b/spec/bundler/bundler/plugin/source_list_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin::SourceList do
+ SourceList = Bundler::Plugin::SourceList
+
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new "/" }
+ end
+
+ subject(:source_list) { SourceList.new }
+
+ describe "adding sources uses classes for plugin" do
+ it "uses Plugin::Installer::Rubygems for rubygems sources" do
+ source = source_list.
+ add_rubygems_source("remotes" => ["https://existing-rubygems.org"])
+ expect(source).to be_instance_of(Bundler::Plugin::Installer::Rubygems)
+ end
+
+ it "uses Plugin::Installer::Git for git sources" do
+ source = source_list.
+ add_git_source("uri" => "git://existing-git.org/path.git")
+ expect(source).to be_instance_of(Bundler::Plugin::Installer::Git)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb
new file mode 100644
index 0000000000..9266fad1eb
--- /dev/null
+++ b/spec/bundler/bundler/plugin_spec.rb
@@ -0,0 +1,309 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Plugin do
+ Plugin = Bundler::Plugin
+
+ let(:installer) { double(:installer) }
+ let(:index) { double(:index) }
+ let(:spec) { double(:spec) }
+ let(:spec2) { double(:spec2) }
+
+ before do
+ build_lib "new-plugin", :path => lib_path("new-plugin") do |s|
+ s.write "plugins.rb"
+ end
+
+ build_lib "another-plugin", :path => lib_path("another-plugin") do |s|
+ s.write "plugins.rb"
+ end
+
+ allow(spec).to receive(:full_gem_path).
+ and_return(lib_path("new-plugin").to_s)
+ allow(spec).to receive(:load_paths).
+ and_return([lib_path("new-plugin").join("lib").to_s])
+
+ allow(spec2).to receive(:full_gem_path).
+ and_return(lib_path("another-plugin").to_s)
+ allow(spec2).to receive(:load_paths).
+ and_return([lib_path("another-plugin").join("lib").to_s])
+
+ allow(Plugin::Installer).to receive(:new) { installer }
+ allow(Plugin).to receive(:index) { index }
+ allow(index).to receive(:register_plugin)
+ end
+
+ describe "install command" do
+ let(:opts) { { "version" => "~> 1.0", "source" => "foo" } }
+
+ before do
+ allow(installer).to receive(:install).with(["new-plugin"], opts) do
+ { "new-plugin" => spec }
+ end
+ end
+
+ it "passes the name and options to installer" do
+ allow(installer).to receive(:install).with(["new-plugin"], opts) do
+ { "new-plugin" => spec }
+ end.once
+
+ subject.install ["new-plugin"], opts
+ end
+
+ it "validates the installed plugin" do
+ allow(subject).
+ to receive(:validate_plugin!).with(lib_path("new-plugin")).once
+
+ subject.install ["new-plugin"], opts
+ end
+
+ it "registers the plugin with index" do
+ allow(index).to receive(:register_plugin).
+ with("new-plugin", lib_path("new-plugin").to_s, [lib_path("new-plugin").join("lib").to_s], []).once
+ subject.install ["new-plugin"], opts
+ end
+
+ context "multiple plugins" do
+ it do
+ allow(installer).to receive(:install).
+ with(["new-plugin", "another-plugin"], opts) do
+ {
+ "new-plugin" => spec,
+ "another-plugin" => spec2,
+ }
+ end.once
+
+ allow(subject).to receive(:validate_plugin!).twice
+ allow(index).to receive(:register_plugin).twice
+ subject.install ["new-plugin", "another-plugin"], opts
+ end
+ end
+ end
+
+ describe "evaluate gemfile for plugins" do
+ let(:definition) { double("definition") }
+ let(:builder) { double("builder") }
+ let(:gemfile) { bundled_app("Gemfile") }
+
+ before do
+ allow(Plugin::DSL).to receive(:new) { builder }
+ allow(builder).to receive(:eval_gemfile).with(gemfile)
+ allow(builder).to receive(:to_definition) { definition }
+ allow(builder).to receive(:inferred_plugins) { [] }
+ end
+
+ it "doesn't calls installer without any plugins" do
+ allow(definition).to receive(:dependencies) { [] }
+ allow(installer).to receive(:install_definition).never
+
+ subject.gemfile_install(gemfile)
+ end
+
+ context "with dependencies" do
+ let(:plugin_specs) do
+ {
+ "new-plugin" => spec,
+ "another-plugin" => spec2,
+ }
+ end
+
+ before do
+ allow(index).to receive(:installed?) { nil }
+ allow(definition).to receive(:dependencies) { [Bundler::Dependency.new("new-plugin", ">=0"), Bundler::Dependency.new("another-plugin", ">=0")] }
+ allow(installer).to receive(:install_definition) { plugin_specs }
+ end
+
+ it "should validate and register the plugins" do
+ expect(subject).to receive(:validate_plugin!).twice
+ expect(subject).to receive(:register_plugin).twice
+
+ subject.gemfile_install(gemfile)
+ end
+
+ it "should pass the optional plugins to #register_plugin" do
+ allow(builder).to receive(:inferred_plugins) { ["another-plugin"] }
+
+ expect(subject).to receive(:register_plugin).
+ with("new-plugin", spec, false).once
+
+ expect(subject).to receive(:register_plugin).
+ with("another-plugin", spec2, true).once
+
+ subject.gemfile_install(gemfile)
+ end
+ end
+ end
+
+ describe "#command?" do
+ it "returns true value for commands in index" do
+ allow(index).
+ to receive(:command_plugin).with("newcommand") { "my-plugin" }
+ result = subject.command? "newcommand"
+ expect(result).to be_truthy
+ end
+
+ it "returns false value for commands not in index" do
+ allow(index).to receive(:command_plugin).with("newcommand") { nil }
+ result = subject.command? "newcommand"
+ expect(result).to be_falsy
+ end
+ end
+
+ describe "#exec_command" do
+ it "raises UndefinedCommandError when command is not found" do
+ allow(index).to receive(:command_plugin).with("newcommand") { nil }
+ expect { subject.exec_command("newcommand", []) }.
+ to raise_error(Plugin::UndefinedCommandError)
+ end
+ end
+
+ describe "#source?" do
+ it "returns true value for sources in index" do
+ allow(index).
+ to receive(:command_plugin).with("foo-source") { "my-plugin" }
+ result = subject.command? "foo-source"
+ expect(result).to be_truthy
+ end
+
+ it "returns false value for source not in index" do
+ allow(index).to receive(:command_plugin).with("foo-source") { nil }
+ result = subject.command? "foo-source"
+ expect(result).to be_falsy
+ end
+ end
+
+ describe "#source" do
+ it "raises UnknownSourceError when source is not found" do
+ allow(index).to receive(:source_plugin).with("bar") { nil }
+ expect { subject.source("bar") }.
+ to raise_error(Plugin::UnknownSourceError)
+ end
+
+ it "loads the plugin, if not loaded" do
+ allow(index).to receive(:source_plugin).with("foo-bar") { "plugin_name" }
+
+ expect(subject).to receive(:load_plugin).with("plugin_name")
+ subject.source("foo-bar")
+ end
+
+ it "returns the class registered with #add_source" do
+ allow(index).to receive(:source_plugin).with("foo") { "plugin_name" }
+ stub_const "NewClass", Class.new
+
+ subject.add_source("foo", NewClass)
+ expect(subject.source("foo")).to be(NewClass)
+ end
+ end
+
+ describe "#source_from_lock" do
+ it "returns instance of registered class initialized with locked opts" do
+ opts = { "type" => "l_source", "remote" => "xyz", "other" => "random" }
+ allow(index).to receive(:source_plugin).with("l_source") { "plugin_name" }
+
+ stub_const "SClass", Class.new
+ s_instance = double(:s_instance)
+ subject.add_source("l_source", SClass)
+
+ expect(SClass).to receive(:new).
+ with(hash_including("type" => "l_source", "uri" => "xyz", "other" => "random")) { s_instance }
+ expect(subject.source_from_lock(opts)).to be(s_instance)
+ end
+ end
+
+ describe "#root" do
+ context "in app dir" do
+ before do
+ gemfile ""
+ end
+
+ it "returns plugin dir in app .bundle path" do
+ expect(subject.root).to eq(bundled_app.join(".bundle/plugin"))
+ end
+ end
+
+ context "outside app dir" do
+ it "returns plugin dir in global bundle path" do
+ Dir.chdir tmp
+ expect(subject.root).to eq(home.join(".bundle/plugin"))
+ end
+ end
+ end
+
+ describe "#add_hook" do
+ it "raises an ArgumentError on an unregistered event" do
+ ran = false
+ expect do
+ Plugin.add_hook("unregistered-hook") { ran = true }
+ end.to raise_error(ArgumentError)
+ expect(ran).to be(false)
+ end
+ end
+
+ describe "#hook" do
+ before do
+ path = lib_path("foo-plugin")
+ build_lib "foo-plugin", :path => path do |s|
+ s.write "plugins.rb", code
+ end
+
+ Bundler::Plugin::Events.send(:reset)
+ Bundler::Plugin::Events.send(:define, :EVENT_1, "event-1")
+ Bundler::Plugin::Events.send(:define, :EVENT_2, "event-2")
+
+ allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT_1).
+ and_return(["foo-plugin"])
+ allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT_2).
+ and_return(["foo-plugin"])
+ allow(index).to receive(:plugin_path).with("foo-plugin").and_return(path)
+ allow(index).to receive(:load_paths).with("foo-plugin").and_return([])
+ end
+
+ let(:code) { <<-RUBY }
+ Bundler::Plugin::API.hook("event-1") { puts "hook for event 1" }
+ RUBY
+
+ it "raises an ArgumentError on an unregistered event" do
+ expect do
+ Plugin.hook("unregistered-hook")
+ end.to raise_error(ArgumentError)
+ end
+
+ it "executes the hook" do
+ out = capture(:stdout) do
+ Plugin.hook(Bundler::Plugin::Events::EVENT_1)
+ end.strip
+
+ expect(out).to eq("hook for event 1")
+ end
+
+ context "single plugin declaring more than one hook" do
+ let(:code) { <<-RUBY }
+ Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT_1) {}
+ Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT_2) {}
+ puts "loaded"
+ RUBY
+
+ it "evals plugins.rb once" do
+ out = capture(:stdout) do
+ Plugin.hook(Bundler::Plugin::Events::EVENT_1)
+ Plugin.hook(Bundler::Plugin::Events::EVENT_2)
+ end.strip
+
+ expect(out).to eq("loaded")
+ end
+ end
+
+ context "a block is passed" do
+ let(:code) { <<-RUBY }
+ Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT_1) { |&blk| blk.call }
+ RUBY
+
+ it "is passed to the hook" do
+ out = capture(:stdout) do
+ Plugin.hook(Bundler::Plugin::Events::EVENT_1) { puts "win" }
+ end.strip
+
+ expect(out).to eq("win")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/psyched_yaml_spec.rb b/spec/bundler/bundler/psyched_yaml_spec.rb
new file mode 100644
index 0000000000..d5d68c5cc3
--- /dev/null
+++ b/spec/bundler/bundler/psyched_yaml_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require "bundler/psyched_yaml"
+
+RSpec.describe "Bundler::YamlLibrarySyntaxError" do
+ it "is raised on YAML parse errors" do
+ expect { YAML.parse "{foo" }.to raise_error(Bundler::YamlLibrarySyntaxError)
+ end
+end
diff --git a/spec/bundler/bundler/remote_specification_spec.rb b/spec/bundler/bundler/remote_specification_spec.rb
new file mode 100644
index 0000000000..8115e026d8
--- /dev/null
+++ b/spec/bundler/bundler/remote_specification_spec.rb
@@ -0,0 +1,187 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::RemoteSpecification do
+ let(:name) { "foo" }
+ let(:version) { Gem::Version.new("1.0.0") }
+ let(:platform) { Gem::Platform::RUBY }
+ let(:spec_fetcher) { double(:spec_fetcher) }
+
+ subject { described_class.new(name, version, platform, spec_fetcher) }
+
+ it "is Comparable" do
+ expect(described_class.ancestors).to include(Comparable)
+ end
+
+ it "can match platforms" do
+ expect(described_class.ancestors).to include(Bundler::MatchPlatform)
+ end
+
+ describe "#fetch_platform" do
+ let(:remote_spec) { double(:remote_spec, :platform => "jruby") }
+
+ before { allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) }
+
+ it "should return the spec platform" do
+ expect(subject.fetch_platform).to eq("jruby")
+ end
+ end
+
+ describe "#full_name" do
+ context "when platform is ruby" do
+ it "should return the spec name and version" do
+ expect(subject.full_name).to eq("foo-1.0.0")
+ end
+ end
+
+ context "when platform is nil" do
+ let(:platform) { nil }
+
+ it "should return the spec name and version" do
+ expect(subject.full_name).to eq("foo-1.0.0")
+ end
+ end
+
+ context "when platform is a non-ruby platform" do
+ let(:platform) { "jruby" }
+
+ it "should return the spec name, version, and platform" do
+ expect(subject.full_name).to eq("foo-1.0.0-jruby")
+ end
+ end
+ end
+
+ describe "#<=>" do
+ let(:other_name) { name }
+ let(:other_version) { version }
+ let(:other_platform) { platform }
+ let(:other_spec_fetcher) { spec_fetcher }
+
+ shared_examples_for "a comparison" do
+ context "which exactly matches" do
+ it "returns 0" do
+ expect(subject <=> other).to eq(0)
+ end
+ end
+
+ context "which is different by name" do
+ let(:other_name) { "a" }
+ it "returns 1" do
+ expect(subject <=> other).to eq(1)
+ end
+ end
+
+ context "which has a lower version" do
+ let(:other_version) { Gem::Version.new("0.9.0") }
+ it "returns 1" do
+ expect(subject <=> other).to eq(1)
+ end
+ end
+
+ context "which has a higher version" do
+ let(:other_version) { Gem::Version.new("1.1.0") }
+ it "returns -1" do
+ expect(subject <=> other).to eq(-1)
+ end
+ end
+
+ context "which has a different platform" do
+ let(:other_platform) { Gem::Platform.new("x86-mswin32") }
+ it "returns -1" do
+ expect(subject <=> other).to eq(-1)
+ end
+ end
+ end
+
+ context "comparing another Bundler::RemoteSpecification" do
+ let(:other) do
+ Bundler::RemoteSpecification.new(other_name, other_version,
+ other_platform, nil)
+ end
+
+ it_should_behave_like "a comparison"
+ end
+
+ context "comparing a Gem::Specification" do
+ let(:other) do
+ Gem::Specification.new(other_name, other_version).tap do |s|
+ s.platform = other_platform
+ end
+ end
+
+ it_should_behave_like "a comparison"
+ end
+
+ context "comparing a non sortable object" do
+ let(:other) { Object.new }
+ let(:remote_spec) { double(:remote_spec, :platform => "jruby") }
+
+ before do
+ allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec)
+ allow(remote_spec).to receive(:<=>).and_return(nil)
+ end
+
+ it "should use default object comparison" do
+ expect(subject <=> other).to eq(nil)
+ end
+ end
+ end
+
+ describe "#__swap__" do
+ let(:spec) { double(:spec, :dependencies => []) }
+ let(:new_spec) { double(:new_spec, :dependencies => [], :runtime_dependencies => []) }
+
+ before { subject.instance_variable_set(:@_remote_specification, spec) }
+
+ it "should replace remote specification with the passed spec" do
+ expect(subject.instance_variable_get(:@_remote_specification)).to be(spec)
+ subject.__swap__(new_spec)
+ expect(subject.instance_variable_get(:@_remote_specification)).to be(new_spec)
+ end
+ end
+
+ describe "#sort_obj" do
+ context "when platform is ruby" do
+ it "should return a sorting delegate array with name, version, and -1" do
+ expect(subject.sort_obj).to match_array(["foo", version, -1])
+ end
+ end
+
+ context "when platform is not ruby" do
+ let(:platform) { "jruby" }
+
+ it "should return a sorting delegate array with name, version, and 1" do
+ expect(subject.sort_obj).to match_array(["foo", version, 1])
+ end
+ end
+ end
+
+ describe "method missing" do
+ context "and is present in Gem::Specification" do
+ let(:remote_spec) { double(:remote_spec, :authors => "abcd") }
+
+ before do
+ allow(subject).to receive(:_remote_specification).and_return(remote_spec)
+ expect(subject.methods.map(&:to_sym)).not_to include(:authors)
+ end
+
+ it "should send through to Gem::Specification" do
+ expect(subject.authors).to eq("abcd")
+ end
+ end
+ end
+
+ describe "respond to missing?" do
+ context "and is present in Gem::Specification" do
+ let(:remote_spec) { double(:remote_spec, :authors => "abcd") }
+
+ before do
+ allow(subject).to receive(:_remote_specification).and_return(remote_spec)
+ expect(subject.methods.map(&:to_sym)).not_to include(:authors)
+ end
+
+ it "should send through to Gem::Specification" do
+ expect(subject.respond_to?(:authors)).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/retry_spec.rb b/spec/bundler/bundler/retry_spec.rb
new file mode 100644
index 0000000000..b893580d72
--- /dev/null
+++ b/spec/bundler/bundler/retry_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Retry do
+ it "return successful result if no errors" do
+ attempts = 0
+ result = Bundler::Retry.new(nil, nil, 3).attempt do
+ attempts += 1
+ :success
+ end
+ expect(result).to eq(:success)
+ expect(attempts).to eq(1)
+ end
+
+ it "returns the first valid result" do
+ jobs = [proc { raise "foo" }, proc { :bar }, proc { raise "foo" }]
+ attempts = 0
+ result = Bundler::Retry.new(nil, nil, 3).attempt do
+ attempts += 1
+ jobs.shift.call
+ end
+ expect(result).to eq(:bar)
+ expect(attempts).to eq(2)
+ end
+
+ it "raises the last error" do
+ errors = [StandardError, StandardError, StandardError, Bundler::GemfileNotFound]
+ attempts = 0
+ expect do
+ Bundler::Retry.new(nil, nil, 3).attempt do
+ attempts += 1
+ raise errors.shift
+ end
+ end.to raise_error(Bundler::GemfileNotFound)
+ expect(attempts).to eq(4)
+ end
+
+ it "raises exceptions" do
+ error = Bundler::GemfileNotFound
+ attempts = 0
+ expect do
+ Bundler::Retry.new(nil, error).attempt do
+ attempts += 1
+ raise error
+ end
+ end.to raise_error(error)
+ expect(attempts).to eq(1)
+ end
+
+ context "logging" do
+ let(:error) { Bundler::GemfileNotFound }
+ let(:failure_message) { "Retrying test due to error (2/2): #{error} #{error}" }
+
+ context "with debugging on" do
+ it "print error message with newline" do
+ allow(Bundler.ui).to receive(:debug?).and_return(true)
+ expect(Bundler.ui).to_not receive(:info)
+ expect(Bundler.ui).to receive(:warn).with(failure_message, true)
+
+ expect do
+ Bundler::Retry.new("test", [], 1).attempt do
+ raise error
+ end
+ end.to raise_error(error)
+ end
+ end
+
+ context "with debugging off" do
+ it "print error message with newlines" do
+ allow(Bundler.ui).to receive(:debug?).and_return(false)
+ expect(Bundler.ui).to receive(:info).with("").twice
+ expect(Bundler.ui).to receive(:warn).with(failure_message, false)
+
+ expect do
+ Bundler::Retry.new("test", [], 1).attempt do
+ raise error
+ end
+ end.to raise_error(error)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ruby_dsl_spec.rb b/spec/bundler/bundler/ruby_dsl_spec.rb
new file mode 100644
index 0000000000..bc1ca98457
--- /dev/null
+++ b/spec/bundler/bundler/ruby_dsl_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require "bundler/ruby_dsl"
+
+RSpec.describe Bundler::RubyDsl do
+ class MockDSL
+ include Bundler::RubyDsl
+
+ attr_reader :ruby_version
+ end
+
+ let(:dsl) { MockDSL.new }
+ let(:ruby_version) { "2.0.0" }
+ let(:version) { "2.0.0" }
+ let(:engine) { "jruby" }
+ let(:engine_version) { "9000" }
+ let(:patchlevel) { "100" }
+ let(:options) do
+ { :patchlevel => patchlevel,
+ :engine => engine,
+ :engine_version => engine_version }
+ end
+
+ let(:invoke) do
+ proc do
+ args = Array(ruby_version) + [options]
+ dsl.ruby(*args)
+ end
+ end
+
+ subject do
+ invoke.call
+ dsl.ruby_version
+ end
+
+ describe "#ruby_version" do
+ shared_examples_for "it stores the ruby version" do
+ it "stores the version" do
+ expect(subject.versions).to eq(Array(ruby_version))
+ expect(subject.gem_version.version).to eq(version)
+ end
+
+ it "stores the engine details" do
+ expect(subject.engine).to eq(engine)
+ expect(subject.engine_versions).to eq(Array(engine_version))
+ end
+
+ it "stores the patchlevel" do
+ expect(subject.patchlevel).to eq(patchlevel)
+ end
+ end
+
+ context "with a plain version" do
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with a single requirement" do
+ let(:ruby_version) { ">= 2.0.0" }
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with two requirements in the same string" do
+ let(:ruby_version) { ">= 2.0.0, < 3.0" }
+ it "raises an error" do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "with two requirements" do
+ let(:ruby_version) { ["~> 2.0.0", "> 2.0.1"] }
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with multiple engine versions" do
+ let(:engine_version) { ["> 200", "< 300"] }
+ it_behaves_like "it stores the ruby version"
+ end
+
+ context "with no options hash" do
+ let(:invoke) { proc { dsl.ruby(ruby_version) } }
+
+ let(:patchlevel) { nil }
+ let(:engine) { "ruby" }
+ let(:engine_version) { version }
+
+ it_behaves_like "it stores the ruby version"
+
+ context "and with multiple requirements" do
+ let(:ruby_version) { ["~> 2.0.0", "> 2.0.1"] }
+ let(:engine_version) { ruby_version }
+ it_behaves_like "it stores the ruby version"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ruby_version_spec.rb b/spec/bundler/bundler/ruby_version_spec.rb
new file mode 100644
index 0000000000..46a1b2918b
--- /dev/null
+++ b/spec/bundler/bundler/ruby_version_spec.rb
@@ -0,0 +1,524 @@
+# frozen_string_literal: true
+
+require "bundler/ruby_version"
+
+RSpec.describe "Bundler::RubyVersion and its subclasses" do
+ let(:version) { "2.0.0" }
+ let(:patchlevel) { "645" }
+ let(:engine) { "jruby" }
+ let(:engine_version) { "2.0.1" }
+
+ describe Bundler::RubyVersion do
+ subject { Bundler::RubyVersion.new(version, patchlevel, engine, engine_version) }
+
+ let(:ruby_version) { subject }
+ let(:other_version) { version }
+ let(:other_patchlevel) { patchlevel }
+ let(:other_engine) { engine }
+ let(:other_engine_version) { engine_version }
+ let(:other_ruby_version) { Bundler::RubyVersion.new(other_version, other_patchlevel, other_engine, other_engine_version) }
+
+ describe "#initialize" do
+ context "no engine is passed" do
+ let(:engine) { nil }
+
+ it "should set ruby as the engine" do
+ expect(subject.engine).to eq("ruby")
+ end
+ end
+
+ context "no engine_version is passed" do
+ let(:engine_version) { nil }
+
+ it "should set engine version as the passed version" do
+ expect(subject.engine_versions).to eq(["2.0.0"])
+ end
+ end
+
+ context "with engine in symbol" do
+ let(:engine) { :jruby }
+
+ it "should coerce engine to string" do
+ expect(subject.engine).to eq("jruby")
+ end
+ end
+
+ context "is called with multiple requirements" do
+ let(:version) { ["<= 2.0.0", "> 1.9.3"] }
+ let(:engine_version) { nil }
+
+ it "sets the versions" do
+ expect(subject.versions).to eq(version)
+ end
+
+ it "sets the engine versions" do
+ expect(subject.engine_versions).to eq(version)
+ end
+ end
+
+ context "is called with multiple engine requirements" do
+ let(:engine_version) { [">= 2.0", "< 2.3"] }
+
+ it "sets the engine versions" do
+ expect(subject.engine_versions).to eq(engine_version)
+ end
+ end
+ end
+
+ describe ".from_string" do
+ shared_examples_for "returning" do
+ it "returns the original RubyVersion" do
+ expect(described_class.from_string(subject.to_s)).to eq(subject)
+ end
+ end
+
+ include_examples "returning"
+
+ context "no patchlevel" do
+ let(:patchlevel) { nil }
+
+ include_examples "returning"
+ end
+
+ context "engine is ruby" do
+ let(:engine) { "ruby" }
+ let(:engine_version) { version }
+
+ include_examples "returning"
+ end
+
+ context "with multiple requirements" do
+ let(:engine_version) { ["> 9", "< 11"] }
+ let(:version) { ["> 8", "< 10"] }
+ let(:patchlevel) { nil }
+
+ it "returns nil" do
+ expect(described_class.from_string(subject.to_s)).to be_nil
+ end
+ end
+ end
+
+ describe "#to_s" do
+ it "should return info string with the ruby version, patchlevel, engine, and engine version" do
+ expect(subject.to_s).to eq("ruby 2.0.0p645 (jruby 2.0.1)")
+ end
+
+ context "no patchlevel" do
+ let(:patchlevel) { nil }
+
+ it "should return info string with the version, engine, and engine version" do
+ expect(subject.to_s).to eq("ruby 2.0.0 (jruby 2.0.1)")
+ end
+ end
+
+ context "engine is ruby" do
+ let(:engine) { "ruby" }
+
+ it "should return info string with the ruby version and patchlevel" do
+ expect(subject.to_s).to eq("ruby 2.0.0p645")
+ end
+ end
+
+ context "with multiple requirements" do
+ let(:engine_version) { ["> 9", "< 11"] }
+ let(:version) { ["> 8", "< 10"] }
+ let(:patchlevel) { nil }
+
+ it "should return info string with all requirements" do
+ expect(subject.to_s).to eq("ruby > 8, < 10 (jruby > 9, < 11)")
+ end
+ end
+ end
+
+ describe "#==" do
+ shared_examples_for "two ruby versions are not equal" do
+ it "should return false" do
+ expect(subject).to_not eq(other_ruby_version)
+ end
+ end
+
+ context "the versions, pathlevels, engines, and engine_versions match" do
+ it "should return true" do
+ expect(subject).to eq(other_ruby_version)
+ end
+ end
+
+ context "the versions do not match" do
+ let(:other_version) { "1.21.6" }
+
+ it_behaves_like "two ruby versions are not equal"
+ end
+
+ context "the patchlevels do not match" do
+ let(:other_patchlevel) { "21" }
+
+ it_behaves_like "two ruby versions are not equal"
+ end
+
+ context "the engines do not match" do
+ let(:other_engine) { "ruby" }
+
+ it_behaves_like "two ruby versions are not equal"
+ end
+
+ context "the engine versions do not match" do
+ let(:other_engine_version) { "1.11.2" }
+
+ it_behaves_like "two ruby versions are not equal"
+ end
+ end
+
+ describe "#host" do
+ before do
+ allow(RbConfig::CONFIG).to receive(:[]).with("host_cpu").and_return("x86_64")
+ allow(RbConfig::CONFIG).to receive(:[]).with("host_vendor").and_return("apple")
+ allow(RbConfig::CONFIG).to receive(:[]).with("host_os").and_return("darwin14.5.0")
+ end
+
+ it "should return an info string with the host cpu, vendor, and os" do
+ expect(subject.host).to eq("x86_64-apple-darwin14.5.0")
+ end
+
+ it "memoizes the info string with the host cpu, vendor, and os" do
+ expect(RbConfig::CONFIG).to receive(:[]).with("host_cpu").once.and_call_original
+ expect(RbConfig::CONFIG).to receive(:[]).with("host_vendor").once.and_call_original
+ expect(RbConfig::CONFIG).to receive(:[]).with("host_os").once.and_call_original
+ 2.times { ruby_version.host }
+ end
+ end
+
+ describe "#gem_version" do
+ let(:gem_version) { "2.0.0" }
+ let(:gem_version_obj) { Gem::Version.new(gem_version) }
+
+ shared_examples_for "it parses the version from the requirement string" do |version|
+ let(:version) { version }
+ it "should return the underlying version" do
+ expect(ruby_version.gem_version).to eq(gem_version_obj)
+ expect(ruby_version.gem_version.version).to eq(gem_version)
+ end
+ end
+
+ it_behaves_like "it parses the version from the requirement string", "2.0.0"
+ it_behaves_like "it parses the version from the requirement string", ">= 2.0.0"
+ it_behaves_like "it parses the version from the requirement string", "~> 2.0.0"
+ it_behaves_like "it parses the version from the requirement string", "< 2.0.0"
+ it_behaves_like "it parses the version from the requirement string", "= 2.0.0"
+ it_behaves_like "it parses the version from the requirement string", ["> 2.0.0", "< 2.4.5"]
+ end
+
+ describe "#diff" do
+ let(:engine) { "ruby" }
+
+ shared_examples_for "there is a difference in the engines" do
+ it "should return a tuple with :engine and the two different engines" do
+ expect(ruby_version.diff(other_ruby_version)).to eq([:engine, engine, other_engine])
+ end
+ end
+
+ shared_examples_for "there is a difference in the versions" do
+ it "should return a tuple with :version and the two different versions" do
+ expect(ruby_version.diff(other_ruby_version)).to eq([:version, Array(version).join(", "), Array(other_version).join(", ")])
+ end
+ end
+
+ shared_examples_for "there is a difference in the engine versions" do
+ it "should return a tuple with :engine_version and the two different engine versions" do
+ expect(ruby_version.diff(other_ruby_version)).to eq([:engine_version, Array(engine_version).join(", "), Array(other_engine_version).join(", ")])
+ end
+ end
+
+ shared_examples_for "there is a difference in the patchlevels" do
+ it "should return a tuple with :patchlevel and the two different patchlevels" do
+ expect(ruby_version.diff(other_ruby_version)).to eq([:patchlevel, patchlevel, other_patchlevel])
+ end
+ end
+
+ shared_examples_for "there are no differences" do
+ it "should return nil" do
+ expect(ruby_version.diff(other_ruby_version)).to be_nil
+ end
+ end
+
+ context "all things match exactly" do
+ it_behaves_like "there are no differences"
+ end
+
+ context "detects engine discrepancies first" do
+ let(:other_version) { "2.0.1" }
+ let(:other_patchlevel) { "643" }
+ let(:other_engine) { "rbx" }
+ let(:other_engine_version) { "2.0.0" }
+
+ it_behaves_like "there is a difference in the engines"
+ end
+
+ context "detects version discrepancies second" do
+ let(:other_version) { "2.0.1" }
+ let(:other_patchlevel) { "643" }
+ let(:other_engine_version) { "2.0.0" }
+
+ it_behaves_like "there is a difference in the versions"
+ end
+
+ context "detects version discrepancies with multiple requirements second" do
+ let(:other_version) { "2.0.1" }
+ let(:other_patchlevel) { "643" }
+ let(:other_engine_version) { "2.0.0" }
+
+ let(:version) { ["> 2.0.0", "< 1.0.0"] }
+
+ it_behaves_like "there is a difference in the versions"
+ end
+
+ context "detects engine version discrepancies third" do
+ let(:other_patchlevel) { "643" }
+ let(:other_engine_version) { "2.0.0" }
+
+ it_behaves_like "there is a difference in the engine versions"
+ end
+
+ context "detects engine version discrepancies with multiple requirements third" do
+ let(:other_patchlevel) { "643" }
+ let(:other_engine_version) { "2.0.0" }
+
+ let(:engine_version) { ["> 2.0.0", "< 1.0.0"] }
+
+ it_behaves_like "there is a difference in the engine versions"
+ end
+
+ context "detects patchlevel discrepancies last" do
+ let(:other_patchlevel) { "643" }
+
+ it_behaves_like "there is a difference in the patchlevels"
+ end
+
+ context "successfully matches gem requirements" do
+ let(:version) { ">= 2.0.0" }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.0.0" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there are no differences"
+ end
+
+ context "successfully matches multiple gem requirements" do
+ let(:version) { [">= 2.0.0", "< 2.4.5"] }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { ["~> 2.0.1", "< 2.4.5"] }
+ let(:other_version) { "2.0.0" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there are no differences"
+ end
+
+ context "successfully detects bad gem requirements with versions with multiple requirements" do
+ let(:version) { ["~> 2.0.0", "< 2.0.5"] }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.0.5" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there is a difference in the versions"
+ end
+
+ context "successfully detects bad gem requirements with versions" do
+ let(:version) { "~> 2.0.0" }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.1.0" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there is a difference in the versions"
+ end
+
+ context "successfully detects bad gem requirements with patchlevels" do
+ let(:version) { ">= 2.0.0" }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.0.0" }
+ let(:other_patchlevel) { "645" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.0.5" }
+
+ it_behaves_like "there is a difference in the patchlevels"
+ end
+
+ context "successfully detects bad gem requirements with engine versions" do
+ let(:version) { ">= 2.0.0" }
+ let(:patchlevel) { "< 643" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { "2.0.0" }
+ let(:other_patchlevel) { "642" }
+ let(:other_engine) { "ruby" }
+ let(:other_engine_version) { "2.1.0" }
+
+ it_behaves_like "there is a difference in the engine versions"
+ end
+
+ context "with a patchlevel of -1" do
+ let(:version) { ">= 2.0.0" }
+ let(:patchlevel) { "-1" }
+ let(:engine) { "ruby" }
+ let(:engine_version) { "~> 2.0.1" }
+ let(:other_version) { version }
+ let(:other_engine) { engine }
+ let(:other_engine_version) { engine_version }
+
+ context "and comparing with another patchlevel of -1" do
+ let(:other_patchlevel) { patchlevel }
+
+ it_behaves_like "there are no differences"
+ end
+
+ context "and comparing with a patchlevel that is not -1" do
+ let(:other_patchlevel) { "642" }
+
+ it_behaves_like "there is a difference in the patchlevels"
+ end
+ end
+ end
+
+ describe "#system" do
+ subject { Bundler::RubyVersion.system }
+
+ let(:bundler_system_ruby_version) { subject }
+
+ before do
+ Bundler::RubyVersion.instance_variable_set("@ruby_version", nil)
+ end
+
+ it "should return an instance of Bundler::RubyVersion" do
+ expect(subject).to be_kind_of(Bundler::RubyVersion)
+ end
+
+ it "memoizes the instance of Bundler::RubyVersion" do
+ expect(Bundler::RubyVersion).to receive(:new).once.and_call_original
+ 2.times { subject }
+ end
+
+ describe "#version" do
+ it "should return a copy of the value of RUBY_VERSION" do
+ expect(subject.versions).to eq([RUBY_VERSION])
+ expect(subject.versions.first).to_not be(RUBY_VERSION)
+ end
+ end
+
+ describe "#engine" do
+ context "RUBY_ENGINE is defined" do
+ before { stub_const("RUBY_ENGINE", "jruby") }
+ before { stub_const("JRUBY_VERSION", "2.1.1") }
+
+ it "should return a copy of the value of RUBY_ENGINE" do
+ expect(subject.engine).to eq("jruby")
+ expect(subject.engine).to_not be(RUBY_ENGINE)
+ end
+ end
+
+ context "RUBY_ENGINE is not defined" do
+ before { stub_const("RUBY_ENGINE", nil) }
+
+ it "should return the string 'ruby'" do
+ expect(subject.engine).to eq("ruby")
+ end
+ end
+ end
+
+ describe "#engine_version" do
+ context "engine is ruby" do
+ before do
+ stub_const("RUBY_VERSION", "2.2.4")
+ stub_const("RUBY_ENGINE", "ruby")
+ end
+
+ it "should return a copy of the value of RUBY_VERSION" do
+ expect(bundler_system_ruby_version.engine_versions).to eq(["2.2.4"])
+ expect(bundler_system_ruby_version.engine_versions.first).to_not be(RUBY_VERSION)
+ end
+ end
+
+ context "engine is rbx" do
+ before do
+ stub_const("RUBY_ENGINE", "rbx")
+ stub_const("Rubinius::VERSION", "2.0.0")
+ end
+
+ it "should return a copy of the value of Rubinius::VERSION" do
+ expect(bundler_system_ruby_version.engine_versions).to eq(["2.0.0"])
+ expect(bundler_system_ruby_version.engine_versions.first).to_not be(Rubinius::VERSION)
+ end
+ end
+
+ context "engine is jruby" do
+ before do
+ stub_const("RUBY_ENGINE", "jruby")
+ stub_const("JRUBY_VERSION", "2.1.1")
+ end
+
+ it "should return a copy of the value of JRUBY_VERSION" do
+ expect(subject.engine_versions).to eq(["2.1.1"])
+ expect(bundler_system_ruby_version.engine_versions.first).to_not be(JRUBY_VERSION)
+ end
+ end
+
+ context "engine is some other ruby engine" do
+ before do
+ stub_const("RUBY_ENGINE", "not_supported_ruby_engine")
+ stub_const("RUBY_ENGINE_VERSION", "1.2.3")
+ end
+
+ it "returns RUBY_ENGINE_VERSION" do
+ expect(bundler_system_ruby_version.engine_versions).to eq(["1.2.3"])
+ end
+ end
+ end
+
+ describe "#patchlevel" do
+ it "should return a string with the value of RUBY_PATCHLEVEL" do
+ expect(subject.patchlevel).to eq(RUBY_PATCHLEVEL.to_s)
+ end
+ end
+ end
+
+ describe "#to_gem_version_with_patchlevel" do
+ shared_examples_for "the patchlevel is omitted" do
+ it "does not include a patch level" do
+ expect(subject.to_gem_version_with_patchlevel.to_s).to eq(version)
+ end
+ end
+
+ context "with nil patch number" do
+ let(:patchlevel) { nil }
+
+ it_behaves_like "the patchlevel is omitted"
+ end
+
+ context "with negative patch number" do
+ let(:patchlevel) { -1 }
+
+ it_behaves_like "the patchlevel is omitted"
+ end
+
+ context "with a valid patch number" do
+ it "uses the specified patchlevel as patchlevel" do
+ expect(subject.to_gem_version_with_patchlevel.to_s).to eq("#{version}.#{patchlevel}")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/rubygems_integration_spec.rb b/spec/bundler/bundler/rubygems_integration_spec.rb
new file mode 100644
index 0000000000..b1b15d9e5d
--- /dev/null
+++ b/spec/bundler/bundler/rubygems_integration_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::RubygemsIntegration do
+ it "uses the same chdir lock as rubygems", :rubygems => "2.1" do
+ expect(Bundler.rubygems.ext_lock).to eq(Gem::Ext::Builder::CHDIR_MONITOR)
+ end
+
+ context "#validate" do
+ let(:spec) do
+ Gem::Specification.new do |s|
+ s.name = "to-validate"
+ s.version = "1.0.0"
+ s.loaded_from = __FILE__
+ end
+ end
+ subject { Bundler.rubygems.validate(spec) }
+
+ it "skips overly-strict gemspec validation", :rubygems => "< 1.7" do
+ expect(spec).to_not receive(:validate)
+ subject
+ end
+
+ it "validates with packaging mode disabled", :rubygems => "1.7" do
+ expect(spec).to receive(:validate).with(false)
+ subject
+ end
+
+ it "should set a summary to avoid an overly-strict error", :rubygems => "~> 1.7.0" do
+ spec.summary = nil
+ expect { subject }.not_to raise_error
+ expect(spec.summary).to eq("")
+ end
+
+ context "with an invalid spec" do
+ before do
+ expect(spec).to receive(:validate).with(false).
+ and_raise(Gem::InvalidSpecificationException.new("TODO is not an author"))
+ end
+
+ it "should raise a Gem::InvalidSpecificationException and produce a helpful warning message",
+ :rubygems => "1.7" do
+ expect { subject }.to raise_error(Gem::InvalidSpecificationException,
+ "The gemspec at #{__FILE__} is not valid. "\
+ "Please fix this gemspec.\nThe validation error was 'TODO is not an author'\n")
+ end
+ end
+ end
+
+ describe "#configuration" do
+ it "handles Gem::SystemExitException errors" do
+ allow(Gem).to receive(:configuration) { raise Gem::SystemExitException.new(1) }
+ expect { Bundler.rubygems.configuration }.to raise_error(Gem::SystemExitException)
+ end
+ end
+
+ describe "#download_gem", :rubygems => ">= 2.0" do
+ let(:bundler_retry) { double(Bundler::Retry) }
+ let(:retry) { double("Bundler::Retry") }
+ let(:uri) { URI.parse("https://foo.bar") }
+ let(:path) { Gem.path.first }
+ let(:spec) do
+ spec = Bundler::RemoteSpecification.new("Foo", Gem::Version.new("2.5.2"),
+ Gem::Platform::RUBY, nil)
+ spec.remote = Bundler::Source::Rubygems::Remote.new(uri.to_s)
+ spec
+ end
+ let(:fetcher) { double("gem_remote_fetcher") }
+
+ it "successfully downloads gem with retries" do
+ expect(Bundler.rubygems).to receive(:gem_remote_fetcher).and_return(fetcher)
+ expect(fetcher).to receive(:headers=).with("X-Gemfile-Source" => "https://foo.bar")
+ expect(Bundler::Retry).to receive(:new).with("download gem from #{uri}/").
+ and_return(bundler_retry)
+ expect(bundler_retry).to receive(:attempts).and_yield
+ expect(fetcher).to receive(:download).with(spec, uri, path)
+
+ Bundler.rubygems.download_gem(spec, uri, path)
+ end
+ end
+
+ describe "#fetch_all_remote_specs", :rubygems => ">= 2.0" do
+ let(:uri) { URI("https://example.com") }
+ let(:fetcher) { double("gem_remote_fetcher") }
+ let(:specs_response) { Marshal.dump(["specs"]) }
+ let(:prerelease_specs_response) { Marshal.dump(["prerelease_specs"]) }
+
+ context "when a rubygems source mirror is set" do
+ let(:orig_uri) { URI("http://zombo.com") }
+ let(:remote_with_mirror) { double("remote", :uri => uri, :original_uri => orig_uri) }
+
+ it "sets the 'X-Gemfile-Source' header containing the original source" do
+ expect(Bundler.rubygems).to receive(:gem_remote_fetcher).twice.and_return(fetcher)
+ expect(fetcher).to receive(:headers=).with("X-Gemfile-Source" => "http://zombo.com").twice
+ expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(specs_response)
+ expect(fetcher).to receive(:fetch_path).with(uri + "prerelease_specs.4.8.gz").and_return(prerelease_specs_response)
+ result = Bundler.rubygems.fetch_all_remote_specs(remote_with_mirror)
+ expect(result).to eq(%w[specs prerelease_specs])
+ end
+ end
+
+ context "when there is no rubygems source mirror set" do
+ let(:remote_no_mirror) { double("remote", :uri => uri, :original_uri => nil) }
+
+ it "does not set the 'X-Gemfile-Source' header" do
+ expect(Bundler.rubygems).to receive(:gem_remote_fetcher).twice.and_return(fetcher)
+ expect(fetcher).to_not receive(:headers=)
+ expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(specs_response)
+ expect(fetcher).to receive(:fetch_path).with(uri + "prerelease_specs.4.8.gz").and_return(prerelease_specs_response)
+ result = Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror)
+ expect(result).to eq(%w[specs prerelease_specs])
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/settings/validator_spec.rb b/spec/bundler/bundler/settings/validator_spec.rb
new file mode 100644
index 0000000000..e4ffd89435
--- /dev/null
+++ b/spec/bundler/bundler/settings/validator_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Settings::Validator do
+ describe ".validate!" do
+ def validate!(key, value, settings)
+ transformed_key = Bundler.settings.key_for(key)
+ if value.nil?
+ settings.delete(transformed_key)
+ else
+ settings[transformed_key] = value
+ end
+ described_class.validate!(key, value, settings)
+ settings
+ end
+
+ it "path and path.system are mutually exclusive" do
+ expect(validate!("path", "bundle", {})).to eq("BUNDLE_PATH" => "bundle")
+ expect(validate!("path", "bundle", "BUNDLE_PATH__SYSTEM" => false)).to eq("BUNDLE_PATH" => "bundle")
+ expect(validate!("path", "bundle", "BUNDLE_PATH__SYSTEM" => true)).to eq("BUNDLE_PATH" => "bundle")
+ expect(validate!("path", nil, "BUNDLE_PATH__SYSTEM" => true)).to eq("BUNDLE_PATH__SYSTEM" => true)
+ expect(validate!("path", nil, "BUNDLE_PATH__SYSTEM" => false)).to eq("BUNDLE_PATH__SYSTEM" => false)
+ expect(validate!("path", nil, {})).to eq({})
+
+ expect(validate!("path.system", true, "BUNDLE_PATH" => "bundle")).to eq("BUNDLE_PATH__SYSTEM" => true)
+ expect(validate!("path.system", false, "BUNDLE_PATH" => "bundle")).to eq("BUNDLE_PATH" => "bundle", "BUNDLE_PATH__SYSTEM" => false)
+ expect(validate!("path.system", nil, "BUNDLE_PATH" => "bundle")).to eq("BUNDLE_PATH" => "bundle")
+ expect(validate!("path.system", true, {})).to eq("BUNDLE_PATH__SYSTEM" => true)
+ expect(validate!("path.system", false, {})).to eq("BUNDLE_PATH__SYSTEM" => false)
+ expect(validate!("path.system", nil, {})).to eq({})
+ end
+
+ it "a group cannot be in both `with` & `without` simultaneously" do
+ expect do
+ validate!("with", "", {})
+ validate!("with", nil, {})
+ validate!("with", "", "BUNDLE_WITHOUT" => "a")
+ validate!("with", nil, "BUNDLE_WITHOUT" => "a")
+ validate!("with", "b:c", "BUNDLE_WITHOUT" => "a")
+
+ validate!("without", "", {})
+ validate!("without", nil, {})
+ validate!("without", "", "BUNDLE_WITH" => "a")
+ validate!("without", nil, "BUNDLE_WITH" => "a")
+ validate!("without", "b:c", "BUNDLE_WITH" => "a")
+ end.not_to raise_error
+
+ expect { validate!("with", "b:c", "BUNDLE_WITHOUT" => "c:d") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip
+ Setting `with` to "b:c" failed:
+ - a group cannot be in both `with` & `without` simultaneously
+ - `without` is current set to [:c, :d]
+ - the `c` groups conflict
+ EOS
+
+ expect { validate!("without", "b:c", "BUNDLE_WITH" => "c:d") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip
+ Setting `without` to "b:c" failed:
+ - a group cannot be in both `with` & `without` simultaneously
+ - `with` is current set to [:c, :d]
+ - the `c` groups conflict
+ EOS
+ end
+ end
+
+ describe described_class::Rule do
+ let(:keys) { %w[key] }
+ let(:description) { "rule description" }
+ let(:validate) { proc { raise "validate called!" } }
+ subject(:rule) { described_class.new(keys, description, &validate) }
+
+ describe "#validate!" do
+ it "calls the block" do
+ expect { rule.validate!("key", nil, {}) }.to raise_error(RuntimeError, /validate called!/)
+ end
+ end
+
+ describe "#fail!" do
+ it "raises with a helpful message" do
+ expect { subject.fail!("key", "value", "reason1", "reason2") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip
+ Setting `key` to "value" failed:
+ - rule description
+ - reason1
+ - reason2
+ EOS
+ end
+ end
+
+ describe "#set" do
+ it "works when the value has not changed" do
+ allow(Bundler.ui).to receive(:info).never
+
+ subject.set({}, "key", nil)
+ subject.set({ "BUNDLE_KEY" => "value" }, "key", "value")
+ end
+
+ it "prints out when the value is changing" do
+ settings = {}
+
+ expect(Bundler.ui).to receive(:info).with("Setting `key` to \"value\", since rule description, reason1")
+ subject.set(settings, "key", "value", "reason1")
+ expect(settings).to eq("BUNDLE_KEY" => "value")
+
+ expect(Bundler.ui).to receive(:info).with("Setting `key` to \"value2\", since rule description, reason2")
+ subject.set(settings, "key", "value2", "reason2")
+ expect(settings).to eq("BUNDLE_KEY" => "value2")
+
+ expect(Bundler.ui).to receive(:info).with("Setting `key` to nil, since rule description, reason3")
+ subject.set(settings, "key", nil, "reason3")
+ expect(settings).to eq({})
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/settings_spec.rb b/spec/bundler/bundler/settings_spec.rb
new file mode 100644
index 0000000000..1a31493e20
--- /dev/null
+++ b/spec/bundler/bundler/settings_spec.rb
@@ -0,0 +1,326 @@
+# frozen_string_literal: true
+
+require "bundler/settings"
+
+RSpec.describe Bundler::Settings do
+ subject(:settings) { described_class.new(bundled_app) }
+
+ describe "#set_local" do
+ context "when the local config file is not found" do
+ subject(:settings) { described_class.new(nil) }
+
+ it "raises a GemfileNotFound error with explanation" do
+ expect { subject.set_local("foo", "bar") }.
+ to raise_error(Bundler::GemfileNotFound, "Could not locate Gemfile")
+ end
+ end
+ end
+
+ describe "load_config" do
+ let(:hash) do
+ {
+ "build.thrift" => "--with-cppflags=-D_FORTIFY_SOURCE=0",
+ "build.libv8" => "--with-system-v8",
+ "build.therubyracer" => "--with-v8-dir",
+ "build.pg" => "--with-pg-config=/usr/local/Cellar/postgresql92/9.2.8_1/bin/pg_config",
+ "gem.coc" => "false",
+ "gem.mit" => "false",
+ "gem.test" => "minitest",
+ "thingy" => <<-EOS.tr("\n", " "),
+--asdf --fdsa --ty=oh man i hope this doesnt break bundler because
+that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
+--very-important-option=DontDeleteRoo
+--very-important-option=DontDeleteRoo
+--very-important-option=DontDeleteRoo
+--very-important-option=DontDeleteRoo
+ EOS
+ "xyz" => "zyx",
+ }
+ end
+
+ before do
+ hash.each do |key, value|
+ settings.set_local key, value
+ end
+ end
+
+ it "can load the config" do
+ loaded = settings.send(:load_config, bundled_app("config"))
+ expected = Hash[hash.map do |k, v|
+ [settings.send(:key_for, k), v.to_s]
+ end]
+ expect(loaded).to eq(expected)
+ end
+
+ context "when BUNDLE_IGNORE_CONFIG is set" do
+ before { ENV["BUNDLE_IGNORE_CONFIG"] = "TRUE" }
+
+ it "ignores the config" do
+ loaded = settings.send(:load_config, bundled_app("config"))
+ expect(loaded).to eq({})
+ end
+ end
+ end
+
+ describe "#global_config_file" do
+ context "when $HOME is not accessible" do
+ context "when $TMPDIR is not writable" do
+ it "does not raise" do
+ expect(Bundler.rubygems).to receive(:user_home).twice.and_return(nil)
+ expect(FileUtils).to receive(:mkpath).twice.with(File.join(Dir.tmpdir, "bundler", "home")).and_raise(Errno::EROFS, "Read-only file system @ dir_s_mkdir - /tmp/bundler")
+
+ expect(subject.send(:global_config_file)).to be_nil
+ end
+ end
+ end
+ end
+
+ describe "#[]" do
+ context "when the local config file is not found" do
+ subject(:settings) { described_class.new }
+
+ it "does not raise" do
+ expect do
+ subject["foo"]
+ end.not_to raise_error
+ end
+ end
+
+ context "when not set" do
+ context "when default value present" do
+ it "retrieves value" do
+ expect(settings[:retry]).to be 3
+ end
+ end
+
+ it "returns nil" do
+ expect(settings[:buttermilk]).to be nil
+ end
+ end
+
+ context "when is boolean" do
+ it "returns a boolean" do
+ settings.set_local :frozen, "true"
+ expect(settings[:frozen]).to be true
+ end
+ context "when specific gem is configured" do
+ it "returns a boolean" do
+ settings.set_local "ignore_messages.foobar", "true"
+ expect(settings["ignore_messages.foobar"]).to be true
+ end
+ end
+ end
+
+ context "when is number" do
+ it "returns a number" do
+ settings.set_local :ssl_verify_mode, "1"
+ expect(settings[:ssl_verify_mode]).to be 1
+ end
+ end
+
+ context "when it's not possible to write to the file" do
+ it "raises an PermissionError with explanation" do
+ expect(bundler_fileutils).to receive(:mkdir_p).with(settings.send(:local_config_file).dirname).
+ and_raise(Errno::EACCES)
+ expect { settings.set_local :frozen, "1" }.
+ to raise_error(Bundler::PermissionError, /config/)
+ end
+ end
+ end
+
+ describe "#temporary" do
+ it "reset after used" do
+ Bundler.settings.set_local :no_install, true
+
+ Bundler.settings.temporary(:no_install => false) do
+ expect(Bundler.settings[:no_install]).to eq false
+ end
+
+ expect(Bundler.settings[:no_install]).to eq true
+ end
+
+ it "returns the return value of the block" do
+ ret = Bundler.settings.temporary({}) { :ret }
+ expect(ret).to eq :ret
+ end
+
+ context "when called without a block" do
+ it "leaves the setting changed" do
+ Bundler.settings.temporary(:foo => :random)
+ expect(Bundler.settings[:foo]).to eq "random"
+ end
+
+ it "returns nil" do
+ expect(Bundler.settings.temporary(:foo => :bar)).to be_nil
+ end
+ end
+ end
+
+ describe "#set_global" do
+ context "when it's not possible to write to the file" do
+ it "raises an PermissionError with explanation" do
+ expect(bundler_fileutils).to receive(:mkdir_p).with(settings.send(:global_config_file).dirname).
+ and_raise(Errno::EACCES)
+ expect { settings.set_global(:frozen, "1") }.
+ to raise_error(Bundler::PermissionError, %r{\.bundle/config})
+ end
+ end
+ end
+
+ describe "#pretty_values_for" do
+ it "prints the converted value rather than the raw string" do
+ bool_key = described_class::BOOL_KEYS.first
+ settings.set_local(bool_key, "false")
+ expect(subject.pretty_values_for(bool_key)).to eq [
+ "Set for your local app (#{bundled_app("config")}): false",
+ ]
+ end
+ end
+
+ describe "#mirror_for" do
+ let(:uri) { URI("https://rubygems.org/") }
+
+ context "with no configured mirror" do
+ it "returns the original URI" do
+ expect(settings.mirror_for(uri)).to eq(uri)
+ end
+
+ it "converts a string parameter to a URI" do
+ expect(settings.mirror_for("https://rubygems.org/")).to eq(uri)
+ end
+ end
+
+ context "with a configured mirror" do
+ let(:mirror_uri) { URI("https://rubygems-mirror.org/") }
+
+ before { settings.set_local "mirror.https://rubygems.org/", mirror_uri.to_s }
+
+ it "returns the mirror URI" do
+ expect(settings.mirror_for(uri)).to eq(mirror_uri)
+ end
+
+ it "converts a string parameter to a URI" do
+ expect(settings.mirror_for("https://rubygems.org/")).to eq(mirror_uri)
+ end
+
+ it "normalizes the URI" do
+ expect(settings.mirror_for("https://rubygems.org")).to eq(mirror_uri)
+ end
+
+ it "is case insensitive" do
+ expect(settings.mirror_for("HTTPS://RUBYGEMS.ORG/")).to eq(mirror_uri)
+ end
+
+ context "with a file URI" do
+ let(:mirror_uri) { URI("file:/foo/BAR/baz/qUx/") }
+
+ it "returns the mirror URI" do
+ expect(settings.mirror_for(uri)).to eq(mirror_uri)
+ end
+
+ it "converts a string parameter to a URI" do
+ expect(settings.mirror_for("file:/foo/BAR/baz/qUx/")).to eq(mirror_uri)
+ end
+
+ it "normalizes the URI" do
+ expect(settings.mirror_for("file:/foo/BAR/baz/qUx")).to eq(mirror_uri)
+ end
+ end
+ end
+ end
+
+ describe "#credentials_for" do
+ let(:uri) { URI("https://gemserver.example.org/") }
+ let(:credentials) { "username:password" }
+
+ context "with no configured credentials" do
+ it "returns nil" do
+ expect(settings.credentials_for(uri)).to be_nil
+ end
+ end
+
+ context "with credentials configured by URL" do
+ before { settings.set_local "https://gemserver.example.org/", credentials }
+
+ it "returns the configured credentials" do
+ expect(settings.credentials_for(uri)).to eq(credentials)
+ end
+ end
+
+ context "with credentials configured by hostname" do
+ before { settings.set_local "gemserver.example.org", credentials }
+
+ it "returns the configured credentials" do
+ expect(settings.credentials_for(uri)).to eq(credentials)
+ end
+ end
+ end
+
+ describe "URI normalization" do
+ it "normalizes HTTP URIs in credentials configuration" do
+ settings.set_local "http://gemserver.example.org", "username:password"
+ expect(settings.all).to include("http://gemserver.example.org/")
+ end
+
+ it "normalizes HTTPS URIs in credentials configuration" do
+ settings.set_local "https://gemserver.example.org", "username:password"
+ expect(settings.all).to include("https://gemserver.example.org/")
+ end
+
+ it "normalizes HTTP URIs in mirror configuration" do
+ settings.set_local "mirror.http://rubygems.org", "http://rubygems-mirror.org"
+ expect(settings.all).to include("mirror.http://rubygems.org/")
+ end
+
+ it "normalizes HTTPS URIs in mirror configuration" do
+ settings.set_local "mirror.https://rubygems.org", "http://rubygems-mirror.org"
+ expect(settings.all).to include("mirror.https://rubygems.org/")
+ end
+
+ it "does not normalize other config keys that happen to contain 'http'" do
+ settings.set_local "local.httparty", home("httparty")
+ expect(settings.all).to include("local.httparty")
+ end
+
+ it "does not normalize other config keys that happen to contain 'https'" do
+ settings.set_local "local.httpsmarty", home("httpsmarty")
+ expect(settings.all).to include("local.httpsmarty")
+ end
+
+ it "reads older keys without trailing slashes" do
+ settings.set_local "mirror.https://rubygems.org", "http://rubygems-mirror.org"
+ expect(settings.mirror_for("https://rubygems.org/")).to eq(
+ URI("http://rubygems-mirror.org/")
+ )
+ end
+
+ it "normalizes URIs with a fallback_timeout option" do
+ settings.set_local "mirror.https://rubygems.org/.fallback_timeout", "true"
+ expect(settings.all).to include("mirror.https://rubygems.org/.fallback_timeout")
+ end
+
+ it "normalizes URIs with a fallback_timeout option without a trailing slash" do
+ settings.set_local "mirror.https://rubygems.org.fallback_timeout", "true"
+ expect(settings.all).to include("mirror.https://rubygems.org/.fallback_timeout")
+ end
+ end
+
+ describe "BUNDLE_ keys format" do
+ let(:settings) { described_class.new(bundled_app(".bundle")) }
+
+ it "converts older keys without double dashes" do
+ config("BUNDLE_MY__PERSONAL.RACK" => "~/Work/git/rack")
+ expect(settings["my.personal.rack"]).to eq("~/Work/git/rack")
+ end
+
+ it "converts older keys without trailing slashes and double dashes" do
+ config("BUNDLE_MIRROR__HTTPS://RUBYGEMS.ORG" => "http://rubygems-mirror.org")
+ expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org")
+ end
+
+ it "reads newer keys format properly" do
+ config("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://rubygems-mirror.org")
+ expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org")
+ end
+ end
+end
diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb
new file mode 100644
index 0000000000..b66c43fd92
--- /dev/null
+++ b/spec/bundler/bundler/shared_helpers_spec.rb
@@ -0,0 +1,504 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::SharedHelpers do
+ let(:ext_lock_double) { double(:ext_lock) }
+
+ before do
+ allow(Bundler.rubygems).to receive(:ext_lock).and_return(ext_lock_double)
+ allow(ext_lock_double).to receive(:synchronize) {|&block| block.call }
+ end
+
+ subject { Bundler::SharedHelpers }
+
+ describe "#default_gemfile" do
+ before { ENV["BUNDLE_GEMFILE"] = "/path/Gemfile" }
+
+ context "Gemfile is present" do
+ let(:expected_gemfile_path) { Pathname.new("/path/Gemfile") }
+
+ it "returns the Gemfile path" do
+ expect(subject.default_gemfile).to eq(expected_gemfile_path)
+ end
+ end
+
+ context "Gemfile is not present" do
+ before { ENV["BUNDLE_GEMFILE"] = nil }
+
+ it "raises a GemfileNotFound error" do
+ expect { subject.default_gemfile }.to raise_error(
+ Bundler::GemfileNotFound, "Could not locate Gemfile"
+ )
+ end
+ end
+
+ context "Gemfile is not an absolute path" do
+ before { ENV["BUNDLE_GEMFILE"] = "Gemfile" }
+
+ let(:expected_gemfile_path) { Pathname.new("Gemfile").expand_path }
+
+ it "returns the Gemfile path" do
+ expect(subject.default_gemfile).to eq(expected_gemfile_path)
+ end
+ end
+ end
+
+ describe "#default_lockfile" do
+ context "gemfile is gems.rb" do
+ let(:gemfile_path) { Pathname.new("/path/gems.rb") }
+ let(:expected_lockfile_path) { Pathname.new("/path/gems.locked") }
+
+ before { allow(subject).to receive(:default_gemfile).and_return(gemfile_path) }
+
+ it "returns the gems.locked path" do
+ expect(subject.default_lockfile).to eq(expected_lockfile_path)
+ end
+ end
+
+ context "is a regular Gemfile" do
+ let(:gemfile_path) { Pathname.new("/path/Gemfile") }
+ let(:expected_lockfile_path) { Pathname.new("/path/Gemfile.lock") }
+
+ before { allow(subject).to receive(:default_gemfile).and_return(gemfile_path) }
+
+ it "returns the lock file path" do
+ expect(subject.default_lockfile).to eq(expected_lockfile_path)
+ end
+ end
+ end
+
+ describe "#default_bundle_dir" do
+ context ".bundle does not exist" do
+ it "returns nil" do
+ expect(subject.default_bundle_dir).to be_nil
+ end
+ end
+
+ context ".bundle is global .bundle" do
+ let(:global_rubygems_dir) { Pathname.new("#{bundled_app}") }
+
+ before do
+ Dir.mkdir ".bundle"
+ allow(Bundler.rubygems).to receive(:user_home).and_return(global_rubygems_dir)
+ end
+
+ it "returns nil" do
+ expect(subject.default_bundle_dir).to be_nil
+ end
+ end
+
+ context ".bundle is not global .bundle" do
+ let(:global_rubygems_dir) { Pathname.new("/path/rubygems") }
+ let(:expected_bundle_dir_path) { Pathname.new("#{bundled_app}/.bundle") }
+
+ before do
+ Dir.mkdir ".bundle"
+ allow(Bundler.rubygems).to receive(:user_home).and_return(global_rubygems_dir)
+ end
+
+ it "returns the .bundle path" do
+ expect(subject.default_bundle_dir).to eq(expected_bundle_dir_path)
+ end
+ end
+ end
+
+ describe "#in_bundle?" do
+ it "calls the find_gemfile method" do
+ expect(subject).to receive(:find_gemfile)
+ subject.in_bundle?
+ end
+
+ shared_examples_for "correctly determines whether to return a Gemfile path" do
+ context "currently in directory with a Gemfile" do
+ before { File.new("Gemfile", "w") }
+
+ it "returns path of the bundle Gemfile" do
+ expect(subject.in_bundle?).to eq("#{bundled_app}/Gemfile")
+ end
+ end
+
+ context "currently in directory without a Gemfile" do
+ it "returns nil" do
+ expect(subject.in_bundle?).to be_nil
+ end
+ end
+ end
+
+ context "ENV['BUNDLE_GEMFILE'] set" do
+ before { ENV["BUNDLE_GEMFILE"] = "/path/Gemfile" }
+
+ it "returns ENV['BUNDLE_GEMFILE']" do
+ expect(subject.in_bundle?).to eq("/path/Gemfile")
+ end
+ end
+
+ context "ENV['BUNDLE_GEMFILE'] not set" do
+ before { ENV["BUNDLE_GEMFILE"] = nil }
+
+ it_behaves_like "correctly determines whether to return a Gemfile path"
+ end
+
+ context "ENV['BUNDLE_GEMFILE'] is blank" do
+ before { ENV["BUNDLE_GEMFILE"] = "" }
+
+ it_behaves_like "correctly determines whether to return a Gemfile path"
+ end
+ end
+
+ describe "#chdir" do
+ let(:op_block) { proc { Dir.mkdir "nested_dir" } }
+
+ before { Dir.mkdir "chdir_test_dir" }
+
+ it "executes the passed block while in the specified directory" do
+ subject.chdir("chdir_test_dir", &op_block)
+ expect(Pathname.new("chdir_test_dir/nested_dir")).to exist
+ end
+ end
+
+ describe "#pwd" do
+ it "returns the current absolute path" do
+ expect(subject.pwd).to eq(bundled_app)
+ end
+ end
+
+ describe "#with_clean_git_env" do
+ let(:with_clean_git_env_block) { proc { Dir.mkdir "with_clean_git_env_test_dir" } }
+
+ before do
+ ENV["GIT_DIR"] = "ORIGINAL_ENV_GIT_DIR"
+ ENV["GIT_WORK_TREE"] = "ORIGINAL_ENV_GIT_WORK_TREE"
+ end
+
+ it "executes the passed block" do
+ subject.with_clean_git_env(&with_clean_git_env_block)
+ expect(Pathname.new("with_clean_git_env_test_dir")).to exist
+ end
+
+ context "when a block is passed" do
+ let(:with_clean_git_env_block) do
+ proc do
+ Dir.mkdir "git_dir_test_dir" unless ENV["GIT_DIR"].nil?
+ Dir.mkdir "git_work_tree_test_dir" unless ENV["GIT_WORK_TREE"].nil?
+ end end
+
+ it "uses a fresh git env for execution" do
+ subject.with_clean_git_env(&with_clean_git_env_block)
+ expect(Pathname.new("git_dir_test_dir")).to_not exist
+ expect(Pathname.new("git_work_tree_test_dir")).to_not exist
+ end
+ end
+
+ context "passed block does not throw errors" do
+ let(:with_clean_git_env_block) do
+ proc do
+ ENV["GIT_DIR"] = "NEW_ENV_GIT_DIR"
+ ENV["GIT_WORK_TREE"] = "NEW_ENV_GIT_WORK_TREE"
+ end end
+
+ it "restores the git env after" do
+ subject.with_clean_git_env(&with_clean_git_env_block)
+ expect(ENV["GIT_DIR"]).to eq("ORIGINAL_ENV_GIT_DIR")
+ expect(ENV["GIT_WORK_TREE"]).to eq("ORIGINAL_ENV_GIT_WORK_TREE")
+ end
+ end
+
+ context "passed block throws errors" do
+ let(:with_clean_git_env_block) do
+ proc do
+ ENV["GIT_DIR"] = "NEW_ENV_GIT_DIR"
+ ENV["GIT_WORK_TREE"] = "NEW_ENV_GIT_WORK_TREE"
+ raise RuntimeError.new
+ end end
+
+ it "restores the git env after" do
+ expect { subject.with_clean_git_env(&with_clean_git_env_block) }.to raise_error(RuntimeError)
+ expect(ENV["GIT_DIR"]).to eq("ORIGINAL_ENV_GIT_DIR")
+ expect(ENV["GIT_WORK_TREE"]).to eq("ORIGINAL_ENV_GIT_WORK_TREE")
+ end
+ end
+ end
+
+ describe "#set_bundle_environment" do
+ before do
+ ENV["BUNDLE_GEMFILE"] = "Gemfile"
+ end
+
+ shared_examples_for "ENV['PATH'] gets set correctly" do
+ before { Dir.mkdir ".bundle" }
+
+ it "ensures bundle bin path is in ENV['PATH']" do
+ subject.set_bundle_environment
+ paths = ENV["PATH"].split(File::PATH_SEPARATOR)
+ expect(paths).to include("#{Bundler.bundle_path}/bin")
+ end
+ end
+
+ shared_examples_for "ENV['RUBYOPT'] gets set correctly" do
+ it "ensures -rbundler/setup is at the beginning of ENV['RUBYOPT']" do
+ subject.set_bundle_environment
+ expect(ENV["RUBYOPT"].split(" ")).to start_with("-rbundler/setup")
+ end
+ end
+
+ shared_examples_for "ENV['RUBYLIB'] gets set correctly" do
+ let(:ruby_lib_path) { "stubbed_ruby_lib_dir" }
+
+ before do
+ allow(Bundler::SharedHelpers).to receive(:bundler_ruby_lib).and_return(ruby_lib_path)
+ end
+
+ it "ensures bundler's ruby version lib path is in ENV['RUBYLIB']" do
+ subject.set_bundle_environment
+ paths = (ENV["RUBYLIB"]).split(File::PATH_SEPARATOR)
+ expect(paths).to include(ruby_lib_path)
+ end
+ end
+
+ it "calls the appropriate set methods" do
+ expect(subject).to receive(:set_path)
+ expect(subject).to receive(:set_rubyopt)
+ expect(subject).to receive(:set_rubylib)
+ subject.set_bundle_environment
+ end
+
+ it "exits if bundle path contains the unix-like path separator" do
+ if Gem.respond_to?(:path_separator)
+ allow(Gem).to receive(:path_separator).and_return(":")
+ else
+ stub_const("File::PATH_SEPARATOR", ":".freeze)
+ end
+ allow(Bundler).to receive(:bundle_path) { Pathname.new("so:me/dir/bin") }
+ expect { subject.send(:validate_bundle_path) }.to raise_error(
+ Bundler::PathError,
+ "Your bundle path contains text matching \":\", which is the " \
+ "path separator for your system. Bundler cannot " \
+ "function correctly when the Bundle path contains the " \
+ "system's PATH separator. Please change your " \
+ "bundle path to not match \":\".\nYour current bundle " \
+ "path is '#{Bundler.bundle_path}'."
+ )
+ end
+
+ context "with a jruby path_separator regex", :ruby => "1.9" do
+ # In versions of jruby that supported ruby 1.8, the path separator was the standard File::PATH_SEPARATOR
+ let(:regex) { Regexp.new("(?<!jar:file|jar|file|classpath|uri:classloader|uri|http|https):") }
+ it "does not exit if bundle path is the standard uri path" do
+ allow(Bundler.rubygems).to receive(:path_separator).and_return(regex)
+ allow(Bundler).to receive(:bundle_path) { Pathname.new("uri:classloader:/WEB-INF/gems") }
+ expect { subject.send(:validate_bundle_path) }.not_to raise_error
+ end
+
+ it "exits if bundle path contains another directory" do
+ allow(Bundler.rubygems).to receive(:path_separator).and_return(regex)
+ allow(Bundler).to receive(:bundle_path) {
+ Pathname.new("uri:classloader:/WEB-INF/gems:other/dir")
+ }
+
+ expect { subject.send(:validate_bundle_path) }.to raise_error(
+ Bundler::PathError,
+ "Your bundle path contains text matching " \
+ "/(?<!jar:file|jar|file|classpath|uri:classloader|uri|http|https):/, which is the " \
+ "path separator for your system. Bundler cannot " \
+ "function correctly when the Bundle path contains the " \
+ "system's PATH separator. Please change your " \
+ "bundle path to not match " \
+ "/(?<!jar:file|jar|file|classpath|uri:classloader|uri|http|https):/." \
+ "\nYour current bundle path is '#{Bundler.bundle_path}'."
+ )
+ end
+ end
+
+ context "ENV['PATH'] does not exist" do
+ before { ENV.delete("PATH") }
+
+ it_behaves_like "ENV['PATH'] gets set correctly"
+ end
+
+ context "ENV['PATH'] is empty" do
+ before { ENV["PATH"] = "" }
+
+ it_behaves_like "ENV['PATH'] gets set correctly"
+ end
+
+ context "ENV['PATH'] exists" do
+ before { ENV["PATH"] = "/some_path/bin" }
+
+ it_behaves_like "ENV['PATH'] gets set correctly"
+ end
+
+ context "ENV['PATH'] already contains the bundle bin path" do
+ let(:bundle_path) { "#{Bundler.bundle_path}/bin" }
+
+ before do
+ ENV["PATH"] = bundle_path
+ end
+
+ it_behaves_like "ENV['PATH'] gets set correctly"
+
+ it "ENV['PATH'] should only contain one instance of bundle bin path" do
+ subject.set_bundle_environment
+ paths = (ENV["PATH"]).split(File::PATH_SEPARATOR)
+ expect(paths.count(bundle_path)).to eq(1)
+ end
+ end
+
+ context "ENV['RUBYOPT'] does not exist" do
+ before { ENV.delete("RUBYOPT") }
+
+ it_behaves_like "ENV['RUBYOPT'] gets set correctly"
+ end
+
+ context "ENV['RUBYOPT'] exists without -rbundler/setup" do
+ before { ENV["RUBYOPT"] = "-I/some_app_path/lib" }
+
+ it_behaves_like "ENV['RUBYOPT'] gets set correctly"
+ end
+
+ context "ENV['RUBYOPT'] exists and contains -rbundler/setup" do
+ before { ENV["RUBYOPT"] = "-rbundler/setup" }
+
+ it_behaves_like "ENV['RUBYOPT'] gets set correctly"
+ end
+
+ context "ENV['RUBYLIB'] does not exist" do
+ before { ENV.delete("RUBYLIB") }
+
+ it_behaves_like "ENV['RUBYLIB'] gets set correctly"
+ end
+
+ context "ENV['RUBYLIB'] is empty" do
+ before { ENV["PATH"] = "" }
+
+ it_behaves_like "ENV['RUBYLIB'] gets set correctly"
+ end
+
+ context "ENV['RUBYLIB'] exists" do
+ before { ENV["PATH"] = "/some_path/bin" }
+
+ it_behaves_like "ENV['RUBYLIB'] gets set correctly"
+ end
+
+ context "bundle executable in ENV['BUNDLE_BIN_PATH'] does not exist" do
+ before { ENV["BUNDLE_BIN_PATH"] = "/does/not/exist" }
+ before { Bundler.rubygems.replace_bin_path [], [] }
+
+ it "sets BUNDLE_BIN_PATH to the bundle executable file" do
+ subject.set_bundle_environment
+ bundle_exe = ruby_core? ? "../../../../exe/bundle" : "../../../exe/bundle"
+ expect(ENV["BUNDLE_BIN_PATH"]).to eq(File.expand_path(bundle_exe, __FILE__))
+ end
+ end
+
+ context "ENV['RUBYLIB'] already contains the bundler's ruby version lib path" do
+ let(:ruby_lib_path) { "stubbed_ruby_lib_dir" }
+
+ before do
+ ENV["RUBYLIB"] = ruby_lib_path
+ end
+
+ it_behaves_like "ENV['RUBYLIB'] gets set correctly"
+
+ it "ENV['RUBYLIB'] should only contain one instance of bundler's ruby version lib path" do
+ subject.set_bundle_environment
+ paths = (ENV["RUBYLIB"]).split(File::PATH_SEPARATOR)
+ expect(paths.count(ruby_lib_path)).to eq(1)
+ end
+ end
+ end
+
+ describe "#filesystem_access" do
+ context "system has proper permission access" do
+ let(:file_op_block) { proc {|path| FileUtils.mkdir_p(path) } }
+
+ it "performs the operation in the passed block" do
+ subject.filesystem_access("./test_dir", &file_op_block)
+ expect(Pathname.new("test_dir")).to exist
+ end
+ end
+
+ context "system throws Errno::EACESS" do
+ let(:file_op_block) { proc {|_path| raise Errno::EACCES } }
+
+ it "raises a PermissionError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::PermissionError
+ )
+ end
+ end
+
+ context "system throws Errno::EAGAIN" do
+ let(:file_op_block) { proc {|_path| raise Errno::EAGAIN } }
+
+ it "raises a TemporaryResourceError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::TemporaryResourceError
+ )
+ end
+ end
+
+ context "system throws Errno::EPROTO" do
+ let(:file_op_block) { proc {|_path| raise Errno::EPROTO } }
+
+ it "raises a VirtualProtocolError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::VirtualProtocolError
+ )
+ end
+ end
+
+ context "system throws Errno::ENOTSUP", :ruby => "1.9" do
+ let(:file_op_block) { proc {|_path| raise Errno::ENOTSUP } }
+
+ it "raises a OperationNotSupportedError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::OperationNotSupportedError
+ )
+ end
+ end
+
+ context "system throws Errno::ENOSPC" do
+ let(:file_op_block) { proc {|_path| raise Errno::ENOSPC } }
+
+ it "raises a NoSpaceOnDeviceError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::NoSpaceOnDeviceError
+ )
+ end
+ end
+
+ context "system throws an unhandled SystemCallError" do
+ let(:error) { SystemCallError.new("Shields down", 1337) }
+ let(:file_op_block) { proc {|_path| raise error } }
+
+ it "raises a GenericSystemCallError" do
+ expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(
+ Bundler::GenericSystemCallError, /error accessing.+underlying.+Shields down/m
+ )
+ end
+ end
+ end
+
+ describe "#const_get_safely" do
+ module TargetNamespace
+ VALID_CONSTANT = 1
+ end
+
+ context "when the namespace does have the requested constant" do
+ it "returns the value of the requested constant" do
+ expect(subject.const_get_safely(:VALID_CONSTANT, TargetNamespace)).to eq(1)
+ end
+ end
+
+ context "when the requested constant is passed as a string" do
+ it "returns the value of the requested constant" do
+ expect(subject.const_get_safely("VALID_CONSTANT", TargetNamespace)).to eq(1)
+ end
+ end
+
+ context "when the namespace does not have the requested constant" do
+ it "returns nil" do
+ expect(subject.const_get_safely("INVALID_CONSTANT", TargetNamespace)).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/git/git_proxy_spec.rb b/spec/bundler/bundler/source/git/git_proxy_spec.rb
new file mode 100644
index 0000000000..3a29c97461
--- /dev/null
+++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source::Git::GitProxy do
+ let(:path) { Pathname("path") }
+ let(:uri) { "https://github.com/bundler/bundler.git" }
+ let(:ref) { "HEAD" }
+ let(:revision) { nil }
+ let(:git_source) { nil }
+ subject { described_class.new(path, uri, ref, revision, git_source) }
+
+ context "with configured credentials" do
+ it "adds username and password to URI" do
+ Bundler.settings.temporary(uri => "u:p")
+ expect(subject).to receive(:git_retry).with(match("https://u:p@github.com/bundler/bundler.git"))
+ subject.checkout
+ end
+
+ it "adds username and password to URI for host" do
+ Bundler.settings.temporary("github.com" => "u:p")
+ expect(subject).to receive(:git_retry).with(match("https://u:p@github.com/bundler/bundler.git"))
+ subject.checkout
+ end
+
+ it "does not add username and password to mismatched URI" do
+ Bundler.settings.temporary("https://u:p@github.com/bundler/bundler-mismatch.git" => "u:p")
+ expect(subject).to receive(:git_retry).with(match(uri))
+ subject.checkout
+ end
+
+ it "keeps original userinfo" do
+ Bundler.settings.temporary("github.com" => "u:p")
+ original = "https://orig:info@github.com/bundler/bundler.git"
+ subject = described_class.new(Pathname("path"), original, "HEAD")
+ expect(subject).to receive(:git_retry).with(match(original))
+ subject.checkout
+ end
+ end
+
+ describe "#version" do
+ context "with a normal version number" do
+ before do
+ expect(subject).to receive(:git).with("--version").
+ and_return("git version 1.2.3")
+ end
+
+ it "returns the git version number" do
+ expect(subject.version).to eq("1.2.3")
+ end
+
+ it "does not raise an error when passed into Gem::Version.create" do
+ expect { Gem::Version.create subject.version }.not_to raise_error
+ end
+ end
+
+ context "with a OSX version number" do
+ before do
+ expect(subject).to receive(:git).with("--version").
+ and_return("git version 1.2.3 (Apple Git-BS)")
+ end
+
+ it "strips out OSX specific additions in the version string" do
+ expect(subject.version).to eq("1.2.3")
+ end
+
+ it "does not raise an error when passed into Gem::Version.create" do
+ expect { Gem::Version.create subject.version }.not_to raise_error
+ end
+ end
+
+ context "with a msysgit version number" do
+ before do
+ expect(subject).to receive(:git).with("--version").
+ and_return("git version 1.2.3.msysgit.0")
+ end
+
+ it "strips out msysgit specific additions in the version string" do
+ expect(subject.version).to eq("1.2.3")
+ end
+
+ it "does not raise an error when passed into Gem::Version.create" do
+ expect { Gem::Version.create subject.version }.not_to raise_error
+ end
+ end
+ end
+
+ describe "#full_version" do
+ context "with a normal version number" do
+ before do
+ expect(subject).to receive(:git).with("--version").
+ and_return("git version 1.2.3")
+ end
+
+ it "returns the git version number" do
+ expect(subject.full_version).to eq("1.2.3")
+ end
+ end
+
+ context "with a OSX version number" do
+ before do
+ expect(subject).to receive(:git).with("--version").
+ and_return("git version 1.2.3 (Apple Git-BS)")
+ end
+
+ it "does not strip out OSX specific additions in the version string" do
+ expect(subject.full_version).to eq("1.2.3 (Apple Git-BS)")
+ end
+ end
+
+ context "with a msysgit version number" do
+ before do
+ expect(subject).to receive(:git).with("--version").
+ and_return("git version 1.2.3.msysgit.0")
+ end
+
+ it "does not strip out msysgit specific additions in the version string" do
+ expect(subject.full_version).to eq("1.2.3.msysgit.0")
+ end
+ end
+ end
+
+ describe "#copy_to" do
+ let(:destination) { tmpdir("copy_to_path") }
+ let(:submodules) { false }
+
+ context "when given a SHA as a revision" do
+ let(:revision) { "abcd" * 10 }
+
+ it "fails gracefully when resetting to the revision fails" do
+ expect(subject).to receive(:git_retry).with(start_with("clone ")) { destination.mkpath }
+ expect(subject).to receive(:git_retry).with(start_with("fetch "))
+ expect(subject).to receive(:git).with("reset --hard #{revision}").and_raise(Bundler::Source::Git::GitCommandError, "command")
+ expect(subject).not_to receive(:git)
+
+ expect { subject.copy_to(destination, submodules) }.
+ to raise_error(Bundler::Source::Git::MissingGitRevisionError,
+ "Revision #{revision} does not exist in the repository #{uri}. Maybe you misspelled it?")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/git_spec.rb b/spec/bundler/bundler/source/git_spec.rb
new file mode 100644
index 0000000000..f7475a35aa
--- /dev/null
+++ b/spec/bundler/bundler/source/git_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source::Git do
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new("root") }
+ end
+
+ let(:uri) { "https://github.com/foo/bar.git" }
+ let(:options) do
+ { "uri" => uri }
+ end
+
+ subject { described_class.new(options) }
+
+ describe "#to_s" do
+ it "returns a description" do
+ expect(subject.to_s).to eq "https://github.com/foo/bar.git (at master)"
+ end
+
+ context "when the URI contains credentials" do
+ let(:uri) { "https://my-secret-token:x-oauth-basic@github.com/foo/bar.git" }
+
+ it "filters credentials" do
+ expect(subject.to_s).to eq "https://x-oauth-basic@github.com/foo/bar.git (at master)"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/path_spec.rb b/spec/bundler/bundler/source/path_spec.rb
new file mode 100644
index 0000000000..1d13e03ec1
--- /dev/null
+++ b/spec/bundler/bundler/source/path_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source::Path do
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new("root") }
+ end
+
+ describe "#eql?" do
+ subject { described_class.new("path" => "gems/a") }
+
+ context "with two equivalent relative paths from different roots" do
+ let(:a_gem_opts) { { "path" => "../gems/a", "root_path" => Bundler.root.join("nested") } }
+ let(:a_gem) { described_class.new a_gem_opts }
+
+ it "returns true" do
+ expect(subject).to eq a_gem
+ end
+ end
+
+ context "with the same (but not equivalent) relative path from different roots" do
+ subject { described_class.new("path" => "gems/a") }
+
+ let(:a_gem_opts) { { "path" => "gems/a", "root_path" => Bundler.root.join("nested") } }
+ let(:a_gem) { described_class.new a_gem_opts }
+
+ it "returns false" do
+ expect(subject).to_not eq a_gem
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/rubygems/remote_spec.rb b/spec/bundler/bundler/source/rubygems/remote_spec.rb
new file mode 100644
index 0000000000..9a7ab42128
--- /dev/null
+++ b/spec/bundler/bundler/source/rubygems/remote_spec.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+require "bundler/source/rubygems/remote"
+
+RSpec.describe Bundler::Source::Rubygems::Remote do
+ def remote(uri)
+ Bundler::Source::Rubygems::Remote.new(uri)
+ end
+
+ before do
+ allow(Digest(:MD5)).to receive(:hexdigest).with(duck_type(:to_s)) {|string| "MD5HEX(#{string})" }
+ end
+
+ let(:uri_no_auth) { URI("https://gems.example.com") }
+ let(:uri_with_auth) { URI("https://#{credentials}@gems.example.com") }
+ let(:credentials) { "username:password" }
+
+ context "when the original URI has no credentials" do
+ describe "#uri" do
+ it "returns the original URI" do
+ expect(remote(uri_no_auth).uri).to eq(uri_no_auth)
+ end
+
+ it "applies configured credentials" do
+ Bundler.settings.temporary(uri_no_auth.to_s => credentials)
+ expect(remote(uri_no_auth).uri).to eq(uri_with_auth)
+ end
+ end
+
+ describe "#anonymized_uri" do
+ it "returns the original URI" do
+ expect(remote(uri_no_auth).anonymized_uri).to eq(uri_no_auth)
+ end
+
+ it "does not apply given credentials" do
+ Bundler.settings.temporary(uri_no_auth.to_s => credentials)
+ expect(remote(uri_no_auth).anonymized_uri).to eq(uri_no_auth)
+ end
+ end
+
+ describe "#cache_slug" do
+ it "returns the correct slug" do
+ expect(remote(uri_no_auth).cache_slug).to eq("gems.example.com.443.MD5HEX(gems.example.com.443./)")
+ end
+
+ it "only applies the given user" do
+ Bundler.settings.temporary(uri_no_auth.to_s => credentials)
+ expect(remote(uri_no_auth).cache_slug).to eq("gems.example.com.username.443.MD5HEX(gems.example.com.username.443./)")
+ end
+ end
+ end
+
+ context "when the original URI has a username and password" do
+ describe "#uri" do
+ it "returns the original URI" do
+ expect(remote(uri_with_auth).uri).to eq(uri_with_auth)
+ end
+
+ it "does not apply configured credentials" do
+ Bundler.settings.temporary(uri_no_auth.to_s => "other:stuff")
+ expect(remote(uri_with_auth).uri).to eq(uri_with_auth)
+ end
+ end
+
+ describe "#anonymized_uri" do
+ it "returns the URI without username and password" do
+ expect(remote(uri_with_auth).anonymized_uri).to eq(uri_no_auth)
+ end
+
+ it "does not apply given credentials" do
+ Bundler.settings.temporary(uri_no_auth.to_s => "other:stuff")
+ expect(remote(uri_with_auth).anonymized_uri).to eq(uri_no_auth)
+ end
+ end
+
+ describe "#cache_slug" do
+ it "returns the correct slug" do
+ expect(remote(uri_with_auth).cache_slug).to eq("gems.example.com.username.443.MD5HEX(gems.example.com.username.443./)")
+ end
+
+ it "does not apply given credentials" do
+ Bundler.settings.temporary(uri_with_auth.to_s => credentials)
+ expect(remote(uri_with_auth).cache_slug).to eq("gems.example.com.username.443.MD5HEX(gems.example.com.username.443./)")
+ end
+ end
+ end
+
+ context "when the original URI has only a username" do
+ let(:uri) { URI("https://SeCrEt-ToKeN@gem.fury.io/me/") }
+
+ describe "#anonymized_uri" do
+ it "returns the URI without username and password" do
+ expect(remote(uri).anonymized_uri).to eq(URI("https://gem.fury.io/me/"))
+ end
+ end
+
+ describe "#cache_slug" do
+ it "returns the correct slug" do
+ expect(remote(uri).cache_slug).to eq("gem.fury.io.SeCrEt-ToKeN.443.MD5HEX(gem.fury.io.SeCrEt-ToKeN.443./me/)")
+ end
+ end
+ end
+
+ context "when a mirror with inline credentials is configured for the URI" do
+ let(:uri) { URI("https://rubygems.org/") }
+ let(:mirror_uri_with_auth) { URI("https://username:password@rubygems-mirror.org/") }
+ let(:mirror_uri_no_auth) { URI("https://rubygems-mirror.org/") }
+
+ before { Bundler.settings.set_local("mirror.https://rubygems.org/", mirror_uri_with_auth.to_s) }
+
+ specify "#uri returns the mirror URI with credentials" do
+ expect(remote(uri).uri).to eq(mirror_uri_with_auth)
+ end
+
+ specify "#anonymized_uri returns the mirror URI without credentials" do
+ expect(remote(uri).anonymized_uri).to eq(mirror_uri_no_auth)
+ end
+
+ specify "#original_uri returns the original source" do
+ expect(remote(uri).original_uri).to eq(uri)
+ end
+
+ specify "#cache_slug returns the correct slug" do
+ expect(remote(uri).cache_slug).to eq("rubygems.org.443.MD5HEX(rubygems.org.443./)")
+ end
+ end
+
+ context "when a mirror with configured credentials is configured for the URI" do
+ let(:uri) { URI("https://rubygems.org/") }
+ let(:mirror_uri_with_auth) { URI("https://#{credentials}@rubygems-mirror.org/") }
+ let(:mirror_uri_no_auth) { URI("https://rubygems-mirror.org/") }
+
+ before do
+ Bundler.settings.temporary("mirror.https://rubygems.org/" => mirror_uri_no_auth.to_s)
+ Bundler.settings.temporary(mirror_uri_no_auth.to_s => credentials)
+ end
+
+ specify "#uri returns the mirror URI with credentials" do
+ expect(remote(uri).uri).to eq(mirror_uri_with_auth)
+ end
+
+ specify "#anonymized_uri returns the mirror URI without credentials" do
+ expect(remote(uri).anonymized_uri).to eq(mirror_uri_no_auth)
+ end
+
+ specify "#original_uri returns the original source" do
+ expect(remote(uri).original_uri).to eq(uri)
+ end
+
+ specify "#cache_slug returns the original source" do
+ expect(remote(uri).cache_slug).to eq("rubygems.org.443.MD5HEX(rubygems.org.443./)")
+ end
+ end
+
+ context "when there is no mirror set" do
+ describe "#original_uri" do
+ it "is not set" do
+ expect(remote(uri_no_auth).original_uri).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source/rubygems_spec.rb b/spec/bundler/bundler/source/rubygems_spec.rb
new file mode 100644
index 0000000000..7c457a7265
--- /dev/null
+++ b/spec/bundler/bundler/source/rubygems_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source::Rubygems do
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new("root") }
+ end
+
+ describe "caches" do
+ it "includes Bundler.app_cache" do
+ expect(subject.caches).to include(Bundler.app_cache)
+ end
+
+ it "includes GEM_PATH entries" do
+ Gem.path.each do |path|
+ expect(subject.caches).to include(File.expand_path("#{path}/cache"))
+ end
+ end
+
+ it "is an array of strings or pathnames" do
+ subject.caches.each do |cache|
+ expect([String, Pathname]).to include(cache.class)
+ end
+ end
+ end
+
+ describe "#add_remote" do
+ context "when the source is an HTTP(s) URI with no host" do
+ it "raises error" do
+ expect { subject.add_remote("https:rubygems.org") }.to raise_error(ArgumentError)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source_list_spec.rb b/spec/bundler/bundler/source_list_spec.rb
new file mode 100644
index 0000000000..ce3353012c
--- /dev/null
+++ b/spec/bundler/bundler/source_list_spec.rb
@@ -0,0 +1,463 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::SourceList do
+ before do
+ allow(Bundler).to receive(:root) { Pathname.new "./tmp/bundled_app" }
+
+ stub_const "ASourcePlugin", Class.new(Bundler::Plugin::API)
+ ASourcePlugin.source "new_source"
+ allow(Bundler::Plugin).to receive(:source?).with("new_source").and_return(true)
+ end
+
+ subject(:source_list) { Bundler::SourceList.new }
+
+ let(:rubygems_aggregate) { Bundler::Source::Rubygems.new }
+ let(:metadata_source) { Bundler::Source::Metadata.new }
+
+ describe "adding sources" do
+ before do
+ source_list.add_path_source("path" => "/existing/path/to/gem")
+ source_list.add_git_source("uri" => "git://existing-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://existing-rubygems.org"])
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/a")
+ end
+
+ describe "#add_path_source" do
+ before do
+ @duplicate = source_list.add_path_source("path" => "/path/to/gem")
+ @new_source = source_list.add_path_source("path" => "/path/to/gem")
+ end
+
+ it "returns the new path source" do
+ expect(@new_source).to be_instance_of(Bundler::Source::Path)
+ end
+
+ it "passes the provided options to the new source" do
+ expect(@new_source.options).to eq("path" => "/path/to/gem")
+ end
+
+ it "adds the source to the beginning of path_sources" do
+ expect(source_list.path_sources.first).to equal(@new_source)
+ end
+
+ it "removes existing duplicates" do
+ expect(source_list.path_sources).not_to include equal(@duplicate)
+ end
+ end
+
+ describe "#add_git_source" do
+ before do
+ @duplicate = source_list.add_git_source("uri" => "git://host/path.git")
+ @new_source = source_list.add_git_source("uri" => "git://host/path.git")
+ end
+
+ it "returns the new git source" do
+ expect(@new_source).to be_instance_of(Bundler::Source::Git)
+ end
+
+ it "passes the provided options to the new source" do
+ @new_source = source_list.add_git_source("uri" => "git://host/path.git")
+ expect(@new_source.options).to eq("uri" => "git://host/path.git")
+ end
+
+ it "adds the source to the beginning of git_sources" do
+ @new_source = source_list.add_git_source("uri" => "git://host/path.git")
+ expect(source_list.git_sources.first).to equal(@new_source)
+ end
+
+ it "removes existing duplicates" do
+ @duplicate = source_list.add_git_source("uri" => "git://host/path.git")
+ @new_source = source_list.add_git_source("uri" => "git://host/path.git")
+ expect(source_list.git_sources).not_to include equal(@duplicate)
+ end
+
+ context "with the git: protocol" do
+ let(:msg) do
+ "The git source `git://existing-git.org/path.git` " \
+ "uses the `git` protocol, which transmits data without encryption. " \
+ "Disable this warning with `bundle config git.allow_insecure true`, " \
+ "or switch to the `https` protocol to keep your data secure."
+ end
+
+ it "warns about git protocols" do
+ expect(Bundler.ui).to receive(:warn).with(msg)
+ source_list.add_git_source("uri" => "git://existing-git.org/path.git")
+ end
+
+ it "ignores git protocols on request" do
+ Bundler.settings.temporary(:"git.allow_insecure" => true)
+ expect(Bundler.ui).to_not receive(:warn).with(msg)
+ source_list.add_git_source("uri" => "git://existing-git.org/path.git")
+ end
+ end
+ end
+
+ describe "#add_rubygems_source" do
+ before do
+ @duplicate = source_list.add_rubygems_source("remotes" => ["https://rubygems.org/"])
+ @new_source = source_list.add_rubygems_source("remotes" => ["https://rubygems.org/"])
+ end
+
+ it "returns the new rubygems source" do
+ expect(@new_source).to be_instance_of(Bundler::Source::Rubygems)
+ end
+
+ it "passes the provided options to the new source" do
+ expect(@new_source.options).to eq("remotes" => ["https://rubygems.org/"])
+ end
+
+ it "adds the source to the beginning of rubygems_sources" do
+ expect(source_list.rubygems_sources.first).to equal(@new_source)
+ end
+
+ it "removes duplicates" do
+ expect(source_list.rubygems_sources).not_to include equal(@duplicate)
+ end
+ end
+
+ describe "#add_rubygems_remote", :bundler => "< 2" do
+ let!(:returned_source) { source_list.add_rubygems_remote("https://rubygems.org/") }
+
+ it "returns the aggregate rubygems source" do
+ expect(returned_source).to be_instance_of(Bundler::Source::Rubygems)
+ end
+
+ it "adds the provided remote to the beginning of the aggregate source" do
+ source_list.add_rubygems_remote("https://othersource.org")
+ expect(returned_source.remotes).to eq [
+ URI("https://othersource.org/"),
+ URI("https://rubygems.org/"),
+ ]
+ end
+ end
+
+ describe "#add_plugin_source" do
+ before do
+ @duplicate = source_list.add_plugin_source("new_source", "uri" => "http://host/path.")
+ @new_source = source_list.add_plugin_source("new_source", "uri" => "http://host/path.")
+ end
+
+ it "returns the new plugin source" do
+ expect(@new_source).to be_a(Bundler::Plugin::API::Source)
+ end
+
+ it "passes the provided options to the new source" do
+ expect(@new_source.options).to eq("uri" => "http://host/path.")
+ end
+
+ it "adds the source to the beginning of git_sources" do
+ expect(source_list.plugin_sources.first).to equal(@new_source)
+ end
+
+ it "removes existing duplicates" do
+ expect(source_list.plugin_sources).not_to include equal(@duplicate)
+ end
+ end
+ end
+
+ describe "#all_sources" do
+ it "includes the aggregate rubygems source when rubygems sources have been added" do
+ source_list.add_git_source("uri" => "git://host/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://rubygems.org"])
+ source_list.add_path_source("path" => "/path/to/gem")
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/a")
+
+ expect(source_list.all_sources).to include rubygems_aggregate
+ end
+
+ it "includes the aggregate rubygems source when no rubygems sources have been added" do
+ source_list.add_git_source("uri" => "git://host/path.git")
+ source_list.add_path_source("path" => "/path/to/gem")
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/a")
+
+ expect(source_list.all_sources).to include rubygems_aggregate
+ end
+
+ it "returns sources of the same type in the reverse order that they were added" do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://fifth-rubygems.org"])
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/b")
+ source_list.add_rubygems_source("remotes" => ["https://fourth-rubygems.org"])
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://third-rubygems.org"])
+ source_list.add_plugin_source("new_source", "uri" => "https://some.o.url/")
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://second-rubygems.org"])
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_plugin_source("new_source", "uri" => "https://some.url/c")
+ source_list.add_rubygems_source("remotes" => ["https://first-rubygems.org"])
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+
+ expect(source_list.all_sources).to eq [
+ Bundler::Source::Path.new("path" => "/first/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/second/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/third/path/to/gem"),
+ Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://third-git.org/path.git"),
+ ASourcePlugin.new("uri" => "https://some.url/c"),
+ ASourcePlugin.new("uri" => "https://some.o.url/"),
+ ASourcePlugin.new("uri" => "https://some.url/b"),
+ Bundler::Source::Rubygems.new("remotes" => ["https://first-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://second-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://fourth-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://fifth-rubygems.org"]),
+ rubygems_aggregate,
+ metadata_source,
+ ]
+ end
+ end
+
+ describe "#path_sources" do
+ it "returns an empty array when no path sources have been added" do
+ source_list.add_rubygems_remote("https://rubygems.org")
+ source_list.add_git_source("uri" => "git://host/path.git")
+ expect(source_list.path_sources).to be_empty
+ end
+
+ it "returns path sources in the reverse order that they were added" do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_rubygems_remote("https://fifth-rubygems.org")
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_rubygems_remote("https://fourth-rubygems.org")
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_rubygems_remote("https://third-rubygems.org")
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_rubygems_remote("https://second-rubygems.org")
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_rubygems_remote("https://first-rubygems.org")
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+
+ expect(source_list.path_sources).to eq [
+ Bundler::Source::Path.new("path" => "/first/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/second/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/third/path/to/gem"),
+ ]
+ end
+ end
+
+ describe "#git_sources" do
+ it "returns an empty array when no git sources have been added" do
+ source_list.add_rubygems_remote("https://rubygems.org")
+ source_list.add_path_source("path" => "/path/to/gem")
+
+ expect(source_list.git_sources).to be_empty
+ end
+
+ it "returns git sources in the reverse order that they were added" do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_rubygems_remote("https://fifth-rubygems.org")
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_rubygems_remote("https://fourth-rubygems.org")
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_rubygems_remote("https://third-rubygems.org")
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_rubygems_remote("https://second-rubygems.org")
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_rubygems_remote("https://first-rubygems.org")
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+
+ expect(source_list.git_sources).to eq [
+ Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://third-git.org/path.git"),
+ ]
+ end
+ end
+
+ describe "#plugin_sources" do
+ it "returns an empty array when no plugin sources have been added" do
+ source_list.add_rubygems_remote("https://rubygems.org")
+ source_list.add_path_source("path" => "/path/to/gem")
+
+ expect(source_list.plugin_sources).to be_empty
+ end
+
+ it "returns plugin sources in the reverse order that they were added" do
+ source_list.add_plugin_source("new_source", "uri" => "https://third-git.org/path.git")
+ source_list.add_git_source("https://new-git.org")
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_rubygems_remote("https://fourth-rubygems.org")
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_rubygems_remote("https://third-rubygems.org")
+ source_list.add_plugin_source("new_source", "uri" => "git://second-git.org/path.git")
+ source_list.add_rubygems_remote("https://second-rubygems.org")
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_rubygems_remote("https://first-rubygems.org")
+ source_list.add_plugin_source("new_source", "uri" => "git://first-git.org/path.git")
+
+ expect(source_list.plugin_sources).to eq [
+ ASourcePlugin.new("uri" => "git://first-git.org/path.git"),
+ ASourcePlugin.new("uri" => "git://second-git.org/path.git"),
+ ASourcePlugin.new("uri" => "https://third-git.org/path.git"),
+ ]
+ end
+ end
+
+ describe "#rubygems_sources" do
+ it "includes the aggregate rubygems source when rubygems sources have been added" do
+ source_list.add_git_source("uri" => "git://host/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://rubygems.org"])
+ source_list.add_path_source("path" => "/path/to/gem")
+
+ expect(source_list.rubygems_sources).to include rubygems_aggregate
+ end
+
+ it "returns only the aggregate rubygems source when no rubygems sources have been added" do
+ source_list.add_git_source("uri" => "git://host/path.git")
+ source_list.add_path_source("path" => "/path/to/gem")
+
+ expect(source_list.rubygems_sources).to eq [rubygems_aggregate]
+ end
+
+ it "returns rubygems sources in the reverse order that they were added" do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://fifth-rubygems.org"])
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://fourth-rubygems.org"])
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://third-rubygems.org"])
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://second-rubygems.org"])
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://first-rubygems.org"])
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+
+ expect(source_list.rubygems_sources).to eq [
+ Bundler::Source::Rubygems.new("remotes" => ["https://first-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://second-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://fourth-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://fifth-rubygems.org"]),
+ rubygems_aggregate,
+ ]
+ end
+ end
+
+ describe "#get" do
+ context "when it includes an equal source" do
+ let(:rubygems_source) { Bundler::Source::Rubygems.new("remotes" => ["https://rubygems.org"]) }
+ before { @equal_source = source_list.add_rubygems_remote("https://rubygems.org") }
+
+ it "returns the equal source" do
+ expect(source_list.get(rubygems_source)).to be @equal_source
+ end
+ end
+
+ context "when it does not include an equal source" do
+ let(:path_source) { Bundler::Source::Path.new("path" => "/path/to/gem") }
+
+ it "returns nil" do
+ expect(source_list.get(path_source)).to be_nil
+ end
+ end
+ end
+
+ describe "#lock_sources" do
+ before do
+ source_list.add_git_source("uri" => "git://third-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://duplicate-rubygems.org"])
+ source_list.add_plugin_source("new_source", "uri" => "https://third-bar.org/foo")
+ source_list.add_path_source("path" => "/third/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://third-rubygems.org"])
+ source_list.add_path_source("path" => "/second/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://second-rubygems.org"])
+ source_list.add_git_source("uri" => "git://second-git.org/path.git")
+ source_list.add_rubygems_source("remotes" => ["https://first-rubygems.org"])
+ source_list.add_plugin_source("new_source", "uri" => "https://second-plugin.org/random")
+ source_list.add_path_source("path" => "/first/path/to/gem")
+ source_list.add_rubygems_source("remotes" => ["https://duplicate-rubygems.org"])
+ source_list.add_git_source("uri" => "git://first-git.org/path.git")
+ end
+
+ it "combines the rubygems sources into a single instance, removing duplicate remotes from the end", :bundler => "< 2" do
+ expect(source_list.lock_sources).to eq [
+ Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://third-git.org/path.git"),
+ ASourcePlugin.new("uri" => "https://second-plugin.org/random"),
+ ASourcePlugin.new("uri" => "https://third-bar.org/foo"),
+ Bundler::Source::Path.new("path" => "/first/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/second/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/third/path/to/gem"),
+ Bundler::Source::Rubygems.new("remotes" => [
+ "https://duplicate-rubygems.org",
+ "https://first-rubygems.org",
+ "https://second-rubygems.org",
+ "https://third-rubygems.org",
+ ]),
+ ]
+ end
+
+ it "returns all sources, without combining rubygems sources", :bundler => "2" do
+ expect(source_list.lock_sources).to eq [
+ Bundler::Source::Rubygems.new,
+ Bundler::Source::Rubygems.new("remotes" => ["https://duplicate-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://first-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://second-rubygems.org"]),
+ Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]),
+ Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"),
+ Bundler::Source::Git.new("uri" => "git://third-git.org/path.git"),
+ Bundler::Source::Path.new("path" => "/first/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/second/path/to/gem"),
+ Bundler::Source::Path.new("path" => "/third/path/to/gem"),
+ ASourcePlugin.new("uri" => "https://second-plugin.org/random"),
+ ASourcePlugin.new("uri" => "https://third-bar.org/foo"),
+ ]
+ end
+ end
+
+ describe "replace_sources!" do
+ let(:existing_locked_source) { Bundler::Source::Path.new("path" => "/existing/path") }
+ let(:removed_locked_source) { Bundler::Source::Path.new("path" => "/removed/path") }
+
+ let(:locked_sources) { [existing_locked_source, removed_locked_source] }
+
+ before do
+ @existing_source = source_list.add_path_source("path" => "/existing/path")
+ @new_source = source_list.add_path_source("path" => "/new/path")
+ source_list.replace_sources!(locked_sources)
+ end
+
+ it "maintains the order and number of sources" do
+ expect(source_list.path_sources).to eq [@new_source, @existing_source]
+ end
+
+ it "retains the same instance of the new source" do
+ expect(source_list.path_sources[0]).to be @new_source
+ end
+
+ it "replaces the instance of the existing source" do
+ expect(source_list.path_sources[1]).to be existing_locked_source
+ end
+ end
+
+ describe "#cached!" do
+ let(:rubygems_source) { source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) }
+ let(:git_source) { source_list.add_git_source("uri" => "git://host/path.git") }
+ let(:path_source) { source_list.add_path_source("path" => "/path/to/gem") }
+
+ it "calls #cached! on all the sources" do
+ expect(rubygems_source).to receive(:cached!)
+ expect(git_source).to receive(:cached!)
+ expect(path_source).to receive(:cached!)
+ source_list.cached!
+ end
+ end
+
+ describe "#remote!" do
+ let(:rubygems_source) { source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) }
+ let(:git_source) { source_list.add_git_source("uri" => "git://host/path.git") }
+ let(:path_source) { source_list.add_path_source("path" => "/path/to/gem") }
+
+ it "calls #remote! on all the sources" do
+ expect(rubygems_source).to receive(:remote!)
+ expect(git_source).to receive(:remote!)
+ expect(path_source).to receive(:remote!)
+ source_list.remote!
+ end
+ end
+end
diff --git a/spec/bundler/bundler/source_spec.rb b/spec/bundler/bundler/source_spec.rb
new file mode 100644
index 0000000000..9ef8e7e50f
--- /dev/null
+++ b/spec/bundler/bundler/source_spec.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::Source do
+ class ExampleSource < Bundler::Source
+ end
+
+ subject { ExampleSource.new }
+
+ describe "#unmet_deps" do
+ let(:specs) { double(:specs) }
+ let(:unmet_dependency_names) { double(:unmet_dependency_names) }
+
+ before do
+ allow(subject).to receive(:specs).and_return(specs)
+ allow(specs).to receive(:unmet_dependency_names).and_return(unmet_dependency_names)
+ end
+
+ it "should return the names of unmet dependencies" do
+ expect(subject.unmet_deps).to eq(unmet_dependency_names)
+ end
+ end
+
+ describe "#version_message" do
+ let(:spec) { double(:spec, :name => "nokogiri", :version => ">= 1.6", :platform => rb) }
+
+ shared_examples_for "the lockfile specs are not relevant" do
+ it "should return a string with the spec name and version" do
+ expect(subject.version_message(spec)).to eq("nokogiri >= 1.6")
+ end
+ end
+
+ context "when there are locked gems" do
+ let(:locked_gems) { double(:locked_gems) }
+
+ before { allow(Bundler).to receive(:locked_gems).and_return(locked_gems) }
+
+ context "that contain the relevant gem spec" do
+ before do
+ specs = double(:specs)
+ allow(locked_gems).to receive(:specs).and_return(specs)
+ allow(specs).to receive(:find).and_return(locked_gem)
+ end
+
+ context "without a version" do
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => nil) }
+
+ it_behaves_like "the lockfile specs are not relevant"
+ end
+
+ context "with the same version" do
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => ">= 1.6") }
+
+ it_behaves_like "the lockfile specs are not relevant"
+ end
+
+ context "with a different version" do
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "< 1.5") }
+
+ context "with color" do
+ before { Bundler.ui = Bundler::UI::Shell.new }
+
+ it "should return a string with the spec name and version and locked spec version" do
+ expect(subject.version_message(spec)).to eq("nokogiri >= 1.6\e[32m (was < 1.5)\e[0m")
+ end
+ end
+
+ context "without color" do
+ it "should return a string with the spec name and version and locked spec version" do
+ expect(subject.version_message(spec)).to eq("nokogiri >= 1.6 (was < 1.5)")
+ end
+ end
+ end
+
+ context "with a more recent version" do
+ let(:spec) { double(:spec, :name => "nokogiri", :version => "1.6.1", :platform => rb) }
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") }
+
+ context "with color" do
+ before { Bundler.ui = Bundler::UI::Shell.new }
+
+ it "should return a string with the locked spec version in yellow" do
+ expect(subject.version_message(spec)).to eq("nokogiri 1.6.1\e[33m (was 1.7.0)\e[0m")
+ end
+ end
+ end
+
+ context "with an older version" do
+ let(:spec) { double(:spec, :name => "nokogiri", :version => "1.7.1", :platform => rb) }
+ let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") }
+
+ context "with color" do
+ before { Bundler.ui = Bundler::UI::Shell.new }
+
+ it "should return a string with the locked spec version in green" do
+ expect(subject.version_message(spec)).to eq("nokogiri 1.7.1\e[32m (was 1.7.0)\e[0m")
+ end
+ end
+ end
+ end
+
+ context "that do not contain the relevant gem spec" do
+ before do
+ specs = double(:specs)
+ allow(locked_gems).to receive(:specs).and_return(specs)
+ allow(specs).to receive(:find).and_return(nil)
+ end
+
+ it_behaves_like "the lockfile specs are not relevant"
+ end
+ end
+
+ context "when there are no locked gems" do
+ before { allow(Bundler).to receive(:locked_gems).and_return(nil) }
+
+ it_behaves_like "the lockfile specs are not relevant"
+ end
+ end
+
+ describe "#can_lock?" do
+ context "when the passed spec's source is equivalent" do
+ let(:spec) { double(:spec, :source => subject) }
+
+ it "should return true" do
+ expect(subject.can_lock?(spec)).to be_truthy
+ end
+ end
+
+ context "when the passed spec's source is not equivalent" do
+ let(:spec) { double(:spec, :source => double(:other_source)) }
+
+ it "should return false" do
+ expect(subject.can_lock?(spec)).to be_falsey
+ end
+ end
+ end
+
+ describe "#include?" do
+ context "when the passed source is equivalent" do
+ let(:source) { subject }
+
+ it "should return true" do
+ expect(subject).to include(source)
+ end
+ end
+
+ context "when the passed source is not equivalent" do
+ let(:source) { double(:source) }
+
+ it "should return false" do
+ expect(subject).to_not include(source)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/spec_set_spec.rb b/spec/bundler/bundler/spec_set_spec.rb
new file mode 100644
index 0000000000..6fedd38b50
--- /dev/null
+++ b/spec/bundler/bundler/spec_set_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::SpecSet do
+ let(:specs) do
+ [
+ build_spec("a", "1.0"),
+ build_spec("b", "1.0"),
+ build_spec("c", "1.1") do |s|
+ s.dep "a", "< 2.0"
+ s.dep "e", "> 0"
+ end,
+ build_spec("d", "2.0") do |s|
+ s.dep "a", "1.0"
+ s.dep "c", "~> 1.0"
+ end,
+ build_spec("e", "1.0.0.pre.1"),
+ ].flatten
+ end
+
+ subject { described_class.new(specs) }
+
+ context "enumerable methods" do
+ it "has a length" do
+ expect(subject.length).to eq(5)
+ end
+
+ it "has a size" do
+ expect(subject.size).to eq(5)
+ end
+ end
+
+ describe "#find_by_name_and_platform" do
+ let(:platform) { Gem::Platform.new("universal-darwin-64") }
+ let(:platform_spec) { build_spec("b", "2.0", platform).first }
+ let(:specs) do
+ [
+ build_spec("a", "1.0"),
+ platform_spec,
+ ].flatten
+ end
+
+ it "finds spec with given name and platform" do
+ spec = described_class.new(specs).find_by_name_and_platform("b", platform)
+ expect(spec).to eq platform_spec
+ end
+ end
+
+ describe "#merge" do
+ let(:other_specs) do
+ [
+ build_spec("f", "1.0"),
+ build_spec("g", "2.0"),
+ ].flatten
+ end
+
+ let(:other_spec_set) { described_class.new(other_specs) }
+
+ it "merges the items in each gemspec" do
+ new_spec_set = subject.merge(other_spec_set)
+ specs = new_spec_set.to_a.map(&:full_name)
+ expect(specs).to include("a-1.0")
+ expect(specs).to include("f-1.0")
+ end
+ end
+
+ describe "#to_a" do
+ it "returns the specs in order" do
+ expect(subject.to_a.map(&:full_name)).to eq %w[
+ a-1.0
+ b-1.0
+ e-1.0.0.pre.1
+ c-1.1
+ d-2.0
+ ]
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ssl_certs/certificate_manager_spec.rb b/spec/bundler/bundler/ssl_certs/certificate_manager_spec.rb
new file mode 100644
index 0000000000..4250bfc497
--- /dev/null
+++ b/spec/bundler/bundler/ssl_certs/certificate_manager_spec.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+require "bundler/ssl_certs/certificate_manager"
+
+RSpec.describe Bundler::SSLCerts::CertificateManager do
+ let(:rubygems_path) { root }
+ let(:stub_cert) { File.join(root.to_s, "lib", "rubygems", "ssl_certs", "rubygems.org", "ssl-cert.pem") }
+ let(:rubygems_certs_dir) { File.join(root.to_s, "lib", "rubygems", "ssl_certs", "rubygems.org") }
+
+ subject { described_class.new(rubygems_path) }
+
+ # Pretend bundler root is rubygems root
+ before do
+ # Backing up rubygems ceriticates
+ FileUtils.mv(rubygems_certs_dir, rubygems_certs_dir + ".back") if ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"]
+
+ FileUtils.mkdir_p(rubygems_certs_dir)
+ FileUtils.touch(stub_cert)
+ end
+
+ after do
+ FileUtils.rm_rf(rubygems_certs_dir)
+
+ # Restore rubygems certificates
+ FileUtils.mv(rubygems_certs_dir + ".back", rubygems_certs_dir) if ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"]
+ end
+
+ describe "#update_from" do
+ let(:cert_manager) { double(:cert_manager) }
+
+ before { allow(described_class).to receive(:new).with(rubygems_path).and_return(cert_manager) }
+
+ it "should update the certs through a new certificate manager" do
+ allow(cert_manager).to receive(:update!)
+ expect(described_class.update_from!(rubygems_path)).to be_nil
+ end
+ end
+
+ describe "#initialize" do
+ it "should set bundler_cert_path as path of the subdir with bundler ssl certs" do
+ expect(subject.bundler_cert_path).to eq(File.join(root, "lib/bundler/ssl_certs"))
+ end
+
+ it "should set bundler_certs as the paths of the bundler ssl certs" do
+ expect(subject.bundler_certs).to include(File.join(root, "lib/bundler/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem"))
+ expect(subject.bundler_certs).to include(File.join(root, "lib/bundler/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem"))
+ end
+
+ context "when rubygems_path is not nil" do
+ it "should set rubygems_certs" do
+ expect(subject.rubygems_certs).to include(File.join(root, "lib", "rubygems", "ssl_certs", "rubygems.org", "ssl-cert.pem"))
+ end
+ end
+ end
+
+ describe "#up_to_date?" do
+ context "when bundler certs and rubygems certs are the same" do
+ before do
+ bundler_certs = Dir[File.join(root.to_s, "lib", "bundler", "ssl_certs", "**", "*.pem")]
+ FileUtils.rm(stub_cert)
+ FileUtils.cp(bundler_certs, rubygems_certs_dir)
+ end
+
+ it "should return true" do
+ expect(subject).to be_up_to_date
+ end
+ end
+
+ context "when bundler certs and rubygems certs are not the same" do
+ it "should return false" do
+ expect(subject).to_not be_up_to_date
+ end
+ end
+ end
+
+ describe "#update!" do
+ context "when certificate manager is not up to date" do
+ before do
+ allow(subject).to receive(:up_to_date?).and_return(false)
+ allow(bundler_fileutils).to receive(:rm)
+ allow(bundler_fileutils).to receive(:cp)
+ end
+
+ it "should remove the current bundler certs" do
+ expect(bundler_fileutils).to receive(:rm).with(subject.bundler_certs)
+ subject.update!
+ end
+
+ it "should copy the rubygems certs into bundler certs" do
+ expect(bundler_fileutils).to receive(:cp).with(subject.rubygems_certs, subject.bundler_cert_path)
+ subject.update!
+ end
+
+ it "should return nil" do
+ expect(subject.update!).to be_nil
+ end
+ end
+
+ context "when certificate manager is up to date" do
+ before { allow(subject).to receive(:up_to_date?).and_return(true) }
+
+ it "should return nil" do
+ expect(subject.update!).to be_nil
+ end
+ end
+ end
+
+ describe "#connect_to" do
+ let(:host) { "http://www.host.com" }
+ let(:http) { Net::HTTP.new(host, 443) }
+ let(:cert_store) { OpenSSL::X509::Store.new }
+ let(:http_header_response) { double(:http_header_response) }
+
+ before do
+ allow(Net::HTTP).to receive(:new).with(host, 443).and_return(http)
+ allow(OpenSSL::X509::Store).to receive(:new).and_return(cert_store)
+ allow(http).to receive(:head).with("/").and_return(http_header_response)
+ end
+
+ it "should use ssl for the http request" do
+ expect(http).to receive(:use_ssl=).with(true)
+ subject.connect_to(host)
+ end
+
+ it "use verify peer mode" do
+ expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
+ subject.connect_to(host)
+ end
+
+ it "set its cert store as a OpenSSL::X509::Store populated with bundler certs" do
+ expect(cert_store).to receive(:add_file).at_least(:once)
+ expect(http).to receive(:cert_store=).with(cert_store)
+ subject.connect_to(host)
+ end
+
+ it "return the headers of the request response" do
+ expect(subject.connect_to(host)).to eq(http_header_response)
+ end
+ end
+end
diff --git a/spec/bundler/bundler/stub_specification_spec.rb b/spec/bundler/bundler/stub_specification_spec.rb
new file mode 100644
index 0000000000..5521d83769
--- /dev/null
+++ b/spec/bundler/bundler/stub_specification_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::StubSpecification do
+ let(:gemspec) do
+ Gem::Specification.new do |s|
+ s.name = "gemname"
+ s.version = "1.0.0"
+ s.loaded_from = __FILE__
+ end
+ end
+
+ let(:with_bundler_stub_spec) do
+ described_class.from_stub(gemspec)
+ end
+
+ if Bundler.rubygems.provides?(">= 2.1")
+ describe "#from_stub" do
+ it "returns the same stub if already a Bundler::StubSpecification" do
+ stub = described_class.from_stub(with_bundler_stub_spec)
+ expect(stub).to be(with_bundler_stub_spec)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ui/shell_spec.rb b/spec/bundler/bundler/ui/shell_spec.rb
new file mode 100644
index 0000000000..951a446aff
--- /dev/null
+++ b/spec/bundler/bundler/ui/shell_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::UI::Shell do
+ subject { described_class.new }
+
+ before { subject.level = "debug" }
+
+ describe "#info" do
+ before { subject.level = "info" }
+ it "prints to stdout" do
+ expect { subject.info("info") }.to output("info\n").to_stdout
+ end
+ end
+
+ describe "#confirm" do
+ before { subject.level = "confirm" }
+ it "prints to stdout" do
+ expect { subject.confirm("confirm") }.to output("confirm\n").to_stdout
+ end
+ end
+
+ describe "#warn" do
+ before { subject.level = "warn" }
+ it "prints to stdout", :bundler => "< 2" do
+ expect { subject.warn("warning") }.to output("warning\n").to_stdout
+ end
+
+ it "prints to stderr", :bundler => "2" do
+ expect { subject.warn("warning") }.to output("warning\n").to_stderr
+ end
+
+ context "when stderr flag is enabled" do
+ before { Bundler.settings.temporary(:error_on_stderr => true) }
+ it "prints to stderr" do
+ expect { subject.warn("warning!") }.to output("warning!\n").to_stderr
+ end
+ end
+ end
+
+ describe "#debug" do
+ it "prints to stdout" do
+ expect { subject.debug("debug") }.to output("debug\n").to_stdout
+ end
+ end
+
+ describe "#error" do
+ before { subject.level = "error" }
+
+ it "prints to stdout", :bundler => "< 2" do
+ expect { subject.error("error!!!") }.to output("error!!!\n").to_stdout
+ end
+
+ it "prints to stderr", :bundler => "2" do
+ expect { subject.error("error!!!") }.to output("error!!!\n").to_stderr
+ end
+
+ context "when stderr flag is enabled" do
+ before { Bundler.settings.temporary(:error_on_stderr => true) }
+ it "prints to stderr" do
+ expect { subject.error("error!!!") }.to output("error!!!\n").to_stderr
+ end
+
+ context "when stderr is closed" do
+ it "doesn't report anything" do
+ output = capture(:stderr, :closed => true) do
+ subject.error("Something went wrong")
+ end
+ expect(output).to_not eq("Something went wrong\n")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/ui_spec.rb b/spec/bundler/bundler/ui_spec.rb
new file mode 100644
index 0000000000..6ef8729277
--- /dev/null
+++ b/spec/bundler/bundler/ui_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::UI do
+ describe Bundler::UI::Silent do
+ it "has the same instance methods as Shell", :ruby => ">= 1.9" do
+ shell = Bundler::UI::Shell
+ methods = proc do |cls|
+ cls.instance_methods.map do |i|
+ m = shell.instance_method(i)
+ [i, m.parameters]
+ end.sort_by(&:first)
+ end
+ expect(methods.call(described_class)).to eq(methods.call(shell))
+ end
+
+ it "has the same instance class as Shell", :ruby => ">= 1.9" do
+ shell = Bundler::UI::Shell
+ methods = proc do |cls|
+ cls.methods.map do |i|
+ m = shell.method(i)
+ [i, m.parameters]
+ end.sort_by(&:first)
+ end
+ expect(methods.call(described_class)).to eq(methods.call(shell))
+ end
+ end
+
+ describe Bundler::UI::Shell do
+ let(:options) { {} }
+ subject { described_class.new(options) }
+ describe "debug?" do
+ it "returns a boolean" do
+ subject.level = :debug
+ expect(subject.debug?).to eq(true)
+
+ subject.level = :error
+ expect(subject.debug?).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/uri_credentials_filter_spec.rb b/spec/bundler/bundler/uri_credentials_filter_spec.rb
new file mode 100644
index 0000000000..fe52d16306
--- /dev/null
+++ b/spec/bundler/bundler/uri_credentials_filter_spec.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+RSpec.describe Bundler::URICredentialsFilter do
+ subject { described_class }
+
+ describe "#credential_filtered_uri" do
+ shared_examples_for "original type of uri is maintained" do
+ it "maintains same type for return value as uri input type" do
+ expect(subject.credential_filtered_uri(uri)).to be_kind_of(uri.class)
+ end
+ end
+
+ shared_examples_for "sensitive credentials in uri are filtered out" do
+ context "authentication using oauth credentials" do
+ context "specified via 'x-oauth-basic'" do
+ let(:credentials) { "oauth_token:x-oauth-basic@" }
+
+ it "returns the uri without the oauth token" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(URI("https://x-oauth-basic@github.com/company/private-repo").to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+
+ context "specified via 'x'" do
+ let(:credentials) { "oauth_token:x@" }
+
+ it "returns the uri without the oauth token" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(URI("https://x@github.com/company/private-repo").to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+ end
+
+ context "authentication using login credentials" do
+ let(:credentials) { "username1:hunter3@" }
+
+ it "returns the uri without the password" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(URI("https://username1@github.com/company/private-repo").to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+
+ context "authentication without credentials" do
+ let(:credentials) { "" }
+
+ it "returns the same uri" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(uri.to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+ end
+
+ context "uri is a uri object" do
+ let(:uri) { URI("https://#{credentials}github.com/company/private-repo") }
+
+ it_behaves_like "sensitive credentials in uri are filtered out"
+ end
+
+ context "uri is a uri string" do
+ let(:uri) { "https://#{credentials}github.com/company/private-repo" }
+
+ it_behaves_like "sensitive credentials in uri are filtered out"
+ end
+
+ context "uri is a non-uri format string (ex. path)" do
+ let(:uri) { "/path/to/repo" }
+
+ it "returns the same uri" do
+ expect(subject.credential_filtered_uri(uri).to_s).to eq(uri.to_s)
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+
+ context "uri is nil" do
+ let(:uri) { nil }
+
+ it "returns nil" do
+ expect(subject.credential_filtered_uri(uri)).to be_nil
+ end
+
+ it_behaves_like "original type of uri is maintained"
+ end
+ end
+
+ describe "#credential_filtered_string" do
+ let(:str_to_filter) { "This is a git message containing a uri #{uri}!" }
+ let(:credentials) { "" }
+ let(:uri) { URI("https://#{credentials}github.com/company/private-repo") }
+
+ context "with a uri that contains credentials" do
+ let(:credentials) { "oauth_token:x-oauth-basic@" }
+
+ it "returns the string without the sensitive credentials" do
+ expect(subject.credential_filtered_string(str_to_filter, uri)).to eq(
+ "This is a git message containing a uri https://x-oauth-basic@github.com/company/private-repo!"
+ )
+ end
+ end
+
+ context "that does not contains credentials" do
+ it "returns the same string" do
+ expect(subject.credential_filtered_string(str_to_filter, uri)).to eq(str_to_filter)
+ end
+ end
+
+ context "string to filter is nil" do
+ let(:str_to_filter) { nil }
+
+ it "returns nil" do
+ expect(subject.credential_filtered_string(str_to_filter, uri)).to be_nil
+ end
+ end
+
+ context "uri to filter out is nil" do
+ let(:uri) { nil }
+
+ it "returns the same string" do
+ expect(subject.credential_filtered_string(str_to_filter, uri)).to eq(str_to_filter)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/vendored_persistent_spec.rb b/spec/bundler/bundler/vendored_persistent_spec.rb
new file mode 100644
index 0000000000..338431c4a6
--- /dev/null
+++ b/spec/bundler/bundler/vendored_persistent_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+require "bundler/vendored_persistent"
+
+RSpec.describe Bundler::PersistentHTTP do
+ describe "#warn_old_tls_version_rubygems_connection" do
+ let(:uri) { "https://index.rubygems.org" }
+ let(:connection) { instance_double(subject.http_class) }
+ let(:tls_version) { "TLSv1.2" }
+ let(:socket) { double("Socket") }
+ let(:socket_io) { double("SocketIO") }
+
+ before do
+ allow(connection).to receive(:use_ssl?).and_return(!tls_version.nil?)
+ allow(socket).to receive(:io).and_return(socket_io)
+ connection.instance_variable_set(:@socket, socket)
+
+ if tls_version
+ allow(socket_io).to receive(:ssl_version).and_return(tls_version)
+ end
+ end
+
+ shared_examples_for "does not warn" do
+ it "does not warn" do
+ allow(Bundler.ui).to receive(:warn).never
+ subject.warn_old_tls_version_rubygems_connection(URI(uri), connection)
+ end
+ end
+
+ shared_examples_for "does warn" do |*expected|
+ it "warns" do
+ expect(Bundler.ui).to receive(:warn).with(*expected)
+ subject.warn_old_tls_version_rubygems_connection(URI(uri), connection)
+ end
+ end
+
+ context "an HTTPS uri with TLSv1.2" do
+ include_examples "does not warn"
+ end
+
+ context "without SSL" do
+ let(:tls_version) { nil }
+
+ include_examples "does not warn"
+ end
+
+ context "without a socket" do
+ let(:socket) { nil }
+
+ include_examples "does not warn"
+ end
+
+ context "with a different TLD" do
+ let(:uri) { "https://foo.bar" }
+ include_examples "does not warn"
+
+ context "and an outdated TLS version" do
+ let(:tls_version) { "TLSv1" }
+ include_examples "does not warn"
+ end
+ end
+
+ context "with a nonsense TLS version" do
+ let(:tls_version) { "BlahBlah2.0Blah" }
+ include_examples "does not warn"
+ end
+
+ context "with an outdated TLS version" do
+ let(:tls_version) { "TLSv1" }
+ include_examples "does warn",
+ "Warning: Your Ruby version is compiled against a copy of OpenSSL that is very old. " \
+ "Starting in January 2018, RubyGems.org will refuse connection requests from these very old versions of OpenSSL. " \
+ "If you will need to continue installing gems after January 2018, please follow this guide to upgrade: http://ruby.to/tls-outdated.",
+ :wrap => true
+ end
+ end
+end
diff --git a/spec/bundler/bundler/version_ranges_spec.rb b/spec/bundler/bundler/version_ranges_spec.rb
new file mode 100644
index 0000000000..ccbb9285d5
--- /dev/null
+++ b/spec/bundler/bundler/version_ranges_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "bundler/version_ranges"
+
+RSpec.describe Bundler::VersionRanges do
+ describe ".empty?" do
+ shared_examples_for "empty?" do |exp, *req|
+ it "returns #{exp} for #{req}" do
+ r = Gem::Requirement.new(*req)
+ ranges = described_class.for(r)
+ expect(described_class.empty?(*ranges)).to eq(exp), "expected `#{r}` #{exp ? "" : "not "}to be empty"
+ end
+ end
+
+ include_examples "empty?", false
+ include_examples "empty?", false, "!= 1"
+ include_examples "empty?", false, "!= 1", "= 2"
+ include_examples "empty?", false, "!= 1", "> 1"
+ include_examples "empty?", false, "!= 1", ">= 1"
+ include_examples "empty?", false, "= 1", ">= 0.1", "<= 1.1"
+ include_examples "empty?", false, "= 1", ">= 1", "<= 1"
+ include_examples "empty?", false, "= 1", "~> 1"
+ include_examples "empty?", false, ">= 0.z", "= 0"
+ include_examples "empty?", false, ">= 0"
+ include_examples "empty?", false, ">= 1.0.0", "< 2.0.0"
+ include_examples "empty?", false, "~> 1"
+ include_examples "empty?", false, "~> 2.0", "~> 2.1"
+ include_examples "empty?", true, "!= 1", "< 2", "> 2"
+ include_examples "empty?", true, "!= 1", "<= 1", ">= 1"
+ include_examples "empty?", true, "< 2", "> 2"
+ include_examples "empty?", true, "= 1", "!= 1"
+ include_examples "empty?", true, "= 1", "= 2"
+ include_examples "empty?", true, "= 1", "~> 2"
+ include_examples "empty?", true, ">= 0", "<= 0.a"
+ include_examples "empty?", true, "~> 2.0", "~> 3"
+ end
+end
diff --git a/spec/bundler/bundler/worker_spec.rb b/spec/bundler/bundler/worker_spec.rb
new file mode 100644
index 0000000000..2e5642709d
--- /dev/null
+++ b/spec/bundler/bundler/worker_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "bundler/worker"
+
+RSpec.describe Bundler::Worker do
+ let(:size) { 5 }
+ let(:name) { "Spec Worker" }
+ let(:function) { proc {|object, worker_number| [object, worker_number] } }
+ subject { described_class.new(size, name, function) }
+
+ after { subject.stop }
+
+ describe "#initialize" do
+ context "when Thread.start raises ThreadError" do
+ it "raises when no threads can be created" do
+ allow(Thread).to receive(:start).and_raise(ThreadError, "error creating thread")
+
+ expect { subject.enq "a" }.to raise_error(Bundler::ThreadCreationError, "Failed to create threads for the Spec Worker worker: error creating thread")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/bundler/yaml_serializer_spec.rb b/spec/bundler/bundler/yaml_serializer_spec.rb
new file mode 100644
index 0000000000..1241c74bbf
--- /dev/null
+++ b/spec/bundler/bundler/yaml_serializer_spec.rb
@@ -0,0 +1,194 @@
+# frozen_string_literal: true
+
+require "bundler/yaml_serializer"
+
+RSpec.describe Bundler::YAMLSerializer do
+ subject(:serializer) { Bundler::YAMLSerializer }
+
+ describe "#dump" do
+ it "works for simple hash" do
+ hash = { "Q" => "Where does Thursday come before Wednesday? In the dictionary. :P" }
+
+ expected = strip_whitespace <<-YAML
+ ---
+ Q: "Where does Thursday come before Wednesday? In the dictionary. :P"
+ YAML
+
+ expect(serializer.dump(hash)).to eq(expected)
+ end
+
+ it "handles nested hash" do
+ hash = {
+ "nice-one" => {
+ "read_ahead" => "All generalizations are false, including this one",
+ },
+ }
+
+ expected = strip_whitespace <<-YAML
+ ---
+ nice-one:
+ read_ahead: "All generalizations are false, including this one"
+ YAML
+
+ expect(serializer.dump(hash)).to eq(expected)
+ end
+
+ it "array inside an hash" do
+ hash = {
+ "nested_hash" => {
+ "contains_array" => [
+ "Jack and Jill went up the hill",
+ "To fetch a pail of water.",
+ "Jack fell down and broke his crown,",
+ "And Jill came tumbling after.",
+ ],
+ },
+ }
+
+ expected = strip_whitespace <<-YAML
+ ---
+ nested_hash:
+ contains_array:
+ - "Jack and Jill went up the hill"
+ - "To fetch a pail of water."
+ - "Jack fell down and broke his crown,"
+ - "And Jill came tumbling after."
+ YAML
+
+ expect(serializer.dump(hash)).to eq(expected)
+ end
+ end
+
+ describe "#load" do
+ it "works for simple hash" do
+ yaml = strip_whitespace <<-YAML
+ ---
+ Jon: "Air is free dude!"
+ Jack: "Yes.. until you buy a bag of chips!"
+ YAML
+
+ hash = {
+ "Jon" => "Air is free dude!",
+ "Jack" => "Yes.. until you buy a bag of chips!",
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
+
+ it "works for nested hash" do
+ yaml = strip_whitespace <<-YAML
+ ---
+ baa:
+ baa: "black sheep"
+ have: "you any wool?"
+ yes: "merry have I"
+ three: "bags full"
+ YAML
+
+ hash = {
+ "baa" => {
+ "baa" => "black sheep",
+ "have" => "you any wool?",
+ "yes" => "merry have I",
+ },
+ "three" => "bags full",
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
+
+ it "handles colon in key/value" do
+ yaml = strip_whitespace <<-YAML
+ BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/: http://rubygems-mirror.org
+ YAML
+
+ expect(serializer.load(yaml)).to eq("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://rubygems-mirror.org")
+ end
+
+ it "handles arrays inside hashes" do
+ yaml = strip_whitespace <<-YAML
+ ---
+ nested_hash:
+ contains_array:
+ - "Why shouldn't you write with a broken pencil?"
+ - "Because it's pointless!"
+ YAML
+
+ hash = {
+ "nested_hash" => {
+ "contains_array" => [
+ "Why shouldn't you write with a broken pencil?",
+ "Because it's pointless!",
+ ],
+ },
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
+
+ it "handles windows-style CRLF line endings" do
+ yaml = strip_whitespace(<<-YAML).gsub("\n", "\r\n")
+ ---
+ nested_hash:
+ contains_array:
+ - "Why shouldn't you write with a broken pencil?"
+ - "Because it's pointless!"
+ - oh so silly
+ YAML
+
+ hash = {
+ "nested_hash" => {
+ "contains_array" => [
+ "Why shouldn't you write with a broken pencil?",
+ "Because it's pointless!",
+ "oh so silly",
+ ],
+ },
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
+ end
+
+ describe "against yaml lib" do
+ let(:hash) do
+ {
+ "a_joke" => {
+ "my-stand" => "I can totally keep secrets",
+ "but" => "The people I tell them to can't :P",
+ "wouldn't it be funny if this string were empty?" => "",
+ },
+ "more" => {
+ "first" => [
+ "Can a kangaroo jump higher than a house?",
+ "Of course, a house doesn't jump at all.",
+ ],
+ "second" => [
+ "What did the sea say to the sand?",
+ "Nothing, it simply waved.",
+ ],
+ "array with empty string" => [""],
+ },
+ "sales" => {
+ "item" => "A Parachute",
+ "description" => "Only used once, never opened.",
+ },
+ "one-more" => "I'd tell you a chemistry joke but I know I wouldn't get a reaction.",
+ }
+ end
+
+ context "#load" do
+ it "retrieves the original hash" do
+ require "yaml"
+ expect(serializer.load(YAML.dump(hash))).to eq(hash)
+ end
+ end
+
+ context "#dump" do
+ it "retrieves the original hash" do
+ require "yaml"
+ expect(YAML.load(serializer.dump(hash))).to eq(hash)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/cache/cache_path_spec.rb b/spec/bundler/cache/cache_path_spec.rb
new file mode 100644
index 0000000000..69d3809964
--- /dev/null
+++ b/spec/bundler/cache/cache_path_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle package" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ context "with --cache-path" do
+ it "caches gems at given path" do
+ bundle :package, "cache-path" => "vendor/cache-foo"
+ expect(bundled_app("vendor/cache-foo/rack-1.0.0.gem")).to exist
+ end
+ end
+
+ context "with config cache_path" do
+ it "caches gems at given path" do
+ bundle "config cache_path vendor/cache-foo"
+ bundle :package
+ expect(bundled_app("vendor/cache-foo/rack-1.0.0.gem")).to exist
+ end
+ end
+
+ context "with absolute --cache-path" do
+ it "caches gems at given path" do
+ bundle :package, "cache-path" => "/tmp/cache-foo"
+ expect(bundled_app("/tmp/cache-foo/rack-1.0.0.gem")).to exist
+ end
+ end
+end
diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb
new file mode 100644
index 0000000000..4a0b953830
--- /dev/null
+++ b/spec/bundler/cache/gems_spec.rb
@@ -0,0 +1,304 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle cache" do
+ shared_examples_for "when there are only gemsources" do
+ before :each do
+ gemfile <<-G
+ gem 'rack'
+ G
+
+ system_gems "rack-1.0.0", :path => :bundle_path
+ bundle! :cache
+ end
+
+ it "copies the .gem file to vendor/cache" do
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "uses the cache as a source when installing gems" do
+ build_gem "omg", :path => bundled_app("vendor/cache")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "omg"
+ G
+
+ expect(the_bundle).to include_gems "omg 1.0.0"
+ end
+
+ it "uses the cache as a source when installing gems with --local" do
+ system_gems [], :path => :bundle_path
+ bundle "install --local"
+
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
+ it "does not reinstall gems from the cache if they exist on the system" do
+ build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
+ it "does not reinstall gems from the cache if they exist in the bundle" do
+ system_gems "rack-1.0.0", :path => :bundle_path
+
+ gemfile <<-G
+ gem "rack"
+ G
+
+ build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ bundle! :install, :local => true
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
+ it "creates a lockfile" do
+ cache_gems "rack-1.0.0"
+
+ gemfile <<-G
+ gem "rack"
+ G
+
+ bundle "cache"
+
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+ end
+
+ context "using system gems" do
+ before { bundle! "config path.system true" }
+ it_behaves_like "when there are only gemsources"
+ end
+
+ context "installing into a local path" do
+ before { bundle! "config path ./.bundle" }
+ it_behaves_like "when there are only gemsources"
+ end
+
+ describe "when there is a built-in gem", :ruby => "2.0" do
+ before :each do
+ build_repo2 do
+ build_gem "builtin_gem", "1.0.2"
+ end
+
+ build_gem "builtin_gem", "1.0.2", :to_system => true do |s|
+ s.summary = "This builtin_gem is bundled with Ruby"
+ end
+
+ FileUtils.rm("#{system_gem_path}/cache/builtin_gem-1.0.2.gem")
+ end
+
+ it "uses builtin gems when installing to system gems" do
+ bundle! "config path.system true"
+ install_gemfile %(gem 'builtin_gem', '1.0.2')
+ expect(the_bundle).to include_gems("builtin_gem 1.0.2")
+ end
+
+ it "caches remote and builtin gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'builtin_gem', '1.0.2'
+ gem 'rack', '1.0.0'
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/builtin_gem-1.0.2.gem")).to exist
+ end
+
+ it "doesn't make remote request after caching the gem" do
+ build_gem "builtin_gem_2", "1.0.2", :path => bundled_app("vendor/cache") do |s|
+ s.summary = "This builtin_gem is bundled with Ruby"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'builtin_gem_2', '1.0.2'
+ G
+
+ bundle "install --local"
+ expect(the_bundle).to include_gems("builtin_gem_2 1.0.2")
+ end
+
+ it "errors if the builtin gem isn't available to cache" do
+ bundle! "config path.system true"
+
+ install_gemfile <<-G
+ gem 'builtin_gem', '1.0.2'
+ G
+
+ bundle :cache
+ expect(exitstatus).to_not eq(0) if exitstatus
+ expect(out).to include("builtin_gem-1.0.2 is built in to Ruby, and can't be cached")
+ end
+ end
+
+ describe "when there are also git sources" do
+ before do
+ build_git "foo"
+ system_gems "rack-1.0.0"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ gem 'rack'
+ G
+ end
+
+ it "still works" do
+ bundle :cache
+
+ system_gems []
+ bundle "install --local"
+
+ expect(the_bundle).to include_gems("rack 1.0.0", "foo 1.0")
+ end
+
+ it "should not explode if the lockfile is not present" do
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ bundle :cache
+
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+ end
+
+ describe "when previously cached" do
+ before :each do
+ build_repo2
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ gem "actionpack"
+ G
+ bundle :cache
+ expect(cached_gem("rack-1.0.0")).to exist
+ expect(cached_gem("actionpack-2.3.2")).to exist
+ expect(cached_gem("activesupport-2.3.2")).to exist
+ end
+
+ it "re-caches during install" do
+ cached_gem("rack-1.0.0").rmtree
+ bundle :install
+ expect(out).to include("Updating files in vendor/cache")
+ expect(cached_gem("rack-1.0.0")).to exist
+ end
+
+ it "adds and removes when gems are updated" do
+ update_repo2
+ bundle "update", :all => bundle_update_requires_all?
+ expect(cached_gem("rack-1.2")).to exist
+ expect(cached_gem("rack-1.0.0")).not_to exist
+ end
+
+ it "adds new gems and dependencies" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rails"
+ G
+ expect(cached_gem("rails-2.3.2")).to exist
+ expect(cached_gem("activerecord-2.3.2")).to exist
+ end
+
+ it "removes .gems for removed gems and dependencies" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ G
+ expect(cached_gem("rack-1.0.0")).to exist
+ expect(cached_gem("actionpack-2.3.2")).not_to exist
+ expect(cached_gem("activesupport-2.3.2")).not_to exist
+ end
+
+ it "removes .gems when gem changes to git source" do
+ build_git "rack"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack", :git => "#{lib_path("rack-1.0")}"
+ gem "actionpack"
+ G
+ expect(cached_gem("rack-1.0.0")).not_to exist
+ expect(cached_gem("actionpack-2.3.2")).to exist
+ expect(cached_gem("activesupport-2.3.2")).to exist
+ end
+
+ it "doesn't remove gems that are for another platform" do
+ simulate_platform "java" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "platform_specific"
+ G
+
+ bundle :cache
+ expect(cached_gem("platform_specific-1.0-java")).to exist
+ end
+
+ simulate_new_machine
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "platform_specific"
+ G
+
+ expect(cached_gem("platform_specific-1.0-#{Bundler.local_platform}")).to exist
+ expect(cached_gem("platform_specific-1.0-java")).to exist
+ end
+
+ it "doesn't remove gems with mismatched :rubygems_version or :date" do
+ cached_gem("rack-1.0.0").rmtree
+ build_gem "rack", "1.0.0",
+ :path => bundled_app("vendor/cache"),
+ :rubygems_version => "1.3.2"
+ simulate_new_machine
+
+ bundle :install
+ expect(cached_gem("rack-1.0.0")).to exist
+ end
+
+ it "handles directories and non .gem files in the cache" do
+ bundled_app("vendor/cache/foo").mkdir
+ File.open(bundled_app("vendor/cache/bar"), "w") {|f| f.write("not a gem") }
+ bundle :cache
+ end
+
+ it "does not say that it is removing gems when it isn't actually doing so" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ bundle "cache"
+ bundle "install"
+ expect(out).not_to match(/removing/i)
+ end
+
+ it "does not warn about all if it doesn't have any git/path dependency" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ bundle "cache"
+ expect(out).not_to match(/\-\-all/)
+ end
+
+ it "should install gems with the name bundler in them (that aren't bundler)" do
+ build_gem "foo-bundler", "1.0",
+ :path => bundled_app("vendor/cache")
+
+ install_gemfile <<-G
+ gem "foo-bundler"
+ G
+
+ expect(the_bundle).to include_gems "foo-bundler 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb
new file mode 100644
index 0000000000..33387dbbb2
--- /dev/null
+++ b/spec/bundler/cache/git_spec.rb
@@ -0,0 +1,214 @@
+# frozen_string_literal: true
+
+RSpec.describe "git base name" do
+ it "base_name should strip private repo uris" do
+ source = Bundler::Source::Git.new("uri" => "git@github.com:bundler.git")
+ expect(source.send(:base_name)).to eq("bundler")
+ end
+
+ it "base_name should strip network share paths" do
+ source = Bundler::Source::Git.new("uri" => "//MachineName/ShareFolder")
+ expect(source.send(:base_name)).to eq("ShareFolder")
+ end
+end
+
+%w[cache package].each do |cmd|
+ RSpec.describe "bundle #{cmd} with git" do
+ it "copies repository to vendor cache and uses it" do
+ git = build_git "foo"
+ ref = git.ref_for("master", 11)
+
+ install_gemfile <<-G
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "#{cmd}", forgotten_command_line_options([:all, :cache_all] => true)
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.bundlecache")).to be_file
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "copies repository to vendor cache and uses it even when installed with bundle --path" do
+ git = build_git "foo"
+ ref = git.ref_for("master", 11)
+
+ install_gemfile <<-G
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "install --path vendor/bundle"
+ bundle "#{cmd}", forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "runs twice without exploding" do
+ build_git "foo"
+
+ install_gemfile! <<-G
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle! "#{cmd}", forgotten_command_line_options([:all, :cache_all] => true)
+ bundle! "#{cmd}", forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(last_command.stdout).to include "Updating files in vendor/cache"
+ FileUtils.rm_rf lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "tracks updates" do
+ git = build_git "foo"
+ old_ref = git.ref_for("master", 11)
+
+ install_gemfile <<-G
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "#{cmd}", forgotten_command_line_options([:all, :cache_all] => true)
+
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "puts :CACHE"
+ end
+
+ ref = git.ref_for("master", 11)
+ expect(ref).not_to eq(old_ref)
+
+ bundle! "update", :all => bundle_update_requires_all?
+ bundle! "#{cmd}", forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{old_ref}")).not_to exist
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ run! "require 'foo'"
+ expect(out).to eq("CACHE")
+ end
+
+ it "tracks updates when specifying the gem" do
+ git = build_git "foo"
+ old_ref = git.ref_for("master", 11)
+
+ install_gemfile <<-G
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle! cmd, forgotten_command_line_options([:all, :cache_all] => true)
+
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "puts :CACHE"
+ end
+
+ ref = git.ref_for("master", 11)
+ expect(ref).not_to eq(old_ref)
+
+ bundle "update foo"
+
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{old_ref}")).not_to exist
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ run "require 'foo'"
+ expect(out).to eq("CACHE")
+ end
+
+ it "uses the local repository to generate the cache" do
+ git = build_git "foo"
+ ref = git.ref_for("master", 11)
+
+ gemfile <<-G
+ gem "foo", :git => '#{lib_path("foo-invalid")}', :branch => :master
+ G
+
+ bundle %(config local.foo #{lib_path("foo-1.0")})
+ bundle "install"
+ bundle "#{cmd}", forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/foo-invalid-#{ref}")).to exist
+
+ # Updating the local still uses the local.
+ update_git "foo" do |s|
+ s.write "lib/foo.rb", "puts :LOCAL"
+ end
+
+ run "require 'foo'"
+ expect(out).to eq("LOCAL")
+ end
+
+ it "copies repository to vendor cache, including submodules" do
+ build_git "submodule", "1.0"
+
+ git = build_git "has_submodule", "1.0" do |s|
+ s.add_dependency "submodule"
+ end
+
+ Dir.chdir(lib_path("has_submodule-1.0")) do
+ sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0"
+ `git commit -m "submodulator"`
+ end
+
+ install_gemfile <<-G
+ git "#{lib_path("has_submodule-1.0")}", :submodules => true do
+ gem "has_submodule"
+ end
+ G
+
+ ref = git.ref_for("master", 11)
+ bundle "#{cmd}", forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/has_submodule-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/has_submodule-1.0-#{ref}/submodule-1.0")).to exist
+ expect(the_bundle).to include_gems "has_submodule 1.0"
+ end
+
+ it "displays warning message when detecting git repo in Gemfile", :bundler => "< 2" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "#{cmd}"
+
+ expect(out).to include("Your Gemfile contains path and git dependencies.")
+ end
+
+ it "does not display warning message if cache_all is set in bundle config" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+ bundle cmd
+
+ expect(out).not_to include("Your Gemfile contains path and git dependencies.")
+ end
+
+ it "caches pre-evaluated gemspecs" do
+ git = build_git "foo"
+
+ # Insert a gemspec method that shells out
+ spec_lines = lib_path("foo-1.0/foo.gemspec").read.split("\n")
+ spec_lines.insert(-2, "s.description = `echo bob`")
+ update_git("foo") {|s| s.write "foo.gemspec", spec_lines.join("\n") }
+
+ install_gemfile <<-G
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+
+ ref = git.ref_for("master", 11)
+ gemspec = bundled_app("vendor/cache/foo-1.0-#{ref}/foo.gemspec").read
+ expect(gemspec).to_not match("`echo bob`")
+ end
+ end
+end
diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb
new file mode 100644
index 0000000000..8c6a843476
--- /dev/null
+++ b/spec/bundler/cache/path_spec.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+%w[cache package].each do |cmd|
+ RSpec.describe "bundle #{cmd} with path" do
+ it "is no-op when the path is within the bundle" do
+ build_lib "foo", :path => bundled_app("lib/foo")
+
+ install_gemfile <<-G
+ gem "foo", :path => '#{bundled_app("lib/foo")}'
+ G
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+ expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "copies when the path is outside the bundle " do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0/.bundlecache")).to be_file
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "copies when the path is outside the bundle and the paths intersect" do
+ libname = File.basename(Dir.pwd) + "_gem"
+ libpath = File.join(File.dirname(Dir.pwd), libname)
+
+ build_lib libname, :path => libpath
+
+ install_gemfile <<-G
+ gem "#{libname}", :path => '#{libpath}'
+ G
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+ expect(bundled_app("vendor/cache/#{libname}")).to exist
+ expect(bundled_app("vendor/cache/#{libname}/.bundlecache")).to be_file
+
+ FileUtils.rm_rf libpath
+ expect(the_bundle).to include_gems "#{libname} 1.0"
+ end
+
+ it "updates the path on each cache" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+
+ build_lib "foo" do |s|
+ s.write "lib/foo.rb", "puts :CACHE"
+ end
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/foo-1.0")).to exist
+ FileUtils.rm_rf lib_path("foo-1.0")
+
+ run "require 'foo'"
+ expect(out).to eq("CACHE")
+ end
+
+ it "removes stale entries cache" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+
+ install_gemfile <<-G
+ gem "bar", :path => '#{lib_path("bar-1.0")}'
+ G
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+ expect(bundled_app("vendor/cache/bar-1.0")).not_to exist
+ end
+
+ it "raises a warning without --all", :bundler => "< 2" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle cmd
+ expect(out).to match(/please pass the \-\-all flag/)
+ expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
+ end
+
+ it "stores the given flag" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+ build_lib "bar"
+
+ install_gemfile <<-G
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ gem "bar", :path => '#{lib_path("bar-1.0")}'
+ G
+
+ bundle cmd
+ expect(bundled_app("vendor/cache/bar-1.0")).to exist
+ end
+
+ it "can rewind chosen configuration" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle cmd, forgotten_command_line_options([:all, :cache_all] => true)
+ build_lib "baz"
+
+ gemfile <<-G
+ gem "foo", :path => '#{lib_path("foo-1.0")}'
+ gem "baz", :path => '#{lib_path("baz-1.0")}'
+ G
+
+ bundle "#{cmd} --no-all"
+ expect(bundled_app("vendor/cache/baz-1.0")).not_to exist
+ end
+ end
+end
diff --git a/spec/bundler/cache/platform_spec.rb b/spec/bundler/cache/platform_spec.rb
new file mode 100644
index 0000000000..c0622a3c94
--- /dev/null
+++ b/spec/bundler/cache/platform_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle cache with multiple platforms" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ platforms :mri, :rbx do
+ gem "rack", "1.0.0"
+ end
+
+ platforms :jruby do
+ gem "activesupport", "2.3.5"
+ end
+ G
+
+ lockfile <<-G
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+ activesupport (2.3.5)
+
+ PLATFORMS
+ ruby
+ java
+
+ DEPENDENCIES
+ rack (1.0.0)
+ activesupport (2.3.5)
+ G
+
+ cache_gems "rack-1.0.0", "activesupport-2.3.5"
+ end
+
+ it "ensures that a successful bundle install does not delete gems for other platforms" do
+ bundle! "install"
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/activesupport-2.3.5.gem")).to exist
+ end
+
+ it "ensures that a successful bundle update does not delete gems for other platforms" do
+ bundle! "update", :all => bundle_update_requires_all?
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/activesupport-2.3.5.gem")).to exist
+ end
+end
diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb
new file mode 100644
index 0000000000..9f11adbcf8
--- /dev/null
+++ b/spec/bundler/commands/add_spec.rb
@@ -0,0 +1,217 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle add" do
+ before :each do
+ build_repo2 do
+ build_gem "foo", "1.1"
+ build_gem "foo", "2.0"
+ build_gem "baz", "1.2.3"
+ build_gem "bar", "0.12.3"
+ build_gem "cat", "0.12.3.pre"
+ build_gem "dog", "1.1.3.pre"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "weakling", "~> 0.0.1"
+ G
+ end
+
+ context "when no gems are specified" do
+ it "shows error" do
+ bundle "add"
+
+ expect(last_command.bundler_err).to include("Please specify gems to add")
+ end
+ end
+
+ describe "without version specified" do
+ it "version requirement becomes ~> major.minor.patch when resolved version is < 1.0" do
+ bundle "add 'bar'"
+ expect(bundled_app("Gemfile").read).to match(/gem "bar", "~> 0.12.3"/)
+ expect(the_bundle).to include_gems "bar 0.12.3"
+ end
+
+ it "version requirement becomes ~> major.minor when resolved version is > 1.0" do
+ bundle "add 'baz'"
+ expect(bundled_app("Gemfile").read).to match(/gem "baz", "~> 1.2"/)
+ expect(the_bundle).to include_gems "baz 1.2.3"
+ end
+
+ it "version requirement becomes ~> major.minor.patch.pre when resolved version is < 1.0" do
+ bundle "add 'cat'"
+ expect(bundled_app("Gemfile").read).to match(/gem "cat", "~> 0.12.3.pre"/)
+ expect(the_bundle).to include_gems "cat 0.12.3.pre"
+ end
+
+ it "version requirement becomes ~> major.minor.pre when resolved version is > 1.0.pre" do
+ bundle "add 'dog'"
+ expect(bundled_app("Gemfile").read).to match(/gem "dog", "~> 1.1.pre"/)
+ expect(the_bundle).to include_gems "dog 1.1.3.pre"
+ end
+ end
+
+ describe "with --version" do
+ it "adds dependency of specified version and runs install" do
+ bundle "add 'foo' --version='~> 1.0'"
+ expect(bundled_app("Gemfile").read).to match(/gem "foo", "~> 1.0"/)
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "adds multiple version constraints when specified" do
+ requirements = ["< 3.0", "> 1.0"]
+ bundle "add 'foo' --version='#{requirements.join(", ")}'"
+ expect(bundled_app("Gemfile").read).to match(/gem "foo", #{Gem::Requirement.new(requirements).as_list.map(&:dump).join(', ')}/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --group" do
+ it "adds dependency for the specified group" do
+ bundle "add 'foo' --group='development'"
+ expect(bundled_app("Gemfile").read).to match(/gem "foo", "~> 2.0", :group => :development/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+
+ it "adds dependency to more than one group" do
+ bundle "add 'foo' --group='development, test'"
+ expect(bundled_app("Gemfile").read).to match(/gem "foo", "~> 2.0", :groups => \[:development, :test\]/)
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --source" do
+ it "adds dependency with specified source" do
+ bundle "add 'foo' --source='file://#{gem_repo2}'"
+
+ expect(bundled_app("Gemfile").read).to match(%r{gem "foo", "~> 2.0", :source => "file:\/\/#{gem_repo2}"})
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --skip-install" do
+ it "adds gem to Gemfile but is not installed" do
+ bundle "add foo --skip-install --version=2.0"
+
+ expect(bundled_app("Gemfile").read).to match(/gem "foo", "= 2.0"/)
+ expect(the_bundle).to_not include_gems "foo 2.0"
+ end
+ end
+
+ it "using combination of short form options works like long form" do
+ bundle "add 'foo' -s='file://#{gem_repo2}' -g='development' -v='~>1.0'"
+ expect(bundled_app("Gemfile").read).to include %(gem "foo", "~> 1.0", :group => :development, :source => "file://#{gem_repo2}")
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "shows error message when version is not formatted correctly" do
+ bundle "add 'foo' -v='~>1 . 0'"
+ expect(out).to match("Invalid gem requirement pattern '~>1 . 0'")
+ end
+
+ it "shows error message when gem cannot be found" do
+ bundle "add 'werk_it'"
+ expect(out).to match("Could not find gem 'werk_it' in")
+
+ bundle "add 'werk_it' -s='file://#{gem_repo2}'"
+ expect(out).to match("Could not find gem 'werk_it' in rubygems repository")
+ end
+
+ it "shows error message when source cannot be reached" do
+ bundle "add 'baz' --source='http://badhostasdf'"
+ expect(out).to include("Could not reach host badhostasdf. Check your network connection and try again.")
+
+ bundle "add 'baz' --source='file://does/not/exist'"
+ expect(out).to include("Could not fetch specs from file://does/not/exist/")
+ end
+
+ describe "with --optimistic" do
+ it "adds optimistic version" do
+ bundle! "add 'foo' --optimistic"
+ expect(bundled_app("Gemfile").read).to include %(gem "foo", ">= 2.0")
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --strict option" do
+ it "adds strict version" do
+ bundle! "add 'foo' --strict"
+ expect(bundled_app("Gemfile").read).to include %(gem "foo", "= 2.0")
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with no option" do
+ it "adds pessimistic version" do
+ bundle! "add 'foo'"
+ expect(bundled_app("Gemfile").read).to include %(gem "foo", "~> 2.0")
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+ end
+
+ describe "with --optimistic and --strict" do
+ it "throws error" do
+ bundle "add 'foo' --strict --optimistic"
+
+ expect(out).to include("You can not specify `--strict` and `--optimistic` at the same time")
+ end
+ end
+
+ context "multiple gems" do
+ it "adds multiple gems to gemfile" do
+ bundle! "add bar baz"
+
+ expect(bundled_app("Gemfile").read).to match(/gem "bar", "~> 0.12.3"/)
+ expect(bundled_app("Gemfile").read).to match(/gem "baz", "~> 1.2"/)
+ end
+
+ it "throws error if any of the specified gems are present in the gemfile with different version" do
+ bundle "add weakling bar"
+
+ expect(out).to include("You cannot specify the same gem twice with different version requirements")
+ expect(out).to include("You specified: weakling (~> 0.0.1) and weakling (>= 0).")
+ end
+ end
+
+ describe "when a gem is added which is already specified in Gemfile with version" do
+ it "shows an error when added with different version requirement" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack", "1.0"
+ G
+
+ bundle "add 'rack' --version=1.1"
+
+ expect(out).to include("You cannot specify the same gem twice with different version requirements")
+ expect(out).to include("If you want to update the gem version, run `bundle update rack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive")
+ end
+
+ it "shows error when added without version requirements" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack", "1.0"
+ G
+
+ bundle "add 'rack'"
+
+ expect(out).to include("Gem already added.")
+ expect(out).to include("You cannot specify the same gem twice with different version requirements")
+ expect(out).not_to include("If you want to update the gem version, run `bundle update rack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive")
+ end
+ end
+
+ describe "when a gem is added which is already specified in Gemfile without version" do
+ it "shows an error when added with different version requirement" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ G
+
+ bundle "add 'rack' --version=1.1"
+
+ expect(out).to include("You cannot specify the same gem twice with different version requirements")
+ expect(out).to include("If you want to update the gem version, run `bundle update rack`.")
+ expect(out).not_to include("You may also need to change the version requirement specified in the Gemfile if it's too restrictive")
+ end
+ end
+end
diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb
new file mode 100644
index 0000000000..6a705d3423
--- /dev/null
+++ b/spec/bundler/commands/binstubs_spec.rb
@@ -0,0 +1,453 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle binstubs <gem>" do
+ context "when the gem exists in the lockfile" do
+ it "sets up the binstub" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack"
+
+ expect(bundled_app("bin/rackup")).to exist
+ end
+
+ it "does not install other binstubs" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle "binstubs rails"
+
+ expect(bundled_app("bin/rackup")).not_to exist
+ expect(bundled_app("bin/rails")).to exist
+ end
+
+ it "does install multiple binstubs" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle "binstubs rails rack"
+
+ expect(bundled_app("bin/rackup")).to exist
+ expect(bundled_app("bin/rails")).to exist
+ end
+
+ it "allows installing all binstubs" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ bundle! :binstubs, :all => true
+
+ expect(bundled_app("bin/rails")).to exist
+ expect(bundled_app("bin/rake")).to exist
+ end
+
+ it "displays an error when used without any gem" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs"
+ expect(exitstatus).to eq(1) if exitstatus
+ expect(out).to include("`bundle binstubs` needs at least one gem to run.")
+ end
+
+ it "displays an error when used with --all and gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack", :all => true
+ expect(last_command).to be_failure
+ expect(last_command.bundler_err).to include("Cannot specify --all with specific gems")
+ end
+
+ context "when generating bundle binstub outside bundler" do
+ it "should abort" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack"
+
+ File.open("bin/bundle", "wb") do |file|
+ file.print "OMG"
+ end
+
+ sys_exec "bin/rackup"
+
+ expect(last_command.stderr).to include("was not generated by Bundler")
+ end
+ end
+
+ context "the bundle binstub" do
+ before do
+ if system_bundler_version == :bundler
+ system_gems :bundler
+ elsif system_bundler_version
+ build_repo4 do
+ build_gem "bundler", system_bundler_version do |s|
+ s.executables = "bundle"
+ s.bindir = "exe"
+ s.write "exe/bundle", "puts %(system bundler #{system_bundler_version}\\n\#{ARGV.inspect})"
+ end
+ end
+ system_gems "bundler-#{system_bundler_version}", :gem_repo => gem_repo4
+ end
+ build_repo2 do
+ build_gem "prints_loaded_gems", "1.0" do |s|
+ s.executables = "print_loaded_gems"
+ s.bindir = "exe"
+ s.write "exe/print_loaded_gems", <<-R
+ specs = Gem.loaded_specs.values.reject {|s| Bundler.rubygems.spec_default_gem?(s) }
+ puts specs.map(&:full_name).sort.inspect
+ R
+ end
+ end
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ gem "prints_loaded_gems"
+ G
+ bundle! "binstubs bundler rack prints_loaded_gems"
+ end
+
+ # When environment has a same version of bundler as default gems.
+ # `system_gems "bundler-x.y.z"` will detect system binstub.
+ # We need to avoid it by virtual version of bundler.
+ let(:system_bundler_version) { Gem::Version.new(Bundler::VERSION).bump.to_s }
+
+ context "when system bundler was used" do
+ # Support master branch of bundler
+ if ENV["BUNDLER_SPEC_SUB_VERSION"]
+ let(:system_bundler_version) { Bundler::VERSION }
+ end
+ it "runs bundler" do
+ sys_exec! "#{bundled_app("bin/bundle")} install"
+ expect(out).to eq %(system bundler #{system_bundler_version}\n["install"])
+ end
+ end
+
+ context "when BUNDLER_VERSION is set" do
+ let(:system_bundler_version) { Bundler::VERSION }
+
+ it "runs the correct version of bundler" do
+ sys_exec "BUNDLER_VERSION='999.999.999' #{bundled_app("bin/bundle")} install"
+ expect(exitstatus).to eq(42) if exitstatus
+ expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
+ end
+ end
+
+ context "when a lockfile exists with a locked bundler version" do
+ let(:system_bundler_version) { Bundler::VERSION }
+
+ it "runs the correct version of bundler when the version is newer" do
+ lockfile lockfile.gsub(system_bundler_version, "999.999.999")
+ sys_exec "#{bundled_app("bin/bundle")} install"
+ expect(exitstatus).to eq(42) if exitstatus
+ expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
+ end
+
+ it "runs the correct version of bundler when the version is older" do
+ simulate_bundler_version "55"
+ lockfile lockfile.gsub(system_bundler_version, "44.0")
+ sys_exec "#{bundled_app("bin/bundle")} install"
+ expect(exitstatus).to eq(42) if exitstatus
+ expect(last_command.stderr).to include("Activating bundler (44.0) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '44.0'`")
+ end
+
+ it "runs the correct version of bundler when the version is a pre-release" do
+ simulate_bundler_version "55"
+ lockfile lockfile.gsub(system_bundler_version, "2.12.0.a")
+ sys_exec "#{bundled_app("bin/bundle")} install"
+ expect(exitstatus).to eq(42) if exitstatus
+ expect(last_command.stderr).to include("Activating bundler (2.12.0.a) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '2.12.0.a'`")
+ end
+ end
+
+ context "when update --bundler is called" do
+ before { lockfile.gsub(system_bundler_version, "1.1.1") }
+
+ it "calls through to the latest bundler version" do
+ sys_exec! "#{bundled_app("bin/bundle")} update --bundler"
+ expect(last_command.stdout).to eq %(system bundler #{system_bundler_version}\n["update", "--bundler"])
+ end
+
+ it "calls through to the explicit bundler version" do
+ sys_exec "#{bundled_app("bin/bundle")} update --bundler=999.999.999"
+ expect(exitstatus).to eq(42) if exitstatus
+ expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
+ end
+ end
+
+ context "without a lockfile" do
+ it "falls back to the latest installed bundler" do
+ FileUtils.rm bundled_app("Gemfile.lock")
+ sys_exec! bundled_app("bin/bundle").to_s
+ expect(out).to eq "system bundler #{system_bundler_version}\n[]"
+ end
+ end
+
+ context "using another binstub" do
+ let(:system_bundler_version) { :bundler }
+ it "loads all gems" do
+ sys_exec! bundled_app("bin/print_loaded_gems").to_s
+ # RG < 2.0.14 didn't have a `Gem::Specification#default_gem?`
+ # This is dirty detection for old RG versions.
+ if File.dirname(Bundler.load.specs["bundler"][0].loaded_from) =~ %r{specifications/default}
+ expect(out).to eq %(["prints_loaded_gems-1.0", "rack-1.2"])
+ else
+ expect(out).to eq %(["bundler-#{Bundler::VERSION}", "prints_loaded_gems-1.0", "rack-1.2"])
+ end
+ end
+
+ context "when requesting a different bundler version" do
+ before { lockfile lockfile.gsub(Bundler::VERSION, "999.999.999") }
+
+ it "attempts to load that version", :ruby_repo do
+ sys_exec bundled_app("bin/rackup").to_s
+ expect(exitstatus).to eq(42) if exitstatus
+ expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:").
+ and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
+ end
+ end
+ end
+ end
+
+ it "installs binstubs from git gems" do
+ FileUtils.mkdir_p(lib_path("foo/bin"))
+ FileUtils.touch(lib_path("foo/bin/foo"))
+ build_git "foo", "1.0", :path => lib_path("foo") do |s|
+ s.executables = %w[foo]
+ end
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo")}"
+ G
+
+ bundle "binstubs foo"
+
+ expect(bundled_app("bin/foo")).to exist
+ end
+
+ it "installs binstubs from path gems" do
+ FileUtils.mkdir_p(lib_path("foo/bin"))
+ FileUtils.touch(lib_path("foo/bin/foo"))
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.executables = %w[foo]
+ end
+ install_gemfile <<-G
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ bundle "binstubs foo"
+
+ expect(bundled_app("bin/foo")).to exist
+ end
+
+ it "sets correct permissions for binstubs" do
+ with_umask(0o002) do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack"
+ binary = bundled_app("bin/rackup")
+ expect(File.stat(binary).mode.to_s(8)).to eq("100775")
+ end
+ end
+
+ context "when using --shebang" do
+ it "sets the specified shebang for the the binstub" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack --shebang jruby"
+
+ expect(File.open("bin/rackup").gets).to eq("#!/usr/bin/env jruby\n")
+ end
+ end
+ end
+
+ context "when the gem doesn't exist" do
+ it "displays an error with correct status" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ G
+
+ bundle "binstubs doesnt_exist"
+
+ expect(exitstatus).to eq(7) if exitstatus
+ expect(out).to include("Could not find gem 'doesnt_exist'.")
+ end
+ end
+
+ context "--path" do
+ it "sets the binstubs dir" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack --path exec"
+
+ expect(bundled_app("exec/rackup")).to exist
+ end
+
+ it "setting is saved for bundle install", :bundler => "< 2" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle! "binstubs rack", forgotten_command_line_options([:path, :bin] => "exec")
+ bundle! :install
+
+ expect(bundled_app("exec/rails")).to exist
+ end
+ end
+
+ context "with --standalone option" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ it "generates a standalone binstub" do
+ bundle! "binstubs rack --standalone"
+ expect(bundled_app("bin/rackup")).to exist
+ end
+
+ it "generates a binstub that does not depend on rubygems or bundler" do
+ bundle! "binstubs rack --standalone"
+ expect(File.read(bundled_app("bin/rackup"))).to_not include("Gem.bin_path")
+ end
+
+ context "when specified --path option" do
+ it "generates a standalone binstub at the given path" do
+ bundle! "binstubs rack --standalone --path foo"
+ expect(bundled_app("foo/rackup")).to exist
+ end
+ end
+ end
+
+ context "when the bin already exists" do
+ it "doesn't overwrite and warns" do
+ FileUtils.mkdir_p(bundled_app("bin"))
+ File.open(bundled_app("bin/rackup"), "wb") do |file|
+ file.print "OMG"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack"
+
+ expect(bundled_app("bin/rackup")).to exist
+ expect(File.read(bundled_app("bin/rackup"))).to eq("OMG")
+ expect(out).to include("Skipped rackup")
+ expect(out).to include("overwrite skipped stubs, use --force")
+ end
+
+ context "when using --force" do
+ it "overwrites the binstub" do
+ FileUtils.mkdir_p(bundled_app("bin"))
+ File.open(bundled_app("bin/rackup"), "wb") do |file|
+ file.print "OMG"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs rack --force"
+
+ expect(bundled_app("bin/rackup")).to exist
+ expect(File.read(bundled_app("bin/rackup"))).not_to eq("OMG")
+ end
+ end
+ end
+
+ context "when the gem has no bins" do
+ it "suggests child gems if they have bins" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack-obama"
+ G
+
+ bundle "binstubs rack-obama"
+ expect(out).to include("rack-obama has no executables")
+ expect(out).to include("rack has: rackup")
+ end
+
+ it "works if child gems don't have bins" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "actionpack"
+ G
+
+ bundle "binstubs actionpack"
+ expect(out).to include("no executables for the gem actionpack")
+ end
+
+ it "works if the gem has development dependencies" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "with_development_dependency"
+ G
+
+ bundle "binstubs with_development_dependency"
+ expect(out).to include("no executables for the gem with_development_dependency")
+ end
+ end
+
+ context "when BUNDLE_INSTALL is specified" do
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "config auto_install 1"
+ bundle "binstubs rack"
+ expect(out).to include("Installing rack 1.0.0")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does nothing when already up to date" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "config auto_install 1"
+ bundle "binstubs rack", :env => { "BUNDLE_INSTALL" => 1 }
+ expect(out).not_to include("Installing rack 1.0.0")
+ end
+ end
+end
diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb
new file mode 100644
index 0000000000..f2af446fbf
--- /dev/null
+++ b/spec/bundler/commands/check_spec.rb
@@ -0,0 +1,354 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle check" do
+ it "returns success when the Gemfile is satisfied" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ bundle :check
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "works with the --gemfile flag when not in the directory" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ Dir.chdir tmp
+ bundle "check --gemfile bundled_app/Gemfile"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "creates a Gemfile.lock by default if one does not exist" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ FileUtils.rm("Gemfile.lock")
+
+ bundle "check"
+
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+
+ it "does not create a Gemfile.lock if --dry-run was passed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ FileUtils.rm("Gemfile.lock")
+
+ bundle "check --dry-run"
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ end
+
+ it "prints a generic error if the missing gems are unresolvable" do
+ system_gems ["rails-2.3.2"]
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ bundle :check
+ expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.")
+ end
+
+ it "prints a generic error if a Gemfile.lock does not exist and a toplevel dependency does not exist" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ bundle :check
+ expect(exitstatus).to be > 0 if exitstatus
+ expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.")
+ end
+
+ it "prints a generic message if you changed your lockfile" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rails'
+ G
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rails_fail'
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ gem "rails_fail"
+ G
+
+ bundle :check
+ expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.")
+ end
+
+ it "remembers --without option from install", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ group :foo do
+ gem "rack"
+ end
+ G
+
+ bundle! "install --without foo"
+ bundle! "check"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "uses the without setting" do
+ bundle! "config without foo"
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ group :foo do
+ gem "rack"
+ end
+ G
+
+ bundle! "check"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "ensures that gems are actually installed and not just cached" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :group => :foo
+ G
+
+ bundle :install, forgotten_command_line_options(:without => "foo")
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "check"
+ expect(out).to include("* rack (1.0.0)")
+ expect(exitstatus).to eq(1) if exitstatus
+ end
+
+ it "ignores missing gems restricted to other platforms" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ platforms :#{not_local_tag} do
+ gem "activesupport"
+ end
+ G
+
+ system_gems "rack-1.0.0", :path => :bundle_path
+
+ lockfile <<-G
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ activesupport (2.3.5)
+ rack (1.0.0)
+
+ PLATFORMS
+ #{local}
+ #{not_local}
+
+ DEPENDENCIES
+ rack
+ activesupport
+ G
+
+ bundle :check
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "works with env conditionals" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ env :NOT_GOING_TO_BE_SET do
+ gem "activesupport"
+ end
+ G
+
+ system_gems "rack-1.0.0", :path => :bundle_path
+
+ lockfile <<-G
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ activesupport (2.3.5)
+ rack (1.0.0)
+
+ PLATFORMS
+ #{local}
+ #{not_local}
+
+ DEPENDENCIES
+ rack
+ activesupport
+ G
+
+ bundle :check
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "outputs an error when the default Gemfile is not found" do
+ bundle :check
+ expect(exitstatus).to eq(10) if exitstatus
+ expect(out).to include("Could not locate Gemfile")
+ end
+
+ it "does not output fatal error message" do
+ bundle :check
+ expect(exitstatus).to eq(10) if exitstatus
+ expect(out).not_to include("Unfortunately, a fatal error has occurred. ")
+ end
+
+ it "should not crash when called multiple times on a new machine" do
+ gemfile <<-G
+ gem 'rails', '3.0.0.beta3'
+ gem 'paperclip', :git => 'git://github.com/thoughtbot/paperclip.git'
+ G
+
+ simulate_new_machine
+ bundle "check"
+ last_out = out
+ 3.times do
+ bundle :check
+ expect(out).to eq(last_out)
+ end
+ end
+
+ it "fails when there's no lock file and frozen is set" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "foo"
+ G
+
+ bundle! "install", forgotten_command_line_options(:deployment => true)
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ bundle :check
+ expect(last_command).to be_failure
+ end
+
+ context "--path", :bundler => "< 2" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+ bundle "install --path vendor/bundle"
+
+ FileUtils.rm_rf(bundled_app(".bundle"))
+ end
+
+ it "returns success" do
+ bundle! "check --path vendor/bundle"
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "should write to .bundle/config", :bundler => "< 2" do
+ bundle "check --path vendor/bundle"
+ bundle! "check"
+ end
+ end
+
+ context "--path vendor/bundle after installing gems in the default directory" do
+ it "returns false" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ bundle "check --path vendor/bundle"
+ expect(exitstatus).to eq(1) if exitstatus
+ expect(out).to match(/The following gems are missing/)
+ end
+ end
+
+ describe "when locked" do
+ before :each do
+ system_gems "rack-1.0.0"
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "1.0"
+ G
+ end
+
+ it "returns success when the Gemfile is satisfied" do
+ bundle :install
+ bundle :check
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "shows what is missing with the current Gemfile if it is not satisfied" do
+ simulate_new_machine
+ bundle :check
+ expect(out).to match(/The following gems are missing/)
+ expect(out).to include("* rack (1.0")
+ end
+ end
+
+ describe "BUNDLED WITH" do
+ def lock_with(bundler_version = nil)
+ lock = <<-L
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+ L
+
+ if bundler_version
+ lock += "\n BUNDLED WITH\n #{bundler_version}\n"
+ end
+
+ lock
+ end
+
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ context "is not present" do
+ it "does not change the lock" do
+ lockfile lock_with(nil)
+ bundle :check
+ lockfile_should_be lock_with(nil)
+ end
+ end
+
+ context "is newer" do
+ it "does not change the lock but warns" do
+ lockfile lock_with(Bundler::VERSION.succ)
+ bundle! :check
+ expect(last_command.bundler_err).to include("the running version of Bundler (#{Bundler::VERSION}) is older than the version that created the lockfile (#{Bundler::VERSION.succ})")
+ lockfile_should_be lock_with(Bundler::VERSION.succ)
+ end
+ end
+
+ context "is older" do
+ it "does not change the lock" do
+ lockfile lock_with("1.10.1")
+ bundle :check
+ lockfile_should_be lock_with("1.10.1")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb
new file mode 100644
index 0000000000..37cbeeb4e7
--- /dev/null
+++ b/spec/bundler/commands/clean_spec.rb
@@ -0,0 +1,771 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle clean" do
+ def should_have_gems(*gems)
+ gems.each do |g|
+ expect(vendored_gems("gems/#{g}")).to exist
+ expect(vendored_gems("specifications/#{g}.gemspec")).to exist
+ expect(vendored_gems("cache/#{g}.gem")).to exist
+ end
+ end
+
+ def should_not_have_gems(*gems)
+ gems.each do |g|
+ expect(vendored_gems("gems/#{g}")).not_to exist
+ expect(vendored_gems("specifications/#{g}.gemspec")).not_to exist
+ expect(vendored_gems("cache/#{g}.gem")).not_to exist
+ end
+ end
+
+ it "removes unused gems that are different" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle! "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false)
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ G
+ bundle! "install"
+
+ bundle! :clean
+
+ expect(out).to include("Removing foo (1.0)")
+
+ should_have_gems "thin-1.0", "rack-1.0.0"
+ should_not_have_gems "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "removes old version of gem if unused" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "0.9.1"
+ gem "foo"
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false)
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ gem "foo"
+ G
+ bundle "install"
+
+ bundle :clean
+
+ expect(out).to include("Removing rack (0.9.1)")
+
+ should_have_gems "foo-1.0", "rack-1.0.0"
+ should_not_have_gems "rack-0.9.1"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "removes new version of gem if unused" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ gem "foo"
+ G
+
+ bundle! "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false)
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "0.9.1"
+ gem "foo"
+ G
+ bundle! "update rack"
+
+ bundle! :clean
+
+ expect(out).to include("Removing rack (1.0.0)")
+
+ should_have_gems "foo-1.0", "rack-0.9.1"
+ should_not_have_gems "rack-1.0.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "removes gems in bundle without groups" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "foo"
+
+ group :test_group do
+ gem "rack", "1.0.0"
+ end
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+ bundle "install", forgotten_command_line_options(:without => "test_group")
+ bundle :clean
+
+ expect(out).to include("Removing rack (1.0.0)")
+
+ should_have_gems "foo-1.0"
+ should_not_have_gems "rack-1.0.0"
+
+ expect(vendored_gems("bin/rackup")).to_not exist
+ end
+
+ it "does not remove cached git dir if it's being used" do
+ build_git "foo"
+ revision = revision_for(lib_path("foo-1.0"))
+ git_path = lib_path("foo-1.0")
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ git "#{git_path}", :ref => "#{revision}" do
+ gem "foo"
+ end
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ bundle :clean
+
+ digest = Digest(:SHA1).hexdigest(git_path.to_s)
+ cache_path = Bundler::VERSION.start_with?("1.") ? vendored_gems("cache/bundler/git/foo-1.0-#{digest}") : home(".bundle/cache/git/foo-1.0-#{digest}")
+ expect(cache_path).to exist
+ end
+
+ it "removes unused git gems" do
+ build_git "foo", :path => lib_path("foo")
+ git_path = lib_path("foo")
+ revision = revision_for(git_path)
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ git "#{git_path}", :ref => "#{revision}" do
+ gem "foo"
+ end
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ G
+ bundle "install"
+
+ bundle :clean
+
+ expect(out).to include("Removing foo (#{revision[0..11]})")
+
+ expect(vendored_gems("gems/rack-1.0.0")).to exist
+ expect(vendored_gems("bundler/gems/foo-#{revision[0..11]}")).not_to exist
+ digest = Digest(:SHA1).hexdigest(git_path.to_s)
+ expect(vendored_gems("cache/bundler/git/foo-#{digest}")).not_to exist
+
+ expect(vendored_gems("specifications/rack-1.0.0.gemspec")).to exist
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "removes old git gems" do
+ build_git "foo-bar", :path => lib_path("foo-bar")
+ revision = revision_for(lib_path("foo-bar"))
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ git "#{lib_path("foo-bar")}" do
+ gem "foo-bar"
+ end
+ G
+
+ bundle! "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ update_git "foo", :path => lib_path("foo-bar")
+ revision2 = revision_for(lib_path("foo-bar"))
+
+ bundle! "update", :all => bundle_update_requires_all?
+ bundle! :clean
+
+ expect(out).to include("Removing foo-bar (#{revision[0..11]})")
+
+ expect(vendored_gems("gems/rack-1.0.0")).to exist
+ expect(vendored_gems("bundler/gems/foo-bar-#{revision[0..11]}")).not_to exist
+ expect(vendored_gems("bundler/gems/foo-bar-#{revision2[0..11]}")).to exist
+
+ expect(vendored_gems("specifications/rack-1.0.0.gemspec")).to exist
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "does not remove nested gems in a git repo" do
+ build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport")
+ build_git "rails", "3.0", :path => lib_path("rails") do |s|
+ s.add_dependency "activesupport", "= 3.0"
+ end
+ revision = revision_for(lib_path("rails"))
+
+ gemfile <<-G
+ gem "activesupport", :git => "#{lib_path("rails")}", :ref => '#{revision}'
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+ bundle :clean
+ expect(out).to include("")
+
+ expect(vendored_gems("bundler/gems/rails-#{revision[0..11]}")).to exist
+ end
+
+ it "does not remove git sources that are in without groups" do
+ build_git "foo", :path => lib_path("foo")
+ git_path = lib_path("foo")
+ revision = revision_for(git_path)
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ group :test do
+ git "#{git_path}", :ref => "#{revision}" do
+ gem "foo"
+ end
+ end
+ G
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :without => "test")
+
+ bundle :clean
+
+ expect(out).to include("")
+ expect(vendored_gems("bundler/gems/foo-#{revision[0..11]}")).to exist
+ digest = Digest(:SHA1).hexdigest(git_path.to_s)
+ expect(vendored_gems("cache/bundler/git/foo-#{digest}")).to_not exist
+ end
+
+ it "does not blow up when using without groups" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+
+ group :development do
+ gem "foo"
+ end
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :without => "development")
+
+ bundle :clean
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ it "displays an error when used without --path" do
+ bundle! "config path.system true"
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ G
+
+ bundle :clean
+
+ expect(exitstatus).to eq(15) if exitstatus
+ expect(out).to include("--force")
+ end
+
+ # handling bundle clean upgrade path from the pre's
+ it "removes .gem/.gemspec file even if there's no corresponding gem dir" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "foo"
+ G
+ bundle "install"
+
+ FileUtils.rm(vendored_gems("bin/rackup"))
+ FileUtils.rm_rf(vendored_gems("gems/thin-1.0"))
+ FileUtils.rm_rf(vendored_gems("gems/rack-1.0.0"))
+
+ bundle :clean
+
+ should_not_have_gems "thin-1.0", "rack-1.0"
+ should_have_gems "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).not_to exist
+ end
+
+ it "does not call clean automatically when using system gems" do
+ bundle! "config path.system true"
+
+ bundle! :config
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "rack"
+ G
+
+ bundle! "info thin"
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ sys_exec! "gem list"
+ expect(out).to include("rack (1.0.0)").and include("thin (1.0)")
+ end
+
+ it "--clean should override the bundle setting on install", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "rack"
+ G
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => true)
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+ bundle "install"
+
+ should_have_gems "rack-1.0.0"
+ should_not_have_gems "thin-1.0"
+ end
+
+ it "--clean should override the bundle setting on update", :bundler => "< 2" do
+ build_repo2
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "foo"
+ G
+ bundle! "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => true)
+
+ update_repo2 do
+ build_gem "foo", "1.0.1"
+ end
+
+ bundle! "update", :all => bundle_update_requires_all?
+
+ should_have_gems "foo-1.0.1"
+ should_not_have_gems "foo-1.0"
+ end
+
+ it "automatically cleans when path has not been set", :bundler => "2" do
+ build_repo2
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+
+ gem "foo"
+ G
+
+ update_repo2 do
+ build_gem "foo", "1.0.1"
+ end
+
+ bundle! "update", :all => true
+
+ files = Pathname.glob(bundled_app(".bundle", Bundler.ruby_scope, "*", "*"))
+ files.map! {|f| f.to_s.sub(bundled_app(".bundle", Bundler.ruby_scope).to_s, "") }
+ expect(files.sort).to eq %w[
+ /cache/foo-1.0.1.gem
+ /gems/foo-1.0.1
+ /specifications/foo-1.0.1.gemspec
+ ]
+ end
+
+ it "does not clean automatically on --path" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "rack"
+ G
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+ bundle "install"
+
+ should_have_gems "rack-1.0.0", "thin-1.0"
+ end
+
+ it "does not clean on bundle update with --path" do
+ build_repo2
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "foo"
+ G
+ bundle! "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ update_repo2 do
+ build_gem "foo", "1.0.1"
+ end
+
+ bundle! :update, :all => bundle_update_requires_all?
+ should_have_gems "foo-1.0", "foo-1.0.1"
+ end
+
+ it "does not clean on bundle update when using --system" do
+ bundle! "config path.system true"
+
+ build_repo2
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "foo"
+ G
+ bundle! "install"
+
+ update_repo2 do
+ build_gem "foo", "1.0.1"
+ end
+ bundle! :update, :all => bundle_update_requires_all?
+
+ sys_exec! "gem list"
+ expect(out).to include("foo (1.0.1, 1.0)")
+ end
+
+ it "cleans system gems when --force is used" do
+ bundle! "config path.system true"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "foo"
+ gem "rack"
+ G
+ bundle :install
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+ bundle :install
+ bundle "clean --force"
+
+ expect(out).to include("Removing foo (1.0)")
+ sys_exec "gem list"
+ expect(out).not_to include("foo (1.0)")
+ expect(out).to include("rack (1.0.0)")
+ end
+
+ describe "when missing permissions" do
+ before { ENV["BUNDLE_PATH__SYSTEM"] = "true" }
+ let(:system_cache_path) { system_gem_path("cache") }
+ after do
+ FileUtils.chmod(0o755, system_cache_path)
+ end
+ it "returns a helpful error message" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "foo"
+ gem "rack"
+ G
+ bundle :install
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+ bundle :install
+
+ FileUtils.chmod(0o500, system_cache_path)
+
+ bundle :clean, :force => true
+
+ expect(out).to include(system_gem_path.to_s)
+ expect(out).to include("grant write permissions")
+
+ sys_exec "gem list"
+ expect(out).to include("foo (1.0)")
+ expect(out).to include("rack (1.0.0)")
+ end
+ end
+
+ it "cleans git gems with a 7 length git revision" do
+ build_git "foo"
+ revision = revision_for(lib_path("foo-1.0"))
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ # mimic 7 length git revisions in Gemfile.lock
+ gemfile_lock = File.read(bundled_app("Gemfile.lock")).split("\n")
+ gemfile_lock.each_with_index do |line, index|
+ gemfile_lock[index] = line[0..(11 + 7)] if line.include?(" revision:")
+ end
+ File.open(bundled_app("Gemfile.lock"), "w") do |file|
+ file.print gemfile_lock.join("\n")
+ end
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ bundle :clean
+
+ expect(out).not_to include("Removing foo (1.0 #{revision[0..6]})")
+
+ expect(vendored_gems("bundler/gems/foo-1.0-#{revision[0..6]}")).to exist
+ end
+
+ it "when using --force on system gems, it doesn't remove binaries" do
+ bundle! "config path.system true"
+
+ build_repo2
+ update_repo2 do
+ build_gem "bindir" do |s|
+ s.bindir = "exe"
+ s.executables = "foo"
+ end
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "bindir"
+ G
+ bundle :install
+
+ bundle "clean --force"
+
+ sys_exec "foo"
+
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(out).to eq("1.0")
+ end
+
+ it "doesn't blow up on path gems without a .gempsec" do
+ relative_path = "vendor/private_gems/bar-1.0"
+ absolute_path = bundled_app(relative_path)
+ FileUtils.mkdir_p("#{absolute_path}/lib/bar")
+ File.open("#{absolute_path}/lib/bar/bar.rb", "wb") do |file|
+ file.puts "module Bar; end"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "foo"
+ gem "bar", "1.0", :path => "#{relative_path}"
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+ bundle! :clean
+ end
+
+ it "doesn't remove gems in dry-run mode with path set" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false)
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ G
+
+ bundle :install
+
+ bundle "clean --dry-run"
+
+ expect(out).not_to include("Removing foo (1.0)")
+ expect(out).to include("Would have removed foo (1.0)")
+
+ should_have_gems "thin-1.0", "rack-1.0.0", "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "doesn't remove gems in dry-run mode with no path set" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false)
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ G
+
+ bundle :install
+
+ bundle "configuration --delete path"
+
+ bundle "clean --dry-run"
+
+ expect(out).not_to include("Removing foo (1.0)")
+ expect(out).to include("Would have removed foo (1.0)")
+
+ should_have_gems "thin-1.0", "rack-1.0.0", "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "doesn't store dry run as a config setting" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false)
+ bundle "config dry_run false"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ G
+
+ bundle :install
+
+ bundle "clean"
+
+ expect(out).to include("Removing foo (1.0)")
+ expect(out).not_to include("Would have removed foo (1.0)")
+
+ should_have_gems "thin-1.0", "rack-1.0.0"
+ should_not_have_gems "foo-1.0"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "foo"
+ G
+
+ bundle! "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false)
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "weakling"
+ G
+
+ bundle! "config auto_install 1"
+ bundle! :clean
+ expect(out).to include("Installing weakling 0.0.3")
+ should_have_gems "thin-1.0", "rack-1.0.0", "weakling-0.0.3"
+ should_not_have_gems "foo-1.0"
+ end
+
+ it "doesn't remove extensions artifacts from bundled git gems after clean", :ruby_repo, :rubygems => "2.2" do
+ build_git "very_simple_git_binary", &:add_c_extension
+
+ revision = revision_for(lib_path("very_simple_git_binary-1.0"))
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}"
+ G
+
+ bundle! "install", forgotten_command_line_options(:path => "vendor/bundle")
+ expect(vendored_gems("bundler/gems/extensions")).to exist
+ expect(vendored_gems("bundler/gems/very_simple_git_binary-1.0-#{revision[0..11]}")).to exist
+
+ bundle! :clean
+ expect(out).to eq("")
+
+ expect(vendored_gems("bundler/gems/extensions")).to exist
+ expect(vendored_gems("bundler/gems/very_simple_git_binary-1.0-#{revision[0..11]}")).to exist
+ end
+
+ it "removes extension directories", :ruby_repo, :rubygems => "2.2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "very_simple_binary"
+ gem "simple_binary"
+ G
+
+ bundle! "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ very_simple_binary_extensions_dir =
+ Pathname.glob("#{vendored_gems}/extensions/*/*/very_simple_binary-1.0").first
+
+ simple_binary_extensions_dir =
+ Pathname.glob("#{vendored_gems}/extensions/*/*/simple_binary-1.0").first
+
+ expect(very_simple_binary_extensions_dir).to exist
+ expect(simple_binary_extensions_dir).to exist
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "thin"
+ gem "simple_binary"
+ G
+
+ bundle! "install"
+ bundle! :clean
+ expect(out).to eq("Removing very_simple_binary (1.0)")
+
+ expect(very_simple_binary_extensions_dir).not_to exist
+ expect(simple_binary_extensions_dir).to exist
+ end
+end
diff --git a/spec/bundler/commands/config_spec.rb b/spec/bundler/commands/config_spec.rb
new file mode 100644
index 0000000000..9e49357465
--- /dev/null
+++ b/spec/bundler/commands/config_spec.rb
@@ -0,0 +1,384 @@
+# frozen_string_literal: true
+
+RSpec.describe ".bundle/config" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "1.0.0"
+ G
+ end
+
+ describe "config" do
+ before { bundle "config foo bar" }
+
+ it "prints a detailed report of local and user configuration" do
+ bundle "config"
+
+ expect(out).to include("Settings are listed in order of priority. The top value will be used")
+ expect(out).to include("foo\nSet for the current user")
+ expect(out).to include(": \"bar\"")
+ end
+
+ context "given --parseable flag" do
+ it "prints a minimal report of local and user configuration" do
+ bundle "config --parseable"
+ expect(out).to include("foo=bar")
+ end
+
+ context "with global config" do
+ it "prints config assigned to local scope" do
+ bundle "config --local foo bar2"
+ bundle "config --parseable"
+ expect(out).to include("foo=bar2")
+ end
+ end
+
+ context "with env overwrite" do
+ it "prints config with env" do
+ bundle "config --parseable", :env => { "BUNDLE_FOO" => "bar3" }
+ expect(out).to include("foo=bar3")
+ end
+ end
+ end
+ end
+
+ describe "BUNDLE_APP_CONFIG" do
+ it "can be moved with an environment variable" do
+ ENV["BUNDLE_APP_CONFIG"] = tmp("foo/bar").to_s
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ expect(bundled_app(".bundle")).not_to exist
+ expect(tmp("foo/bar/config")).to exist
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "can provide a relative path with the environment variable" do
+ FileUtils.mkdir_p bundled_app("omg")
+ Dir.chdir bundled_app("omg")
+
+ ENV["BUNDLE_APP_CONFIG"] = "../foo"
+ bundle "install", forgotten_command_line_options(:path => "vendor/bundle")
+
+ expect(bundled_app(".bundle")).not_to exist
+ expect(bundled_app("../foo/config")).to exist
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ describe "global" do
+ before(:each) { bundle :install }
+
+ it "is the default" do
+ bundle "config foo global"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("global")
+ end
+
+ it "can also be set explicitly" do
+ bundle! "config --global foo global"
+ run! "puts Bundler.settings[:foo]"
+ expect(out).to eq("global")
+ end
+
+ it "has lower precedence than local" do
+ bundle "config --local foo local"
+
+ bundle "config --global foo global"
+ expect(out).to match(/Your application has set foo to "local"/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("local")
+ end
+
+ it "has lower precedence than env" do
+ begin
+ ENV["BUNDLE_FOO"] = "env"
+
+ bundle "config --global foo global"
+ expect(out).to match(/You have a bundler environment variable for foo set to "env"/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("env")
+ ensure
+ ENV.delete("BUNDLE_FOO")
+ end
+ end
+
+ it "can be deleted" do
+ bundle "config --global foo global"
+ bundle "config --delete foo"
+
+ run "puts Bundler.settings[:foo] == nil"
+ expect(out).to eq("true")
+ end
+
+ it "warns when overriding" do
+ bundle "config --global foo previous"
+ bundle "config --global foo global"
+ expect(out).to match(/You are replacing the current global value of foo/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("global")
+ end
+
+ it "does not warn when using the same value twice" do
+ bundle "config --global foo value"
+ bundle "config --global foo value"
+ expect(out).not_to match(/You are replacing the current global value of foo/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("value")
+ end
+
+ it "expands the path at time of setting" do
+ bundle "config --global local.foo .."
+ run "puts Bundler.settings['local.foo']"
+ expect(out).to eq(File.expand_path(Dir.pwd + "/.."))
+ end
+
+ it "saves with parseable option" do
+ bundle "config --global --parseable foo value"
+ expect(out).to eq("foo=value")
+ run "puts Bundler.settings['foo']"
+ expect(out).to eq("value")
+ end
+
+ context "when replacing a current value with the parseable flag" do
+ before { bundle "config --global foo value" }
+ it "prints the current value in a parseable format" do
+ bundle "config --global --parseable foo value2"
+ expect(out).to eq "foo=value2"
+ run "puts Bundler.settings['foo']"
+ expect(out).to eq("value2")
+ end
+ end
+ end
+
+ describe "local" do
+ before(:each) { bundle :install }
+
+ it "can also be set explicitly" do
+ bundle "config --local foo local"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("local")
+ end
+
+ it "has higher precedence than env" do
+ begin
+ ENV["BUNDLE_FOO"] = "env"
+ bundle "config --local foo local"
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("local")
+ ensure
+ ENV.delete("BUNDLE_FOO")
+ end
+ end
+
+ it "can be deleted" do
+ bundle "config --local foo local"
+ bundle "config --delete foo"
+
+ run "puts Bundler.settings[:foo] == nil"
+ expect(out).to eq("true")
+ end
+
+ it "warns when overriding" do
+ bundle "config --local foo previous"
+ bundle "config --local foo local"
+ expect(out).to match(/You are replacing the current local value of foo/)
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("local")
+ end
+
+ it "expands the path at time of setting" do
+ bundle "config --local local.foo .."
+ run "puts Bundler.settings['local.foo']"
+ expect(out).to eq(File.expand_path(Dir.pwd + "/.."))
+ end
+
+ it "can be deleted with parseable option" do
+ bundle "config --local foo value"
+ bundle "config --delete --parseable foo"
+ expect(out).to eq ""
+ run "puts Bundler.settings['foo'] == nil"
+ expect(out).to eq("true")
+ end
+ end
+
+ describe "env" do
+ before(:each) { bundle :install }
+
+ it "can set boolean properties via the environment" do
+ ENV["BUNDLE_FROZEN"] = "true"
+
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("true")
+ end
+
+ it "can set negative boolean properties via the environment" do
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("false")
+
+ ENV["BUNDLE_FROZEN"] = "false"
+
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("false")
+
+ ENV["BUNDLE_FROZEN"] = "0"
+
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("false")
+
+ ENV["BUNDLE_FROZEN"] = ""
+
+ run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end"
+ expect(out).to eq("false")
+ end
+
+ it "can set properties with periods via the environment" do
+ ENV["BUNDLE_FOO__BAR"] = "baz"
+
+ run "puts Bundler.settings['foo.bar']"
+ expect(out).to eq("baz")
+ end
+ end
+
+ describe "parseable option" do
+ it "prints an empty string" do
+ bundle "config foo --parseable"
+
+ expect(out).to eq ""
+ end
+
+ it "only prints the value of the config" do
+ bundle "config foo local"
+ bundle "config foo --parseable"
+
+ expect(out).to eq "foo=local"
+ end
+
+ it "can print global config" do
+ bundle "config --global bar value"
+ bundle "config bar --parseable"
+
+ expect(out).to eq "bar=value"
+ end
+
+ it "prefers local config over global" do
+ bundle "config --local bar value2"
+ bundle "config --global bar value"
+ bundle "config bar --parseable"
+
+ expect(out).to eq "bar=value2"
+ end
+ end
+
+ describe "gem mirrors" do
+ before(:each) { bundle :install }
+
+ it "configures mirrors using keys with `mirror.`" do
+ bundle "config --local mirror.http://gems.example.org http://gem-mirror.example.org"
+ run(<<-E)
+Bundler.settings.gem_mirrors.each do |k, v|
+ puts "\#{k} => \#{v}"
+end
+E
+ expect(out).to eq("http://gems.example.org/ => http://gem-mirror.example.org/")
+ end
+ end
+
+ describe "quoting" do
+ before(:each) { gemfile "# no gems" }
+ let(:long_string) do
+ "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \
+ "--with-xslt-dir=/usr/pkg"
+ end
+
+ it "saves quotes" do
+ bundle "config foo something\\'"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("something'")
+ end
+
+ it "doesn't return quotes around values", :ruby => "1.9" do
+ bundle "config foo '1'"
+ run "puts Bundler.settings.send(:global_config_file).read"
+ expect(out).to include('"1"')
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq("1")
+ end
+
+ it "doesn't duplicate quotes around values", :if => (RUBY_VERSION >= "2.1") do
+ bundled_app(".bundle").mkpath
+ File.open(bundled_app(".bundle/config"), "w") do |f|
+ f.write 'BUNDLE_FOO: "$BUILD_DIR"'
+ end
+
+ bundle "config bar baz"
+ run "puts Bundler.settings.send(:local_config_file).read"
+
+ # Starting in Ruby 2.1, YAML automatically adds double quotes
+ # around some values, including $ and newlines.
+ expect(out).to include('BUNDLE_FOO: "$BUILD_DIR"')
+ end
+
+ it "doesn't duplicate quotes around long wrapped values" do
+ bundle "config foo #{long_string}"
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq(long_string)
+
+ bundle "config bar baz"
+
+ run "puts Bundler.settings[:foo]"
+ expect(out).to eq(long_string)
+ end
+ end
+
+ describe "very long lines" do
+ before(:each) { bundle :install }
+
+ let(:long_string) do
+ "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \
+ "--with-xslt-dir=/usr/pkg"
+ end
+
+ let(:long_string_without_special_characters) do
+ "here is quite a long string that will wrap to a second line but will not be " \
+ "surrounded by quotes"
+ end
+
+ it "doesn't wrap values" do
+ bundle "config foo #{long_string}"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to match(long_string)
+ end
+
+ it "can read wrapped unquoted values" do
+ bundle "config foo #{long_string_without_special_characters}"
+ run "puts Bundler.settings[:foo]"
+ expect(out).to match(long_string_without_special_characters)
+ end
+ end
+end
+
+RSpec.describe "setting gemfile via config" do
+ context "when only the non-default Gemfile exists" do
+ it "persists the gemfile location to .bundle/config" do
+ File.open(bundled_app("NotGemfile"), "w") do |f|
+ f.write <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+ end
+
+ bundle "config --local gemfile #{bundled_app("NotGemfile")}"
+ expect(File.exist?(".bundle/config")).to eq(true)
+
+ bundle "config"
+ expect(out).to include("NotGemfile")
+ end
+ end
+end
diff --git a/spec/bundler/commands/console_spec.rb b/spec/bundler/commands/console_spec.rb
new file mode 100644
index 0000000000..9bf66e8f5b
--- /dev/null
+++ b/spec/bundler/commands/console_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle console", :bundler => "< 2" do
+ before :each do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+ G
+ end
+
+ it "starts IRB with the default group loaded" do
+ bundle "console" do |input, _, _|
+ input.puts("puts RACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+
+ it "uses IRB as default console" do
+ bundle "console" do |input, _, _|
+ input.puts("__method__")
+ input.puts("exit")
+ end
+ expect(out).to include(":irb_binding")
+ end
+
+ it "starts another REPL if configured as such" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "pry"
+ G
+ bundle "config console pry"
+
+ bundle "console" do |input, _, _|
+ input.puts("__method__")
+ input.puts("exit")
+ end
+ expect(out).to include(":__pry__")
+ end
+
+ it "falls back to IRB if the other REPL isn't available" do
+ bundle "config console pry"
+ # make sure pry isn't there
+
+ bundle "console" do |input, _, _|
+ input.puts("__method__")
+ input.puts("exit")
+ end
+ expect(out).to include(":irb_binding")
+ end
+
+ it "doesn't load any other groups" do
+ bundle "console" do |input, _, _|
+ input.puts("puts ACTIVESUPPORT")
+ input.puts("exit")
+ end
+ expect(out).to include("NameError")
+ end
+
+ describe "when given a group" do
+ it "loads the given group" do
+ bundle "console test" do |input, _, _|
+ input.puts("puts ACTIVESUPPORT")
+ input.puts("exit")
+ end
+ expect(out).to include("2.3.5")
+ end
+
+ it "loads the default group" do
+ bundle "console test" do |input, _, _|
+ input.puts("puts RACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+
+ it "doesn't load other groups" do
+ bundle "console test" do |input, _, _|
+ input.puts("puts RACK_MIDDLEWARE")
+ input.puts("exit")
+ end
+ expect(out).to include("NameError")
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+ gem "foo"
+ G
+
+ bundle "config auto_install 1"
+ bundle :console do |input, _, _|
+ input.puts("puts 'hello'")
+ input.puts("exit")
+ end
+ expect(out).to include("Installing foo 1.0")
+ expect(out).to include("hello")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+end
diff --git a/spec/bundler/commands/doctor_spec.rb b/spec/bundler/commands/doctor_spec.rb
new file mode 100644
index 0000000000..5260e6cb36
--- /dev/null
+++ b/spec/bundler/commands/doctor_spec.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+require "find"
+require "stringio"
+require "bundler/cli"
+require "bundler/cli/doctor"
+
+RSpec.describe "bundle doctor" do
+ before(:each) do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ @stdout = StringIO.new
+
+ [:error, :warn].each do |method|
+ allow(Bundler.ui).to receive(method).and_wrap_original do |m, message|
+ m.call message
+ @stdout.puts message
+ end
+ end
+ end
+
+ context "when all files in home are readable/writable" do
+ before(:each) do
+ stat = double("stat")
+ unwritable_file = double("file")
+ allow(Find).to receive(:find).with(Bundler.home.to_s) { [unwritable_file] }
+ allow(File).to receive(:stat).with(unwritable_file) { stat }
+ allow(stat).to receive(:uid) { Process.uid }
+ allow(File).to receive(:writable?).with(unwritable_file) { true }
+ allow(File).to receive(:readable?).with(unwritable_file) { true }
+ end
+
+ it "exits with no message if the installed gem has no C extensions" do
+ expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error
+ expect(@stdout.string).to be_empty
+ end
+
+ it "exits with no message if the installed gem's C extension dylib breakage is fine" do
+ doctor = Bundler::CLI::Doctor.new({})
+ expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"]
+ expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/lib/libSystem.dylib"]
+ allow(File).to receive(:exist?).and_call_original
+ allow(File).to receive(:exist?).with("/usr/lib/libSystem.dylib").and_return(true)
+ expect { doctor.run }.not_to(raise_error, @stdout.string)
+ expect(@stdout.string).to be_empty
+ end
+
+ it "exits with a message if one of the linked libraries is missing" do
+ doctor = Bundler::CLI::Doctor.new({})
+ expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"]
+ expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib"]
+ allow(File).to receive(:exist?).and_call_original
+ allow(File).to receive(:exist?).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_return(false)
+ expect { doctor.run }.to raise_error(Bundler::ProductionError, strip_whitespace(<<-E).strip), @stdout.string
+ The following gems are missing OS dependencies:
+ * bundler: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib
+ * rack: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib
+ E
+ end
+ end
+
+ context "when home contains files that are not readable/writable" do
+ before(:each) do
+ @stat = double("stat")
+ @unwritable_file = double("file")
+ allow(Find).to receive(:find).with(Bundler.home.to_s) { [@unwritable_file] }
+ allow(File).to receive(:stat).with(@unwritable_file) { @stat }
+ end
+
+ it "exits with an error if home contains files that are not readable/writable" do
+ allow(@stat).to receive(:uid) { Process.uid }
+ allow(File).to receive(:writable?).with(@unwritable_file) { false }
+ allow(File).to receive(:readable?).with(@unwritable_file) { false }
+ expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error
+ expect(@stdout.string).to include(
+ "Files exist in the Bundler home that are not readable/writable by the current user. These files are:\n - #{@unwritable_file}"
+ )
+ expect(@stdout.string).not_to include("No issues")
+ end
+
+ context "when home contains files that are not owned by the current process" do
+ before(:each) do
+ allow(@stat).to receive(:uid) { 0o0000 }
+ end
+
+ it "exits with an error if home contains files that are not readable/writable and are not owned by the current user" do
+ allow(File).to receive(:writable?).with(@unwritable_file) { false }
+ allow(File).to receive(:readable?).with(@unwritable_file) { false }
+ expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error
+ expect(@stdout.string).to include(
+ "Files exist in the Bundler home that are owned by another user, and are not readable/writable. These files are:\n - #{@unwritable_file}"
+ )
+ expect(@stdout.string).not_to include("No issues")
+ end
+
+ it "exits with a warning if home contains files that are read/write but not owned by current user" do
+ allow(File).to receive(:writable?).with(@unwritable_file) { true }
+ allow(File).to receive(:readable?).with(@unwritable_file) { true }
+ expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error
+ expect(@stdout.string).to include(
+ "Files exist in the Bundler home that are owned by another user, but are still readable/writable. These files are:\n - #{@unwritable_file}"
+ )
+ expect(@stdout.string).not_to include("No issues")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb
new file mode 100644
index 0000000000..6835305d55
--- /dev/null
+++ b/spec/bundler/commands/exec_spec.rb
@@ -0,0 +1,858 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle exec" do
+ let(:system_gems_to_install) { %w[rack-1.0.0 rack-0.9.1] }
+ before :each do
+ system_gems(system_gems_to_install, :path => :bundle_path)
+ end
+
+ it "works with --gemfile flag" do
+ create_file "CustomGemfile", <<-G
+ gem "rack", "1.0.0"
+ G
+
+ bundle "exec --gemfile CustomGemfile rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "activates the correct gem" do
+ gemfile <<-G
+ gem "rack", "0.9.1"
+ G
+
+ bundle "exec rackup"
+ expect(out).to eq("0.9.1")
+ end
+
+ it "works when the bins are in ~/.bundle" do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ bundle "exec rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "works when running from a random directory", :ruby_repo do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ bundle "exec 'cd #{tmp("gems")} && rackup'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+
+ expect(out).to include("1.0.0")
+ end
+
+ it "works when exec'ing something else" do
+ install_gemfile 'gem "rack"'
+ bundle "exec echo exec"
+ expect(out).to eq("exec")
+ end
+
+ it "works when exec'ing to ruby" do
+ install_gemfile 'gem "rack"'
+ bundle "exec ruby -e 'puts %{hi}'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(out).to eq("hi")
+ end
+
+ it "accepts --verbose" do
+ install_gemfile 'gem "rack"'
+ bundle "exec --verbose echo foobar"
+ expect(out).to eq("foobar")
+ end
+
+ it "passes --verbose to command if it is given after the command" do
+ install_gemfile 'gem "rack"'
+ bundle "exec echo --verbose"
+ expect(out).to eq("--verbose")
+ end
+
+ it "handles --keep-file-descriptors" do
+ require "tempfile"
+
+ command = Tempfile.new("io-test")
+ command.sync = true
+ command.write <<-G
+ if ARGV[0]
+ IO.for_fd(ARGV[0].to_i)
+ else
+ require 'tempfile'
+ io = Tempfile.new("io-test-fd")
+ args = %W[#{Gem.ruby} -I#{lib} #{bindir.join("bundle")} exec --keep-file-descriptors #{Gem.ruby} #{command.path} \#{io.to_i}]
+ args << { io.to_i => io } if RUBY_VERSION >= "2.0"
+ exec(*args)
+ end
+ G
+
+ install_gemfile ""
+ with_env_vars "RUBYOPT" => "-r#{spec_dir.join("support/hax")}" do
+ sys_exec "#{Gem.ruby} #{command.path}"
+ end
+
+ if Bundler.current_ruby.ruby_2?
+ expect(out).to eq("")
+ else
+ expect(out).to eq("Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec.")
+ end
+
+ expect(err).to lack_errors
+ end
+
+ it "accepts --keep-file-descriptors" do
+ install_gemfile ""
+ bundle "exec --keep-file-descriptors echo foobar"
+
+ expect(err).to lack_errors
+ end
+
+ it "can run a command named --verbose" do
+ install_gemfile 'gem "rack"'
+ File.open("--verbose", "w") do |f|
+ f.puts "#!/bin/sh"
+ f.puts "echo foobar"
+ end
+ File.chmod(0o744, "--verbose")
+ with_path_as(".") do
+ bundle "exec -- --verbose"
+ end
+ expect(out).to eq("foobar")
+ end
+
+ it "handles different versions in different bundles" do
+ build_repo2 do
+ build_gem "rack_two", "1.0.0" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "0.9.1"
+ G
+
+ Dir.chdir bundled_app2 do
+ install_gemfile bundled_app2("Gemfile"), <<-G
+ source "file://#{gem_repo2}"
+ gem "rack_two", "1.0.0"
+ G
+ end
+
+ bundle! "exec rackup"
+
+ expect(out).to eq("0.9.1")
+
+ Dir.chdir bundled_app2 do
+ bundle! "exec rackup"
+ expect(out).to eq("1.0.0")
+ end
+ end
+
+ it "handles gems installed with --without" do
+ install_gemfile <<-G, forgotten_command_line_options(:without => "middleware")
+ source "file://#{gem_repo1}"
+ gem "rack" # rack 0.9.1 and 1.0 exist
+
+ group :middleware do
+ gem "rack_middleware" # rack_middleware depends on rack 0.9.1
+ end
+ G
+
+ bundle "exec rackup"
+
+ expect(out).to eq("0.9.1")
+ expect(the_bundle).not_to include_gems "rack_middleware 1.0"
+ end
+
+ it "does not duplicate already exec'ed RUBYOPT" do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ rubyopt = ENV["RUBYOPT"]
+ rubyopt = "-rbundler/setup #{rubyopt}"
+
+ bundle "exec 'echo $RUBYOPT'"
+ expect(out).to have_rubyopts(rubyopt)
+
+ bundle "exec 'echo $RUBYOPT'", :env => { "RUBYOPT" => rubyopt }
+ expect(out).to have_rubyopts(rubyopt)
+ end
+
+ it "does not duplicate already exec'ed RUBYLIB" do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ rubylib = ENV["RUBYLIB"]
+ rubylib = "#{rubylib}".split(File::PATH_SEPARATOR).unshift "#{bundler_path}"
+ rubylib = rubylib.uniq.join(File::PATH_SEPARATOR)
+
+ bundle "exec 'echo $RUBYLIB'"
+ expect(out).to include(rubylib)
+
+ bundle "exec 'echo $RUBYLIB'", :env => { "RUBYLIB" => rubylib }
+ expect(out).to include(rubylib)
+ end
+
+ it "errors nicely when the argument doesn't exist" do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ bundle "exec foobarbaz"
+ expect(exitstatus).to eq(127) if exitstatus
+ expect(out).to include("bundler: command not found: foobarbaz")
+ expect(out).to include("Install missing gem executables with `bundle install`")
+ end
+
+ it "errors nicely when the argument is not executable" do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ bundle "exec touch foo"
+ bundle "exec ./foo"
+ expect(exitstatus).to eq(126) if exitstatus
+ expect(out).to include("bundler: not executable: ./foo")
+ end
+
+ it "errors nicely when no arguments are passed" do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ bundle "exec"
+ expect(exitstatus).to eq(128) if exitstatus
+ expect(out).to include("bundler: exec needs a command to run")
+ end
+
+ it "raises a helpful error when exec'ing to something outside of the bundle", :ruby_repo, :rubygems => ">= 2.5.2" do
+ bundle! "config clean false" # want to keep the rackup binstub
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "with_license"
+ G
+ [true, false].each do |l|
+ bundle! "config disable_exec_load #{l}"
+ bundle "exec rackup"
+ expect(last_command.stderr).to include "can't find executable rackup for gem rack. rack is not currently included in the bundle, perhaps you meant to add it to your Gemfile?"
+ end
+ end
+
+ # Different error message on old RG versions (before activate_bin_path) because they
+ # called `Kernel#gem` directly
+ it "raises a helpful error when exec'ing to something outside of the bundle", :rubygems => "< 2.5.2" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "with_license"
+ G
+ [true, false].each do |l|
+ bundle! "config disable_exec_load #{l}"
+ bundle "exec rackup", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(last_command.stderr).to include "rack is not part of the bundle. Add it to your Gemfile."
+ end
+ end
+
+ describe "with help flags" do
+ each_prefix = proc do |string, &blk|
+ 1.upto(string.length) {|l| blk.call(string[0, l]) }
+ end
+ each_prefix.call("exec") do |exec|
+ describe "when #{exec} is used" do
+ before(:each) do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ create_file("print_args", <<-'RUBY')
+ #!/usr/bin/env ruby
+ puts "args: #{ARGV.inspect}"
+ RUBY
+ bundled_app("print_args").chmod(0o755)
+ end
+
+ it "shows executable's man page when --help is after the executable" do
+ bundle "#{exec} print_args --help"
+ expect(out).to eq('args: ["--help"]')
+ end
+
+ it "shows executable's man page when --help is after the executable and an argument" do
+ bundle "#{exec} print_args foo --help"
+ expect(out).to eq('args: ["foo", "--help"]')
+
+ bundle "#{exec} print_args foo bar --help"
+ expect(out).to eq('args: ["foo", "bar", "--help"]')
+
+ bundle "#{exec} print_args foo --help bar"
+ expect(out).to eq('args: ["foo", "--help", "bar"]')
+ end
+
+ it "shows executable's man page when the executable has a -" do
+ FileUtils.mv(bundled_app("print_args"), bundled_app("docker-template"))
+ bundle "#{exec} docker-template build discourse --help"
+ expect(out).to eq('args: ["build", "discourse", "--help"]')
+ end
+
+ it "shows executable's man page when --help is after another flag" do
+ bundle "#{exec} print_args --bar --help"
+ expect(out).to eq('args: ["--bar", "--help"]')
+ end
+
+ it "uses executable's original behavior for -h" do
+ bundle "#{exec} print_args -h"
+ expect(out).to eq('args: ["-h"]')
+ end
+
+ it "shows bundle-exec's man page when --help is between exec and the executable" do
+ with_fake_man do
+ bundle "#{exec} --help cat"
+ end
+ expect(out).to include(%(["#{root}/man/bundle-exec.1"]))
+ end
+
+ it "shows bundle-exec's man page when --help is before exec" do
+ with_fake_man do
+ bundle "--help #{exec}"
+ end
+ expect(out).to include(%(["#{root}/man/bundle-exec.1"]))
+ end
+
+ it "shows bundle-exec's man page when -h is before exec" do
+ with_fake_man do
+ bundle "-h #{exec}"
+ end
+ expect(out).to include(%(["#{root}/man/bundle-exec.1"]))
+ end
+
+ it "shows bundle-exec's man page when --help is after exec" do
+ with_fake_man do
+ bundle "#{exec} --help"
+ end
+ expect(out).to include(%(["#{root}/man/bundle-exec.1"]))
+ end
+
+ it "shows bundle-exec's man page when -h is after exec" do
+ with_fake_man do
+ bundle "#{exec} -h"
+ end
+ expect(out).to include(%(["#{root}/man/bundle-exec.1"]))
+ end
+ end
+ end
+ end
+
+ describe "with gem executables" do
+ describe "run from a random directory", :ruby_repo do
+ before(:each) do
+ install_gemfile <<-G
+ gem "rack"
+ G
+ end
+
+ it "works when unlocked" do
+ bundle "exec 'cd #{tmp("gems")} && rackup'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(out).to eq("1.0.0")
+ expect(out).to include("1.0.0")
+ end
+
+ it "works when locked" do
+ expect(the_bundle).to be_locked
+ bundle "exec 'cd #{tmp("gems")} && rackup'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(out).to include("1.0.0")
+ end
+ end
+
+ describe "from gems bundled via :path" do
+ before(:each) do
+ build_lib "fizz", :path => home("fizz") do |s|
+ s.executables = "fizz"
+ end
+
+ install_gemfile <<-G
+ gem "fizz", :path => "#{File.expand_path(home("fizz"))}"
+ G
+ end
+
+ it "works when unlocked" do
+ bundle "exec fizz"
+ expect(out).to eq("1.0")
+ end
+
+ it "works when locked" do
+ expect(the_bundle).to be_locked
+
+ bundle "exec fizz"
+ expect(out).to eq("1.0")
+ end
+ end
+
+ describe "from gems bundled via :git" do
+ before(:each) do
+ build_git "fizz_git" do |s|
+ s.executables = "fizz_git"
+ end
+
+ install_gemfile <<-G
+ gem "fizz_git", :git => "#{lib_path("fizz_git-1.0")}"
+ G
+ end
+
+ it "works when unlocked" do
+ bundle "exec fizz_git"
+ expect(out).to eq("1.0")
+ end
+
+ it "works when locked" do
+ expect(the_bundle).to be_locked
+ bundle "exec fizz_git"
+ expect(out).to eq("1.0")
+ end
+ end
+
+ describe "from gems bundled via :git with no gemspec" do
+ before(:each) do
+ build_git "fizz_no_gemspec", :gemspec => false do |s|
+ s.executables = "fizz_no_gemspec"
+ end
+
+ install_gemfile <<-G
+ gem "fizz_no_gemspec", "1.0", :git => "#{lib_path("fizz_no_gemspec-1.0")}"
+ G
+ end
+
+ it "works when unlocked" do
+ bundle "exec fizz_no_gemspec"
+ expect(out).to eq("1.0")
+ end
+
+ it "works when locked" do
+ expect(the_bundle).to be_locked
+ bundle "exec fizz_no_gemspec"
+ expect(out).to eq("1.0")
+ end
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "0.9.1"
+ gem "foo"
+ G
+
+ bundle "config auto_install 1"
+ bundle "exec rackup"
+ expect(out).to include("Installing foo 1.0")
+ end
+
+ describe "with gems bundled via :path with invalid gemspecs", :ruby_repo do
+ it "outputs the gemspec validation errors", :rubygems => ">= 1.7.2" do
+ build_lib "foo"
+
+ gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s
+ File.open(gemspec, "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = 'foo'
+ s.version = '1.0'
+ s.summary = 'TODO: Add summary'
+ s.authors = 'Me'
+ end
+ G
+ end
+
+ install_gemfile <<-G
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "exec irb"
+
+ expect(err).to match("The gemspec at #{lib_path("foo-1.0").join("foo.gemspec")} is not valid")
+ expect(err).to match('"TODO" is not a summary')
+ end
+ end
+
+ describe "with gems bundled for deployment" do
+ it "works when calling bundler from another script" do
+ gemfile <<-G
+ module Monkey
+ def bin_path(a,b,c)
+ raise Gem::GemNotFoundException.new('Fail')
+ end
+ end
+ Bundler.rubygems.extend(Monkey)
+ G
+ bundle "install --deployment"
+ bundle "exec ruby -e '`#{bindir.join("bundler")} -v`; puts $?.success?'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(out).to match("true")
+ end
+ end
+
+ context "`load`ing a ruby file instead of `exec`ing" do
+ let(:path) { bundled_app("ruby_executable") }
+ let(:shebang) { "#!/usr/bin/env ruby" }
+ let(:executable) { <<-RUBY.gsub(/^ */, "").strip }
+ #{shebang}
+
+ require "rack"
+ puts "EXEC: \#{caller.grep(/load/).empty? ? 'exec' : 'load'}"
+ puts "ARGS: \#{$0} \#{ARGV.join(' ')}"
+ puts "RACK: \#{RACK}"
+ process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip
+ puts "PROCESS: \#{process_title}"
+ RUBY
+
+ before do
+ path.open("w") {|f| f << executable }
+ path.chmod(0o755)
+
+ install_gemfile <<-G
+ gem "rack"
+ G
+ end
+
+ let(:exec) { "EXEC: load" }
+ let(:args) { "ARGS: #{path} arg1 arg2" }
+ let(:rack) { "RACK: 1.0.0" }
+ let(:process) do
+ title = "PROCESS: #{path}"
+ title += " arg1 arg2" if RUBY_VERSION >= "2.1"
+ title
+ end
+ let(:exit_code) { 0 }
+ let(:expected) { [exec, args, rack, process].join("\n") }
+ let(:expected_err) { "" }
+
+ subject { bundle "exec #{path} arg1 arg2", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" } }
+
+ shared_examples_for "it runs" do
+ it "like a normally executed executable" do
+ subject
+ expect(exitstatus).to eq(exit_code) if exitstatus
+ expect(last_command.stderr).to eq(expected_err)
+ expect(last_command.stdout).to eq(expected)
+ end
+ end
+
+ it_behaves_like "it runs"
+
+ context "the executable exits explicitly" do
+ let(:executable) { super() << "\nexit #{exit_code}\nputs 'POST_EXIT'\n" }
+
+ context "with exit 0" do
+ it_behaves_like "it runs"
+ end
+
+ context "with exit 99" do
+ let(:exit_code) { 99 }
+ it_behaves_like "it runs"
+ end
+ end
+
+ context "the executable exits by SignalException" do
+ let(:executable) do
+ ex = super()
+ ex << "\n"
+ if LessThanProc.with(RUBY_VERSION).call("1.9")
+ # Ruby < 1.9 needs a flush for a exit by signal, later
+ # rubies do not
+ ex << "STDOUT.flush\n"
+ end
+ ex << "raise SignalException, 'SIGTERM'\n"
+ ex
+ end
+ let(:expected_err) { ENV["TRAVIS"] ? "Terminated" : "" }
+ let(:exit_code) do
+ # signal mask 128 + plus signal 15 -> TERM
+ # this is specified by C99
+ 128 + 15
+ end
+ it_behaves_like "it runs"
+ end
+
+ context "the executable is empty", :bundler => "< 2" do
+ let(:executable) { "" }
+
+ let(:exit_code) { 0 }
+ let(:expected) { "#{path} is empty" }
+ let(:expected_err) { "" }
+ if LessThanProc.with(RUBY_VERSION).call("1.9")
+ # Kernel#exec in ruby < 1.9 will raise Errno::ENOEXEC if the command content is empty,
+ # even if the command is set as an executable.
+ pending "Kernel#exec is different"
+ else
+ it_behaves_like "it runs"
+ end
+ end
+
+ context "the executable is empty", :bundler => "2" do
+ let(:executable) { "" }
+
+ let(:exit_code) { 0 }
+ let(:expected_err) { "#{path} is empty" }
+ let(:expected) { "" }
+ it_behaves_like "it runs"
+ end
+
+ context "the executable raises", :bundler => "< 2" do
+ let(:executable) { super() << "\nraise 'ERROR'" }
+ let(:exit_code) { 1 }
+ let(:expected) { super() << "\nbundler: failed to load command: #{path} (#{path})" }
+ let(:expected_err) do
+ "RuntimeError: ERROR\n #{path}:10" +
+ (Bundler.current_ruby.ruby_18? ? "" : ":in `<top (required)>'")
+ end
+ it_behaves_like "it runs"
+ end
+
+ context "the executable raises", :bundler => "2" do
+ let(:executable) { super() << "\nraise 'ERROR'" }
+ let(:exit_code) { 1 }
+ let(:expected_err) do
+ "bundler: failed to load command: #{path} (#{path})" \
+ "\nRuntimeError: ERROR\n #{path}:10:in `<top (required)>'"
+ end
+ it_behaves_like "it runs"
+ end
+
+ context "the executable raises an error without a backtrace", :bundler => "< 2" do
+ let(:executable) { super() << "\nclass Err < Exception\ndef backtrace; end;\nend\nraise Err" }
+ let(:exit_code) { 1 }
+ let(:expected) { super() << "\nbundler: failed to load command: #{path} (#{path})" }
+ let(:expected_err) { "Err: Err" }
+
+ it_behaves_like "it runs"
+ end
+
+ context "the executable raises an error without a backtrace", :bundler => "2" do
+ let(:executable) { super() << "\nclass Err < Exception\ndef backtrace; end;\nend\nraise Err" }
+ let(:exit_code) { 1 }
+ let(:expected_err) { "bundler: failed to load command: #{path} (#{path})\nErr: Err" }
+ let(:expected) { super() }
+
+ it_behaves_like "it runs"
+ end
+
+ context "when the file uses the current ruby shebang", :ruby_repo do
+ let(:shebang) { "#!#{Gem.ruby}" }
+ it_behaves_like "it runs"
+ end
+
+ context "when Bundler.setup fails", :bundler => "< 2" do
+ before do
+ gemfile <<-G
+ gem 'rack', '2'
+ G
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ end
+
+ let(:exit_code) { Bundler::GemNotFound.new.status_code }
+ let(:expected) { <<-EOS.strip }
+\e[31mCould not find gem 'rack (= 2)' in any of the gem sources listed in your Gemfile.\e[0m
+\e[33mRun `bundle install` to install missing gems.\e[0m
+ EOS
+
+ it_behaves_like "it runs"
+ end
+
+ context "when Bundler.setup fails", :bundler => "2" do
+ before do
+ gemfile <<-G
+ gem 'rack', '2'
+ G
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ end
+
+ let(:exit_code) { Bundler::GemNotFound.new.status_code }
+ let(:expected) { <<-EOS.strip }
+\e[31mCould not find gem 'rack (= 2)' in locally installed gems.
+The source contains 'rack' at: 1.0.0\e[0m
+\e[33mRun `bundle install` to install missing gems.\e[0m
+ EOS
+
+ it_behaves_like "it runs"
+ end
+
+ context "when the executable exits non-zero via at_exit" do
+ let(:executable) { super() + "\n\nat_exit { $! ? raise($!) : exit(1) }" }
+ let(:exit_code) { 1 }
+
+ it_behaves_like "it runs"
+ end
+
+ context "when disable_exec_load is set" do
+ let(:exec) { "EXEC: exec" }
+ let(:process) { "PROCESS: ruby #{path} arg1 arg2" }
+
+ before do
+ bundle "config disable_exec_load true"
+ end
+
+ it_behaves_like "it runs"
+ end
+
+ context "regarding $0 and __FILE__" do
+ let(:executable) { super() + <<-'RUBY' }
+
+ puts "$0: #{$0.inspect}"
+ puts "__FILE__: #{__FILE__.inspect}"
+ RUBY
+
+ let(:expected) { super() + <<-EOS.chomp }
+
+$0: #{path.to_s.inspect}
+__FILE__: #{path.to_s.inspect}
+ EOS
+
+ it_behaves_like "it runs"
+
+ context "when the path is relative" do
+ let(:path) { super().relative_path_from(bundled_app) }
+
+ if LessThanProc.with(RUBY_VERSION).call("1.9")
+ pending "relative paths have ./ __FILE__"
+ else
+ it_behaves_like "it runs"
+ end
+ end
+
+ context "when the path is relative with a leading ./" do
+ let(:path) { Pathname.new("./#{super().relative_path_from(Pathname.pwd)}") }
+
+ if LessThanProc.with(RUBY_VERSION).call("< 1.9")
+ pending "relative paths with ./ have absolute __FILE__"
+ else
+ it_behaves_like "it runs"
+ end
+ end
+ end
+
+ context "signal handling" do
+ let(:test_signals) do
+ open3_reserved_signals = %w[CHLD CLD PIPE]
+ reserved_signals = %w[SEGV BUS ILL FPE VTALRM KILL STOP EXIT]
+ bundler_signals = %w[INT]
+
+ Signal.list.keys - (bundler_signals + reserved_signals + open3_reserved_signals)
+ end
+
+ context "signals being trapped by bundler" do
+ let(:executable) { strip_whitespace <<-RUBY }
+ #{shebang}
+ begin
+ Thread.new do
+ puts 'Started' # For process sync
+ STDOUT.flush
+ sleep 1 # ignore quality_spec
+ raise "Didn't receive INT at all"
+ end.join
+ rescue Interrupt
+ puts "foo"
+ end
+ RUBY
+
+ it "receives the signal", :ruby => ">= 1.9.3" do
+ bundle!("exec #{path}") do |_, o, thr|
+ o.gets # Consumes 'Started' and ensures that thread has started
+ Process.kill("INT", thr.pid)
+ end
+
+ expect(out).to eq("foo")
+ end
+ end
+
+ context "signals not being trapped by bunder" do
+ let(:executable) { strip_whitespace <<-RUBY }
+ #{shebang}
+
+ signals = #{test_signals.inspect}
+ result = signals.map do |sig|
+ Signal.trap(sig, "IGNORE")
+ end
+ puts result.select { |ret| ret == "IGNORE" }.count
+ RUBY
+
+ it "makes sure no unexpected signals are restored to DEFAULT" do
+ test_signals.each do |n|
+ Signal.trap(n, "IGNORE")
+ end
+
+ bundle!("exec #{path}")
+
+ expect(out).to eq(test_signals.count.to_s)
+ end
+ end
+ end
+ end
+
+ context "nested bundle exec" do
+ let(:system_gems_to_install) { super() << :bundler }
+
+ context "with shared gems disabled" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ bundle :install, :system_bundler => true, :path => "vendor/bundler"
+ end
+
+ it "overrides disable_shared_gems so bundler can be found" do
+ skip "bundler 1.16.x is not support with Ruby 2.6 on Travis CI" if RUBY_VERSION >= "2.6"
+
+ file = bundled_app("file_that_bundle_execs.rb")
+ create_file(file, <<-RB)
+ #!#{Gem.ruby}
+ puts `bundle exec echo foo`
+ RB
+ file.chmod(0o777)
+ bundle! "exec #{file}", :system_bundler => true
+ expect(out).to eq("foo")
+ end
+ end
+
+ context "with a system gem that shadows a default gem" do
+ let(:openssl_version) { "99.9.9" }
+ let(:expected) { ruby "gem 'openssl', '< 999999'; require 'openssl'; puts OpenSSL::VERSION", :artifice => nil }
+
+ it "only leaves the default gem in the stdlib available" do
+ skip "openssl isn't a default gem" if expected.empty?
+
+ install_gemfile! "" # must happen before installing the broken system gem
+
+ build_repo4 do
+ build_gem "openssl", openssl_version do |s|
+ s.write("lib/openssl.rb", <<-RB)
+ raise "custom openssl should not be loaded, it's not in the gemfile!"
+ RB
+ end
+ end
+
+ system_gems(:bundler, "openssl-#{openssl_version}", :gem_repo => gem_repo4)
+
+ file = bundled_app("require_openssl.rb")
+ create_file(file, <<-RB)
+ #!/usr/bin/env ruby
+ require "openssl"
+ puts OpenSSL::VERSION
+ warn Gem.loaded_specs.values.map(&:full_name)
+ RB
+ file.chmod(0o777)
+
+ aggregate_failures do
+ expect(bundle!("exec #{file}", :artifice => nil)).to eq(expected)
+ expect(bundle!("exec bundle exec #{file}", :artifice => nil)).to eq(expected)
+ expect(bundle!("exec ruby #{file}", :artifice => nil)).to eq(expected)
+ # Ignore expectaion for default bundler gem conflict.
+ unless ENV["BUNDLER_SPEC_SUB_VERSION"]
+ expect(run!(file.read, :artifice => nil)).to eq(expected)
+ end
+ end
+
+ # sanity check that we get the newer, custom version without bundler
+ sys_exec("#{Gem.ruby} #{file}")
+ expect(last_command.stderr).to include("custom openssl should not be loaded")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/commands/help_spec.rb b/spec/bundler/commands/help_spec.rb
new file mode 100644
index 0000000000..56b1b6f722
--- /dev/null
+++ b/spec/bundler/commands/help_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle help" do
+ # RubyGems 1.4+ no longer load gem plugins so this test is no longer needed
+ it "complains if older versions of bundler are installed", :rubygems => "< 1.4" do
+ system_gems "bundler-0.8.1"
+
+ bundle "help"
+ expect(err).to include("older than 0.9")
+ expect(err).to include("running `gem cleanup bundler`.")
+ end
+
+ it "uses mann when available" do
+ with_fake_man do
+ bundle "help gemfile"
+ end
+ expect(out).to eq(%(["#{root}/man/gemfile.5"]))
+ end
+
+ it "prefixes bundle commands with bundle- when finding the groff files" do
+ with_fake_man do
+ bundle "help install"
+ end
+ expect(out).to eq(%(["#{root}/man/bundle-install.1"]))
+ end
+
+ it "simply outputs the txt file when there is no man on the path" do
+ with_path_as("") do
+ bundle "help install"
+ end
+ expect(out).to match(/BUNDLE-INSTALL/)
+ end
+
+ it "still outputs the old help for commands that do not have man pages yet" do
+ bundle "help version"
+ expect(out).to include("Prints the bundler's version information")
+ end
+
+ it "looks for a binary and executes it with --help option if it's named bundler-<task>" do
+ File.open(tmp("bundler-testtasks"), "w", 0o755) do |f|
+ f.puts "#!/usr/bin/env ruby\nputs ARGV.join(' ')\n"
+ end
+
+ with_path_added(tmp) do
+ bundle "help testtasks"
+ end
+
+ expect(exitstatus).to be_zero if exitstatus
+ expect(out).to eq("--help")
+ end
+
+ it "is called when the --help flag is used after the command" do
+ with_fake_man do
+ bundle "install --help"
+ end
+ expect(out).to eq(%(["#{root}/man/bundle-install.1"]))
+ end
+
+ it "is called when the --help flag is used before the command" do
+ with_fake_man do
+ bundle "--help install"
+ end
+ expect(out).to eq(%(["#{root}/man/bundle-install.1"]))
+ end
+
+ it "is called when the -h flag is used before the command" do
+ with_fake_man do
+ bundle "-h install"
+ end
+ expect(out).to eq(%(["#{root}/man/bundle-install.1"]))
+ end
+
+ it "is called when the -h flag is used after the command" do
+ with_fake_man do
+ bundle "install -h"
+ end
+ expect(out).to eq(%(["#{root}/man/bundle-install.1"]))
+ end
+
+ it "has helpful output when using --help flag for a non-existent command" do
+ with_fake_man do
+ bundle "instill -h"
+ end
+ expect(out).to include('Could not find command "instill".')
+ end
+
+ it "is called when only using the --help flag" do
+ with_fake_man do
+ bundle "--help"
+ end
+ expect(out).to eq(%(["#{root}/man/bundle.1"]))
+
+ with_fake_man do
+ bundle "-h"
+ end
+ expect(out).to eq(%(["#{root}/man/bundle.1"]))
+ end
+end
diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb
new file mode 100644
index 0000000000..a9ab8fc210
--- /dev/null
+++ b/spec/bundler/commands/info_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle info" do
+ context "info from specific gem in gemfile" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+ end
+
+ it "prints information about the current gem" do
+ bundle "info rails"
+ expect(out).to include "* rails (2.3.2)
+\tSummary: This is just a fake gem for testing
+\tHomepage: http://example.com"
+ expect(out).to match(%r{Path\: .*\/rails\-2\.3\.2})
+ end
+
+ context "given a gem that is not installed" do
+ it "prints missing gem error" do
+ bundle "info foo"
+ expect(out).to eq "Could not find gem 'foo'."
+ end
+ end
+
+ context "given a default gem shippped in ruby", :ruby_repo do
+ it "prints information about the default gem", :if => (RUBY_VERSION >= "2.0") do
+ bundle "info rdoc"
+ expect(out).to include("* rdoc")
+ expect(out).to include("Default Gem: yes")
+ end
+ end
+
+ context "when gem does not have homepage" do
+ before do
+ build_repo1 do
+ build_gem "rails", "2.3.2" do |s|
+ s.executables = "rails"
+ s.summary = "Just another test gem"
+ end
+ end
+ end
+
+ it "excludes the homepage field from the output" do
+ expect(out).to_not include("Homepage:")
+ end
+ end
+
+ context "given --path option" do
+ it "prints the path to the gem" do
+ bundle "info rails"
+ expect(out).to match(%r{.*\/rails\-2\.3\.2})
+ end
+ end
+ end
+end
diff --git a/spec/bundler/commands/init_spec.rb b/spec/bundler/commands/init_spec.rb
new file mode 100644
index 0000000000..9b5bd95814
--- /dev/null
+++ b/spec/bundler/commands/init_spec.rb
@@ -0,0 +1,181 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle init" do
+ it "generates a Gemfile", :bundler => "< 2" do
+ bundle! :init
+ expect(out).to include("Writing new Gemfile")
+ expect(bundled_app("Gemfile")).to be_file
+ end
+
+ it "generates a gems.rb", :bundler => "2" do
+ bundle! :init
+ expect(out).to include("Writing new gems.rb")
+ expect(bundled_app("gems.rb")).to be_file
+ end
+
+ context "when a Gemfile already exists", :bundler => "< 2" do
+ before do
+ create_file "Gemfile", <<-G
+ gem "rails"
+ G
+ end
+
+ it "does not change existing Gemfiles" do
+ expect { bundle :init }.not_to change { File.read(bundled_app("Gemfile")) }
+ end
+
+ it "notifies the user that an existing Gemfile already exists" do
+ bundle :init
+ expect(out).to include("Gemfile already exists")
+ end
+ end
+
+ context "when gems.rb already exists", :bundler => ">= 2" do
+ before do
+ create_file("gems.rb", <<-G)
+ gem "rails"
+ G
+ end
+
+ it "does not change existing Gemfiles" do
+ expect { bundle :init }.not_to change { File.read(bundled_app("gems.rb")) }
+ end
+
+ it "notifies the user that an existing gems.rb already exists" do
+ bundle :init
+ expect(out).to include("gems.rb already exists")
+ end
+ end
+
+ context "when a Gemfile exists in a parent directory", :bundler => "< 2" do
+ let(:subdir) { "child_dir" }
+
+ it "lets users generate a Gemfile in a child directory" do
+ bundle! :init
+
+ FileUtils.mkdir bundled_app(subdir)
+
+ Dir.chdir bundled_app(subdir) do
+ bundle! :init
+ end
+
+ expect(out).to include("Writing new Gemfile")
+ expect(bundled_app("#{subdir}/Gemfile")).to be_file
+ end
+ end
+
+ context "when the dir is not writable by the current user" do
+ let(:subdir) { "child_dir" }
+
+ it "notifies the user that it can not write to it" do
+ FileUtils.mkdir bundled_app(subdir)
+ # chmod a-w it
+ mode = File.stat(bundled_app(subdir)).mode ^ 0o222
+ FileUtils.chmod mode, bundled_app(subdir)
+
+ Dir.chdir bundled_app(subdir) do
+ bundle :init
+ end
+
+ expect(out).to include("directory is not writable")
+ expect(Dir[bundled_app("#{subdir}/*")]).to be_empty
+ end
+ end
+
+ context "when a gems.rb file exists in a parent directory", :bundler => ">= 2" do
+ let(:subdir) { "child_dir" }
+
+ it "lets users generate a Gemfile in a child directory" do
+ bundle! :init
+
+ FileUtils.mkdir bundled_app(subdir)
+
+ Dir.chdir bundled_app(subdir) do
+ bundle! :init
+ end
+
+ expect(out).to include("Writing new gems.rb")
+ expect(bundled_app("#{subdir}/gems.rb")).to be_file
+ end
+ end
+
+ context "given --gemspec option", :bundler => "< 2" do
+ let(:spec_file) { tmp.join("test.gemspec") }
+
+ it "should generate from an existing gemspec" do
+ File.open(spec_file, "w") do |file|
+ file << <<-S
+ Gem::Specification.new do |s|
+ s.name = 'test'
+ s.add_dependency 'rack', '= 1.0.1'
+ s.add_development_dependency 'rspec', '1.2'
+ end
+ S
+ end
+
+ bundle :init, :gemspec => spec_file
+
+ gemfile = if Bundler::VERSION[0, 2] == "1."
+ bundled_app("Gemfile").read
+ else
+ bundled_app("gems.rb").read
+ end
+ expect(gemfile).to match(%r{source 'https://rubygems.org'})
+ expect(gemfile.scan(/gem "rack", "= 1.0.1"/).size).to eq(1)
+ expect(gemfile.scan(/gem "rspec", "= 1.2"/).size).to eq(1)
+ expect(gemfile.scan(/group :development/).size).to eq(1)
+ end
+
+ context "when gemspec file is invalid" do
+ it "notifies the user that specification is invalid" do
+ File.open(spec_file, "w") do |file|
+ file << <<-S
+ Gem::Specification.new do |s|
+ s.name = 'test'
+ s.invalid_method_name
+ end
+ S
+ end
+
+ bundle :init, :gemspec => spec_file
+ expect(last_command.bundler_err).to include("There was an error while loading `test.gemspec`")
+ end
+ end
+ end
+
+ context "when init_gems_rb setting is enabled" do
+ before { bundle "config init_gems_rb true" }
+
+ context "given --gemspec option", :bundler => "< 2" do
+ let(:spec_file) { tmp.join("test.gemspec") }
+
+ before do
+ File.open(spec_file, "w") do |file|
+ file << <<-S
+ Gem::Specification.new do |s|
+ s.name = 'test'
+ s.add_dependency 'rack', '= 1.0.1'
+ s.add_development_dependency 'rspec', '1.2'
+ end
+ S
+ end
+ end
+
+ it "should generate from an existing gemspec" do
+ bundle :init, :gemspec => spec_file
+
+ gemfile = bundled_app("gems.rb").read
+ expect(gemfile).to match(%r{source 'https://rubygems.org'})
+ expect(gemfile.scan(/gem "rack", "= 1.0.1"/).size).to eq(1)
+ expect(gemfile.scan(/gem "rspec", "= 1.2"/).size).to eq(1)
+ expect(gemfile.scan(/group :development/).size).to eq(1)
+ end
+
+ it "prints message to user" do
+ bundle :init, :gemspec => spec_file
+
+ expect(out).to include("Writing new gems.rb")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb
new file mode 100644
index 0000000000..b7ffc89a34
--- /dev/null
+++ b/spec/bundler/commands/inject_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle inject", :bundler => "< 2" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ context "without a lockfile" do
+ it "locks with the injected gems" do
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app("Gemfile.lock").read).to match(/rack-obama/)
+ end
+ end
+
+ context "with a lockfile" do
+ before do
+ bundle "install"
+ end
+
+ it "adds the injected gems to the Gemfile" do
+ expect(bundled_app("Gemfile").read).not_to match(/rack-obama/)
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app("Gemfile").read).to match(/rack-obama/)
+ end
+
+ it "locks with the injected gems" do
+ expect(bundled_app("Gemfile.lock").read).not_to match(/rack-obama/)
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app("Gemfile.lock").read).to match(/rack-obama/)
+ end
+ end
+
+ context "with injected gems already in the Gemfile" do
+ it "doesn't add existing gems" do
+ bundle "inject 'rack' '> 0'"
+ expect(out).to match(/cannot specify the same gem twice/i)
+ end
+ end
+
+ context "incorrect arguments" do
+ it "fails when more than 2 arguments are passed" do
+ bundle "inject gem_name 1 v"
+ expect(out).to eq(<<-E.strip)
+ERROR: "bundle inject" was called with arguments ["gem_name", "1", "v"]
+Usage: "bundle inject GEM VERSION"
+ E
+ end
+ end
+
+ context "with source option" do
+ it "add gem with source option in gemfile" do
+ bundle "inject 'foo' '>0' --source file://#{gem_repo1}"
+ gemfile = bundled_app("Gemfile").read
+ str = "gem \"foo\", \"> 0\", :source => \"file://#{gem_repo1}\""
+ expect(gemfile).to include str
+ end
+ end
+
+ context "with group option" do
+ it "add gem with group option in gemfile" do
+ bundle "inject 'rack-obama' '>0' --group=development"
+ gemfile = bundled_app("Gemfile").read
+ str = "gem \"rack-obama\", \"> 0\", :group => :development"
+ expect(gemfile).to include str
+ end
+
+ it "add gem with multiple groups in gemfile" do
+ bundle "inject 'rack-obama' '>0' --group=development,test"
+ gemfile = bundled_app("Gemfile").read
+ str = "gem \"rack-obama\", \"> 0\", :groups => [:development, :test]"
+ expect(gemfile).to include str
+ end
+ end
+
+ context "when frozen" do
+ before do
+ bundle "install"
+ if Bundler.feature_flag.bundler_2_mode?
+ bundle! "config --local deployment true"
+ else
+ bundle! "config --local frozen true"
+ end
+ end
+
+ it "injects anyway" do
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app("Gemfile").read).to match(/rack-obama/)
+ end
+
+ it "locks with the injected gems" do
+ expect(bundled_app("Gemfile.lock").read).not_to match(/rack-obama/)
+ bundle "inject 'rack-obama' '> 0'"
+ expect(bundled_app("Gemfile.lock").read).to match(/rack-obama/)
+ end
+
+ it "restores frozen afterwards" do
+ bundle "inject 'rack-obama' '> 0'"
+ config = YAML.load(bundled_app(".bundle/config").read)
+ expect(config["BUNDLE_DEPLOYMENT"] || config["BUNDLE_FROZEN"]).to eq("true")
+ end
+
+ it "doesn't allow Gemfile changes" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack-obama"
+ G
+ bundle "inject 'rack' '> 0'"
+ expect(out).to match(/trying to install in deployment mode after changing/)
+
+ expect(bundled_app("Gemfile.lock").read).not_to match(/rack-obama/)
+ end
+ end
+end
diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb
new file mode 100644
index 0000000000..394f672fef
--- /dev/null
+++ b/spec/bundler/commands/install_spec.rb
@@ -0,0 +1,587 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with gem sources" do
+ describe "the simple case" do
+ it "prints output and returns if no dependencies are specified" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ G
+
+ bundle :install
+ expect(out).to match(/no dependencies/)
+ end
+
+ it "does not make a lockfile if the install fails" do
+ install_gemfile <<-G
+ raise StandardError, "FAIL"
+ G
+
+ expect(last_command.bundler_err).to include('StandardError, "FAIL"')
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ end
+
+ it "creates a Gemfile.lock" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+
+ it "does not create ./.bundle by default", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle! :install # can't use install_gemfile since it sets retry
+ expect(bundled_app(".bundle")).not_to exist
+ end
+
+ it "does not create ./.bundle by default when installing to system gems" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle! :install, :env => { "BUNDLE_PATH__SYSTEM" => true } # can't use install_gemfile since it sets retry
+ expect(bundled_app(".bundle")).not_to exist
+ end
+
+ it "creates lock files based on the Gemfile name" do
+ gemfile bundled_app("OmgFile"), <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "1.0"
+ G
+
+ bundle "install --gemfile OmgFile"
+
+ expect(bundled_app("OmgFile.lock")).to exist
+ end
+
+ it "doesn't delete the lockfile if one already exists" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ lockfile = File.read(bundled_app("Gemfile.lock"))
+
+ install_gemfile <<-G
+ raise StandardError, "FAIL"
+ G
+
+ expect(File.read(bundled_app("Gemfile.lock"))).to eq(lockfile)
+ end
+
+ it "does not touch the lockfile if nothing changed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ expect { run "1" }.not_to change { File.mtime(bundled_app("Gemfile.lock")) }
+ end
+
+ it "fetches gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ expect(default_bundle_path("gems/rack-1.0.0")).to exist
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+
+ it "fetches gems when multiple versions are specified" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack', "> 0.9", "< 1.0"
+ G
+
+ expect(default_bundle_path("gems/rack-0.9.1")).to exist
+ expect(the_bundle).to include_gems("rack 0.9.1")
+ end
+
+ it "fetches gems when multiple versions are specified take 2" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack', "< 1.0", "> 0.9"
+ G
+
+ expect(default_bundle_path("gems/rack-0.9.1")).to exist
+ expect(the_bundle).to include_gems("rack 0.9.1")
+ end
+
+ it "raises an appropriate error when gems are specified using symbols" do
+ install_gemfile(<<-G)
+ source "file://#{gem_repo1}"
+ gem :rack
+ G
+ expect(exitstatus).to eq(4) if exitstatus
+ end
+
+ it "pulls in dependencies" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ expect(the_bundle).to include_gems "actionpack 2.3.2", "rails 2.3.2"
+ end
+
+ it "does the right version" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "0.9.1"
+ G
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ end
+
+ it "does not install the development dependency" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "with_development_dependency"
+ G
+
+ expect(the_bundle).to include_gems("with_development_dependency 1.0.0").
+ and not_include_gems("activesupport 2.3.5")
+ end
+
+ it "resolves correctly" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activemerchant"
+ gem "rails"
+ G
+
+ expect(the_bundle).to include_gems "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2"
+ end
+
+ it "activates gem correctly according to the resolved gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport", "2.3.5"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activemerchant"
+ gem "rails"
+ G
+
+ expect(the_bundle).to include_gems "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2"
+ end
+
+ it "does not reinstall any gem that is already available locally" do
+ system_gems "activesupport-2.3.2", :path => :bundle_path
+
+ build_repo2 do
+ build_gem "activesupport", "2.3.2" do |s|
+ s.write "lib/activesupport.rb", "ACTIVESUPPORT = 'fail'"
+ end
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activerecord", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems "activesupport 2.3.2"
+ end
+
+ it "works when the gemfile specifies gems that only exist in the system" do
+ build_gem "foo", :to_bundle => true
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "foo"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "foo 1.0.0"
+ end
+
+ it "prioritizes local gems over remote gems" do
+ build_gem "rack", "1.0.0", :to_bundle => true do |s|
+ s.add_dependency "activesupport", "2.3.5"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ end
+
+ describe "with a gem that installs multiple platforms" do
+ it "installs gems for the local platform as first choice" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "platform_specific"
+ G
+
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0.0 #{Bundler.local_platform}")
+ end
+
+ it "falls back on plain ruby" do
+ simulate_platform "foo-bar-baz"
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "platform_specific"
+ G
+
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0.0 RUBY")
+ end
+
+ it "installs gems for java" do
+ simulate_platform "java"
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "platform_specific"
+ G
+
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0.0 JAVA")
+ end
+
+ it "installs gems for windows" do
+ simulate_platform mswin
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "platform_specific"
+ G
+
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0.0 MSWIN")
+ end
+ end
+
+ describe "doing bundle install foo" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ it "works" do
+ bundle "install", forgotten_command_line_options(:path => "vendor")
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "allows running bundle install --system without deleting foo", :bundler => "< 2" do
+ bundle "install", forgotten_command_line_options(:path => "vendor")
+ bundle "install", forgotten_command_line_options(:system => true)
+ FileUtils.rm_rf(bundled_app("vendor"))
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "allows running bundle install --system after deleting foo", :bundler => "< 2" do
+ bundle "install", forgotten_command_line_options(:path => "vendor")
+ FileUtils.rm_rf(bundled_app("vendor"))
+ bundle "install", forgotten_command_line_options(:system => true)
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ it "finds gems in multiple sources", :bundler => "< 2" do
+ build_repo2
+ update_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ source "file://#{gem_repo2}"
+
+ gem "activesupport", "1.2.3"
+ gem "rack", "1.2"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.2", "activesupport 1.2.3"
+ end
+
+ it "gives a useful error if no sources are set" do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ bundle :install
+ expect(out).to include("Your Gemfile has no gem server sources")
+ end
+
+ it "creates a Gemfile.lock on a blank Gemfile" do
+ install_gemfile <<-G
+ G
+
+ expect(File.exist?(bundled_app("Gemfile.lock"))).to eq(true)
+ end
+
+ context "throws a warning if a gem is added twice in Gemfile" do
+ it "without version requirements" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ gem "rack"
+ G
+
+ expect(out).to include("Your Gemfile lists the gem rack (>= 0) more than once.")
+ expect(out).to include("Remove any duplicate entries and specify the gem only once (per group).")
+ expect(out).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.")
+ end
+
+ it "with same versions" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack", "1.0"
+ gem "rack", "1.0"
+ G
+
+ expect(out).to include("Your Gemfile lists the gem rack (= 1.0) more than once.")
+ expect(out).to include("Remove any duplicate entries and specify the gem only once (per group).")
+ expect(out).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.")
+ end
+ end
+
+ context "throws an error if a gem is added twice in Gemfile" do
+ it "when version of one dependency is not specified" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ gem "rack", "1.0"
+ G
+
+ expect(out).to include("You cannot specify the same gem twice with different version requirements")
+ expect(out).to include("You specified: rack (>= 0) and rack (= 1.0).")
+ end
+
+ it "when different versions of both dependencies are specified" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack", "1.0"
+ gem "rack", "1.1"
+ G
+
+ expect(out).to include("You cannot specify the same gem twice with different version requirements")
+ expect(out).to include("You specified: rack (= 1.0) and rack (= 1.1).")
+ end
+ end
+
+ it "gracefully handles error when rubygems server is unavailable" do
+ install_gemfile <<-G, :artifice => nil
+ source "file://#{gem_repo1}"
+ source "http://localhost:9384" do
+ gem 'foo'
+ end
+ G
+
+ bundle :install, :artifice => nil
+ expect(out).to include("Could not fetch specs from http://localhost:9384/")
+ expect(out).not_to include("file://")
+ end
+
+ it "fails gracefully when downloading an invalid specification from the full index", :rubygems => "2.5" do
+ build_repo2 do
+ build_gem "ajp-rails", "0.0.0", :gemspec => false, :skip_validation => true do |s|
+ bad_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]]
+ s.
+ instance_variable_get(:@spec).
+ instance_variable_set(:@dependencies, bad_deps)
+
+ raise "failed to set bad deps" unless s.dependencies == bad_deps
+ end
+ build_gem "ruby-ajp", "1.0.0"
+ end
+
+ install_gemfile <<-G, :full_index => true
+ source "file:\/\/localhost#{gem_repo2}"
+
+ gem "ajp-rails", "0.0.0"
+ G
+
+ expect(last_command.stdboth).not_to match(/Error Report/i)
+ expect(last_command.bundler_err).to include("An error occurred while installing ajp-rails (0.0.0), and Bundler cannot continue.").
+ and include(normalize_uri_file("Make sure that `gem install ajp-rails -v '0.0.0' --source 'file://localhost#{gem_repo2}/'` succeeds before bundling."))
+ end
+
+ it "doesn't blow up when the local .bundle/config is empty" do
+ FileUtils.mkdir_p(bundled_app(".bundle"))
+ FileUtils.touch(bundled_app(".bundle/config"))
+
+ install_gemfile(<<-G)
+ source "file://#{gem_repo1}"
+
+ gem 'foo'
+ G
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ it "doesn't blow up when the global .bundle/config is empty" do
+ FileUtils.mkdir_p("#{Bundler.rubygems.user_home}/.bundle")
+ FileUtils.touch("#{Bundler.rubygems.user_home}/.bundle/config")
+
+ install_gemfile(<<-G)
+ source "file://#{gem_repo1}"
+
+ gem 'foo'
+ G
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+ end
+
+ describe "Ruby version in Gemfile.lock" do
+ include Bundler::GemHelpers
+
+ context "and using an unsupported Ruby version" do
+ it "prints an error" do
+ install_gemfile <<-G
+ ::RUBY_VERSION = '2.0.1'
+ ruby '~> 2.2'
+ G
+ expect(out).to include("Your Ruby version is 2.0.1, but your Gemfile specified ~> 2.2")
+ end
+ end
+
+ context "and using a supported Ruby version" do
+ before do
+ install_gemfile <<-G
+ ::RUBY_VERSION = '2.1.3'
+ ::RUBY_PATCHLEVEL = 100
+ ruby '~> 2.1.0'
+ G
+ end
+
+ it "writes current Ruby version to Gemfile.lock" do
+ lockfile_should_be <<-L
+ GEM
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ ruby 2.1.3p100
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "updates Gemfile.lock with updated incompatible ruby version" do
+ install_gemfile <<-G
+ ::RUBY_VERSION = '2.2.3'
+ ::RUBY_PATCHLEVEL = 100
+ ruby '~> 2.2.0'
+ G
+
+ lockfile_should_be <<-L
+ GEM
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ ruby 2.2.3p100
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+ end
+
+ describe "when Bundler root contains regex chars" do
+ before do
+ root_dir = tmp("foo[]bar")
+
+ FileUtils.mkdir_p(root_dir)
+ in_app_root_custom(root_dir)
+ end
+
+ it "doesn't blow up" do
+ build_lib "foo"
+ gemfile = <<-G
+ gem 'foo', :path => "#{lib_path("foo-1.0")}"
+ G
+ File.open("Gemfile", "w") do |file|
+ file.puts gemfile
+ end
+
+ bundle :install
+
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+ end
+
+ describe "when requesting a quiet install via --quiet" do
+ it "should be quiet" do
+ gemfile <<-G
+ gem 'rack'
+ G
+
+ bundle :install, :quiet => true
+ expect(out).to include("Could not find gem 'rack'")
+ expect(out).to_not include("Your Gemfile has no gem server sources")
+ end
+ end
+
+ describe "when bundle path does not have write access" do
+ before do
+ FileUtils.mkdir_p(bundled_app("vendor"))
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+ end
+
+ it "should display a proper message to explain the problem" do
+ FileUtils.chmod(0o500, bundled_app("vendor"))
+
+ bundle :install, forgotten_command_line_options(:path => "vendor")
+ expect(out).to include(bundled_app("vendor").to_s)
+ expect(out).to include("grant write permissions")
+ end
+ end
+
+ context "after installing with --standalone" do
+ before do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ forgotten_command_line_options(:path => "bundle")
+ bundle! "install", :standalone => true
+ end
+
+ it "includes the standalone path" do
+ bundle! "binstubs rack", :standalone => true
+ standalone_line = File.read(bundled_app("bin/rackup")).each_line.find {|line| line.include? "$:.unshift" }.strip
+ expect(standalone_line).to eq %($:.unshift File.expand_path "../../bundle", path.realpath)
+ end
+ end
+
+ describe "when bundle install is executed with unencoded authentication" do
+ before do
+ gemfile <<-G
+ source 'https://rubygems.org/'
+ gem "."
+ G
+ end
+
+ it "should display a helpful messag explaining how to fix it" do
+ bundle :install, :env => { "BUNDLE_RUBYGEMS__ORG" => "user:pass{word" }
+ expect(exitstatus).to eq(17) if exitstatus
+ expect(out).to eq("Please CGI escape your usernames and passwords before " \
+ "setting them for authentication.")
+ end
+ end
+end
diff --git a/spec/bundler/commands/issue_spec.rb b/spec/bundler/commands/issue_spec.rb
new file mode 100644
index 0000000000..04c575130e
--- /dev/null
+++ b/spec/bundler/commands/issue_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle issue" do
+ it "exits with a message" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ bundle "issue"
+ expect(out).to include "Did you find an issue with Bundler?"
+ expect(out).to include "## Environment"
+ expect(out).to include "## Gemfile"
+ expect(out).to include "## Bundle Doctor"
+ end
+end
diff --git a/spec/bundler/commands/licenses_spec.rb b/spec/bundler/commands/licenses_spec.rb
new file mode 100644
index 0000000000..d61d3492f3
--- /dev/null
+++ b/spec/bundler/commands/licenses_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle licenses" do
+ before :each do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ gem "with_license"
+ G
+ end
+
+ it "prints license information for all gems in the bundle" do
+ bundle "licenses"
+
+ loaded_bundler_spec = Bundler.load.specs["bundler"]
+ expected = if !loaded_bundler_spec.empty?
+ loaded_bundler_spec[0].license
+ else
+ "Unknown"
+ end
+
+ expect(out).to include("bundler: #{expected}")
+ expect(out).to include("with_license: MIT")
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ gem "with_license"
+ gem "foo"
+ G
+
+ bundle "config auto_install 1"
+ bundle :licenses
+ expect(out).to include("Installing foo 1.0")
+ end
+end
diff --git a/spec/bundler/commands/list_spec.rb b/spec/bundler/commands/list_spec.rb
new file mode 100644
index 0000000000..5305176c65
--- /dev/null
+++ b/spec/bundler/commands/list_spec.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle list", :bundler => "2" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ gem "rspec", :group => [:test]
+ G
+ end
+
+ context "with name-only and paths option" do
+ it "raises an error" do
+ bundle "list --name-only --paths"
+
+ expect(out).to eq "The `--name-only` and `--paths` options cannot be used together"
+ end
+ end
+
+ context "with without-group and only-group option" do
+ it "raises an error" do
+ bundle "list --without-group dev --only-group test"
+
+ expect(out).to eq "The `--only-group` and `--without-group` options cannot be used together"
+ end
+ end
+
+ describe "with without-group option" do
+ context "when group is present" do
+ it "prints the gems not in the specified group" do
+ bundle! "list --without-group test"
+
+ expect(out).to include(" * rack (1.0.0)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+ end
+
+ context "when group is not found" do
+ it "raises an error" do
+ bundle "list --without-group random"
+
+ expect(out).to eq "`random` group could not be found."
+ end
+ end
+ end
+
+ describe "with only-group option" do
+ context "when group is present" do
+ it "prints the gems in the specified group" do
+ bundle! "list --only-group default"
+
+ expect(out).to include(" * rack (1.0.0)")
+ expect(out).not_to include(" * rspec (1.2.7)")
+ end
+ end
+
+ context "when group is not found" do
+ it "raises an error" do
+ bundle "list --only-group random"
+
+ expect(out).to eq "`random` group could not be found."
+ end
+ end
+ end
+
+ context "with name-only option" do
+ it "prints only the name of the gems in the bundle" do
+ bundle "list --name-only"
+
+ expect(out).to include("rack")
+ expect(out).to include("rspec")
+ end
+ end
+
+ context "with paths option" do
+ before do
+ build_repo2 do
+ build_gem "bar"
+ end
+
+ build_git "git_test", "1.0.0", :path => lib_path("git_test")
+
+ build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s|
+ s.write("Gemfile", "source :rubygems\ngemspec")
+ s.add_dependency "bar", "=1.0.0"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ gem "rails"
+ gem "git_test", :git => "#{lib_path("git_test")}"
+ gemspec :path => "#{tmp.join("gemspec_test")}"
+ G
+
+ bundle! "install"
+ end
+
+ it "prints the path of each gem in the bundle" do
+ bundle "list --paths"
+ expect(out).to match(%r{.*\/rails\-2\.3\.2})
+ expect(out).to match(%r{.*\/rack\-1\.2})
+ expect(out).to match(%r{.*\/git_test\-\w})
+ expect(out).to match(%r{.*\/gemspec_test})
+ end
+ end
+
+ context "when no gems are in the gemfile" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+
+ it "prints message saying no gems are in the bundle" do
+ bundle "list"
+ expect(out).to include("No gems in the Gemfile")
+ end
+ end
+
+ it "lists gems installed in the bundle" do
+ bundle "list"
+ expect(out).to include(" * rack (1.0.0)")
+ end
+
+ it "aliases the ls command to list" do
+ bundle "ls"
+ expect(out).to include("Gems included by the bundle")
+ end
+end
diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb
new file mode 100644
index 0000000000..0b77605f01
--- /dev/null
+++ b/spec/bundler/commands/lock_spec.rb
@@ -0,0 +1,322 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle lock" do
+ def strip_lockfile(lockfile)
+ strip_whitespace(lockfile).sub(/\n\Z/, "")
+ end
+
+ def read_lockfile(file = "Gemfile.lock")
+ strip_lockfile bundled_app(file).read
+ end
+
+ let(:repo) { gem_repo1 }
+
+ before :each do
+ gemfile <<-G
+ source "file://localhost#{repo}"
+ gem "rails"
+ gem "with_license"
+ gem "foo"
+ G
+
+ @lockfile = strip_lockfile(normalize_uri_file(<<-L))
+ GEM
+ remote: file://localhost#{repo}/
+ specs:
+ actionmailer (2.3.2)
+ activesupport (= 2.3.2)
+ actionpack (2.3.2)
+ activesupport (= 2.3.2)
+ activerecord (2.3.2)
+ activesupport (= 2.3.2)
+ activeresource (2.3.2)
+ activesupport (= 2.3.2)
+ activesupport (2.3.2)
+ foo (1.0)
+ rails (2.3.2)
+ actionmailer (= 2.3.2)
+ actionpack (= 2.3.2)
+ activerecord (= 2.3.2)
+ activeresource (= 2.3.2)
+ rake (= 10.0.2)
+ rake (10.0.2)
+ with_license (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo
+ rails
+ with_license
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "prints a lockfile when there is no existing lockfile with --print" do
+ bundle "lock --print"
+
+ expect(out).to eq(@lockfile)
+ end
+
+ it "prints a lockfile when there is an existing lockfile with --print" do
+ lockfile @lockfile
+
+ bundle "lock --print"
+
+ expect(out).to eq(@lockfile)
+ end
+
+ it "writes a lockfile when there is no existing lockfile" do
+ bundle "lock"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "writes a lockfile when there is an outdated lockfile using --update" do
+ lockfile @lockfile.gsub("2.3.2", "2.3.1")
+
+ bundle! "lock --update"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "does not fetch remote specs when using the --local option" do
+ bundle "lock --update --local"
+
+ expect(out).to match(/sources listed in your Gemfile|installed locally/)
+ end
+
+ it "writes to a custom location using --lockfile" do
+ bundle "lock --lockfile=lock"
+
+ expect(out).to match(/Writing lockfile to.+lock/)
+ expect(read_lockfile "lock").to eq(@lockfile)
+ expect { read_lockfile }.to raise_error(Errno::ENOENT)
+ end
+
+ it "writes to custom location using --lockfile when a default lockfile is present" do
+ bundle "install"
+ bundle "lock --lockfile=lock"
+
+ expect(out).to match(/Writing lockfile to.+lock/)
+ expect(read_lockfile("lock")).to eq(@lockfile)
+ end
+
+ it "update specific gems using --update" do
+ lockfile @lockfile.gsub("2.3.2", "2.3.1").gsub("10.0.2", "10.0.1")
+
+ bundle "lock --update rails rake"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "errors when updating a missing specific gems using --update" do
+ lockfile @lockfile
+
+ bundle "lock --update blahblah"
+ expect(out).to eq("Could not find gem 'blahblah'.")
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ # see update_spec for more coverage on same options. logic is shared so it's not necessary
+ # to repeat coverage here.
+ context "conservative updates" do
+ before do
+ build_repo4 do
+ build_gem "foo", %w[1.4.3 1.4.4] do |s|
+ s.add_dependency "bar", "~> 2.0"
+ end
+ build_gem "foo", %w[1.4.5 1.5.0] do |s|
+ s.add_dependency "bar", "~> 2.1"
+ end
+ build_gem "foo", %w[1.5.1] do |s|
+ s.add_dependency "bar", "~> 3.0"
+ end
+ build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0]
+ build_gem "qux", %w[1.0.0 1.0.1 1.1.0 2.0.0]
+ end
+
+ # establish a lockfile set to 1.4.3
+ install_gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'foo', '1.4.3'
+ gem 'bar', '2.0.3'
+ gem 'qux', '1.0.0'
+ G
+
+ # remove 1.4.3 requirement and bar altogether
+ # to setup update specs below
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'foo'
+ gem 'qux'
+ G
+ end
+
+ it "single gem updates dependent gem to minor" do
+ bundle "lock --update foo --patch"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.4.5 bar-2.1.1 qux-1.0.0].sort)
+ end
+
+ it "minor preferred with strict" do
+ bundle "lock --update --minor --strict"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.5.0 bar-2.1.1 qux-1.1.0].sort)
+ end
+ end
+
+ it "supports adding new platforms" do
+ bundle! "lock --add-platform java x86-mingw32"
+
+ lockfile = Bundler::LockfileParser.new(read_lockfile)
+ expect(lockfile.platforms).to match_array(local_platforms.unshift(java, mingw).uniq)
+ end
+
+ it "supports adding the `ruby` platform" do
+ bundle! "lock --add-platform ruby"
+ lockfile = Bundler::LockfileParser.new(read_lockfile)
+ expect(lockfile.platforms).to match_array(local_platforms.unshift("ruby").uniq)
+ end
+
+ it "warns when adding an unknown platform" do
+ bundle "lock --add-platform foobarbaz"
+ expect(out).to include("The platform `foobarbaz` is unknown to RubyGems and adding it will likely lead to resolution errors")
+ end
+
+ it "allows removing platforms" do
+ bundle! "lock --add-platform java x86-mingw32"
+
+ lockfile = Bundler::LockfileParser.new(read_lockfile)
+ expect(lockfile.platforms).to match_array(local_platforms.unshift(java, mingw).uniq)
+
+ bundle! "lock --remove-platform java"
+
+ lockfile = Bundler::LockfileParser.new(read_lockfile)
+ expect(lockfile.platforms).to match_array(local_platforms.unshift(mingw).uniq)
+ end
+
+ it "errors when removing all platforms" do
+ bundle "lock --remove-platform #{local_platforms.join(" ")}"
+ expect(last_command.bundler_err).to include("Removing all platforms from the bundle is not allowed")
+ end
+
+ # from https://github.com/bundler/bundler/issues/4896
+ it "properly adds platforms when platform requirements come from different dependencies" do
+ build_repo4 do
+ build_gem "ffi", "1.9.14"
+ build_gem "ffi", "1.9.14" do |s|
+ s.platform = mingw
+ end
+
+ build_gem "gssapi", "0.1"
+ build_gem "gssapi", "0.2"
+ build_gem "gssapi", "0.3"
+ build_gem "gssapi", "1.2.0" do |s|
+ s.add_dependency "ffi", ">= 1.0.1"
+ end
+
+ build_gem "mixlib-shellout", "2.2.6"
+ build_gem "mixlib-shellout", "2.2.6" do |s|
+ s.platform = "universal-mingw32"
+ s.add_dependency "win32-process", "~> 0.8.2"
+ end
+
+ # we need all these versions to get the sorting the same as it would be
+ # pulling from rubygems.org
+ %w[0.8.3 0.8.2 0.8.1 0.8.0].each do |v|
+ build_gem "win32-process", v do |s|
+ s.add_dependency "ffi", ">= 1.0.0"
+ end
+ end
+ end
+
+ gemfile <<-G
+ source "file://localhost#{gem_repo4}"
+
+ gem "mixlib-shellout"
+ gem "gssapi"
+ G
+
+ simulate_platform(mingw) { bundle! :lock }
+
+ expect(the_bundle.lockfile).to read_as(normalize_uri_file(strip_whitespace(<<-G)))
+ GEM
+ remote: file://localhost#{gem_repo4}/
+ specs:
+ ffi (1.9.14-x86-mingw32)
+ gssapi (1.2.0)
+ ffi (>= 1.0.1)
+ mixlib-shellout (2.2.6-universal-mingw32)
+ win32-process (~> 0.8.2)
+ win32-process (0.8.3)
+ ffi (>= 1.0.0)
+
+ PLATFORMS
+ x86-mingw32
+
+ DEPENDENCIES
+ gssapi
+ mixlib-shellout
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ simulate_platform(rb) { bundle! :lock }
+
+ expect(the_bundle.lockfile).to read_as(normalize_uri_file(strip_whitespace(<<-G)))
+ GEM
+ remote: file://localhost#{gem_repo4}/
+ specs:
+ ffi (1.9.14)
+ ffi (1.9.14-x86-mingw32)
+ gssapi (1.2.0)
+ ffi (>= 1.0.1)
+ mixlib-shellout (2.2.6)
+ mixlib-shellout (2.2.6-universal-mingw32)
+ win32-process (~> 0.8.2)
+ win32-process (0.8.3)
+ ffi (>= 1.0.0)
+
+ PLATFORMS
+ ruby
+ x86-mingw32
+
+ DEPENDENCIES
+ gssapi
+ mixlib-shellout
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ context "when an update is available" do
+ let(:repo) { gem_repo2 }
+
+ before do
+ lockfile(@lockfile)
+ build_repo2 do
+ build_gem "foo", "2.0"
+ end
+ end
+
+ it "does not implicitly update" do
+ bundle! "lock"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "accounts for changes in the gemfile" do
+ gemfile gemfile.gsub('"foo"', '"foo", "2.0"')
+ bundle! "lock"
+
+ expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)"))
+ end
+ end
+end
diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb
new file mode 100644
index 0000000000..e6d6e19122
--- /dev/null
+++ b/spec/bundler/commands/newgem_spec.rb
@@ -0,0 +1,912 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle gem" do
+ def reset!
+ super
+ global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false"
+ end
+
+ def remove_push_guard(gem_name)
+ # Remove exception that prevents public pushes on older RubyGems versions
+ if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0")
+ path = "#{gem_name}/#{gem_name}.gemspec"
+ content = File.read(path).sub(/raise "RubyGems 2\.0 or newer.*/, "")
+ File.open(path, "w") {|f| f.write(content) }
+ end
+ end
+
+ def execute_bundle_gem(gem_name, flag = "", to_remove_push_guard = true)
+ bundle! "gem #{gem_name} #{flag}"
+ remove_push_guard(gem_name) if to_remove_push_guard
+ # reset gemspec cache for each test because of commit 3d4163a
+ Bundler.clear_gemspec_cache
+ end
+
+ def gem_skeleton_assertions(gem_name)
+ expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to exist
+ expect(bundled_app("#{gem_name}/README.md")).to exist
+ expect(bundled_app("#{gem_name}/Gemfile")).to exist
+ expect(bundled_app("#{gem_name}/Rakefile")).to exist
+ expect(bundled_app("#{gem_name}/lib/test/gem.rb")).to exist
+ expect(bundled_app("#{gem_name}/lib/test/gem/version.rb")).to exist
+ end
+
+ before do
+ git_config_content = <<-EOF
+ [user]
+ name = "Bundler User"
+ email = user@example.com
+ [github]
+ user = bundleuser
+ EOF
+ @git_config_location = ENV["GIT_CONFIG"]
+ path = "#{File.expand_path(tmp, File.dirname(__FILE__))}/test_git_config.txt"
+ File.open(path, "w") {|f| f.write(git_config_content) }
+ ENV["GIT_CONFIG"] = path
+ end
+
+ after do
+ FileUtils.rm(ENV["GIT_CONFIG"]) if File.exist?(ENV["GIT_CONFIG"])
+ ENV["GIT_CONFIG"] = @git_config_location
+ end
+
+ shared_examples_for "git config is present" do
+ context "git config user.{name,email} present" do
+ it "sets gemspec author to git user.name if available" do
+ expect(generated_gem.gemspec.authors.first).to eq("Bundler User")
+ end
+
+ it "sets gemspec email to git user.email if available" do
+ expect(generated_gem.gemspec.email.first).to eq("user@example.com")
+ end
+ end
+ end
+
+ shared_examples_for "git config is absent" do
+ it "sets gemspec author to default message if git user.name is not set or empty" do
+ expect(generated_gem.gemspec.authors.first).to eq("TODO: Write your name")
+ end
+
+ it "sets gemspec email to default message if git user.email is not set or empty" do
+ expect(generated_gem.gemspec.email.first).to eq("TODO: Write your email address")
+ end
+ end
+
+ shared_examples_for "--mit flag" do
+ before do
+ execute_bundle_gem(gem_name, "--mit")
+ end
+ it "generates a gem skeleton with MIT license" do
+ gem_skeleton_assertions(gem_name)
+ expect(bundled_app("test-gem/LICENSE.txt")).to exist
+ skel = Bundler::GemHelper.new(bundled_app(gem_name).to_s)
+ expect(skel.gemspec.license).to eq("MIT")
+ end
+ end
+
+ shared_examples_for "--no-mit flag" do
+ before do
+ execute_bundle_gem(gem_name, "--no-mit")
+ end
+ it "generates a gem skeleton without MIT license" do
+ gem_skeleton_assertions(gem_name)
+ expect(bundled_app("test-gem/LICENSE.txt")).to_not exist
+ end
+ end
+
+ shared_examples_for "--coc flag" do
+ before do
+ execute_bundle_gem(gem_name, "--coc", false)
+ end
+ it "generates a gem skeleton with MIT license" do
+ gem_skeleton_assertions(gem_name)
+ expect(bundled_app("test-gem/CODE_OF_CONDUCT.md")).to exist
+ end
+
+ describe "README additions" do
+ it "generates the README with a section for the Code of Conduct" do
+ expect(bundled_app("test-gem/README.md").read).to include("## Code of Conduct")
+ expect(bundled_app("test-gem/README.md").read).to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md")
+ end
+ end
+ end
+
+ shared_examples_for "--no-coc flag" do
+ before do
+ execute_bundle_gem(gem_name, "--no-coc", false)
+ end
+ it "generates a gem skeleton without Code of Conduct" do
+ gem_skeleton_assertions(gem_name)
+ expect(bundled_app("test-gem/CODE_OF_CONDUCT.md")).to_not exist
+ end
+
+ describe "README additions" do
+ it "generates the README without a section for the Code of Conduct" do
+ expect(bundled_app("test-gem/README.md").read).not_to include("## Code of Conduct")
+ expect(bundled_app("test-gem/README.md").read).not_to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md")
+ end
+ end
+ end
+
+ context "README.md" do
+ let(:gem_name) { "test_gem" }
+ let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) }
+
+ context "git config github.user present" do
+ before do
+ execute_bundle_gem(gem_name)
+ end
+
+ it "contribute URL set to git username" do
+ expect(bundled_app("test_gem/README.md").read).not_to include("[USERNAME]")
+ expect(bundled_app("test_gem/README.md").read).to include("github.com/bundleuser")
+ end
+ end
+
+ context "git config github.user is absent" do
+ before do
+ sys_exec("git config --unset github.user")
+ reset!
+ in_app_root
+ bundle "gem #{gem_name}"
+ remove_push_guard(gem_name)
+ end
+
+ it "contribute URL set to [USERNAME]" do
+ expect(bundled_app("test_gem/README.md").read).to include("[USERNAME]")
+ expect(bundled_app("test_gem/README.md").read).not_to include("github.com/bundleuser")
+ end
+ end
+ end
+
+ it "creates a new git repository" do
+ in_app_root
+ bundle "gem test_gem"
+ expect(bundled_app("test_gem/.git")).to exist
+ end
+
+ context "when git is not available" do
+ let(:gem_name) { "test_gem" }
+
+ # This spec cannot have `git` available in the test env
+ before do
+ load_paths = [lib, spec]
+ load_path_str = "-I#{load_paths.join(File::PATH_SEPARATOR)}"
+
+ sys_exec "PATH=\"\" #{Gem.ruby} #{load_path_str} #{bindir.join("bundle")} gem #{gem_name}"
+ end
+
+ it "creates the gem without the need for git" do
+ expect(bundled_app("#{gem_name}/README.md")).to exist
+ end
+
+ it "doesn't create a git repo" do
+ expect(bundled_app("#{gem_name}/.git")).to_not exist
+ end
+
+ it "doesn't create a .gitignore file" do
+ expect(bundled_app("#{gem_name}/.gitignore")).to_not exist
+ end
+ end
+
+ it "generates a valid gemspec" do
+ in_app_root
+ bundle! "gem newgem --bin"
+
+ process_file(bundled_app("newgem", "newgem.gemspec")) do |line|
+ # Simulate replacing TODOs with real values
+ case line
+ when /spec\.metadata\["(?:allowed_push_host|homepage_uri|source_code_uri|changelog_uri)"\]/, /spec\.homepage/
+ line.gsub(/\=.*$/, "= 'http://example.org'")
+ when /spec\.summary/
+ line.gsub(/\=.*$/, "= %q{A short summary of my new gem.}")
+ when /spec\.description/
+ line.gsub(/\=.*$/, "= %q{A longer description of my new gem.}")
+ # Remove exception that prevents public pushes on older RubyGems versions
+ when /raise "RubyGems 2.0 or newer/
+ line.gsub(/.*/, "") if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0")
+ else
+ line
+ end
+ end
+
+ Dir.chdir(bundled_app("newgem")) do
+ gems = ["rake-10.0.2", :bundler]
+ # for Ruby core repository, Ruby 2.6+ has bundler as standard library.
+ gems.delete(:bundler) if ruby_core?
+ system_gems gems, :path => :bundle_path
+ bundle! "exec rake build"
+ end
+
+ expect(last_command.stdboth).not_to include("ERROR")
+ end
+
+ context "gem naming with relative paths" do
+ before do
+ reset!
+ in_app_root
+ end
+
+ it "resolves ." do
+ create_temporary_dir("tmp")
+
+ bundle "gem ."
+
+ expect(bundled_app("tmp/lib/tmp.rb")).to exist
+ end
+
+ it "resolves .." do
+ create_temporary_dir("temp/empty_dir")
+
+ bundle "gem .."
+
+ expect(bundled_app("temp/lib/temp.rb")).to exist
+ end
+
+ it "resolves relative directory" do
+ create_temporary_dir("tmp/empty/tmp")
+
+ bundle "gem ../../empty"
+
+ expect(bundled_app("tmp/empty/lib/empty.rb")).to exist
+ end
+
+ def create_temporary_dir(dir)
+ FileUtils.mkdir_p(dir)
+ Dir.chdir(dir)
+ end
+ end
+
+ context "gem naming with underscore" do
+ let(:gem_name) { "test_gem" }
+
+ before do
+ execute_bundle_gem(gem_name)
+ end
+
+ let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) }
+
+ it "generates a gem skeleton" do
+ expect(bundled_app("test_gem/test_gem.gemspec")).to exist
+ expect(bundled_app("test_gem/Gemfile")).to exist
+ expect(bundled_app("test_gem/Rakefile")).to exist
+ expect(bundled_app("test_gem/lib/test_gem.rb")).to exist
+ expect(bundled_app("test_gem/lib/test_gem/version.rb")).to exist
+ expect(bundled_app("test_gem/.gitignore")).to exist
+
+ expect(bundled_app("test_gem/bin/setup")).to exist
+ expect(bundled_app("test_gem/bin/console")).to exist
+ expect(bundled_app("test_gem/bin/setup")).to be_executable
+ expect(bundled_app("test_gem/bin/console")).to be_executable
+ end
+
+ it "starts with version 0.1.0" do
+ expect(bundled_app("test_gem/lib/test_gem/version.rb").read).to match(/VERSION = "0.1.0"/)
+ end
+
+ it "does not nest constants" do
+ expect(bundled_app("test_gem/lib/test_gem/version.rb").read).to match(/module TestGem/)
+ expect(bundled_app("test_gem/lib/test_gem.rb").read).to match(/module TestGem/)
+ end
+
+ it_should_behave_like "git config is present"
+
+ context "git config user.{name,email} is not set" do
+ before do
+ `git config --unset user.name`
+ `git config --unset user.email`
+ reset!
+ in_app_root
+ bundle "gem #{gem_name}"
+ remove_push_guard(gem_name)
+ end
+
+ it_should_behave_like "git config is absent"
+ end
+
+ it "sets gemspec metadata['allowed_push_host']", :rubygems => "2.0" do
+ expect(generated_gem.gemspec.metadata["allowed_push_host"]).
+ to match(/mygemserver\.com/)
+ end
+
+ it "requires the version file" do
+ expect(bundled_app("test_gem/lib/test_gem.rb").read).to match(%r{require "test_gem/version"})
+ end
+
+ it "creates a base error class" do
+ expect(bundled_app("test_gem/lib/test_gem.rb").read).to match(/class Error < StandardError; end$/)
+ end
+
+ it "runs rake without problems" do
+ system_gems ["rake-10.0.2"]
+
+ rakefile = strip_whitespace <<-RAKEFILE
+ task :default do
+ puts 'SUCCESS'
+ end
+ RAKEFILE
+ File.open(bundled_app("test_gem/Rakefile"), "w") do |file|
+ file.puts rakefile
+ end
+
+ Dir.chdir(bundled_app(gem_name)) do
+ sys_exec(rake)
+ expect(out).to include("SUCCESS")
+ end
+ end
+
+ context "--exe parameter set" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name} --exe"
+ end
+
+ it "builds exe skeleton" do
+ expect(bundled_app("test_gem/exe/test_gem")).to exist
+ end
+
+ it "requires 'test-gem'" do
+ expect(bundled_app("test_gem/exe/test_gem").read).to match(/require "test_gem"/)
+ end
+ end
+
+ context "--bin parameter set" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name} --bin"
+ end
+
+ it "builds exe skeleton" do
+ expect(bundled_app("test_gem/exe/test_gem")).to exist
+ end
+
+ it "requires 'test-gem'" do
+ expect(bundled_app("test_gem/exe/test_gem").read).to match(/require "test_gem"/)
+ end
+ end
+
+ context "no --test parameter" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name}"
+ end
+
+ it "doesn't create any spec/test file" do
+ expect(bundled_app("test_gem/.rspec")).to_not exist
+ expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to_not exist
+ expect(bundled_app("test_gem/spec/spec_helper.rb")).to_not exist
+ expect(bundled_app("test_gem/test/test_test_gem.rb")).to_not exist
+ expect(bundled_app("test_gem/test/minitest_helper.rb")).to_not exist
+ end
+ end
+
+ context "--test parameter set to rspec" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name} --test=rspec"
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("test_gem/.rspec")).to exist
+ expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to exist
+ expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist
+ end
+
+ it "depends on a specific version of rspec", :rubygems => ">= 1.8.1" do
+ remove_push_guard(gem_name)
+ rspec_dep = generated_gem.gemspec.development_dependencies.find {|d| d.name == "rspec" }
+ expect(rspec_dep).to be_specific
+ end
+
+ it "requires 'test-gem'" do
+ expect(bundled_app("test_gem/spec/spec_helper.rb").read).to include(%(require "test_gem"))
+ end
+
+ it "creates a default test which fails" do
+ expect(bundled_app("test_gem/spec/test_gem_spec.rb").read).to include("expect(false).to eq(true)")
+ end
+ end
+
+ context "gem.test setting set to rspec" do
+ before do
+ reset!
+ in_app_root
+ bundle "config gem.test rspec"
+ bundle "gem #{gem_name}"
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("test_gem/.rspec")).to exist
+ expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to exist
+ expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist
+ end
+ end
+
+ context "gem.test setting set to rspec and --test is set to minitest" do
+ before do
+ reset!
+ in_app_root
+ bundle "config gem.test rspec"
+ bundle "gem #{gem_name} --test=minitest"
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("test_gem/test/test_gem_test.rb")).to exist
+ expect(bundled_app("test_gem/test/test_helper.rb")).to exist
+ end
+ end
+
+ context "--test parameter set to minitest" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name} --test=minitest"
+ end
+
+ it "depends on a specific version of minitest", :rubygems => ">= 1.8.1" do
+ remove_push_guard(gem_name)
+ rspec_dep = generated_gem.gemspec.development_dependencies.find {|d| d.name == "minitest" }
+ expect(rspec_dep).to be_specific
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("test_gem/test/test_gem_test.rb")).to exist
+ expect(bundled_app("test_gem/test/test_helper.rb")).to exist
+ end
+
+ it "requires 'test-gem'" do
+ expect(bundled_app("test_gem/test/test_helper.rb").read).to include(%(require "test_gem"))
+ end
+
+ it "requires 'minitest_helper'" do
+ expect(bundled_app("test_gem/test/test_gem_test.rb").read).to include(%(require "test_helper"))
+ end
+
+ it "creates a default test which fails" do
+ expect(bundled_app("test_gem/test/test_gem_test.rb").read).to include("assert false")
+ end
+ end
+
+ context "gem.test setting set to minitest" do
+ before do
+ reset!
+ in_app_root
+ bundle "config gem.test minitest"
+ bundle "gem #{gem_name}"
+ end
+
+ it "creates a default rake task to run the test suite" do
+ rakefile = strip_whitespace <<-RAKEFILE
+ require "bundler/gem_tasks"
+ require "rake/testtask"
+
+ Rake::TestTask.new(:test) do |t|
+ t.libs << "test"
+ t.libs << "lib"
+ t.test_files = FileList["test/**/*_test.rb"]
+ end
+
+ task :default => :test
+ RAKEFILE
+
+ expect(bundled_app("test_gem/Rakefile").read).to eq(rakefile)
+ end
+ end
+
+ context "--test with no arguments" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name} --test"
+ end
+
+ it "defaults to rspec" do
+ expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist
+ expect(bundled_app("test_gem/test/minitest_helper.rb")).to_not exist
+ end
+
+ it "creates a .travis.yml file to test the library against the current Ruby version on Travis CI" do
+ expect(bundled_app("test_gem/.travis.yml").read).to match(/- #{RUBY_VERSION}/)
+ end
+ end
+
+ context "--edit option" do
+ it "opens the generated gemspec in the user's text editor" do
+ reset!
+ in_app_root
+ output = bundle "gem #{gem_name} --edit=echo"
+ gemspec_path = File.join(Dir.pwd, gem_name, "#{gem_name}.gemspec")
+ expect(output).to include("echo \"#{gemspec_path}\"")
+ end
+ end
+ end
+
+ context "testing --mit and --coc options against bundle config settings" do
+ let(:gem_name) { "test-gem" }
+
+ context "with mit option in bundle config settings set to true" do
+ before do
+ global_config "BUNDLE_GEM__MIT" => "true", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false"
+ end
+ after { reset! }
+ it_behaves_like "--mit flag"
+ it_behaves_like "--no-mit flag"
+ end
+
+ context "with mit option in bundle config settings set to false" do
+ it_behaves_like "--mit flag"
+ it_behaves_like "--no-mit flag"
+ end
+
+ context "with coc option in bundle config settings set to true" do
+ before do
+ global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "true"
+ end
+ after { reset! }
+ it_behaves_like "--coc flag"
+ it_behaves_like "--no-coc flag"
+ end
+
+ context "with coc option in bundle config settings set to false" do
+ it_behaves_like "--coc flag"
+ it_behaves_like "--no-coc flag"
+ end
+ end
+
+ context "gem naming with dashed" do
+ let(:gem_name) { "test-gem" }
+
+ before do
+ execute_bundle_gem(gem_name)
+ end
+
+ let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) }
+
+ it "generates a gem skeleton" do
+ expect(bundled_app("test-gem/test-gem.gemspec")).to exist
+ expect(bundled_app("test-gem/Gemfile")).to exist
+ expect(bundled_app("test-gem/Rakefile")).to exist
+ expect(bundled_app("test-gem/lib/test/gem.rb")).to exist
+ expect(bundled_app("test-gem/lib/test/gem/version.rb")).to exist
+ end
+
+ it "starts with version 0.1.0" do
+ expect(bundled_app("test-gem/lib/test/gem/version.rb").read).to match(/VERSION = "0.1.0"/)
+ end
+
+ it "nests constants so they work" do
+ expect(bundled_app("test-gem/lib/test/gem/version.rb").read).to match(/module Test\n module Gem/)
+ expect(bundled_app("test-gem/lib/test/gem.rb").read).to match(/module Test\n module Gem/)
+ end
+
+ it_should_behave_like "git config is present"
+
+ context "git config user.{name,email} is not set" do
+ before do
+ `git config --unset user.name`
+ `git config --unset user.email`
+ reset!
+ in_app_root
+ bundle "gem #{gem_name}"
+ remove_push_guard(gem_name)
+ end
+
+ it_should_behave_like "git config is absent"
+ end
+
+ it "requires the version file" do
+ expect(bundled_app("test-gem/lib/test/gem.rb").read).to match(%r{require "test/gem/version"})
+ end
+
+ it "runs rake without problems" do
+ system_gems ["rake-10.0.2"]
+
+ rakefile = strip_whitespace <<-RAKEFILE
+ task :default do
+ puts 'SUCCESS'
+ end
+ RAKEFILE
+ File.open(bundled_app("test-gem/Rakefile"), "w") do |file|
+ file.puts rakefile
+ end
+
+ Dir.chdir(bundled_app(gem_name)) do
+ sys_exec(rake)
+ expect(out).to include("SUCCESS")
+ end
+ end
+
+ context "--bin parameter set" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name} --bin"
+ end
+
+ it "builds bin skeleton" do
+ expect(bundled_app("test-gem/exe/test-gem")).to exist
+ end
+
+ it "requires 'test/gem'" do
+ expect(bundled_app("test-gem/exe/test-gem").read).to match(%r{require "test/gem"})
+ end
+ end
+
+ context "no --test parameter" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name}"
+ end
+
+ it "doesn't create any spec/test file" do
+ expect(bundled_app("test-gem/.rspec")).to_not exist
+ expect(bundled_app("test-gem/spec/test/gem_spec.rb")).to_not exist
+ expect(bundled_app("test-gem/spec/spec_helper.rb")).to_not exist
+ expect(bundled_app("test-gem/test/test_test/gem.rb")).to_not exist
+ expect(bundled_app("test-gem/test/minitest_helper.rb")).to_not exist
+ end
+ end
+
+ context "--test parameter set to rspec" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name} --test=rspec"
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("test-gem/.rspec")).to exist
+ expect(bundled_app("test-gem/spec/test/gem_spec.rb")).to exist
+ expect(bundled_app("test-gem/spec/spec_helper.rb")).to exist
+ end
+
+ it "requires 'test/gem'" do
+ expect(bundled_app("test-gem/spec/spec_helper.rb").read).to include(%(require "test/gem"))
+ end
+
+ it "creates a default test which fails" do
+ expect(bundled_app("test-gem/spec/test/gem_spec.rb").read).to include("expect(false).to eq(true)")
+ end
+
+ it "creates a default rake task to run the specs" do
+ rakefile = strip_whitespace <<-RAKEFILE
+ require "bundler/gem_tasks"
+ require "rspec/core/rake_task"
+
+ RSpec::Core::RakeTask.new(:spec)
+
+ task :default => :spec
+ RAKEFILE
+
+ expect(bundled_app("test-gem/Rakefile").read).to eq(rakefile)
+ end
+ end
+
+ context "--test parameter set to minitest" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name} --test=minitest"
+ end
+
+ it "builds spec skeleton" do
+ expect(bundled_app("test-gem/test/test/gem_test.rb")).to exist
+ expect(bundled_app("test-gem/test/test_helper.rb")).to exist
+ end
+
+ it "requires 'test/gem'" do
+ expect(bundled_app("test-gem/test/test_helper.rb").read).to match(%r{require "test/gem"})
+ end
+
+ it "requires 'test_helper'" do
+ expect(bundled_app("test-gem/test/test/gem_test.rb").read).to match(/require "test_helper"/)
+ end
+
+ it "creates a default test which fails" do
+ expect(bundled_app("test-gem/test/test/gem_test.rb").read).to match(/assert false/)
+ end
+
+ it "creates a default rake task to run the test suite" do
+ rakefile = strip_whitespace <<-RAKEFILE
+ require "bundler/gem_tasks"
+ require "rake/testtask"
+
+ Rake::TestTask.new(:test) do |t|
+ t.libs << "test"
+ t.libs << "lib"
+ t.test_files = FileList["test/**/*_test.rb"]
+ end
+
+ task :default => :test
+ RAKEFILE
+
+ expect(bundled_app("test-gem/Rakefile").read).to eq(rakefile)
+ end
+ end
+
+ context "--test with no arguments" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem #{gem_name} --test"
+ end
+
+ it "defaults to rspec" do
+ expect(bundled_app("test-gem/spec/spec_helper.rb")).to exist
+ expect(bundled_app("test-gem/test/minitest_helper.rb")).to_not exist
+ end
+ end
+
+ context "--ext parameter set" do
+ before do
+ reset!
+ in_app_root
+ bundle "gem test_gem --ext"
+ end
+
+ it "builds ext skeleton" do
+ expect(bundled_app("test_gem/ext/test_gem/extconf.rb")).to exist
+ expect(bundled_app("test_gem/ext/test_gem/test_gem.h")).to exist
+ expect(bundled_app("test_gem/ext/test_gem/test_gem.c")).to exist
+ end
+
+ it "includes rake-compiler" do
+ expect(bundled_app("test_gem/test_gem.gemspec").read).to include('spec.add_development_dependency "rake-compiler"')
+ end
+
+ it "depends on compile task for build" do
+ rakefile = strip_whitespace <<-RAKEFILE
+ require "bundler/gem_tasks"
+ require "rake/extensiontask"
+
+ task :build => :compile
+
+ Rake::ExtensionTask.new("test_gem") do |ext|
+ ext.lib_dir = "lib/test_gem"
+ end
+
+ task :default => [:clobber, :compile, :spec]
+ RAKEFILE
+
+ expect(bundled_app("test_gem/Rakefile").read).to eq(rakefile)
+ end
+ end
+ end
+
+ describe "uncommon gem names" do
+ it "can deal with two dashes" do
+ bundle "gem a--a"
+ Bundler.clear_gemspec_cache
+
+ expect(bundled_app("a--a/a--a.gemspec")).to exist
+ end
+
+ it "fails gracefully with a ." do
+ bundle "gem foo.gemspec"
+ expect(last_command.bundler_err).to end_with("Invalid gem name foo.gemspec -- `Foo.gemspec` is an invalid constant name")
+ end
+
+ it "fails gracefully with a ^" do
+ bundle "gem ^"
+ expect(last_command.bundler_err).to end_with("Invalid gem name ^ -- `^` is an invalid constant name")
+ end
+
+ it "fails gracefully with a space" do
+ bundle "gem 'foo bar'"
+ expect(last_command.bundler_err).to end_with("Invalid gem name foo bar -- `Foo bar` is an invalid constant name")
+ end
+
+ it "fails gracefully when multiple names are passed" do
+ bundle "gem foo bar baz"
+ expect(last_command.bundler_err).to eq(<<-E.strip)
+ERROR: "bundle gem" was called with arguments ["foo", "bar", "baz"]
+Usage: "bundle gem NAME [OPTIONS]"
+ E
+ end
+ end
+
+ describe "#ensure_safe_gem_name" do
+ before do
+ bundle "gem #{subject}"
+ end
+ after do
+ Bundler.clear_gemspec_cache
+ end
+
+ context "with an existing const name" do
+ subject { "gem" }
+ it { expect(out).to include("Invalid gem name #{subject}") }
+ end
+
+ context "with an existing hyphenated const name" do
+ subject { "gem-specification" }
+ it { expect(out).to include("Invalid gem name #{subject}") }
+ end
+
+ context "starting with an existing const name" do
+ subject { "gem-somenewconstantname" }
+ it { expect(out).not_to include("Invalid gem name #{subject}") }
+ end
+
+ context "ending with an existing const name" do
+ subject { "somenewconstantname-gem" }
+ it { expect(out).not_to include("Invalid gem name #{subject}") }
+ end
+ end
+
+ context "on first run" do
+ before do
+ in_app_root
+ end
+
+ it "asks about test framework" do
+ global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false"
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "rspec"
+ end
+
+ expect(bundled_app("foobar/spec/spec_helper.rb")).to exist
+ rakefile = strip_whitespace <<-RAKEFILE
+ require "bundler/gem_tasks"
+ require "rspec/core/rake_task"
+
+ RSpec::Core::RakeTask.new(:spec)
+
+ task :default => :spec
+ RAKEFILE
+
+ expect(bundled_app("foobar/Rakefile").read).to eq(rakefile)
+ expect(bundled_app("foobar/foobar.gemspec").read).to include('spec.add_development_dependency "rspec"')
+ end
+
+ it "asks about MIT license" do
+ global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false"
+
+ bundle :config
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "yes"
+ end
+
+ expect(bundled_app("foobar/LICENSE.txt")).to exist
+ end
+
+ it "asks about CoC" do
+ global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false"
+
+ bundle "gem foobar" do |input, _, _|
+ input.puts "yes"
+ end
+
+ expect(bundled_app("foobar/CODE_OF_CONDUCT.md")).to exist
+ end
+ end
+
+ context "on conflicts with a previously created file" do
+ it "should fail gracefully" do
+ in_app_root do
+ FileUtils.touch("conflict-foobar")
+ end
+ bundle "gem conflict-foobar"
+ expect(last_command.bundler_err).to include("Errno::ENOTDIR")
+ expect(exitstatus).to eql(32) if exitstatus
+ end
+ end
+
+ context "on conflicts with a previously created directory" do
+ it "should succeed" do
+ in_app_root do
+ FileUtils.mkdir_p("conflict-foobar/Gemfile")
+ end
+ bundle! "gem conflict-foobar"
+ expect(last_command.stdout).to include("file_clash conflict-foobar/Gemfile").
+ and include "Initializing git repo in #{bundled_app("conflict-foobar")}"
+ end
+ end
+end
diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb
new file mode 100644
index 0000000000..5cab846fb5
--- /dev/null
+++ b/spec/bundler/commands/open_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle open" do
+ before :each do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+ end
+
+ it "opens the gem with BUNDLER_EDITOR as highest priority" do
+ bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+ expect(out).to include("bundler_editor #{default_bundle_path("gems", "rails-2.3.2")}")
+ end
+
+ it "opens the gem with VISUAL as 2nd highest priority" do
+ bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("visual #{default_bundle_path("gems", "rails-2.3.2")}")
+ end
+
+ it "opens the gem with EDITOR as 3rd highest priority" do
+ bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("editor #{default_bundle_path("gems", "rails-2.3.2")}")
+ end
+
+ it "complains if no EDITOR is set" do
+ bundle "open rails", :env => { "EDITOR" => "", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to eq("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR")
+ end
+
+ it "complains if gem not in bundle" do
+ bundle "open missing", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to match(/could not find gem 'missing'/i)
+ end
+
+ it "does not blow up if the gem to open does not have a Gemfile" do
+ git = build_git "foo"
+ ref = git.ref_for("master", 11)
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "open foo", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to match("editor #{default_bundle_path.join("bundler/gems/foo-1.0-#{ref}")}")
+ end
+
+ it "suggests alternatives for similar-sounding gems" do
+ bundle "open Rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to match(/did you mean rails\?/i)
+ end
+
+ it "opens the gem with short words" do
+ bundle "open rec", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+
+ expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2")}")
+ end
+
+ it "select the gem from many match gems" do
+ env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+ bundle "open active", :env => env do |input, _, _|
+ input.puts "2"
+ end
+
+ expect(out).to match(/bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}\z/)
+ end
+
+ it "allows selecting exit from many match gems" do
+ env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" }
+ bundle! "open active", :env => env do |input, _, _|
+ input.puts "0"
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ gem "foo"
+ G
+
+ bundle "config auto_install 1"
+ bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).to include("Installing foo 1.0")
+ end
+
+ it "opens the editor with a clean env" do
+ bundle "open", :env => { "EDITOR" => "sh -c 'env'", "VISUAL" => "", "BUNDLER_EDITOR" => "" }
+ expect(out).not_to include("BUNDLE_GEMFILE=")
+ end
+end
diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb
new file mode 100644
index 0000000000..fc1f1772e7
--- /dev/null
+++ b/spec/bundler/commands/outdated_spec.rb
@@ -0,0 +1,782 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle outdated" do
+ before :each do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ build_git "zebra", :path => lib_path("zebra")
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "zebra", :git => "#{lib_path("zebra")}"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "activesupport", "2.3.5"
+ gem "weakling", "~> 0.0.1"
+ gem "duradura", '7.0'
+ gem "terranova", '8'
+ G
+ end
+
+ describe "with no arguments" do
+ it "returns a sorted list of outdated gems" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "weakling", "0.2"
+ update_git "foo", :path => lib_path("foo")
+ update_git "zebra", :path => lib_path("zebra")
+ end
+
+ bundle "outdated"
+
+ expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)")
+ expect(out).to include("weakling (newest 0.2, installed 0.0.3, requested ~> 0.0.1)")
+ expect(out).to include("foo (newest 1.0")
+
+ # Gem names are one per-line, between "*" and their parenthesized version.
+ gem_list = out.split("\n").map {|g| g[/\* (.*) \(/, 1] }.compact
+ expect(gem_list).to eq(gem_list.sort)
+ end
+
+ it "returns non zero exit status if outdated gems present" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ bundle "outdated"
+
+ expect(exitstatus).to_not be_zero if exitstatus
+ end
+
+ it "returns success exit status if no outdated gems present" do
+ bundle "outdated"
+
+ expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "adds gem group to dependency output when repo is updated" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ end
+ G
+
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle "outdated --verbose"
+ expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5) in groups \"development, test\"")
+ end
+ end
+
+ describe "with --group option" do
+ def test_group_option(group = nil, gems_list_size = 1)
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem "duradura", '7.0'
+ gem 'activesupport', '2.3.5'
+ end
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "terranova", "9"
+ build_gem "duradura", "8.0"
+ end
+
+ bundle "outdated --group #{group}"
+
+ # Gem names are one per-line, between "*" and their parenthesized version.
+ gem_list = out.split("\n").map {|g| g[/\* (.*) \(/, 1] }.compact
+ expect(gem_list).to eq(gem_list.sort)
+ expect(gem_list.size).to eq gems_list_size
+ end
+
+ it "not outdated gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ gem "duradura", '7.0'
+ end
+ G
+
+ bundle "outdated --group"
+ expect(out).to include("Bundle up to date!")
+ end
+
+ it "returns a sorted list of outdated gems from one group => 'default'" do
+ test_group_option("default")
+
+ expect(out).to include("===== Group default =====")
+ expect(out).to include("terranova (")
+
+ expect(out).not_to include("===== Group development, test =====")
+ expect(out).not_to include("activesupport")
+ expect(out).not_to include("duradura")
+ end
+
+ it "returns a sorted list of outdated gems from one group => 'development'" do
+ test_group_option("development", 2)
+
+ expect(out).not_to include("===== Group default =====")
+ expect(out).not_to include("terranova (")
+
+ expect(out).to include("===== Group development, test =====")
+ expect(out).to include("activesupport")
+ expect(out).to include("duradura")
+ end
+ end
+
+ describe "with --groups option" do
+ it "not outdated gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ gem "duradura", '7.0'
+ end
+ G
+
+ bundle "outdated --groups"
+ expect(out).to include("Bundle up to date!")
+ end
+
+ it "returns a sorted list of outdated gems by groups" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ gem "duradura", '7.0'
+ end
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "terranova", "9"
+ build_gem "duradura", "8.0"
+ end
+
+ bundle "outdated --groups"
+ expect(out).to include("===== Group default =====")
+ expect(out).to include("terranova (newest 9, installed 8, requested = 8)")
+ expect(out).to include("===== Group development, test =====")
+ expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)")
+ expect(out).to include("duradura (newest 8.0, installed 7.0, requested = 7.0)")
+
+ expect(out).not_to include("weakling (")
+
+ # TODO: check gems order inside the group
+ end
+ end
+
+ describe "with --local option" do
+ it "uses local cache to return a list of outdated gems" do
+ update_repo2 do
+ build_gem "activesupport", "2.3.4"
+ end
+
+ bundle! "config clean false"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "2.3.4"
+ G
+
+ bundle "outdated --local"
+
+ expect(out).to include("activesupport (newest 2.3.5, installed 2.3.4, requested = 2.3.4)")
+ end
+
+ it "doesn't hit repo2" do
+ FileUtils.rm_rf(gem_repo2)
+
+ bundle "outdated --local"
+ expect(out).not_to match(/Fetching (gem|version|dependency) metadata from/)
+ end
+ end
+
+ shared_examples_for "a minimal output is desired" do
+ context "and gems are outdated" do
+ before do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "weakling", "0.2"
+ end
+ end
+
+ it "outputs a sorted list of outdated gems with a more minimal format" do
+ minimal_output = "activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)\n" \
+ "weakling (newest 0.2, installed 0.0.3, requested ~> 0.0.1)"
+ subject
+ expect(out).to eq(minimal_output)
+ end
+ end
+
+ context "and no gems are outdated" do
+ it "has empty output" do
+ subject
+ expect(out).to eq("")
+ end
+ end
+ end
+
+ describe "with --parseable option" do
+ subject { bundle "outdated --parseable" }
+
+ it_behaves_like "a minimal output is desired"
+ end
+
+ describe "with aliased --porcelain option" do
+ subject { bundle "outdated --porcelain" }
+
+ it_behaves_like "a minimal output is desired"
+ end
+
+ describe "with specified gems" do
+ it "returns list of outdated gems" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ bundle "outdated foo"
+ expect(out).not_to include("activesupport (newest")
+ expect(out).to include("foo (newest 1.0")
+ end
+ end
+
+ describe "pre-release gems" do
+ context "without the --pre option" do
+ it "ignores pre-release versions" do
+ update_repo2 do
+ build_gem "activesupport", "3.0.0.beta"
+ end
+
+ bundle "outdated"
+ expect(out).not_to include("activesupport (3.0.0.beta > 2.3.5)")
+ end
+ end
+
+ context "with the --pre option" do
+ it "includes pre-release versions" do
+ update_repo2 do
+ build_gem "activesupport", "3.0.0.beta"
+ end
+
+ bundle "outdated --pre"
+ expect(out).to include("activesupport (newest 3.0.0.beta, installed 2.3.5, requested = 2.3.5)")
+ end
+ end
+
+ context "when current gem is a pre-release" do
+ it "includes the gem" do
+ update_repo2 do
+ build_gem "activesupport", "3.0.0.beta.1"
+ build_gem "activesupport", "3.0.0.beta.2"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "3.0.0.beta.1"
+ G
+
+ bundle "outdated"
+ expect(out).to include("(newest 3.0.0.beta.2, installed 3.0.0.beta.1, requested = 3.0.0.beta.1)")
+ end
+ end
+ end
+
+ describe "with --strict option" do
+ it "only reports gems that have a newer version that matches the specified dependency version requirements" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "weakling", "0.0.5"
+ end
+
+ bundle "outdated --strict"
+
+ expect(out).to_not include("activesupport (newest")
+ expect(out).to include("(newest 0.0.5, installed 0.0.3, requested ~> 0.0.1)")
+ end
+
+ it "only reports gem dependencies when they can actually be updated" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack_middleware", "1.0"
+ G
+
+ bundle "outdated --strict"
+
+ expect(out).to_not include("rack (1.2")
+ end
+
+ describe "and filter options" do
+ it "only reports gems that match requirement and patch filter level" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "~> 2.3"
+ gem "weakling", ">= 0.0.1"
+ G
+
+ update_repo2 do
+ build_gem "activesupport", %w[2.4.0 3.0.0]
+ build_gem "weakling", "0.0.5"
+ end
+
+ bundle "outdated --strict --filter-patch"
+
+ expect(out).to_not include("activesupport (newest")
+ expect(out).to include("(newest 0.0.5, installed 0.0.3")
+ end
+
+ it "only reports gems that match requirement and minor filter level" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "~> 2.3"
+ gem "weakling", ">= 0.0.1"
+ G
+
+ update_repo2 do
+ build_gem "activesupport", %w[2.3.9]
+ build_gem "weakling", "0.1.5"
+ end
+
+ bundle "outdated --strict --filter-minor"
+
+ expect(out).to_not include("activesupport (newest")
+ expect(out).to include("(newest 0.1.5, installed 0.0.3")
+ end
+
+ it "only reports gems that match requirement and major filter level" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "~> 2.3"
+ gem "weakling", ">= 0.0.1"
+ G
+
+ update_repo2 do
+ build_gem "activesupport", %w[2.4.0 2.5.0]
+ build_gem "weakling", "1.1.5"
+ end
+
+ bundle "outdated --strict --filter-major"
+
+ expect(out).to_not include("activesupport (newest")
+ expect(out).to include("(newest 1.1.5, installed 0.0.3")
+ end
+ end
+ end
+
+ describe "with invalid gem name" do
+ it "returns could not find gem name" do
+ bundle "outdated invalid_gem_name"
+ expect(out).to include("Could not find gem 'invalid_gem_name'.")
+ end
+
+ it "returns non-zero exit code" do
+ bundle "outdated invalid_gem_name"
+ expect(exitstatus).to_not be_zero if exitstatus
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "0.9.1"
+ gem "foo"
+ G
+
+ bundle "config auto_install 1"
+ bundle :outdated
+ expect(out).to include("Installing foo 1.0")
+ end
+
+ context "after bundle install --deployment", :bundler => "< 2" do
+ before do
+ install_gemfile <<-G, forgotten_command_line_options(:deployment => true)
+ source "file://#{gem_repo2}"
+
+ gem "rack"
+ gem "foo"
+ G
+ end
+
+ it "outputs a helpful message about being in deployment mode" do
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle "outdated"
+ expect(last_command).to be_failure
+ expect(out).to include("You are trying to check outdated gems in deployment mode.")
+ expect(out).to include("Run `bundle outdated` elsewhere.")
+ expect(out).to include("If this is a development machine, remove the ")
+ expect(out).to include("Gemfile freeze\nby running `bundle install --no-deployment`.")
+ end
+ end
+
+ context "after bundle config deployment true" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "rack"
+ gem "foo"
+ G
+ bundle! "config deployment true"
+ end
+
+ it "outputs a helpful message about being in deployment mode" do
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle "outdated"
+ expect(last_command).to be_failure
+ expect(out).to include("You are trying to check outdated gems in deployment mode.")
+ expect(out).to include("Run `bundle outdated` elsewhere.")
+ expect(out).to include("If this is a development machine, remove the ")
+ expect(out).to include("Gemfile freeze\nby running `bundle config --delete deployment`.")
+ end
+ end
+
+ context "update available for a gem on a different platform" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "laduradura", '= 5.15.2'
+ G
+ end
+
+ it "reports that no updates are available" do
+ bundle "outdated"
+ expect(out).to include("Bundle up to date!")
+ end
+ end
+
+ context "update available for a gem on the same platform while multiple platforms used for gem" do
+ it "reports that updates are available if the Ruby platform is used" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby]
+ G
+
+ bundle "outdated"
+ expect(out).to include("Bundle up to date!")
+ end
+
+ it "reports that updates are available if the JRuby platform is used" do
+ simulate_ruby_engine "jruby", "1.6.7" do
+ simulate_platform "jruby" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby]
+ G
+
+ bundle "outdated"
+ expect(out).to include("Outdated gems included in the bundle:")
+ expect(out).to include("laduradura (newest 5.15.3, installed 5.15.2, requested = 5.15.2)")
+ end
+ end
+ end
+ end
+
+ shared_examples_for "version update is detected" do
+ it "reports that a gem has a newer version" do
+ subject
+ expect(out).to include("Outdated gems included in the bundle:")
+ expect(out).to include("activesupport (newest")
+ expect(out).to_not include("ERROR REPORT TEMPLATE")
+ end
+ end
+
+ shared_examples_for "major version updates are detected" do
+ before do
+ update_repo2 do
+ build_gem "activesupport", "3.3.5"
+ build_gem "weakling", "0.8.0"
+ end
+ end
+
+ it_behaves_like "version update is detected"
+ end
+
+ context "when on a new machine" do
+ before do
+ simulate_new_machine
+
+ update_git "foo", :path => lib_path("foo")
+ update_repo2 do
+ build_gem "activesupport", "3.3.5"
+ build_gem "weakling", "0.8.0"
+ end
+ end
+
+ subject { bundle "outdated" }
+ it_behaves_like "version update is detected"
+ end
+
+ shared_examples_for "minor version updates are detected" do
+ before do
+ update_repo2 do
+ build_gem "activesupport", "2.7.5"
+ build_gem "weakling", "2.0.1"
+ end
+ end
+
+ it_behaves_like "version update is detected"
+ end
+
+ shared_examples_for "patch version updates are detected" do
+ before do
+ update_repo2 do
+ build_gem "activesupport", "2.3.7"
+ build_gem "weakling", "0.3.1"
+ end
+ end
+
+ it_behaves_like "version update is detected"
+ end
+
+ shared_examples_for "no version updates are detected" do
+ it "does not detect any version updates" do
+ subject
+ expect(out).to include("updates to display.")
+ expect(out).to_not include("ERROR REPORT TEMPLATE")
+ expect(out).to_not include("activesupport (newest")
+ expect(out).to_not include("weakling (newest")
+ end
+ end
+
+ shared_examples_for "major version is ignored" do
+ before do
+ update_repo2 do
+ build_gem "activesupport", "3.3.5"
+ build_gem "weakling", "1.0.1"
+ end
+ end
+
+ it_behaves_like "no version updates are detected"
+ end
+
+ shared_examples_for "minor version is ignored" do
+ before do
+ update_repo2 do
+ build_gem "activesupport", "2.4.5"
+ build_gem "weakling", "0.3.1"
+ end
+ end
+
+ it_behaves_like "no version updates are detected"
+ end
+
+ shared_examples_for "patch version is ignored" do
+ before do
+ update_repo2 do
+ build_gem "activesupport", "2.3.6"
+ build_gem "weakling", "0.0.4"
+ end
+ end
+
+ it_behaves_like "no version updates are detected"
+ end
+
+ describe "with --filter-major option" do
+ subject { bundle "outdated --filter-major" }
+
+ it_behaves_like "major version updates are detected"
+ it_behaves_like "minor version is ignored"
+ it_behaves_like "patch version is ignored"
+ end
+
+ describe "with --filter-minor option" do
+ subject { bundle "outdated --filter-minor" }
+
+ it_behaves_like "minor version updates are detected"
+ it_behaves_like "major version is ignored"
+ it_behaves_like "patch version is ignored"
+ end
+
+ describe "with --filter-patch option" do
+ subject { bundle "outdated --filter-patch" }
+
+ it_behaves_like "patch version updates are detected"
+ it_behaves_like "major version is ignored"
+ it_behaves_like "minor version is ignored"
+ end
+
+ describe "with --filter-minor --filter-patch options" do
+ subject { bundle "outdated --filter-minor --filter-patch" }
+
+ it_behaves_like "minor version updates are detected"
+ it_behaves_like "patch version updates are detected"
+ it_behaves_like "major version is ignored"
+ end
+
+ describe "with --filter-major --filter-minor options" do
+ subject { bundle "outdated --filter-major --filter-minor" }
+
+ it_behaves_like "major version updates are detected"
+ it_behaves_like "minor version updates are detected"
+ it_behaves_like "patch version is ignored"
+ end
+
+ describe "with --filter-major --filter-patch options" do
+ subject { bundle "outdated --filter-major --filter-patch" }
+
+ it_behaves_like "major version updates are detected"
+ it_behaves_like "patch version updates are detected"
+ it_behaves_like "minor version is ignored"
+ end
+
+ describe "with --filter-major --filter-minor --filter-patch options" do
+ subject { bundle "outdated --filter-major --filter-minor --filter-patch" }
+
+ it_behaves_like "major version updates are detected"
+ it_behaves_like "minor version updates are detected"
+ it_behaves_like "patch version updates are detected"
+ end
+
+ context "conservative updates" do
+ context "without update-strict" do
+ before do
+ build_repo4 do
+ build_gem "patch", %w[1.0.0 1.0.1]
+ build_gem "minor", %w[1.0.0 1.0.1 1.1.0]
+ build_gem "major", %w[1.0.0 1.0.1 1.1.0 2.0.0]
+ end
+
+ # establish a lockfile set to 1.0.0
+ install_gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'patch', '1.0.0'
+ gem 'minor', '1.0.0'
+ gem 'major', '1.0.0'
+ G
+
+ # remove 1.4.3 requirement and bar altogether
+ # to setup update specs below
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'patch'
+ gem 'minor'
+ gem 'major'
+ G
+ end
+
+ it "shows nothing when patching and filtering to minor" do
+ bundle "outdated --patch --filter-minor"
+
+ expect(out).to include("No minor updates to display.")
+ expect(out).not_to include("patch (newest")
+ expect(out).not_to include("minor (newest")
+ expect(out).not_to include("major (newest")
+ end
+
+ it "shows all gems when patching and filtering to patch" do
+ bundle "outdated --patch --filter-patch"
+
+ expect(out).to include("patch (newest 1.0.1")
+ expect(out).to include("minor (newest 1.0.1")
+ expect(out).to include("major (newest 1.0.1")
+ end
+
+ it "shows minor and major when updating to minor and filtering to patch and minor" do
+ bundle "outdated --minor --filter-minor"
+
+ expect(out).not_to include("patch (newest")
+ expect(out).to include("minor (newest 1.1.0")
+ expect(out).to include("major (newest 1.1.0")
+ end
+
+ it "shows minor when updating to major and filtering to minor with parseable" do
+ bundle "outdated --major --filter-minor --parseable"
+
+ expect(out).not_to include("patch (newest")
+ expect(out).to include("minor (newest")
+ expect(out).not_to include("major (newest")
+ end
+ end
+
+ context "with update-strict" do
+ before do
+ build_repo4 do
+ build_gem "foo", %w[1.4.3 1.4.4] do |s|
+ s.add_dependency "bar", "~> 2.0"
+ end
+ build_gem "foo", %w[1.4.5 1.5.0] do |s|
+ s.add_dependency "bar", "~> 2.1"
+ end
+ build_gem "foo", %w[1.5.1] do |s|
+ s.add_dependency "bar", "~> 3.0"
+ end
+ build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0]
+ build_gem "qux", %w[1.0.0 1.1.0 2.0.0]
+ end
+
+ # establish a lockfile set to 1.4.3
+ install_gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'foo', '1.4.3'
+ gem 'bar', '2.0.3'
+ gem 'qux', '1.0.0'
+ G
+
+ # remove 1.4.3 requirement and bar altogether
+ # to setup update specs below
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'foo'
+ gem 'qux'
+ G
+ end
+
+ it "shows gems with update-strict updating to patch and filtering to patch" do
+ bundle "outdated --patch --update-strict --filter-patch"
+
+ expect(out).to include("foo (newest 1.4.4")
+ expect(out).to include("bar (newest 2.0.5")
+ expect(out).not_to include("qux (newest")
+ end
+ end
+ end
+
+ describe "with --only-explicit" do
+ it "does not report outdated dependent gems" do
+ build_repo4 do
+ build_gem "weakling", %w[0.2 0.3] do |s|
+ s.add_dependency "bar", "~> 2.1"
+ end
+ build_gem "bar", %w[2.1 2.2]
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'weakling', '0.2'
+ gem 'bar', '2.1'
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'weakling'
+ G
+
+ bundle "outdated --only-explicit"
+
+ expect(out).to include("weakling (newest 0.3")
+ expect(out).not_to include("bar (newest 2.2")
+ end
+ end
+end
diff --git a/spec/bundler/commands/package_spec.rb b/spec/bundler/commands/package_spec.rb
new file mode 100644
index 0000000000..6351909bc7
--- /dev/null
+++ b/spec/bundler/commands/package_spec.rb
@@ -0,0 +1,306 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle package" do
+ context "with --gemfile" do
+ it "finds the gemfile" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ bundle "package --gemfile=NotGemfile"
+
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with --all" do
+ context "without a gemspec" do
+ it "caches all dependencies except bundler itself" do
+ gemfile <<-D
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ gem 'bundler'
+ D
+
+ bundle :package, forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist
+ end
+ end
+
+ context "with a gemspec" do
+ context "that has the same name as the gem" do
+ before do
+ File.open(bundled_app("mygem.gemspec"), "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "mygem"
+ s.version = "0.1.1"
+ s.summary = ""
+ s.authors = ["gem author"]
+ s.add_development_dependency "nokogiri", "=1.4.2"
+ end
+ G
+ end
+ end
+
+ it "caches all dependencies except bundler and the gemspec specified gem" do
+ gemfile <<-D
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ gemspec
+ D
+
+ bundle! :package, forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist
+ expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist
+ expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist
+ end
+ end
+
+ context "that has a different name as the gem" do
+ before do
+ File.open(bundled_app("mygem_diffname.gemspec"), "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "mygem"
+ s.version = "0.1.1"
+ s.summary = ""
+ s.authors = ["gem author"]
+ s.add_development_dependency "nokogiri", "=1.4.2"
+ end
+ G
+ end
+ end
+
+ it "caches all dependencies except bundler and the gemspec specified gem" do
+ gemfile <<-D
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ gemspec
+ D
+
+ bundle! :package, forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist
+ expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist
+ expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist
+ end
+ end
+ end
+
+ context "with multiple gemspecs" do
+ before do
+ File.open(bundled_app("mygem.gemspec"), "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "mygem"
+ s.version = "0.1.1"
+ s.summary = ""
+ s.authors = ["gem author"]
+ s.add_development_dependency "nokogiri", "=1.4.2"
+ end
+ G
+ end
+ File.open(bundled_app("mygem_client.gemspec"), "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "mygem_test"
+ s.version = "0.1.1"
+ s.summary = ""
+ s.authors = ["gem author"]
+ s.add_development_dependency "weakling", "=0.0.3"
+ end
+ G
+ end
+ end
+
+ it "caches all dependencies except bundler and the gemspec specified gems" do
+ gemfile <<-D
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ gemspec :name => 'mygem'
+ gemspec :name => 'mygem_test'
+ D
+
+ bundle! :package, forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist
+ expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist
+ expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist
+ expect(bundled_app("vendor/cache/mygem_test-0.1.1.gem")).to_not exist
+ expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist
+ end
+ end
+ end
+
+ context "with --path", :bundler => "< 2" do
+ it "sets root directory for gems" do
+ gemfile <<-D
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ D
+
+ bundle! :package, forgotten_command_line_options(:path => bundled_app("test"))
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(bundled_app("test/vendor/cache/")).to exist
+ end
+ end
+
+ context "with --no-install" do
+ it "puts the gems in vendor/cache but does not install them" do
+ gemfile <<-D
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ D
+
+ bundle! "package --no-install"
+
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "does not prevent installing gems with bundle install" do
+ gemfile <<-D
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ D
+
+ bundle! "package --no-install"
+ bundle! "install"
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with --all-platforms" do
+ it "puts the gems in vendor/cache even for other rubies", :ruby => "2.1" do
+ gemfile <<-D
+ source "file://#{gem_repo1}"
+ gem 'rack', :platforms => :ruby_19
+ D
+
+ bundle "package --all-platforms"
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+ end
+
+ context "with --frozen" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ bundle "install"
+ end
+
+ subject { bundle :package, forgotten_command_line_options(:frozen => true) }
+
+ it "tries to install with frozen" do
+ bundle! "config deployment true"
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rack-obama"
+ G
+ subject
+ expect(exitstatus).to eq(16) if exitstatus
+ expect(out).to include("deployment mode")
+ expect(out).to include("You have added to the Gemfile")
+ expect(out).to include("* rack-obama")
+ bundle "env"
+ expect(out).to include("frozen").or include("deployment")
+ end
+ end
+end
+
+RSpec.describe "bundle install with gem sources" do
+ describe "when cached and locked" do
+ it "does not hit the remote at all" do
+ build_repo2
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ G
+
+ bundle :pack
+ simulate_new_machine
+ FileUtils.rm_rf gem_repo2
+
+ bundle "install --local"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not hit the remote at all" do
+ build_repo2
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ G
+
+ bundle! :pack
+ simulate_new_machine
+ FileUtils.rm_rf gem_repo2
+
+ bundle! :install, forgotten_command_line_options(:deployment => true, :path => "vendor/bundle")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not reinstall already-installed gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ bundle :pack
+
+ build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s|
+ s.write "lib/rack.rb", "raise 'omg'"
+ end
+
+ bundle :install
+ expect(err).to lack_errors
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "ignores cached gems for the wrong platform" do
+ simulate_platform "java" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "platform_specific"
+ G
+ bundle :pack
+ end
+
+ simulate_new_machine
+
+ simulate_platform "ruby" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "platform_specific"
+ G
+ run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
+ expect(out).to eq("1.0.0 RUBY")
+ end
+ end
+
+ it "does not update the cache if --no-cache is passed" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ bundled_app("vendor/cache").mkpath
+ expect(bundled_app("vendor/cache").children).to be_empty
+
+ bundle "install --no-cache"
+ expect(bundled_app("vendor/cache").children).to be_empty
+ end
+ end
+end
diff --git a/spec/bundler/commands/pristine_spec.rb b/spec/bundler/commands/pristine_spec.rb
new file mode 100644
index 0000000000..0bfc37560a
--- /dev/null
+++ b/spec/bundler/commands/pristine_spec.rb
@@ -0,0 +1,192 @@
+# frozen_string_literal: true
+
+require "bundler/vendored_fileutils"
+
+RSpec.describe "bundle pristine", :ruby_repo do
+ before :each do
+ build_lib "baz", :path => bundled_app do |s|
+ s.version = "1.0.0"
+ s.add_development_dependency "baz-dev", "=1.0.0"
+ end
+
+ build_repo2 do
+ build_gem "weakling"
+ build_gem "baz-dev", "1.0.0"
+ build_gem "very_simple_binary", &:add_c_extension
+ build_git "foo", :path => lib_path("foo")
+ build_git "git_with_ext", :path => lib_path("git_with_ext"), &:add_c_extension
+ build_lib "bar", :path => lib_path("bar")
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+ gem "weakling"
+ gem "very_simple_binary"
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "git_with_ext", :git => "#{lib_path("git_with_ext")}"
+ gem "bar", :path => "#{lib_path("bar")}"
+
+ gemspec
+ G
+ end
+
+ context "when sourced from RubyGems" do
+ it "reverts using cached .gem file" do
+ spec = Bundler.definition.specs["weakling"].first
+ changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt")
+
+ FileUtils.touch(changes_txt)
+ expect(changes_txt).to be_file
+
+ bundle "pristine"
+ expect(changes_txt).to_not be_file
+ end
+
+ it "does not delete the bundler gem" do
+ ENV["BUNDLER_SPEC_KEEP_DEFAULT_BUNDLER_GEM"] = "true"
+ system_gems :bundler
+ bundle! "install"
+ bundle! "pristine", :system_bundler => true
+ bundle! "-v", :system_bundler => true
+
+ expected = if Bundler::VERSION < "2.0"
+ "Bundler version"
+ else
+ Bundler::VERSION
+ end
+
+ expect(out).to start_with(expected)
+ end
+ end
+
+ context "when sourced from git repo" do
+ it "reverts by resetting to current revision`" do
+ spec = Bundler.definition.specs["foo"].first
+ changed_file = Pathname.new(spec.full_gem_path).join("lib/foo.rb")
+ diff = "#Pristine spec changes"
+
+ File.open(changed_file, "a") {|f| f.puts diff }
+ expect(File.read(changed_file)).to include(diff)
+
+ bundle! "pristine"
+ expect(File.read(changed_file)).to_not include(diff)
+ end
+
+ it "removes added files" do
+ spec = Bundler.definition.specs["foo"].first
+ changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt")
+
+ FileUtils.touch(changes_txt)
+ expect(changes_txt).to be_file
+
+ bundle! "pristine"
+ expect(changes_txt).not_to be_file
+ end
+ end
+
+ context "when sourced from gemspec" do
+ it "displays warning and ignores changes when sourced from gemspec" do
+ spec = Bundler.definition.specs["baz"].first
+ changed_file = Pathname.new(spec.full_gem_path).join("lib/baz.rb")
+ diff = "#Pristine spec changes"
+
+ File.open(changed_file, "a") {|f| f.puts diff }
+ expect(File.read(changed_file)).to include(diff)
+
+ bundle "pristine"
+ expect(File.read(changed_file)).to include(diff)
+ expect(out).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is sourced from local path.")
+ end
+
+ it "reinstall gemspec dependency" do
+ spec = Bundler.definition.specs["baz-dev"].first
+ changed_file = Pathname.new(spec.full_gem_path).join("lib/baz-dev.rb")
+ diff = "#Pristine spec changes"
+
+ File.open(changed_file, "a") {|f| f.puts "#Pristine spec changes" }
+ expect(File.read(changed_file)).to include(diff)
+
+ bundle "pristine"
+ expect(File.read(changed_file)).to_not include(diff)
+ end
+ end
+
+ context "when sourced from path" do
+ it "displays warning and ignores changes when sourced from local path" do
+ spec = Bundler.definition.specs["bar"].first
+ changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt")
+ FileUtils.touch(changes_txt)
+ expect(changes_txt).to be_file
+ bundle "pristine"
+ expect(out).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is sourced from local path.")
+ expect(changes_txt).to be_file
+ end
+ end
+
+ context "when passing a list of gems to pristine" do
+ it "resets them" do
+ foo = Bundler.definition.specs["foo"].first
+ foo_changes_txt = Pathname.new(foo.full_gem_path).join("lib/changes.txt")
+ FileUtils.touch(foo_changes_txt)
+ expect(foo_changes_txt).to be_file
+
+ bar = Bundler.definition.specs["bar"].first
+ bar_changes_txt = Pathname.new(bar.full_gem_path).join("lib/changes.txt")
+ FileUtils.touch(bar_changes_txt)
+ expect(bar_changes_txt).to be_file
+
+ weakling = Bundler.definition.specs["weakling"].first
+ weakling_changes_txt = Pathname.new(weakling.full_gem_path).join("lib/changes.txt")
+ FileUtils.touch(weakling_changes_txt)
+ expect(weakling_changes_txt).to be_file
+
+ bundle! "pristine foo bar weakling"
+
+ expect(out).to include("Cannot pristine bar (1.0). Gem is sourced from local path.").
+ and include("Installing weakling 1.0")
+
+ expect(weakling_changes_txt).not_to be_file
+ expect(foo_changes_txt).not_to be_file
+ expect(bar_changes_txt).to be_file
+ end
+
+ it "raises when one of them is not in the lockfile" do
+ bundle "pristine abcabcabc"
+ expect(out).to include("Could not find gem 'abcabcabc'.")
+ end
+ end
+
+ context "when a build config exists for one of the gems" do
+ let(:very_simple_binary) { Bundler.definition.specs["very_simple_binary"].first }
+ let(:c_ext_dir) { Pathname.new(very_simple_binary.full_gem_path).join("ext") }
+ let(:build_opt) { "--with-ext-lib=#{c_ext_dir}" }
+ before { bundle "config build.very_simple_binary -- #{build_opt}" }
+
+ # This just verifies that the generated Makefile from the c_ext gem makes
+ # use of the build_args from the bundle config
+ it "applies the config when installing the gem" do
+ bundle! "pristine"
+
+ makefile_contents = File.read(c_ext_dir.join("Makefile").to_s)
+ expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/)
+ expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/)
+ end
+ end
+
+ context "when a build config exists for a git sourced gem" do
+ let(:git_with_ext) { Bundler.definition.specs["git_with_ext"].first }
+ let(:c_ext_dir) { Pathname.new(git_with_ext.full_gem_path).join("ext") }
+ let(:build_opt) { "--with-ext-lib=#{c_ext_dir}" }
+ before { bundle "config build.git_with_ext -- #{build_opt}" }
+
+ # This just verifies that the generated Makefile from the c_ext gem makes
+ # use of the build_args from the bundle config
+ it "applies the config when installing the gem" do
+ bundle! "pristine"
+
+ makefile_contents = File.read(c_ext_dir.join("Makefile").to_s)
+ expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/)
+ expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/)
+ end
+ end
+end
diff --git a/spec/bundler/commands/remove_spec.rb b/spec/bundler/commands/remove_spec.rb
new file mode 100644
index 0000000000..faeb654b14
--- /dev/null
+++ b/spec/bundler/commands/remove_spec.rb
@@ -0,0 +1,583 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle remove" do
+ context "when no gems are specified" do
+ it "throws error" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ G
+
+ bundle "remove"
+
+ expect(out).to include("Please specify gems to remove.")
+ end
+ end
+
+ context "when --install flag is specified" do
+ it "removes gems from .bundle" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ bundle! "remove rack --install"
+
+ expect(out).to include("rack was removed.")
+ expect(the_bundle).to_not include_gems "rack"
+ end
+ end
+
+ describe "remove single gem from gemfile" do
+ context "when gem is present in gemfile" do
+ it "shows success for removed gem" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when gem is not present in gemfile" do
+ it "shows warning for gem that could not be removed" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile")} so it could not be removed.")
+ end
+ end
+ end
+
+ describe "remove mutiple gems from gemfile" do
+ context "when all gems are present in gemfile" do
+ it "shows success fir all removed gems" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle! "remove rack rails"
+
+ expect(out).to include("rack was removed.")
+ expect(out).to include("rails was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when some gems are not present in the gemfile" do
+ it "shows warning for those not present and success for those that can be removed" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rails"
+ gem "minitest"
+ gem "rspec"
+ G
+
+ bundle "remove rails rack minitest"
+
+ expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile")} so it could not be removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rails"
+ gem "minitest"
+ gem "rspec"
+ G
+ end
+ end
+ end
+
+ context "with inline groups" do
+ it "removes the specified gem" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", :group => [:dev]
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ describe "with group blocks" do
+ context "when single group block with gem to be removed is present" do
+ it "removes the group block" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ gem "rspec"
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when an empty block is also present" do
+ it "removes all empty blocks" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ gem "rspec"
+ end
+
+ group :dev do
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when the gem belongs to mutiple groups" do
+ it "removes the groups" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test, :serioustest do
+ gem "rspec"
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when the gem is present in mutiple groups" do
+ it "removes all empty blocks" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :one do
+ gem "rspec"
+ end
+
+ group :two do
+ gem "rspec"
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+ end
+
+ describe "nested group blocks" do
+ context "when all the groups will be empty after removal" do
+ it "removes the empty nested blocks" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ group :serioustest do
+ gem "rspec"
+ end
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "when outer group will not be empty after removal" do
+ it "removes only empty blocks" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ gem "rack-test"
+
+ group :serioustest do
+ gem "rspec"
+ end
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ gem "rack-test"
+
+ end
+ G
+ end
+ end
+
+ context "when inner group will not be empty after removal" do
+ it "removes only empty blocks" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ group :serioustest do
+ gem "rspec"
+ gem "rack-test"
+ end
+ end
+ G
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ group :test do
+ group :serioustest do
+ gem "rack-test"
+ end
+ end
+ G
+ end
+ end
+ end
+
+ describe "arbitrary gemfile" do
+ context "when mutiple gems are present in same line" do
+ it "shows warning for gems not removed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"; gem "rails"
+ G
+
+ bundle "remove rails"
+
+ if Gem::VERSION >= "1.6.0"
+ expect(out).to include("Gems could not be removed. rack (>= 0) would also have been removed.")
+ else
+ expect(out).to include("Gems could not be removed. rack (>= 0, runtime) would also have been removed.")
+ end
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"; gem "rails"
+ G
+ end
+ end
+
+ context "when some gems could not be removed" do
+ it "shows warning for gems not removed and success for those removed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem"rack"
+ gem"rspec"
+ gem "rails"
+ gem "minitest"
+ G
+
+ bundle! "remove rails rack rspec minitest"
+
+ expect(out).to include("rails was removed.")
+ expect(out).to include("minitest was removed.")
+ expect(out).to include("rack, rspec could not be removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ gem"rack"
+ gem"rspec"
+ G
+ end
+ end
+ end
+
+ context "with sources" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "rspec"
+ end
+ end
+
+ it "removes gems and empty source blocks" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+
+ source "file://#{gem_repo3}" do
+ gem "rspec"
+ end
+ G
+
+ bundle! "install"
+
+ bundle! "remove rspec"
+
+ expect(out).to include("rspec was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+ end
+ end
+
+ describe "with eval_gemfile" do
+ context "when gems are present in both gemfiles" do
+ it "removes the gems" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+
+ gem "rack"
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ end
+ end
+
+ context "when gems are present in other gemfile" do
+ it "removes the gems" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ G
+
+ bundle! "remove rack"
+
+ expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"")
+ expect(out).to include("rack was removed.")
+ end
+ end
+
+ context "when gems to be removed are not specified in any of the gemfiles" do
+ it "throws error for the gems not present" do
+ # an empty gemfile
+ # indicating the gem is not present in the gemfile
+ create_file "Gemfile-other", <<-G
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile")} so it could not be removed.")
+ end
+ end
+
+ context "when the gem is present in parent file but not in gemfile specified by eval_gemfile" do
+ it "removes the gem" do
+ create_file "Gemfile-other", <<-G
+ gem "rails"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile-other")} so it could not be removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ G
+ end
+ end
+
+ context "when gems can not be removed from other gemfile" do
+ it "shows error" do
+ create_file "Gemfile-other", <<-G
+ gem "rails"; gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rack"
+ G
+
+ bundle "remove rack"
+
+ expect(out).to include("rack was removed.")
+ if Gem::VERSION >= "1.6.0"
+ expect(out).to include("Gems could not be removed. rails (>= 0) would also have been removed.")
+ else
+ expect(out).to include("Gems could not be removed. rails (>= 0, runtime) would also have been removed.")
+ end
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ G
+ end
+ end
+
+ context "when gems could not be removed from parent gemfile" do
+ it "shows error" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rails"; gem "rack"
+ G
+
+ bundle "remove rack"
+
+ if Gem::VERSION >= "1.6.0"
+ expect(out).to include("Gems could not be removed. rails (>= 0) would also have been removed.")
+ else
+ expect(out).to include("Gems could not be removed. rails (>= 0, runtime) would also have been removed.")
+ end
+ expect(bundled_app("Gemfile-other").read).to include("gem \"rack\"")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem "rails"; gem "rack"
+ G
+ end
+ end
+
+ context "when gem present in gemfiles but could not be removed from one from one of them" do
+ it "removes gem which can be removed and shows warning for file from which it can not be removed" do
+ create_file "Gemfile-other", <<-G
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ eval_gemfile "Gemfile-other"
+ gem"rack"
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"")
+ end
+ end
+ end
+
+ context "with install_if" do
+ it "removes gems inside blocks and empty blocks" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ install_if(lambda { false }) do
+ gem "rack"
+ end
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "with env" do
+ it "removes gems inside blocks and empty blocks" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ env "BUNDLER_TEST" do
+ gem "rack"
+ end
+ G
+
+ bundle! "remove rack"
+
+ expect(out).to include("rack was removed.")
+ gemfile_should_be <<-G
+ source "file://#{gem_repo1}"
+ G
+ end
+ end
+
+ context "with gemspec" do
+ it "should not remove the gem" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("foo.gemspec", "")
+ s.add_dependency "rack"
+ end
+
+ install_gemfile(<<-G)
+ source "file://#{gem_repo1}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ bundle! "remove foo"
+
+ expect(out).to include("foo could not be removed.")
+ end
+ end
+end
diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb
new file mode 100644
index 0000000000..efbe4b13fb
--- /dev/null
+++ b/spec/bundler/commands/show_spec.rb
@@ -0,0 +1,244 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle show", :bundler => "< 2", :ruby => ">= 2.0" do
+ context "with a standard Gemfile" do
+ before :each do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+ end
+
+ it "creates a Gemfile.lock if one did not exist" do
+ FileUtils.rm("Gemfile.lock")
+
+ bundle "show"
+
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+
+ it "creates a Gemfile.lock when invoked with a gem name" do
+ FileUtils.rm("Gemfile.lock")
+
+ bundle "show rails"
+
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+
+ it "prints path if gem exists in bundle" do
+ bundle "show rails"
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ context "when show command deprecation is enabled" do
+ before { bundle "config major_deprecations yes" }
+
+ it "prints path if gem exists in bundle" do
+ bundle "show rails"
+ expect(out).to eq(
+ "[DEPRECATED FOR 2.0] use `bundle info rails` instead of `bundle show rails`\n" +
+ default_bundle_path("gems", "rails-2.3.2").to_s
+ )
+ end
+
+ it "prints the path to the running bundler" do
+ bundle "show bundler"
+ expect(out).to eq(
+ "[DEPRECATED FOR 2.0] use `bundle info bundler` instead of `bundle show bundler`\n" +
+ root.to_s
+ )
+ end
+
+ it "prints path if gem exists in bundle (with --paths option)" do
+ bundle "show rails --paths"
+ expect(out).to eq(
+ "[DEPRECATED FOR 2.0] use `bundle info rails --path` instead of `bundle show rails --paths`\n" +
+ default_bundle_path("gems", "rails-2.3.2").to_s
+ )
+ end
+
+ it "prints path of all gems in bundle sorted by name" do
+ bundle "show --paths"
+
+ expect(out).to include(default_bundle_path("gems", "rake-10.0.2").to_s)
+ expect(out).to include(default_bundle_path("gems", "rails-2.3.2").to_s)
+
+ out_lines = out.split("\n")
+ expect(out_lines[0]).to eq("[DEPRECATED FOR 2.0] use `bundle list` instead of `bundle show --paths`")
+
+ # Gem names are the last component of their path.
+ gem_list = out_lines[1..-1].map {|p| p.split("/").last }
+ expect(gem_list).to eq(gem_list.sort)
+ end
+ end
+
+ it "prints path if gem exists in bundle (with --paths option)" do
+ bundle "show rails --paths"
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ it "warns if path no longer exists on disk" do
+ FileUtils.rm_rf(default_bundle_path("gems", "rails-2.3.2"))
+
+ bundle "show rails"
+
+ expect(out).to match(/has been deleted/i).
+ and include(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ it "prints the path to the running bundler" do
+ bundle "show bundler"
+ expect(out).to eq(root.to_s)
+ end
+
+ it "complains if gem not in bundle" do
+ bundle "show missing"
+ expect(out).to match(/could not find gem 'missing'/i)
+ end
+
+ it "prints path of all gems in bundle sorted by name" do
+ bundle "show --paths"
+
+ expect(out).to include(default_bundle_path("gems", "rake-10.0.2").to_s)
+ expect(out).to include(default_bundle_path("gems", "rails-2.3.2").to_s)
+
+ # Gem names are the last component of their path.
+ gem_list = out.split.map {|p| p.split("/").last }
+ expect(gem_list).to eq(gem_list.sort)
+ end
+
+ it "prints summary of gems" do
+ bundle "show --verbose"
+
+ loaded_bundler_spec = Bundler.load.specs["bundler"]
+ expected = if !loaded_bundler_spec.empty?
+ loaded_bundler_spec[0].homepage
+ else
+ "No website available."
+ end
+
+ expect(out).to include("* actionmailer (2.3.2)")
+ expect(out).to include("\tSummary: This is just a fake gem for testing")
+ expect(out).to include("\tHomepage: #{expected}")
+ expect(out).to include("\tStatus: Up to date")
+ end
+ end
+
+ context "with a git repo in the Gemfile" do
+ before :each do
+ @git = build_git "foo", "1.0"
+ end
+
+ it "prints out git info" do
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+ expect(the_bundle).to include_gems "foo 1.0"
+
+ bundle :show
+ expect(out).to include("foo (1.0 #{@git.ref_for("master", 6)}")
+ end
+
+ it "prints out branch names other than master" do
+ update_git "foo", :branch => "omg" do |s|
+ s.write "lib/foo.rb", "FOO = '1.0.omg'"
+ end
+ @revision = revision_for(lib_path("foo-1.0"))[0...6]
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg"
+ G
+ expect(the_bundle).to include_gems "foo 1.0.omg"
+
+ bundle :show
+ expect(out).to include("foo (1.0 #{@git.ref_for("omg", 6)}")
+ end
+
+ it "doesn't print the branch when tied to a ref" do
+ sha = revision_for(lib_path("foo-1.0"))
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{sha}"
+ G
+
+ bundle :show
+ expect(out).to include("foo (1.0 #{sha[0..6]})")
+ end
+
+ it "handles when a version is a '-' prerelease", :rubygems => "2.1" do
+ @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo"))
+ install_gemfile <<-G
+ gem "foo", "1.0.0-beta.1", :git => "#{lib_path("foo")}"
+ G
+ expect(the_bundle).to include_gems "foo 1.0.0.pre.beta.1"
+
+ bundle! :show
+ expect(out).to include("foo (1.0.0.pre.beta.1")
+ end
+ end
+
+ context "in a fresh gem in a blank git repo" do
+ before :each do
+ build_git "foo", :path => lib_path("foo")
+ in_app_root_custom lib_path("foo")
+ File.open("Gemfile", "w") {|f| f.puts "gemspec" }
+ sys_exec "rm -rf .git && git init"
+ end
+
+ it "does not output git errors" do
+ bundle :show
+ expect(err).to lack_errors
+ end
+ end
+
+ it "performs an automatic bundle install" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo"
+ G
+
+ bundle "config auto_install 1"
+ bundle :show
+ expect(out).to include("Installing foo 1.0")
+ end
+
+ context "with an invalid regexp for gem name" do
+ it "does not find the gem" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ invalid_regexp = "[]"
+
+ bundle "show #{invalid_regexp}"
+ expect(out).to include("Could not find gem '#{invalid_regexp}'.")
+ end
+ end
+
+ context "--outdated option" do
+ # Regression test for https://github.com/bundler/bundler/issues/5375
+ before do
+ build_repo2
+ end
+
+ it "doesn't update gems to newer versions" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+ gem "rails"
+ G
+
+ expect(the_bundle).to include_gem("rails 2.3.2")
+
+ update_repo2 do
+ build_gem "rails", "3.0.0" do |s|
+ s.executables = "rails"
+ end
+ end
+
+ bundle! "show --outdated"
+
+ bundle! "install"
+ expect(the_bundle).to include_gem("rails 2.3.2")
+ end
+ end
+end
diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb
new file mode 100644
index 0000000000..6eb49d3acd
--- /dev/null
+++ b/spec/bundler/commands/update_spec.rb
@@ -0,0 +1,943 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ before :each do
+ build_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ gem "rack-obama"
+ G
+ end
+
+ describe "with no arguments", :bundler => "< 2" do
+ it "updates the entire bundle" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update"
+ expect(out).to include("Bundle updated!")
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "doesn't delete the Gemfile.lock file if something goes wrong" do
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ gem "rack-obama"
+ exit!
+ G
+ bundle "update"
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+ end
+
+ describe "with --all", :bundler => "2" do
+ it "updates the entire bundle" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle! "update", :all => true
+ expect(out).to include("Bundle updated!")
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "doesn't delete the Gemfile.lock file if something goes wrong" do
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ gem "rack-obama"
+ exit!
+ G
+ bundle "update", :all => true
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+ end
+
+ describe "with --gemfile" do
+ it "creates lock files based on the Gemfile name" do
+ gemfile bundled_app("OmgFile"), <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "1.0"
+ G
+
+ bundle! "update --gemfile OmgFile", :all => bundle_update_requires_all?
+
+ expect(bundled_app("OmgFile.lock")).to exist
+ end
+ end
+
+ context "when update_requires_all_flag is set" do
+ before { bundle! "config update_requires_all_flag true" }
+
+ it "errors when passed nothing" do
+ install_gemfile! ""
+ bundle :update
+ expect(out).to eq("To update everything, pass the `--all` flag.")
+ end
+
+ it "errors when passed --all and another option" do
+ install_gemfile! ""
+ bundle "update --all foo"
+ expect(out).to eq("Cannot specify --all along with specific options.")
+ end
+
+ it "updates everything when passed --all" do
+ install_gemfile! ""
+ bundle "update --all"
+ expect(out).to include("Bundle updated!")
+ end
+ end
+
+ describe "--quiet argument" do
+ it "hides UI messages" do
+ bundle "update --quiet"
+ expect(out).not_to include("Bundle updated!")
+ end
+ end
+
+ describe "with a top level dependency" do
+ it "unlocks all child dependencies that are unrelated to other locked dependencies" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update rack-obama"
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 2.3.5"
+ end
+ end
+
+ describe "with an unknown dependency" do
+ it "should inform the user" do
+ bundle "update halting-problem-solver"
+ expect(out).to include "Could not find gem 'halting-problem-solver'"
+ end
+ it "should suggest alternatives" do
+ bundle "update active-support"
+ expect(out).to include "Did you mean activesupport?"
+ end
+ end
+
+ describe "with a child dependency" do
+ it "should update the child dependency" do
+ update_repo2
+ bundle "update rack"
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+ end
+
+ describe "when a possible resolve requires an older version of a locked gem" do
+ context "and only_update_to_newer_versions is set" do
+ before do
+ bundle! "config only_update_to_newer_versions true"
+ end
+
+ it "does not go to an older version" do
+ build_repo4 do
+ build_gem "tilt", "2.0.8"
+ build_gem "slim", "3.0.9" do |s|
+ s.add_dependency "tilt", [">= 1.3.3", "< 2.1"]
+ end
+ build_gem "slim_lint", "0.16.1" do |s|
+ s.add_dependency "slim", [">= 3.0", "< 5.0"]
+ end
+ build_gem "slim-rails", "0.2.1" do |s|
+ s.add_dependency "slim", ">= 0.9.2"
+ end
+ build_gem "slim-rails", "3.1.3" do |s|
+ s.add_dependency "slim", "~> 3.0"
+ end
+ end
+
+ install_gemfile! <<-G
+ source "file:#{gem_repo4}"
+ gem "slim-rails"
+ gem "slim_lint"
+ G
+
+ expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1")
+
+ update_repo4 do
+ build_gem "slim", "4.0.0" do |s|
+ s.add_dependency "tilt", [">= 2.0.6", "< 2.1"]
+ end
+ end
+
+ bundle! "update", :all => bundle_update_requires_all?
+
+ expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1")
+ end
+
+ it "should still downgrade if forced by the Gemfile" do
+ build_repo4 do
+ build_gem "a"
+ build_gem "b", "1.0"
+ build_gem "b", "2.0"
+ end
+
+ install_gemfile! <<-G
+ source "file:#{gem_repo4}"
+ gem "a"
+ gem "b"
+ G
+
+ expect(the_bundle).to include_gems("a 1.0", "b 2.0")
+
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem "a"
+ gem "b", "1.0"
+ G
+
+ bundle! "update b"
+
+ expect(the_bundle).to include_gems("a 1.0", "b 1.0")
+ end
+ end
+ end
+
+ describe "with --local option" do
+ it "doesn't hit repo2" do
+ FileUtils.rm_rf(gem_repo2)
+
+ bundle "update --local --all"
+ expect(out).not_to include("Fetching source index")
+ end
+ end
+
+ describe "with --group option" do
+ it "should update only specified group gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", :group => :development
+ gem "rack"
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+ bundle "update --group development"
+ expect(the_bundle).to include_gems "activesupport 3.0"
+ expect(the_bundle).not_to include_gems "rack 1.2"
+ end
+
+ context "when conservatively updating a group with non-group sub-deps" do
+ it "should update only specified group gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activemerchant", :group => :development
+ gem "activesupport"
+ G
+ update_repo2 do
+ build_gem "activemerchant", "2.0"
+ build_gem "activesupport", "3.0"
+ end
+ bundle "update --conservative --group development"
+ expect(the_bundle).to include_gems "activemerchant 2.0"
+ expect(the_bundle).not_to include_gems "activesupport 3.0"
+ end
+ end
+
+ context "when there is a source with the same name as a gem in a group" do
+ before :each do
+ build_git "foo", :path => lib_path("activesupport")
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", :group => :development
+ gem "foo", :git => "#{lib_path("activesupport")}"
+ G
+ end
+
+ it "should not update the gems from that source" do
+ update_repo2 { build_gem "activesupport", "3.0" }
+ update_git "foo", "2.0", :path => lib_path("activesupport")
+
+ bundle "update --group development"
+ expect(the_bundle).to include_gems "activesupport 3.0"
+ expect(the_bundle).not_to include_gems "foo 2.0"
+ end
+ end
+
+ context "when bundler itself is a transitive dependency" do
+ it "executes without error" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport", :group => :development
+ gem "rack"
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+ bundle "update --group development"
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}"
+ expect(the_bundle).not_to include_gems "rack 1.2"
+ end
+ end
+ end
+
+ describe "in a frozen bundle" do
+ it "should fail loudly", :bundler => "< 2" do
+ bundle! "install --deployment"
+ bundle "update", :all => bundle_update_requires_all?
+
+ expect(last_command).to be_failure
+ expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m)
+ expect(out).to match(/freeze \nby running `bundle install --no-deployment`./m)
+ end
+
+ it "should suggest different command when frozen is set globally", :bundler => "< 2" do
+ bundle! "config --global frozen 1"
+ bundle "update", :all => bundle_update_requires_all?
+ expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m).
+ and match(/freeze \nby running `bundle config --delete frozen`./m)
+ end
+
+ it "should suggest different command when frozen is set globally", :bundler => "2" do
+ bundle! "config --global deployment true"
+ bundle "update", :all => bundle_update_requires_all?
+ expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m).
+ and match(/freeze \nby running `bundle config --delete deployment`./m)
+ end
+ end
+
+ describe "with --source option" do
+ it "should not update gems not included in the source that happen to have the same name", :bundler => "< 2" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ G
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle! "update --source activesupport"
+ expect(the_bundle).to include_gem "activesupport 3.0"
+ end
+
+ it "should not update gems not included in the source that happen to have the same name", :bundler => "2" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ G
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle! "update --source activesupport"
+ expect(the_bundle).not_to include_gem "activesupport 3.0"
+ end
+
+ context "with unlock_source_unlocks_spec set to false" do
+ before { bundle! "config unlock_source_unlocks_spec false" }
+
+ it "should not update gems not included in the source that happen to have the same name" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ G
+ update_repo2 { build_gem "activesupport", "3.0" }
+
+ bundle "update --source activesupport"
+ expect(the_bundle).not_to include_gems "activesupport 3.0"
+ end
+ end
+ end
+
+ context "when there is a child dependency that is also in the gemfile" do
+ before do
+ build_repo2 do
+ build_gem "fred", "1.0"
+ build_gem "harry", "1.0" do |s|
+ s.add_dependency "fred"
+ end
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "harry"
+ gem "fred"
+ G
+ end
+
+ it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "< 2" do
+ update_repo2 do
+ build_gem "fred", "2.0"
+ build_gem "harry", "2.0" do |s|
+ s.add_dependency "fred"
+ end
+ end
+
+ bundle "update --source harry"
+ expect(the_bundle).to include_gems "harry 2.0"
+ expect(the_bundle).to include_gems "fred 1.0"
+ end
+
+ it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "2" do
+ update_repo2 do
+ build_gem "fred", "2.0"
+ build_gem "harry", "2.0" do |s|
+ s.add_dependency "fred"
+ end
+ end
+
+ bundle "update --source harry"
+ expect(the_bundle).to include_gems "harry 1.0", "fred 1.0"
+ end
+ end
+
+ context "when there is a child dependency that appears elsewhere in the dependency graph" do
+ before do
+ build_repo2 do
+ build_gem "fred", "1.0" do |s|
+ s.add_dependency "george"
+ end
+ build_gem "george", "1.0"
+ build_gem "harry", "1.0" do |s|
+ s.add_dependency "george"
+ end
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "harry"
+ gem "fred"
+ G
+ end
+
+ it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "< 2" do
+ update_repo2 do
+ build_gem "george", "2.0"
+ build_gem "harry", "2.0" do |s|
+ s.add_dependency "george"
+ end
+ end
+
+ bundle "update --source harry"
+ expect(the_bundle).to include_gems "harry 2.0"
+ expect(the_bundle).to include_gems "fred 1.0"
+ expect(the_bundle).to include_gems "george 1.0"
+ end
+
+ it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "2" do
+ update_repo2 do
+ build_gem "george", "2.0"
+ build_gem "harry", "2.0" do |s|
+ s.add_dependency "george"
+ end
+ end
+
+ bundle "update --source harry"
+ expect(the_bundle).to include_gems "harry 1.0", "fred 1.0", "george 1.0"
+ end
+ end
+end
+
+RSpec.describe "bundle update in more complicated situations" do
+ before :each do
+ build_repo2
+ end
+
+ it "will eagerly unlock dependencies of a specified gem" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "thin"
+ gem "rack-obama"
+ G
+
+ update_repo2 do
+ build_gem "thin", "2.0" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ bundle "update thin"
+ expect(the_bundle).to include_gems "thin 2.0", "rack 1.2", "rack-obama 1.0"
+ end
+
+ it "will warn when some explicitly updated gems are not updated" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo2}"
+
+ gem "thin"
+ gem "rack-obama"
+ G
+
+ update_repo2 do
+ build_gem("thin", "2.0") {|s| s.add_dependency "rack" }
+ build_gem "rack", "10.0"
+ end
+
+ bundle! "update thin rack-obama"
+ expect(last_command.stdboth).to include "Bundler attempted to update rack-obama but its version stayed the same"
+ expect(the_bundle).to include_gems "thin 2.0", "rack 10.0", "rack-obama 1.0"
+ end
+
+ it "will update only from pinned source" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ source "file://#{gem_repo1}" do
+ gem "thin"
+ end
+ G
+
+ update_repo2 do
+ build_gem "thin", "2.0"
+ end
+
+ bundle "update"
+ expect(the_bundle).to include_gems "thin 1.0"
+ end
+
+ context "when the lockfile is for a different platform" do
+ before do
+ build_repo4 do
+ build_gem("a", "0.9")
+ build_gem("a", "0.9") {|s| s.platform = "java" }
+ build_gem("a", "1.1")
+ build_gem("a", "1.1") {|s| s.platform = "java" }
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem "a"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: file://#{gem_repo4}
+ specs:
+ a (0.9-java)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ a
+ L
+
+ simulate_platform linux
+ end
+
+ it "allows updating" do
+ bundle! :update, :all => true
+ expect(the_bundle).to include_gem "a 1.1"
+ end
+
+ it "allows updating a specific gem" do
+ bundle! "update a"
+ expect(the_bundle).to include_gem "a 1.1"
+ end
+ end
+end
+
+RSpec.describe "bundle update without a Gemfile.lock" do
+ it "should not explode" do
+ build_repo2
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "rack", "1.0"
+ G
+
+ bundle "update", :all => bundle_update_requires_all?
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+end
+
+RSpec.describe "bundle update when a gem depends on a newer version of bundler" do
+ before(:each) do
+ build_repo2 do
+ build_gem "rails", "3.0.1" do |s|
+ s.add_dependency "bundler", Bundler::VERSION.succ
+ end
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rails", "3.0.1"
+ G
+ end
+
+ it "should explain that bundler conflicted", :bundler => "< 2" do
+ bundle "update", :all => bundle_update_requires_all?
+ expect(last_command.stdboth).not_to match(/in snapshot/i)
+ expect(last_command.bundler_err).to match(/current Bundler version/i).
+ and match(/perhaps you need to update bundler/i)
+ end
+
+ it "should warn that the newer version of Bundler would conflict", :bundler => "2" do
+ bundle! "update", :all => true
+ expect(last_command.bundler_err).to include("rails (3.0.1) has dependency bundler").
+ and include("so the dependency is being ignored")
+ expect(the_bundle).to include_gem "rails 3.0.1"
+ end
+end
+
+RSpec.describe "bundle update" do
+ it "shows the previous version of the gem when updated from rubygems source", :bundler => "< 2" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ G
+
+ bundle "update", :all => bundle_update_requires_all?
+ expect(out).to include("Using activesupport 2.3.5")
+
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", :all => bundle_update_requires_all?
+ expect(out).to include("Installing activesupport 3.0 (was 2.3.5)")
+ end
+
+ context "with suppress_install_using_messages set" do
+ before { bundle! "config suppress_install_using_messages true" }
+
+ it "only prints `Using` for versions that have changed" do
+ build_repo4 do
+ build_gem "bar"
+ build_gem "foo"
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo4}"
+ gem "bar"
+ gem "foo"
+ G
+
+ bundle! "update", :all => bundle_update_requires_all?
+ out.gsub!(/RubyGems [\d\.]+ is not threadsafe.*\n?/, "")
+ expect(out).to include "Resolving dependencies...\nBundle updated!"
+
+ update_repo4 do
+ build_gem "foo", "2.0"
+ end
+
+ bundle! "update", :all => bundle_update_requires_all?
+ out.sub!("Removing foo (1.0)\n", "")
+ out.gsub!(/RubyGems [\d\.]+ is not threadsafe.*\n?/, "")
+ expect(out).to include strip_whitespace(<<-EOS).strip
+ Resolving dependencies...
+ Fetching foo 2.0 (was 1.0)
+ Installing foo 2.0 (was 1.0)
+ Bundle updated
+ EOS
+ end
+ end
+
+ it "shows error message when Gemfile.lock is not preset and gem is specified" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ G
+
+ bundle "update nonexisting"
+ expect(out).to include("This Bundle hasn't been installed yet. Run `bundle install` to update and install the bundled gems.")
+ expect(exitstatus).to eq(22) if exitstatus
+ end
+end
+
+RSpec.describe "bundle update --ruby" do
+ before do
+ install_gemfile <<-G
+ ::RUBY_VERSION = '2.1.3'
+ ::RUBY_PATCHLEVEL = 100
+ ruby '~> 2.1.0'
+ G
+ bundle "update --ruby"
+ end
+
+ context "when the Gemfile removes the ruby" do
+ before do
+ install_gemfile <<-G
+ ::RUBY_VERSION = '2.1.4'
+ ::RUBY_PATCHLEVEL = 222
+ G
+ end
+ it "removes the Ruby from the Gemfile.lock" do
+ bundle "update --ruby"
+
+ lockfile_should_be <<-L
+ GEM
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when the Gemfile specified an updated Ruby version" do
+ before do
+ install_gemfile <<-G
+ ::RUBY_VERSION = '2.1.4'
+ ::RUBY_PATCHLEVEL = 222
+ ruby '~> 2.1.0'
+ G
+ end
+ it "updates the Gemfile.lock with the latest version" do
+ bundle "update --ruby"
+
+ lockfile_should_be <<-L
+ GEM
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ ruby 2.1.4p222
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "when a different Ruby is being used than has been versioned" do
+ before do
+ install_gemfile <<-G
+ ::RUBY_VERSION = '2.2.2'
+ ::RUBY_PATCHLEVEL = 505
+ ruby '~> 2.1.0'
+ G
+ end
+ it "shows a helpful error message" do
+ bundle "update --ruby"
+
+ expect(out).to include("Your Ruby version is 2.2.2, but your Gemfile specified ~> 2.1.0")
+ end
+ end
+
+ context "when updating Ruby version and Gemfile `ruby`" do
+ before do
+ install_gemfile <<-G
+ ::RUBY_VERSION = '1.8.3'
+ ::RUBY_PATCHLEVEL = 55
+ ruby '~> 1.8.0'
+ G
+ end
+ it "updates the Gemfile.lock with the latest version" do
+ bundle "update --ruby"
+
+ lockfile_should_be <<-L
+ GEM
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ ruby 1.8.3p55
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+end
+
+RSpec.describe "bundle update --bundler" do
+ it "updates the bundler version in the lockfile without re-resolving" do
+ build_repo4 do
+ build_gem "rack", "1.0"
+ end
+
+ install_gemfile! <<-G
+ source "file:#{gem_repo4}"
+ gem "rack"
+ G
+ lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2')
+
+ FileUtils.rm_r gem_repo4
+
+ bundle! :update, :bundler => true, :verbose => true
+ expect(the_bundle).to include_gem "rack 1.0"
+
+ expect(the_bundle.locked_gems.bundler_version).to eq v(Bundler::VERSION)
+ end
+end
+
+# these specs are slow and focus on integration and therefore are not exhaustive. unit specs elsewhere handle that.
+RSpec.describe "bundle update conservative" do
+ context "patch and minor options" do
+ before do
+ build_repo4 do
+ build_gem "foo", %w[1.4.3 1.4.4] do |s|
+ s.add_dependency "bar", "~> 2.0"
+ end
+ build_gem "foo", %w[1.4.5 1.5.0] do |s|
+ s.add_dependency "bar", "~> 2.1"
+ end
+ build_gem "foo", %w[1.5.1] do |s|
+ s.add_dependency "bar", "~> 3.0"
+ end
+ build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0]
+ build_gem "qux", %w[1.0.0 1.0.1 1.1.0 2.0.0]
+ end
+
+ # establish a lockfile set to 1.4.3
+ install_gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'foo', '1.4.3'
+ gem 'bar', '2.0.3'
+ gem 'qux', '1.0.0'
+ G
+
+ # remove 1.4.3 requirement and bar altogether
+ # to setup update specs below
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'foo'
+ gem 'qux'
+ G
+ end
+
+ context "patch preferred" do
+ it "single gem updates dependent gem to minor" do
+ bundle! "update --patch foo"
+
+ expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.0"
+ end
+
+ it "update all" do
+ bundle! "update --patch", :all => bundle_update_requires_all?
+
+ expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1"
+ end
+ end
+
+ context "minor preferred" do
+ it "single gem updates dependent gem to major" do
+ bundle! "update --minor foo"
+
+ expect(the_bundle).to include_gems "foo 1.5.1", "bar 3.0.0", "qux 1.0.0"
+ end
+ end
+
+ context "strict" do
+ it "patch preferred" do
+ bundle! "update --patch foo bar --strict"
+
+ expect(the_bundle).to include_gems "foo 1.4.4", "bar 2.0.5", "qux 1.0.0"
+ end
+
+ it "minor preferred" do
+ bundle! "update --minor --strict", :all => bundle_update_requires_all?
+
+ expect(the_bundle).to include_gems "foo 1.5.0", "bar 2.1.1", "qux 1.1.0"
+ end
+ end
+ end
+
+ context "eager unlocking" do
+ before do
+ build_repo4 do
+ build_gem "isolated_owner", %w[1.0.1 1.0.2] do |s|
+ s.add_dependency "isolated_dep", "~> 2.0"
+ end
+ build_gem "isolated_dep", %w[2.0.1 2.0.2]
+
+ build_gem "shared_owner_a", %w[3.0.1 3.0.2] do |s|
+ s.add_dependency "shared_dep", "~> 5.0"
+ end
+ build_gem "shared_owner_b", %w[4.0.1 4.0.2] do |s|
+ s.add_dependency "shared_dep", "~> 5.0"
+ end
+ build_gem "shared_dep", %w[5.0.1 5.0.2]
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'isolated_owner'
+
+ gem 'shared_owner_a'
+ gem 'shared_owner_b'
+ G
+
+ lockfile <<-L
+ GEM
+ remote: file://#{gem_repo4}
+ specs:
+ isolated_dep (2.0.1)
+ isolated_owner (1.0.1)
+ isolated_dep (~> 2.0)
+ shared_dep (5.0.1)
+ shared_owner_a (3.0.1)
+ shared_dep (~> 5.0)
+ shared_owner_b (4.0.1)
+ shared_dep (~> 5.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ shared_owner_a
+ shared_owner_b
+ isolated_owner
+
+ BUNDLED WITH
+ 1.13.0
+ L
+ end
+
+ it "should eagerly unlock isolated dependency" do
+ bundle "update isolated_owner"
+
+ expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.1", "shared_owner_b 4.0.1"
+ end
+
+ it "should eagerly unlock shared dependency" do
+ bundle "update shared_owner_a"
+
+ expect(the_bundle).to include_gems "isolated_owner 1.0.1", "isolated_dep 2.0.1", "shared_dep 5.0.2", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1"
+ end
+
+ it "should not eagerly unlock with --conservative" do
+ bundle "update --conservative shared_owner_a isolated_owner"
+
+ expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1"
+ end
+
+ it "should match bundle install conservative update behavior when not eagerly unlocking" do
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'isolated_owner', '1.0.2'
+
+ gem 'shared_owner_a', '3.0.2'
+ gem 'shared_owner_b'
+ G
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1"
+ end
+ end
+
+ context "error handling" do
+ before do
+ gemfile ""
+ end
+
+ it "raises if too many flags are provided" do
+ bundle "update --patch --minor", :all => bundle_update_requires_all?
+
+ expect(last_command.bundler_err).to eq "Provide only one of the following options: minor, patch"
+ end
+ end
+end
diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb
new file mode 100644
index 0000000000..b919c25e0f
--- /dev/null
+++ b/spec/bundler/commands/version_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle version" do
+ context "with -v" do
+ it "outputs the version", :bundler => "< 2" do
+ bundle! "-v"
+ expect(out).to eq("Bundler version #{Bundler::VERSION}")
+ end
+
+ it "outputs the version", :bundler => "2" do
+ bundle! "-v"
+ expect(out).to eq(Bundler::VERSION)
+ end
+ end
+
+ context "with --version" do
+ it "outputs the version", :bundler => "< 2" do
+ bundle! "--version"
+ expect(out).to eq("Bundler version #{Bundler::VERSION}")
+ end
+
+ it "outputs the version", :bundler => "2" do
+ bundle! "--version"
+ expect(out).to eq(Bundler::VERSION)
+ end
+ end
+
+ context "with version" do
+ it "outputs the version with build metadata", :bundler => "< 2" do
+ bundle! "version"
+ expect(out).to match(/\ABundler version #{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit [a-fA-F0-9]{7,}\)\z/)
+ end
+
+ it "outputs the version with build metadata", :bundler => "2" do
+ bundle! "version"
+ expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit [a-fA-F0-9]{7,}\)\z/)
+ end
+ end
+end
diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb
new file mode 100644
index 0000000000..3804d3561c
--- /dev/null
+++ b/spec/bundler/commands/viz_spec.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle viz", :ruby => "1.9.3", :bundler => "< 2", :if => Bundler.which("dot") do
+ let(:ruby_graphviz) do
+ graphviz_glob = base_system_gems.join("cache/ruby-graphviz*")
+ Pathname.glob(graphviz_glob).first
+ end
+
+ before do
+ system_gems ruby_graphviz
+ end
+
+ it "graphs gems from the Gemfile" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ bundle! "viz"
+ expect(out).to include("gem_graph.png")
+
+ bundle! "viz", :format => "debug"
+ expect(out).to eq(strip_whitespace(<<-DOT).strip)
+ digraph Gemfile {
+ concentrate = "true";
+ normalize = "true";
+ nodesep = "0.55";
+ edge[ weight = "2"];
+ node[ fontname = "Arial, Helvetica, SansSerif"];
+ edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"];
+ default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"];
+ rack [style = "filled", fillcolor = "#B9B9D5", label = "rack"];
+ default -> rack [constraint = "false"];
+ "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama"];
+ default -> "rack-obama" [constraint = "false"];
+ "rack-obama" -> rack;
+ }
+ debugging bundle viz...
+ DOT
+ end
+
+ it "graphs gems that are prereleases" do
+ build_repo2 do
+ build_gem "rack", "1.3.pre"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack", "= 1.3.pre"
+ gem "rack-obama"
+ G
+
+ bundle! "viz"
+ expect(out).to include("gem_graph.png")
+
+ bundle! "viz", :format => :debug, :version => true
+ expect(out).to eq(strip_whitespace(<<-EOS).strip)
+ digraph Gemfile {
+ concentrate = "true";
+ normalize = "true";
+ nodesep = "0.55";
+ edge[ weight = "2"];
+ node[ fontname = "Arial, Helvetica, SansSerif"];
+ edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"];
+ default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"];
+ rack [style = "filled", fillcolor = "#B9B9D5", label = "rack\\n1.3.pre"];
+ default -> rack [constraint = "false"];
+ "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama\\n1.0"];
+ default -> "rack-obama" [constraint = "false"];
+ "rack-obama" -> rack;
+ }
+ debugging bundle viz...
+ EOS
+ end
+
+ context "with another gem that has a graphviz file" do
+ before do
+ build_repo4 do
+ build_gem "graphviz", "999" do |s|
+ s.write("lib/graphviz.rb", "abort 'wrong graphviz gem loaded'")
+ end
+ end
+
+ system_gems ruby_graphviz, "graphviz-999", :gem_repo => gem_repo4
+ end
+
+ it "loads the correct ruby-graphviz gem" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ bundle! "viz", :format => "debug"
+ expect(out).to eq(strip_whitespace(<<-DOT).strip)
+ digraph Gemfile {
+ concentrate = "true";
+ normalize = "true";
+ nodesep = "0.55";
+ edge[ weight = "2"];
+ node[ fontname = "Arial, Helvetica, SansSerif"];
+ edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"];
+ default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"];
+ rack [style = "filled", fillcolor = "#B9B9D5", label = "rack"];
+ default -> rack [constraint = "false"];
+ "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama"];
+ default -> "rack-obama" [constraint = "false"];
+ "rack-obama" -> rack;
+ }
+ debugging bundle viz...
+ DOT
+ end
+ end
+
+ context "--without option" do
+ it "one group" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+
+ group :rails do
+ gem "rails"
+ end
+ G
+
+ bundle! "viz --without=rails"
+ expect(out).to include("gem_graph.png")
+ end
+
+ it "two groups" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+
+ group :rack do
+ gem "rack"
+ end
+
+ group :rails do
+ gem "rails"
+ end
+ G
+
+ bundle! "viz --without=rails:rack"
+ expect(out).to include("gem_graph.png")
+ end
+ end
+end
diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb
new file mode 100644
index 0000000000..d4bb595771
--- /dev/null
+++ b/spec/bundler/install/allow_offline_install_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with :allow_offline_install" do
+ before do
+ bundle "config allow_offline_install true"
+ end
+
+ context "with no cached data locally" do
+ it "still installs" do
+ install_gemfile! <<-G, :artifice => "compact_index"
+ source "http://testgemserver.local"
+ gem "rack-obama"
+ G
+ expect(the_bundle).to include_gem("rack 1.0")
+ end
+
+ it "still fails when the network is down" do
+ install_gemfile <<-G, :artifice => "fail"
+ source "http://testgemserver.local"
+ gem "rack-obama"
+ G
+ expect(out).to include("Could not reach host testgemserver.local.")
+ expect(the_bundle).to_not be_locked
+ end
+ end
+
+ context "with cached data locally" do
+ it "will install from the compact index" do
+ system_gems ["rack-1.0.0"], :path => :bundle_path
+
+ bundle! "config clean false"
+ install_gemfile! <<-G, :artifice => "compact_index"
+ source "http://testgemserver.local"
+ gem "rack-obama"
+ gem "rack", "< 1.0"
+ G
+
+ expect(the_bundle).to include_gems("rack-obama 1.0", "rack 0.9.1")
+
+ gemfile <<-G
+ source "http://testgemserver.local"
+ gem "rack-obama"
+ G
+
+ bundle! :update, :artifice => "fail", :all => true
+ expect(last_command.stdboth).to include "Using the cached data for the new index because of a network error"
+
+ expect(the_bundle).to include_gems("rack-obama 1.0", "rack 1.0.0")
+ end
+
+ def break_git_remote_ops!
+ FileUtils.mkdir_p(tmp("broken_path"))
+ File.open(tmp("broken_path/git"), "w", 0o755) do |f|
+ f.puts strip_whitespace(<<-RUBY)
+ #!/usr/bin/env ruby
+ if %w(fetch --force --quiet --tags refs/heads/*:refs/heads/*).-(ARGV).empty? || %w(clone --bare --no-hardlinks --quiet).-(ARGV).empty?
+ warn "git remote ops have been disabled"
+ exit 1
+ end
+ ENV["PATH"] = ENV["PATH"].sub(/^.*?:/, "")
+ exec("git", *ARGV)
+ RUBY
+ end
+
+ old_path = ENV["PATH"]
+ ENV["PATH"] = "#{tmp("broken_path")}:#{ENV["PATH"]}"
+ yield if block_given?
+ ensure
+ ENV["PATH"] = old_path if block_given?
+ end
+
+ it "will install from a cached git repo" do
+ git = build_git "a", "1.0.0", :path => lib_path("a")
+ update_git("a", :path => git.path, :branch => "new_branch")
+ install_gemfile! <<-G
+ gem "a", :git => #{git.path.to_s.dump}
+ G
+
+ break_git_remote_ops! { bundle! :update, :all => true }
+ expect(out).to include("Using cached git data because of network errors")
+ expect(the_bundle).to be_locked
+
+ break_git_remote_ops! do
+ install_gemfile! <<-G
+ gem "a", :git => #{git.path.to_s.dump}, :branch => "new_branch"
+ G
+ end
+ expect(out).to include("Using cached git data because of network errors")
+ expect(the_bundle).to be_locked
+ end
+ end
+end
diff --git a/spec/bundler/install/binstubs_spec.rb b/spec/bundler/install/binstubs_spec.rb
new file mode 100644
index 0000000000..23eb691ab8
--- /dev/null
+++ b/spec/bundler/install/binstubs_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install", :bundler => "< 2" do
+ describe "when system_bindir is set" do
+ # On OS X, Gem.bindir defaults to /usr/bin, so system_bindir is useful if
+ # you want to avoid sudo installs for system gems with OS X's default ruby
+ it "overrides Gem.bindir" do
+ expect(Pathname.new("/usr/bin")).not_to be_writable unless Process.euid == 0
+ gemfile <<-G
+ require 'rubygems'
+ def Gem.bindir; "/usr/bin"; end
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ config "BUNDLE_SYSTEM_BINDIR" => system_gem_path("altbin").to_s
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(system_gem_path("altbin/rackup")).to exist
+ end
+ end
+
+ describe "when multiple gems contain the same exe", :bundler => "< 2" do
+ before do
+ build_repo2 do
+ build_gem "fake", "14" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ install_gemfile <<-G, :binstubs => true
+ source "file://#{gem_repo2}"
+ gem "fake"
+ gem "rack"
+ G
+ end
+
+ it "loads the correct spec's executable" do
+ gembin("rackup")
+ expect(out).to eq("1.2")
+ end
+ end
+end
diff --git a/spec/bundler/install/bundler_spec.rb b/spec/bundler/install/bundler_spec.rb
new file mode 100644
index 0000000000..42863ed89b
--- /dev/null
+++ b/spec/bundler/install/bundler_spec.rb
@@ -0,0 +1,177 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ describe "with bundler dependencies" do
+ before(:each) do
+ build_repo2 do
+ build_gem "rails", "3.0" do |s|
+ s.add_dependency "bundler", ">= 0.9.0.pre"
+ end
+ build_gem "bundler", "0.9.1"
+ build_gem "bundler", Bundler::VERSION
+ end
+ end
+
+ it "are forced to the current bundler version" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rails", "3.0"
+ G
+
+ expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}"
+ end
+
+ it "are not added if not already present" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ expect(the_bundle).not_to include_gems "bundler #{Bundler::VERSION}"
+ end
+
+ it "causes a conflict if explicitly requesting a different version" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rails", "3.0"
+ gem "bundler", "0.9.2"
+ G
+
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Bundler could not find compatible versions for gem "bundler":
+ In Gemfile:
+ bundler (= 0.9.2)
+
+ Current Bundler version:
+ bundler (#{Bundler::VERSION})
+ This Gemfile requires a different version of Bundler.
+ Perhaps you need to update Bundler by running `gem install bundler`?
+
+ Could not find gem 'bundler (= 0.9.2)' in any
+ E
+ expect(last_command.bundler_err).to include(nice_error)
+ end
+
+ it "works for gems with multiple versions in its dependencies" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "multiple_versioned_deps"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "multiple_versioned_deps"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "multiple_versioned_deps 1.0.0"
+ end
+
+ it "includes bundler in the bundle when it's a child dependency" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rails", "3.0"
+ G
+
+ run "begin; gem 'bundler'; puts 'WIN'; rescue Gem::LoadError; puts 'FAIL'; end"
+ expect(out).to eq("WIN")
+ end
+
+ it "allows gem 'bundler' when Bundler is not in the Gemfile or its dependencies" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack"
+ G
+
+ run "begin; gem 'bundler'; puts 'WIN'; rescue Gem::LoadError => e; puts e.backtrace; end"
+ expect(out).to eq("WIN")
+ end
+
+ it "causes a conflict if child dependencies conflict" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activemerchant"
+ gem "rails_fail"
+ G
+
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Bundler could not find compatible versions for gem "activesupport":
+ In Gemfile:
+ activemerchant was resolved to 1.0, which depends on
+ activesupport (>= 2.0.0)
+
+ rails_fail was resolved to 1.0, which depends on
+ activesupport (= 1.2.3)
+ E
+ expect(last_command.bundler_err).to include(nice_error)
+ end
+
+ it "causes a conflict if a child dependency conflicts with the Gemfile" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rails_fail"
+ gem "activesupport", "2.3.5"
+ G
+
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Bundler could not find compatible versions for gem "activesupport":
+ In Gemfile:
+ activesupport (= 2.3.5)
+
+ rails_fail was resolved to 1.0, which depends on
+ activesupport (= 1.2.3)
+ E
+ expect(last_command.bundler_err).to include(nice_error)
+ end
+
+ it "can install dependencies with newer bundler version with system gems", :ruby => "> 2" do
+ bundle! "config path.system true"
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+ gem "rails", "3.0"
+ G
+
+ simulate_bundler_version "99999999.99.1"
+
+ bundle! "check", :env => { "BUNDLER_SPEC_IGNORE_COMPATIBILITY_GUARD" => "1" }
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ it "can install dependencies with newer bundler version with a local path", :ruby => "> 2" do
+ bundle! "config path .bundle"
+ bundle! "config global_path_appends_ruby_scope true"
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+ gem "rails", "3.0"
+ G
+
+ simulate_bundler_version "99999999.99.1"
+
+ bundle! "check", :env => { "BUNDLER_SPEC_IGNORE_COMPATIBILITY_GUARD" => "1" }
+ expect(out).to include("The Gemfile's dependencies are satisfied")
+ end
+
+ context "with allow_bundler_dependency_conflicts set" do
+ before { bundle! "config allow_bundler_dependency_conflicts true" }
+
+ it "are forced to the current bundler version with warnings when no compatible version is found" do
+ build_repo4 do
+ build_gem "requires_nonexistant_bundler" do |s|
+ s.add_runtime_dependency "bundler", "99.99.99.99"
+ end
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo4}"
+ gem "requires_nonexistant_bundler"
+ G
+
+ expect(out).to include "requires_nonexistant_bundler (1.0) has dependency bundler (= 99.99.99.99), " \
+ "which is unsatisfied by the current bundler version #{Bundler::VERSION}, so the dependency is being ignored"
+
+ expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}", "requires_nonexistant_bundler 1.0"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb
new file mode 100644
index 0000000000..3b9d68982a
--- /dev/null
+++ b/spec/bundler/install/deploy_spec.rb
@@ -0,0 +1,423 @@
+# frozen_string_literal: true
+
+RSpec.describe "install with --deployment or --frozen" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ context "with CLI flags", :bundler => "< 2" do
+ it "fails without a lockfile and says that --deployment requires a lock" do
+ bundle "install --deployment"
+ expect(out).to include("The --deployment flag requires a Gemfile.lock")
+ end
+
+ it "fails without a lockfile and says that --frozen requires a lock" do
+ bundle "install --frozen"
+ expect(out).to include("The --frozen flag requires a Gemfile.lock")
+ end
+
+ it "disallows --deployment --system" do
+ bundle "install --deployment --system"
+ expect(out).to include("You have specified both --deployment")
+ expect(out).to include("Please choose only one option")
+ expect(exitstatus).to eq(15) if exitstatus
+ end
+
+ it "disallows --deployment --path --system" do
+ bundle "install --deployment --path . --system"
+ expect(out).to include("You have specified both --path")
+ expect(out).to include("as well as --system")
+ expect(out).to include("Please choose only one option")
+ expect(exitstatus).to eq(15) if exitstatus
+ end
+
+ it "works after you try to deploy without a lock" do
+ bundle "install --deployment"
+ bundle! :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ it "still works if you are not in the app directory and specify --gemfile" do
+ bundle "install"
+ Dir.chdir tmp do
+ simulate_new_machine
+ bundle! :install,
+ forgotten_command_line_options(:gemfile => "#{tmp}/bundled_app/Gemfile",
+ :deployment => true,
+ :path => "vendor/bundle")
+ end
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "works if you exclude a group with a git gem" do
+ build_git "foo"
+ gemfile <<-G
+ group :test do
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ end
+ G
+ bundle :install
+ bundle! :install, forgotten_command_line_options(:deployment => true, :without => "test")
+ end
+
+ it "works when you bundle exec bundle", :ruby_repo do
+ bundle :install
+ bundle "install --deployment"
+ bundle! "exec bundle check"
+ end
+
+ it "works when using path gems from the same path and the version is specified" do
+ build_lib "foo", :path => lib_path("nested/foo")
+ build_lib "bar", :path => lib_path("nested/bar")
+ gemfile <<-G
+ gem "foo", "1.0", :path => "#{lib_path("nested")}"
+ gem "bar", :path => "#{lib_path("nested")}"
+ G
+
+ bundle! :install
+ bundle! :install, forgotten_command_line_options(:deployment => true)
+ end
+
+ it "works when there are credentials in the source URL" do
+ install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true)
+ source "http://user:pass@localgemserver.test/"
+
+ gem "rack-obama", ">= 1.0"
+ G
+
+ bundle! :install, forgotten_command_line_options(:deployment => true).merge(:artifice => "endpoint_strict_basic_authentication")
+ end
+
+ it "works with sources given by a block" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}" do
+ gem "rack"
+ end
+ G
+
+ bundle! :install, forgotten_command_line_options(:deployment => true)
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ describe "with an existing lockfile" do
+ before do
+ bundle "install"
+ end
+
+ it "works with the --deployment flag if you didn't change anything", :bundler => "< 2" do
+ bundle! "install --deployment"
+ end
+
+ it "works with the --frozen flag if you didn't change anything", :bundler => "< 2" do
+ bundle! "install --frozen"
+ end
+
+ it "works with BUNDLE_FROZEN if you didn't change anything" do
+ bundle! :install, :env => { "BUNDLE_FROZEN" => "true" }
+ end
+
+ it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ bundle :install, forgotten_command_line_options(:deployment => true)
+ expect(out).to include("deployment mode")
+ expect(out).to include("You have added to the Gemfile")
+ expect(out).to include("* rack-obama")
+ expect(out).not_to include("You have deleted from the Gemfile")
+ expect(out).not_to include("You have changed in the Gemfile")
+ end
+
+ it "works if a path gem is missing but is in a without group" do
+ build_lib "path_gem"
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rake"
+ gem "path_gem", :path => "#{lib_path("path_gem-1.0")}", :group => :development
+ G
+ expect(the_bundle).to include_gems "path_gem 1.0"
+ FileUtils.rm_r lib_path("path_gem-1.0")
+
+ bundle! :install, forgotten_command_line_options(:path => ".bundle", :without => "development", :deployment => true).merge(:env => { :DEBUG => "1" })
+ run! "puts :WIN"
+ expect(out).to eq("WIN")
+ end
+
+ it "explodes if a path gem is missing" do
+ build_lib "path_gem"
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rake"
+ gem "path_gem", :path => "#{lib_path("path_gem-1.0")}", :group => :development
+ G
+ expect(the_bundle).to include_gems "path_gem 1.0"
+ FileUtils.rm_r lib_path("path_gem-1.0")
+
+ bundle :install, forgotten_command_line_options(:path => ".bundle", :deployment => true)
+ expect(out).to include("The path `#{lib_path("path_gem-1.0")}` does not exist.")
+ end
+
+ it "can have --frozen set via an environment variable", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ ENV["BUNDLE_FROZEN"] = "1"
+ bundle "install"
+ expect(out).to include("deployment mode")
+ expect(out).to include("You have added to the Gemfile")
+ expect(out).to include("* rack-obama")
+ expect(out).not_to include("You have deleted from the Gemfile")
+ expect(out).not_to include("You have changed in the Gemfile")
+ end
+
+ it "can have --deployment set via an environment variable" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ ENV["BUNDLE_DEPLOYMENT"] = "true"
+ bundle "install"
+ expect(out).to include("deployment mode")
+ expect(out).to include("You have added to the Gemfile")
+ expect(out).to include("* rack-obama")
+ expect(out).not_to include("You have deleted from the Gemfile")
+ expect(out).not_to include("You have changed in the Gemfile")
+ end
+
+ it "can have --frozen set to false via an environment variable" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ ENV["BUNDLE_FROZEN"] = "false"
+ ENV["BUNDLE_DEPLOYMENT"] = "false"
+ bundle "install"
+ expect(out).not_to include("deployment mode")
+ expect(out).not_to include("You have added to the Gemfile")
+ expect(out).not_to include("* rack-obama")
+ end
+
+ it "explodes with the --frozen flag if you make a change and don't check in the lockfile", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rack-obama", "1.1"
+ G
+
+ bundle :install, forgotten_command_line_options(:frozen => true)
+ expect(out).to include("deployment mode")
+ expect(out).to include("You have added to the Gemfile")
+ expect(out).to include("* rack-obama (= 1.1)")
+ expect(out).not_to include("You have deleted from the Gemfile")
+ expect(out).not_to include("You have changed in the Gemfile")
+ end
+
+ it "explodes if you remove a gem and don't check in the lockfile" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+ G
+
+ bundle :install, forgotten_command_line_options(:deployment => true)
+ expect(out).to include("deployment mode")
+ expect(out).to include("You have added to the Gemfile:\n* activesupport\n\n")
+ expect(out).to include("You have deleted from the Gemfile:\n* rack")
+ expect(out).not_to include("You have changed in the Gemfile")
+ end
+
+ it "explodes if you add a source" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "git://hubz.com"
+ G
+
+ bundle :install, forgotten_command_line_options(:deployment => true)
+ expect(out).to include("deployment mode")
+ expect(out).to include("You have added to the Gemfile:\n* source: git://hubz.com (at master)")
+ expect(out).not_to include("You have changed in the Gemfile")
+ end
+
+ it "explodes if you unpin a source" do
+ build_git "rack"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-1.0")}"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle :install, forgotten_command_line_options(:deployment => true)
+ expect(out).to include("deployment mode")
+ expect(out).to include("You have deleted from the Gemfile:\n* source: #{lib_path("rack-1.0")} (at master@#{revision_for(lib_path("rack-1.0"))[0..6]}")
+ expect(out).not_to include("You have added to the Gemfile")
+ expect(out).not_to include("You have changed in the Gemfile")
+ end
+
+ it "explodes if you unpin a source, leaving it pinned somewhere else" do
+ build_lib "foo", :path => lib_path("rack/foo")
+ build_git "rack", :path => lib_path("rack")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack")}"
+ gem "foo", :git => "#{lib_path("rack")}"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "foo", :git => "#{lib_path("rack")}"
+ G
+
+ bundle :install, forgotten_command_line_options(:deployment => true)
+ expect(out).to include("deployment mode")
+ expect(out).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `#{lib_path("rack")} (at master@#{revision_for(lib_path("rack"))[0..6]})`")
+ expect(out).not_to include("You have added to the Gemfile")
+ expect(out).not_to include("You have deleted from the Gemfile")
+ end
+
+ context "when replacing a host with the same host with credentials" do
+ let(:success_message) do
+ if Bundler::VERSION.split(".", 2).first == "1"
+ "Could not reach host localgemserver.test"
+ else
+ "Bundle complete!"
+ end
+ end
+
+ before do
+ install_gemfile <<-G
+ source "http://user_name:password@localgemserver.test/"
+ gem "rack"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{local}
+
+ DEPENDENCIES
+ rack
+ G
+ end
+
+ it "prevents the replace by default" do
+ bundle :install, forgotten_command_line_options(:deployment => true)
+
+ expect(out).to match(/The list of sources changed/)
+ end
+
+ context "when allow_deployment_source_credential_changes is true" do
+ before { bundle! "config allow_deployment_source_credential_changes true" }
+
+ it "allows the replace" do
+ bundle :install, forgotten_command_line_options(:deployment => true)
+
+ expect(out).to match(/#{success_message}/)
+ end
+ end
+
+ context "when allow_deployment_source_credential_changes is false" do
+ before { bundle! "config allow_deployment_source_credential_changes false" }
+
+ it "prevents the replace" do
+ bundle :install, forgotten_command_line_options(:deployment => true)
+
+ expect(out).to match(/The list of sources changed/)
+ end
+ end
+
+ context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is true" do
+ before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "true" }
+
+ it "allows the replace" do
+ bundle :install, forgotten_command_line_options(:deployment => true)
+
+ expect(out).to match(/#{success_message}/)
+ end
+ end
+
+ context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is false" do
+ before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "false" }
+
+ it "prevents the replace" do
+ bundle :install, forgotten_command_line_options(:deployment => true)
+
+ expect(out).to match(/The list of sources changed/)
+ end
+ end
+ end
+
+ it "remembers that the bundle is frozen at runtime" do
+ bundle! :lock
+
+ bundle! "config deployment true"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "1.0.0"
+ gem "rack-obama"
+ G
+
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(err).to include strip_whitespace(<<-E).strip
+The dependencies in your gemfile changed
+
+You have added to the Gemfile:
+* rack (= 1.0.0)
+* rack-obama
+
+You have deleted from the Gemfile:
+* rack
+ E
+ end
+ end
+
+ context "with path in Gemfile and packed" do
+ it "works fine after bundle package and bundle install --local" do
+ build_lib "foo", :path => lib_path("foo")
+ install_gemfile! <<-G
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+
+ bundle! :install
+ expect(the_bundle).to include_gems "foo 1.0"
+ bundle! :package, forgotten_command_line_options([:all, :cache_all] => true)
+ expect(bundled_app("vendor/cache/foo")).to be_directory
+
+ bundle! "install --local"
+ expect(out).to include("Updating files in vendor/cache")
+
+ simulate_new_machine
+ bundle! "install --verbose", forgotten_command_line_options(:deployment => true)
+ expect(out).not_to include("You are trying to install in deployment mode after changing your Gemfile")
+ expect(out).not_to include("You have added to the Gemfile")
+ expect(out).not_to include("You have deleted from the Gemfile")
+ expect(out).to include("vendor/cache/foo")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/failure_spec.rb b/spec/bundler/install/failure_spec.rb
new file mode 100644
index 0000000000..b4cdf13857
--- /dev/null
+++ b/spec/bundler/install/failure_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ context "installing a gem fails" do
+ it "prints out why that gem was being installed" do
+ build_repo2 do
+ build_gem "activesupport", "2.3.2" do |s|
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ abort "make installing activesupport-2.3.2 fail"
+ end
+ RUBY
+ end
+ end
+
+ install_gemfile <<-G
+ source "file:\/\/localhost#{gem_repo2}"
+ gem "rails"
+ G
+ expect(last_command.bundler_err).to end_with(normalize_uri_file(<<-M.strip))
+An error occurred while installing activesupport (2.3.2), and Bundler cannot continue.
+Make sure that `gem install activesupport -v '2.3.2' --source 'file://localhost#{gem_repo2}/'` succeeds before bundling.
+
+In Gemfile:
+ rails was resolved to 2.3.2, which depends on
+ actionmailer was resolved to 2.3.2, which depends on
+ activesupport
+ M
+ end
+
+ context "when installing a git gem" do
+ it "does not tell the user to run 'gem install'" do
+ build_git "activesupport", "2.3.2", :path => lib_path("activesupport") do |s|
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ abort "make installing activesupport-2.3.2 fail"
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "file:\/\/localhost#{gem_repo1}"
+ gem "rails"
+ gem "activesupport", :git => "#{lib_path("activesupport")}"
+ G
+
+ expect(last_command.bundler_err).to end_with(<<-M.strip)
+An error occurred while installing activesupport (2.3.2), and Bundler cannot continue.
+
+In Gemfile:
+ rails was resolved to 2.3.2, which depends on
+ actionmailer was resolved to 2.3.2, which depends on
+ activesupport
+ M
+ end
+ end
+
+ context "when installing a gem using a git block" do
+ it "does not tell the user to run 'gem install'" do
+ build_git "activesupport", "2.3.2", :path => lib_path("activesupport") do |s|
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ abort "make installing activesupport-2.3.2 fail"
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "file:\/\/localhost#{gem_repo1}"
+ gem "rails"
+
+ git "#{lib_path("activesupport")}" do
+ gem "activesupport"
+ end
+ G
+
+ expect(last_command.bundler_err).to end_with(<<-M.strip)
+An error occurred while installing activesupport (2.3.2), and Bundler cannot continue.
+
+
+In Gemfile:
+ rails was resolved to 2.3.2, which depends on
+ actionmailer was resolved to 2.3.2, which depends on
+ activesupport
+ M
+ end
+ end
+
+ it "prints out the hint for the remote source when available" do
+ build_repo2 do
+ build_gem "activesupport", "2.3.2" do |s|
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ abort "make installing activesupport-2.3.2 fail"
+ end
+ RUBY
+ end
+ end
+
+ build_repo4 do
+ build_gem "a"
+ end
+
+ install_gemfile <<-G
+ source "file:\/\/localhost#{gem_repo4}"
+ source "file:\/\/localhost#{gem_repo2}" do
+ gem "rails"
+ end
+ G
+ expect(last_command.bundler_err).to end_with(normalize_uri_file(<<-M.strip))
+An error occurred while installing activesupport (2.3.2), and Bundler cannot continue.
+Make sure that `gem install activesupport -v '2.3.2' --source 'file://localhost#{gem_repo2}/'` succeeds before bundling.
+
+In Gemfile:
+ rails was resolved to 2.3.2, which depends on
+ actionmailer was resolved to 2.3.2, which depends on
+ activesupport
+ M
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/eval_gemfile_spec.rb b/spec/bundler/install/gemfile/eval_gemfile_spec.rb
new file mode 100644
index 0000000000..035d3692aa
--- /dev/null
+++ b/spec/bundler/install/gemfile/eval_gemfile_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with gemfile that uses eval_gemfile" do
+ before do
+ build_lib("gunks", :path => bundled_app.join("gems/gunks")) do |s|
+ s.name = "gunks"
+ s.version = "0.0.1"
+ end
+ end
+
+ context "eval-ed Gemfile points to an internal gemspec" do
+ before do
+ create_file "Gemfile-other", <<-G
+ gemspec :path => 'gems/gunks'
+ G
+ end
+
+ it "installs the gemspec specified gem" do
+ install_gemfile <<-G
+ eval_gemfile 'Gemfile-other'
+ G
+ expect(out).to include("Resolving dependencies")
+ expect(out).to include("Bundle complete")
+
+ expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}"
+ end
+ end
+
+ context "eval-ed Gemfile has relative-path gems" do
+ before do
+ build_lib("a", :path => "gems/a")
+ create_file "nested/Gemfile-nested", <<-G
+ gem "a", :path => "../gems/a"
+ G
+
+ gemfile <<-G
+ eval_gemfile "nested/Gemfile-nested"
+ G
+ end
+
+ it "installs the path gem" do
+ bundle! :install
+ expect(the_bundle).to include_gem("a 1.0")
+ end
+
+ # Make sure that we are properly comparing path based gems between the
+ # parsed lockfile and the evaluated gemfile.
+ it "bundles with --deployment" do
+ bundle! :install
+ bundle! :install, forgotten_command_line_options(:deployment => true)
+ end
+ end
+
+ context "Gemfile uses gemspec paths after eval-ing a Gemfile" do
+ before { create_file "other/Gemfile-other" }
+
+ it "installs the gemspec specified gem" do
+ install_gemfile <<-G
+ eval_gemfile 'other/Gemfile-other'
+ gemspec :path => 'gems/gunks'
+ G
+ expect(out).to include("Resolving dependencies")
+ expect(out).to include("Bundle complete")
+
+ expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}"
+ end
+ end
+
+ context "eval-ed Gemfile references other gemfiles" do
+ it "works with relative paths" do
+ create_file "other/Gemfile-other", "gem 'rack'"
+ create_file "other/Gemfile", "eval_gemfile 'Gemfile-other'"
+ create_file "Gemfile-alt", <<-G
+ source "file:#{gem_repo1}"
+ eval_gemfile "other/Gemfile"
+ G
+ install_gemfile! "eval_gemfile File.expand_path('Gemfile-alt')"
+
+ expect(the_bundle).to include_gem "rack 1.0.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb
new file mode 100644
index 0000000000..7ce037730e
--- /dev/null
+++ b/spec/bundler/install/gemfile/gemspec_spec.rb
@@ -0,0 +1,672 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install from an existing gemspec" do
+ before(:each) do
+ build_repo2 do
+ build_gem "bar"
+ build_gem "bar-dev"
+ end
+ end
+
+ it "should install runtime and development dependencies" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("Gemfile", "source :rubygems\ngemspec")
+ s.add_dependency "bar", "=1.0.0"
+ s.add_development_dependency "bar-dev", "=1.0.0"
+ end
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0.0"
+ expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development
+ end
+
+ it "that is hidden should install runtime and development dependencies" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("Gemfile", "source :rubygems\ngemspec")
+ s.add_dependency "bar", "=1.0.0"
+ s.add_development_dependency "bar-dev", "=1.0.0"
+ end
+ FileUtils.mv tmp.join("foo", "foo.gemspec"), tmp.join("foo", ".gemspec")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0.0"
+ expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development
+ end
+
+ it "should handle a list of requirements" do
+ update_repo2 do
+ build_gem "baz", "1.0"
+ build_gem "baz", "1.1"
+ end
+
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("Gemfile", "source :rubygems\ngemspec")
+ s.add_dependency "baz", ">= 1.0", "< 1.1"
+ end
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ expect(the_bundle).to include_gems "baz 1.0"
+ end
+
+ it "should raise if there are no gemspecs available" do
+ build_lib("foo", :path => tmp.join("foo"), :gemspec => false)
+
+ install_gemfile(<<-G)
+ source "file://#{gem_repo2}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+ expect(last_command.bundler_err).to match(/There are no gemspecs at #{tmp.join('foo')}/)
+ end
+
+ it "should raise if there are too many gemspecs available" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("foo2.gemspec", build_spec("foo", "4.0").first.to_ruby)
+ end
+
+ install_gemfile(<<-G)
+ source "file://#{gem_repo2}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+ expect(last_command.bundler_err).to match(/There are multiple gemspecs at #{tmp.join('foo')}/)
+ end
+
+ it "should pick a specific gemspec" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("foo2.gemspec", "")
+ s.add_dependency "bar", "=1.0.0"
+ s.add_development_dependency "bar-dev", "=1.0.0"
+ end
+
+ install_gemfile(<<-G)
+ source "file://#{gem_repo2}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0.0"
+ expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development
+ end
+
+ it "should use a specific group for development dependencies" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("foo2.gemspec", "")
+ s.add_dependency "bar", "=1.0.0"
+ s.add_development_dependency "bar-dev", "=1.0.0"
+ end
+
+ install_gemfile(<<-G)
+ source "file://#{gem_repo2}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo', :development_group => :dev
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0.0"
+ expect(the_bundle).not_to include_gems "bar-dev 1.0.0", :groups => :development
+ expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :dev
+ end
+
+ it "should match a lockfile even if the gemspec defines development dependencies" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.write("Gemfile", "source 'file://#{gem_repo1}'\ngemspec")
+ s.add_dependency "actionpack", "=2.3.2"
+ s.add_development_dependency "rake", "=10.0.2"
+ end
+
+ Dir.chdir(tmp.join("foo")) do
+ bundle "install"
+ # This should really be able to rely on $stderr, but, it's not written
+ # right, so we can't. In fact, this is a bug negation test, and so it'll
+ # ghost pass in future, and will only catch a regression if the message
+ # doesn't change. Exit codes should be used correctly (they can be more
+ # than just 0 and 1).
+ output = bundle("install --deployment")
+ expect(output).not_to match(/You have added to the Gemfile/)
+ expect(output).not_to match(/You have deleted from the Gemfile/)
+ expect(output).not_to match(/install in deployment mode after changing/)
+ end
+ end
+
+ it "should match a lockfile without needing to re-resolve" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.add_dependency "rack"
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ bundle! "install", :verbose => true
+
+ message = "Found no changes, using resolution from the lockfile"
+ expect(out.scan(message).size).to eq(1)
+ end
+
+ it "should match a lockfile without needing to re-resolve with development dependencies" do
+ simulate_platform java
+
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.add_dependency "rack"
+ s.add_development_dependency "thin"
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ bundle! "install", :verbose => true
+
+ message = "Found no changes, using resolution from the lockfile"
+ expect(out.scan(message).size).to eq(1)
+ end
+
+ it "should match a lockfile on non-ruby platforms with a transitive platform dependency" do
+ simulate_platform java
+ simulate_ruby_engine "jruby"
+
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.add_dependency "platform_specific"
+ end
+
+ system_gems "platform_specific-1.0-java", :path => :bundle_path, :keep_path => true
+
+ install_gemfile! <<-G
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+
+ bundle! "update --bundler", :verbose => true
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 JAVA"
+ end
+
+ it "should evaluate the gemspec in its directory" do
+ build_lib("foo", :path => tmp.join("foo"))
+ File.open(tmp.join("foo/foo.gemspec"), "w") do |s|
+ s.write "raise 'ahh' unless Dir.pwd == '#{tmp.join("foo")}'"
+ end
+
+ install_gemfile <<-G
+ gemspec :path => '#{tmp.join("foo")}'
+ G
+ expect(last_command.stdboth).not_to include("ahh")
+ end
+
+ it "allows the gemspec to activate other gems" do
+ ENV["BUNDLE_PATH__SYSTEM"] = "true"
+ # see https://github.com/bundler/bundler/issues/5409
+ #
+ # issue was caused by rubygems having an unresolved gem during a require,
+ # so emulate that
+ system_gems %w[rack-1.0.0 rack-0.9.1 rack-obama-1.0]
+
+ build_lib("foo", :path => bundled_app)
+ gemspec = bundled_app("foo.gemspec").read
+ bundled_app("foo.gemspec").open("w") do |f|
+ f.write "#{gemspec.strip}.tap { gem 'rack-obama'; require 'rack-obama' }"
+ end
+
+ install_gemfile! <<-G
+ gemspec
+ G
+
+ expect(the_bundle).to include_gem "foo 1.0"
+ end
+
+ it "allows conflicts" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.version = "1.0.0"
+ s.add_dependency "bar", "= 1.0.0"
+ end
+ build_gem "deps", :to_bundle => true do |s|
+ s.add_dependency "foo", "= 0.0.1"
+ end
+ build_gem "foo", "0.0.1", :to_bundle => true
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "deps"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0.0"
+ end
+
+ it "does not break Gem.finish_resolve with conflicts", :rubygems => ">= 2" do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.version = "1.0.0"
+ s.add_dependency "bar", "= 1.0.0"
+ end
+ update_repo2 do
+ build_gem "deps" do |s|
+ s.add_dependency "foo", "= 0.0.1"
+ end
+ build_gem "foo", "0.0.1"
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+ gem "deps"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0.0"
+
+ run! "Gem.finish_resolve; puts 'WIN'"
+ expect(out).to eq("WIN")
+ end
+
+ context "in deployment mode" do
+ context "when the lockfile was not updated after a change to the gemspec's dependencies" do
+ it "reports that installation failed" do
+ build_lib "cocoapods", :path => bundled_app do |s|
+ s.add_dependency "activesupport", ">= 1"
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gemspec
+ G
+
+ expect(the_bundle).to include_gems("cocoapods 1.0", "activesupport 2.3.5")
+
+ build_lib "cocoapods", :path => bundled_app do |s|
+ s.add_dependency "activesupport", ">= 1.0.1"
+ end
+
+ bundle :install, forgotten_command_line_options(:deployment => true)
+
+ expect(out).to include("changed")
+ end
+ end
+ end
+
+ context "when child gemspecs conflict with a released gemspec" do
+ before do
+ # build the "parent" gem that depends on another gem in the same repo
+ build_lib "source_conflict", :path => bundled_app do |s|
+ s.add_dependency "rack_middleware"
+ end
+
+ # build the "child" gem that is the same version as a released gem, but
+ # has completely different and conflicting dependency requirements
+ build_lib "rack_middleware", "1.0", :path => bundled_app("rack_middleware") do |s|
+ s.add_dependency "rack", "1.0" # anything other than 0.9.1
+ end
+ end
+
+ it "should install the child gemspec's deps" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gemspec
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ context "with a lockfile and some missing dependencies" do
+ let(:source_uri) { "http://localgemserver.test" }
+
+ context "previously bundled for Ruby" do
+ let(:platform) { "ruby" }
+ let(:explicit_platform) { false }
+
+ before do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.add_dependency "rack", "=1.0.0"
+ end
+
+ if explicit_platform
+ create_file(
+ tmp.join("foo", "foo-#{platform}.gemspec"),
+ build_spec("foo", "1.0", platform) do
+ dep "rack", "=1.0.0"
+ @spec.authors = "authors"
+ @spec.summary = "summary"
+ end.first.to_ruby
+ )
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gemspec :path => "../foo"
+ G
+
+ lockfile <<-L
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+ rack (= 1.0.0)
+
+ GEM
+ remote: #{source_uri}
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ context "using JRuby with explicit platform" do
+ let(:platform) { "java" }
+ let(:explicit_platform) { true }
+
+ it "should install" do
+ simulate_ruby_engine "jruby" do
+ simulate_platform "java" do
+ results = bundle "install", :artifice => "endpoint"
+ expect(results).to include("Installing rack 1.0.0")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+ end
+
+ context "using JRuby" do
+ let(:platform) { "java" }
+
+ it "should install" do
+ simulate_ruby_engine "jruby" do
+ simulate_platform "java" do
+ results = bundle "install", :artifice => "endpoint"
+ expect(results).to include("Installing rack 1.0.0")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+ end
+
+ context "using Windows" do
+ it "should install" do
+ simulate_windows do
+ results = bundle "install", :artifice => "endpoint"
+ expect(results).to include("Installing rack 1.0.0")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+ end
+
+ context "bundled for ruby and jruby" do
+ let(:platform_specific_type) { :runtime }
+ let(:dependency) { "platform_specific" }
+ before do
+ build_repo2 do
+ build_gem "indirect_platform_specific" do |s|
+ s.add_runtime_dependency "platform_specific"
+ end
+ end
+
+ build_lib "foo", :path => "." do |s|
+ if platform_specific_type == :runtime
+ s.add_runtime_dependency dependency
+ elsif platform_specific_type == :development
+ s.add_development_dependency dependency
+ else
+ raise "wrong dependency type #{platform_specific_type}, can only be :development or :runtime"
+ end
+ end
+
+ %w[ruby jruby].each do |platform|
+ simulate_platform(platform) do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo2}"
+ gemspec
+ G
+ end
+ end
+ end
+
+ context "on ruby", :bundler => "< 2" do
+ before do
+ simulate_platform("ruby")
+ bundle :install
+ end
+
+ context "as a runtime dependency" do
+ it "keeps java dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
+ expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+ platform_specific
+
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "as a development dependency" do
+ let(:platform_specific_type) { :development }
+
+ it "keeps java dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
+ expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ foo!
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "with an indirect platform-specific development dependency" do
+ let(:platform_specific_type) { :development }
+ let(:dependency) { "indirect_platform_specific" }
+
+ it "keeps java dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY"
+ expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ indirect_platform_specific (1.0)
+ platform_specific
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ foo!
+ indirect_platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+ end
+
+ context "on ruby", :bundler => "2" do
+ before do
+ simulate_platform("ruby")
+ bundle :install
+ end
+
+ context "as a runtime dependency" do
+ it "keeps java dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
+ expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+ platform_specific
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "as a development dependency" do
+ let(:platform_specific_type) { :development }
+
+ it "keeps java dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
+ expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ foo!
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ context "with an indirect platform-specific development dependency" do
+ let(:platform_specific_type) { :development }
+ let(:dependency) { "indirect_platform_specific" }
+
+ it "keeps java dependencies in the lockfile" do
+ expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY"
+ expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ indirect_platform_specific (1.0)
+ platform_specific
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+
+ PATH
+ remote: .
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ foo!
+ indirect_platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+ end
+ end
+ end
+
+ context "with multiple platforms" do
+ before do
+ build_lib("foo", :path => tmp.join("foo")) do |s|
+ s.version = "1.0.0"
+ s.add_development_dependency "rack"
+ s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "rack", "1.0.0" }.first.to_ruby
+ end
+ end
+
+ it "installs the ruby platform gemspec" do
+ simulate_platform "ruby"
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0.0", "rack 1.0.0"
+ end
+
+ it "installs the ruby platform gemspec and skips dev deps with --without development" do
+ simulate_platform "ruby"
+
+ install_gemfile! <<-G, forgotten_command_line_options(:without => "development")
+ source "file://#{gem_repo1}"
+ gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
+ G
+
+ expect(the_bundle).to include_gem "foo 1.0.0"
+ expect(the_bundle).not_to include_gem "rack"
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb
new file mode 100644
index 0000000000..57d83a5295
--- /dev/null
+++ b/spec/bundler/install/gemfile/git_spec.rb
@@ -0,0 +1,1351 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with git sources" do
+ describe "when floating on master" do
+ before :each do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+ end
+
+ it "fetches gems" do
+ expect(the_bundle).to include_gems("foo 1.0")
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "caches the git repo", :bundler => "< 2" do
+ expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes :size => 1
+ end
+
+ it "caches the git repo globally" do
+ simulate_new_machine
+ bundle! "config global_gem_cache true"
+ bundle! :install
+ expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes :size => 1
+ end
+
+ it "caches the evaluated gemspec" do
+ git = update_git "foo" do |s|
+ s.executables = ["foobar"] # we added this the first time, so keep it now
+ s.files = ["bin/foobar"] # updating git nukes the files list
+ foospec = s.to_ruby.gsub(/s\.files.*/, 's.files = `git ls-files -z`.split("\x0")')
+ s.write "foo.gemspec", foospec
+ end
+
+ bundle "update foo"
+
+ sha = git.ref_for("master", 11)
+ spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec").to_s
+ ruby_code = Gem::Specification.load(spec_file).to_ruby
+ file_code = File.read(spec_file)
+ expect(file_code).to eq(ruby_code)
+ end
+
+ it "does not update the git source implicitly" do
+ update_git "foo"
+
+ in_app_root2 do
+ install_gemfile bundled_app2("Gemfile"), <<-G
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+ end
+
+ in_app_root do
+ run <<-RUBY
+ require 'foo'
+ puts "fail" if defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to be_empty
+ end
+ end
+
+ it "sets up git gem executables on the path" do
+ bundle "exec foobar"
+ expect(out).to eq("1.0")
+ end
+
+ it "complains if pinned specs don't exist in the git repo" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(out).to include("The source contains 'foo' at: 1.0")
+ end
+
+ it "complains with version and platform if pinned specs don't exist in the git repo" do
+ simulate_platform "java"
+
+ build_git "only_java" do |s|
+ s.platform = "java"
+ end
+
+ install_gemfile <<-G
+ platforms :jruby do
+ gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}"
+ end
+ G
+
+ expect(out).to include("The source contains 'only_java' at: 1.0 java")
+ end
+
+ it "complains with multiple versions and platforms if pinned specs don't exist in the git repo" do
+ simulate_platform "java"
+
+ build_git "only_java", "1.0" do |s|
+ s.platform = "java"
+ end
+
+ build_git "only_java", "1.1" do |s|
+ s.platform = "java"
+ s.write "only_java1-0.gemspec", File.read("#{lib_path("only_java-1.0-java")}/only_java.gemspec")
+ end
+
+ install_gemfile <<-G
+ platforms :jruby do
+ gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}"
+ end
+ G
+
+ expect(out).to include("The source contains 'only_java' at: 1.0 java, 1.1 java")
+ end
+
+ it "still works after moving the application directory" do
+ bundle "install --path vendor/bundle"
+ FileUtils.mv bundled_app, tmp("bundled_app.bck")
+
+ Dir.chdir tmp("bundled_app.bck")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "can still install after moving the application directory" do
+ bundle "install --path vendor/bundle"
+ FileUtils.mv bundled_app, tmp("bundled_app.bck")
+
+ update_git "foo", "1.1", :path => lib_path("foo-1.0")
+
+ Dir.chdir tmp("bundled_app.bck")
+ gemfile tmp("bundled_app.bck/Gemfile"), <<-G
+ source "file://#{gem_repo1}"
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+
+ gem "rack", "1.0"
+ G
+
+ bundle "update foo"
+
+ expect(the_bundle).to include_gems "foo 1.1", "rack 1.0"
+ end
+ end
+
+ describe "with an empty git block" do
+ before do
+ build_git "foo"
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ git "#{lib_path("foo-1.0")}" do
+ # this page left intentionally blank
+ end
+ G
+ end
+
+ it "does not explode" do
+ bundle "install"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when specifying a revision" do
+ before(:each) do
+ build_git "foo"
+ @revision = revision_for(lib_path("foo-1.0"))
+ update_git "foo"
+ end
+
+ it "works" do
+ install_gemfile <<-G
+ git "#{lib_path("foo-1.0")}", :ref => "#{@revision}" do
+ gem "foo"
+ end
+ G
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "works when the revision is a symbol" do
+ install_gemfile <<-G
+ git "#{lib_path("foo-1.0")}", :ref => #{@revision.to_sym.inspect} do
+ gem "foo"
+ end
+ G
+ expect(err).to lack_errors
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "works when the revision is a non-head ref" do
+ # want to ensure we don't fallback to master
+ update_git "foo", :path => lib_path("foo-1.0") do |s|
+ s.write("lib/foo.rb", "raise 'FAIL'")
+ end
+
+ Dir.chdir(lib_path("foo-1.0")) do
+ `git update-ref -m 'Bundler Spec!' refs/bundler/1 master~1`
+ end
+
+ # want to ensure we don't fallback to HEAD
+ update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s|
+ s.write("lib/foo.rb", "raise 'FAIL'")
+ end
+
+ install_gemfile! <<-G
+ git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do
+ gem "foo"
+ end
+ G
+ expect(err).to lack_errors
+
+ run! <<-RUBY
+ require 'foo'
+ puts "WIN" if defined?(FOO)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "works when the revision is a non-head ref and it was previously downloaded" do
+ install_gemfile! <<-G
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ # want to ensure we don't fallback to master
+ update_git "foo", :path => lib_path("foo-1.0") do |s|
+ s.write("lib/foo.rb", "raise 'FAIL'")
+ end
+
+ Dir.chdir(lib_path("foo-1.0")) do
+ `git update-ref -m 'Bundler Spec!' refs/bundler/1 master~1`
+ end
+
+ # want to ensure we don't fallback to HEAD
+ update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s|
+ s.write("lib/foo.rb", "raise 'FAIL'")
+ end
+
+ install_gemfile! <<-G
+ git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do
+ gem "foo"
+ end
+ G
+ expect(err).to lack_errors
+
+ run! <<-RUBY
+ require 'foo'
+ puts "WIN" if defined?(FOO)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "does not download random non-head refs" do
+ Dir.chdir(lib_path("foo-1.0")) do
+ sys_exec!("git update-ref -m 'Bundler Spec!' refs/bundler/1 master~1")
+ end
+
+ bundle! "config global_gem_cache true"
+
+ install_gemfile! <<-G
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ # ensure we also git fetch after cloning
+ bundle! :update, :all => bundle_update_requires_all?
+
+ Dir.chdir(Dir[home(".bundle/cache/git/foo-*")].first) do
+ sys_exec("git ls-remote .")
+ end
+
+ expect(out).not_to include("refs/bundler/1")
+ end
+ end
+
+ describe "when specifying a branch" do
+ let(:branch) { "branch" }
+ let(:repo) { build_git("foo").path }
+ before(:each) do
+ update_git("foo", :path => repo, :branch => branch)
+ end
+
+ it "works" do
+ install_gemfile <<-G
+ git "#{repo}", :branch => #{branch.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ context "when the branch starts with a `#`" do
+ let(:branch) { "#149/redirect-url-fragment" }
+ it "works" do
+ install_gemfile <<-G
+ git "#{repo}", :branch => #{branch.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+ end
+
+ context "when the branch includes quotes" do
+ let(:branch) { %('") }
+ it "works" do
+ install_gemfile <<-G
+ git "#{repo}", :branch => #{branch.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+ end
+ end
+
+ describe "when specifying a tag" do
+ let(:tag) { "tag" }
+ let(:repo) { build_git("foo").path }
+ before(:each) do
+ update_git("foo", :path => repo, :tag => tag)
+ end
+
+ it "works" do
+ install_gemfile <<-G
+ git "#{repo}", :tag => #{tag.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ context "when the tag starts with a `#`" do
+ let(:tag) { "#149/redirect-url-fragment" }
+ it "works" do
+ install_gemfile <<-G
+ git "#{repo}", :tag => #{tag.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+ end
+
+ context "when the tag includes quotes" do
+ let(:tag) { %('") }
+ it "works" do
+ install_gemfile <<-G
+ git "#{repo}", :tag => #{tag.dump} do
+ gem "foo"
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+ end
+ end
+
+ describe "when specifying local override" do
+ it "uses the local repository instead of checking a new one out" do
+ # We don't generate it because we actually don't need it
+ # build_git "rack", "0.8"
+
+ build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle! %(config local.rack #{lib_path("local-rack")})
+ bundle! :install
+
+ run "require 'rack'"
+ expect(out).to eq("LOCAL")
+ end
+
+ it "chooses the local repository on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ update_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ run "require 'rack'"
+ expect(out).to eq("LOCAL")
+ end
+
+ it "unlocks the source when the dependencies have changed while switching to the local" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ update_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "rack.gemspec", build_spec("rack", "0.8") { runtime "rspec", "> 0" }.first.to_ruby
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle! %(config local.rack #{lib_path("local-rack")})
+ bundle! :install
+ run! "require 'rack'"
+ expect(out).to eq("LOCAL")
+ end
+
+ it "updates specs on runtime" do
+ system_gems "nokogiri-1.4.2"
+
+ build_git "rack", "0.8"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ lockfile0 = File.read(bundled_app("Gemfile.lock"))
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ update_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.add_dependency "nokogiri", "1.4.2"
+ end
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ run "require 'rack'"
+
+ lockfile1 = File.read(bundled_app("Gemfile.lock"))
+ expect(lockfile1).not_to eq(lockfile0)
+ end
+
+ it "updates ref on install" do
+ build_git "rack", "0.8"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ lockfile0 = File.read(bundled_app("Gemfile.lock"))
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+ update_git "rack", "0.8", :path => lib_path("local-rack")
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle :install
+
+ lockfile1 = File.read(bundled_app("Gemfile.lock"))
+ expect(lockfile1).not_to eq(lockfile0)
+ end
+
+ it "explodes if given path does not exist on install" do
+ build_git "rack", "0.8"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle :install
+ expect(out).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/)
+ end
+
+ it "explodes if branch is not given on install" do
+ build_git "rack", "0.8"
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle :install
+ expect(out).to match(/cannot use local override/i)
+ end
+
+ it "does not explode if disable_local_branch_check is given" do
+ build_git "rack", "0.8"
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle %(config disable_local_branch_check true)
+ bundle :install
+ expect(out).to match(/Bundle complete!/)
+ end
+
+ it "explodes on different branches on install" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ update_git "rack", "0.8", :path => lib_path("local-rack"), :branch => "another" do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle :install
+ expect(out).to match(/is using branch another but Gemfile specifies master/)
+ end
+
+ it "explodes on invalid revision on install" do
+ build_git "rack", "0.8"
+
+ build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle :install
+ expect(out).to match(/The Gemfile lock is pointing to revision \w+/)
+ end
+ end
+
+ describe "specified inline" do
+ # TODO: Figure out how to write this test so that it is not flaky depending
+ # on the current network situation.
+ # it "supports private git URLs" do
+ # gemfile <<-G
+ # gem "thingy", :git => "git@notthere.fallingsnow.net:somebody/thingy.git"
+ # G
+ #
+ # bundle :install
+ #
+ # # p out
+ # # p err
+ # puts err unless err.empty? # This spec fails randomly every so often
+ # err.should include("notthere.fallingsnow.net")
+ # err.should include("ssh")
+ # end
+
+ it "installs from git even if a newer gem is available elsewhere" do
+ build_git "rack", "0.8"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}"
+ G
+
+ expect(the_bundle).to include_gems "rack 0.8"
+ end
+
+ it "installs dependencies from git even if a newer gem is available elsewhere" do
+ system_gems "rack-1.0.0"
+
+ build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s|
+ s.write "lib/rack.rb", "puts 'WIN OVERRIDE'"
+ end
+
+ build_git "foo", :path => lib_path("nested") do |s|
+ s.add_dependency "rack", "= 1.0"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :git => "#{lib_path("nested")}"
+ G
+
+ run "require 'rack'"
+ expect(out).to eq("WIN OVERRIDE")
+ end
+
+ it "correctly unlocks when changing to a git source" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "0.9.1"
+ G
+
+ build_git "rack", :path => lib_path("rack")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "1.0.0", :git => "#{lib_path("rack")}"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "correctly unlocks when changing to a git source without versions" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ build_git "rack", "1.2", :path => lib_path("rack")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack")}"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+ end
+
+ describe "block syntax" do
+ it "pulls all gems from a git block" do
+ build_lib "omg", :path => lib_path("hi2u/omg")
+ build_lib "hi2u", :path => lib_path("hi2u")
+
+ install_gemfile <<-G
+ path "#{lib_path("hi2u")}" do
+ gem "omg"
+ gem "hi2u"
+ end
+ G
+
+ expect(the_bundle).to include_gems "omg 1.0", "hi2u 1.0"
+ end
+ end
+
+ it "uses a ref if specified" do
+ build_git "foo"
+ @revision = revision_for(lib_path("foo-1.0"))
+ update_git "foo"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{@revision}"
+ G
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "correctly handles cases with invalid gemspecs" do
+ build_git "foo" do |s|
+ s.summary = nil
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ gem "rails", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ expect(the_bundle).to include_gems "rails 2.3.2"
+ end
+
+ it "runs the gemspec in the context of its parent directory" do
+ build_lib "bar", :path => lib_path("foo/bar"), :gemspec => false do |s|
+ s.write lib_path("foo/bar/lib/version.rb"), %(BAR_VERSION = '1.0')
+ s.write "bar.gemspec", <<-G
+ $:.unshift Dir.pwd # For 1.9
+ require 'lib/version'
+ Gem::Specification.new do |s|
+ s.name = 'bar'
+ s.author = 'no one'
+ s.version = BAR_VERSION
+ s.summary = 'Bar'
+ s.files = Dir["lib/**/*.rb"]
+ end
+ G
+ end
+
+ build_git "foo", :path => lib_path("foo") do |s|
+ s.write "bin/foo", ""
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "bar", :git => "#{lib_path("foo")}"
+ gem "rails", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0"
+ expect(the_bundle).to include_gems "rails 2.3.2"
+ end
+
+ it "installs from git even if a rubygems gem is present" do
+ build_gem "foo", "1.0", :path => lib_path("fake_foo"), :to_system => true do |s|
+ s.write "lib/foo.rb", "raise 'FAIL'"
+ end
+
+ build_git "foo", "1.0"
+
+ install_gemfile <<-G
+ gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "fakes the gem out if there is no gemspec" do
+ build_git "foo", :gemspec => false
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}"
+ gem "rails", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "catches git errors and spits out useful output" do
+ gemfile <<-G
+ gem "foo", "1.0", :git => "omgomg"
+ G
+
+ bundle :install
+
+ expect(out).to include("Git error:")
+ expect(err).to include("fatal")
+ expect(err).to include("omgomg")
+ end
+
+ it "works when the gem path has spaces in it" do
+ build_git "foo", :path => lib_path("foo space-1.0")
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo space-1.0")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "handles repos that have been force-pushed" do
+ build_git "forced", "1.0"
+
+ install_gemfile <<-G
+ git "#{lib_path("forced-1.0")}" do
+ gem 'forced'
+ end
+ G
+ expect(the_bundle).to include_gems "forced 1.0"
+
+ update_git "forced" do |s|
+ s.write "lib/forced.rb", "FORCED = '1.1'"
+ end
+
+ bundle "update", :all => bundle_update_requires_all?
+ expect(the_bundle).to include_gems "forced 1.1"
+
+ Dir.chdir(lib_path("forced-1.0")) do
+ `git reset --hard HEAD^`
+ end
+
+ bundle "update", :all => bundle_update_requires_all?
+ expect(the_bundle).to include_gems "forced 1.0"
+ end
+
+ it "ignores submodules if :submodule is not passed" do
+ build_git "submodule", "1.0"
+ build_git "has_submodule", "1.0" do |s|
+ s.add_dependency "submodule"
+ end
+ Dir.chdir(lib_path("has_submodule-1.0")) do
+ sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0"
+ `git commit -m "submodulator"`
+ end
+
+ install_gemfile <<-G
+ git "#{lib_path("has_submodule-1.0")}" do
+ gem "has_submodule"
+ end
+ G
+ expect(out).to match(/could not find gem 'submodule/i)
+
+ expect(the_bundle).not_to include_gems "has_submodule 1.0"
+ end
+
+ it "handles repos with submodules" do
+ build_git "submodule", "1.0"
+ build_git "has_submodule", "1.0" do |s|
+ s.add_dependency "submodule"
+ end
+ Dir.chdir(lib_path("has_submodule-1.0")) do
+ sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0"
+ `git commit -m "submodulator"`
+ end
+
+ install_gemfile <<-G
+ git "#{lib_path("has_submodule-1.0")}", :submodules => true do
+ gem "has_submodule"
+ end
+ G
+
+ expect(the_bundle).to include_gems "has_submodule 1.0"
+ end
+
+ it "handles implicit updates when modifying the source info" do
+ git = build_git "foo"
+
+ install_gemfile <<-G
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ update_git "foo"
+ update_git "foo"
+
+ install_gemfile <<-G
+ git "#{lib_path("foo-1.0")}", :ref => "#{git.ref_for("HEAD^")}" do
+ gem "foo"
+ end
+ G
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" if FOO_PREV_REF == '#{git.ref_for("HEAD^^")}'
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "does not to a remote fetch if the revision is cached locally" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ FileUtils.rm_rf(lib_path("foo-1.0"))
+
+ bundle "install"
+ expect(out).not_to match(/updating/i)
+ end
+
+ it "doesn't blow up if bundle install is run twice in a row" do
+ build_git "foo"
+
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "install"
+ bundle "install"
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ it "prints a friendly error if a file blocks the git repo" do
+ build_git "foo"
+
+ FileUtils.mkdir_p(default_bundle_path)
+ FileUtils.touch(default_bundle_path("bundler"))
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(exitstatus).to_not eq(0) if exitstatus
+ expect(out).to include("Bundler could not install a gem because it " \
+ "needs to create a directory, but a file exists " \
+ "- #{default_bundle_path("bundler")}")
+ end
+
+ it "does not duplicate git gem sources" do
+ build_lib "foo", :path => lib_path("nested/foo")
+ build_lib "bar", :path => lib_path("nested/bar")
+
+ build_git "foo", :path => lib_path("nested")
+ build_git "bar", :path => lib_path("nested")
+
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("nested")}"
+ gem "bar", :git => "#{lib_path("nested")}"
+ G
+
+ bundle "install"
+ expect(File.read(bundled_app("Gemfile.lock")).scan("GIT").size).to eq(1)
+ end
+
+ describe "switching sources" do
+ it "doesn't explode when switching Path to Git sources" do
+ build_gem "foo", "1.0", :to_system => true do |s|
+ s.write "lib/foo.rb", "raise 'fail'"
+ end
+ build_lib "foo", "1.0", :path => lib_path("bar/foo")
+ build_git "bar", "1.0", :path => lib_path("bar") do |s|
+ s.add_dependency "foo"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "bar", :path => "#{lib_path("bar")}"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "bar", :git => "#{lib_path("bar")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0", "bar 1.0"
+ end
+
+ it "doesn't explode when switching Gem to Git source" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack-obama"
+ gem "rack", "1.0.0"
+ G
+
+ build_git "rack", "1.0" do |s|
+ s.write "lib/new_file.rb", "puts 'USING GIT'"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack-obama"
+ gem "rack", "1.0.0", :git => "#{lib_path("rack-1.0")}"
+ G
+
+ run "require 'new_file'"
+ expect(out).to eq("USING GIT")
+ end
+ end
+
+ describe "bundle install after the remote has been updated" do
+ it "installs" do
+ build_git "valim"
+
+ install_gemfile <<-G
+ gem "valim", :git => "file://#{lib_path("valim-1.0")}"
+ G
+
+ old_revision = revision_for(lib_path("valim-1.0"))
+ update_git "valim"
+ new_revision = revision_for(lib_path("valim-1.0"))
+
+ lockfile = File.read(bundled_app("Gemfile.lock"))
+ File.open(bundled_app("Gemfile.lock"), "w") do |file|
+ file.puts lockfile.gsub(/revision: #{old_revision}/, "revision: #{new_revision}")
+ end
+
+ bundle "install"
+
+ run <<-R
+ require "valim"
+ puts VALIM_PREV_REF
+ R
+
+ expect(out).to eq(old_revision)
+ end
+
+ it "gives a helpful error message when the remote ref no longer exists" do
+ build_git "foo"
+ revision = revision_for(lib_path("foo-1.0"))
+
+ install_gemfile <<-G
+ gem "foo", :git => "file://#{lib_path("foo-1.0")}", :ref => "#{revision}"
+ G
+ bundle "install"
+ expect(out).to_not match(/Revision.*does not exist/)
+
+ install_gemfile <<-G
+ gem "foo", :git => "file://#{lib_path("foo-1.0")}", :ref => "deadbeef"
+ G
+ bundle "install"
+ expect(out).to include("Revision deadbeef does not exist in the repository")
+ end
+ end
+
+ describe "bundle install --deployment with git sources" do
+ it "works" do
+ build_git "valim", :path => lib_path("valim")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "valim", "= 1.0", :git => "#{lib_path("valim")}"
+ G
+
+ simulate_new_machine
+
+ bundle! :install, forgotten_command_line_options(:deployment => true)
+ end
+ end
+
+ describe "gem install hooks" do
+ it "runs pre-install hooks" do
+ build_git "foo"
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ require 'rubygems'
+ Gem.pre_install_hooks << lambda do |inst|
+ STDERR.puts "Ran pre-install hook: \#{inst.spec.full_name}"
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(err).to eq_err("Ran pre-install hook: foo-1.0")
+ end
+
+ it "runs post-install hooks" do
+ build_git "foo"
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ require 'rubygems'
+ Gem.post_install_hooks << lambda do |inst|
+ STDERR.puts "Ran post-install hook: \#{inst.spec.full_name}"
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(err).to eq_err("Ran post-install hook: foo-1.0")
+ end
+
+ it "complains if the install hook fails" do
+ build_git "foo"
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ require 'rubygems'
+ Gem.pre_install_hooks << lambda do |inst|
+ false
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(out).to include("failed for foo-1.0")
+ end
+ end
+
+ context "with an extension" do
+ it "installs the extension", :ruby_repo do
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("../lib", __FILE__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/foo.rb", "w") do |f|
+ f.puts "FOO = 'YES'"
+ end
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+ expect(out).to eq("YES")
+
+ run! <<-R
+ puts $:.grep(/ext/)
+ R
+ expect(out).to eq(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s)
+ end
+
+ it "does not use old extension after ref changes", :ruby_repo do
+ git_reader = build_git "foo", :no_default => true do |s|
+ s.extensions = ["ext/extconf.rb"]
+ s.write "ext/extconf.rb", <<-RUBY
+ require "mkmf"
+ create_makefile("foo")
+ RUBY
+ s.write "ext/foo.c", "void Init_foo() {}"
+ end
+
+ 2.times do |i|
+ Dir.chdir(git_reader.path) do
+ File.open("ext/foo.c", "w") do |file|
+ file.write <<-C
+ #include "ruby.h"
+ VALUE foo() { return INT2FIX(#{i}); }
+ void Init_foo() { rb_define_global_function("foo", &foo, 0); }
+ C
+ end
+ `git commit -m 'commit for iteration #{i}' ext/foo.c`
+ end
+ git_commit_sha = git_reader.ref_for("HEAD")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{git_commit_sha}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts foo
+ R
+
+ expect(out).to eq(i.to_s)
+ end
+ end
+
+ it "does not prompt to gem install if extension fails" do
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ raise
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(last_command.bundler_err).to end_with(<<-M.strip)
+An error occurred while installing foo (1.0), and Bundler cannot continue.
+
+In Gemfile:
+ foo
+ M
+ expect(out).not_to include("gem install foo")
+ end
+
+ it "does not reinstall the extension", :ruby_repo, :rubygems => ">= 2.3.0" do
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("../lib", __FILE__)
+ FileUtils.mkdir_p(path)
+ cur_time = Time.now.to_f.to_s
+ File.open("\#{path}/foo.rb", "w") do |f|
+ f.puts "FOO = \#{cur_time}"
+ end
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run! <<-R
+ require 'foo'
+ puts FOO
+ R
+
+ installed_time = out
+ expect(installed_time).to match(/\A\d+\.\d+\z/)
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run! <<-R
+ require 'foo'
+ puts FOO
+ R
+ expect(out).to eq(installed_time)
+ end
+ end
+
+ it "ignores git environment variables" do
+ build_git "xxxxxx" do |s|
+ s.executables = "xxxxxxbar"
+ end
+
+ Bundler::SharedHelpers.with_clean_git_env do
+ ENV["GIT_DIR"] = "bar"
+ ENV["GIT_WORK_TREE"] = "bar"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ git "#{lib_path("xxxxxx-1.0")}" do
+ gem 'xxxxxx'
+ end
+ G
+
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(ENV["GIT_DIR"]).to eq("bar")
+ expect(ENV["GIT_WORK_TREE"]).to eq("bar")
+ end
+ end
+
+ describe "without git installed" do
+ it "prints a better error message" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+
+ with_path_as("") do
+ bundle "update", :all => bundle_update_requires_all?
+ end
+ expect(last_command.bundler_err).
+ to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git")
+ end
+
+ it "installs a packaged git gem successfully" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ git "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+ bundle :package, forgotten_command_line_options([:all, :cache_all] => true)
+ simulate_new_machine
+
+ bundle! "install", :env => { "PATH" => "" }
+ expect(out).to_not include("You need to install git to be able to use gems from git repositories.")
+ end
+ end
+
+ describe "when the git source is overridden with a local git repo" do
+ before do
+ bundle! "config --global local.foo #{lib_path("foo")}"
+ end
+
+ describe "and git output is colorized" do
+ before do
+ File.open("#{ENV["HOME"]}/.gitconfig", "w") do |f|
+ f.write("[color]\n\tui = always\n")
+ end
+ end
+
+ it "installs successfully" do
+ build_git "foo", "1.0", :path => lib_path("foo")
+
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo")}", :branch => "master"
+ G
+
+ bundle :install
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+ end
+ end
+
+ context "git sources that include credentials" do
+ context "that are username and password" do
+ let(:credentials) { "user1:password1" }
+
+ it "does not display the password" do
+ install_gemfile <<-G
+ git "https://#{credentials}@github.com/company/private-repo" do
+ gem "foo"
+ end
+ G
+
+ bundle :install
+ expect(last_command.stdboth).to_not include("password1")
+ expect(last_command.stdout).to include("Fetching https://user1@github.com/company/private-repo")
+ end
+ end
+
+ context "that is an oauth token" do
+ let(:credentials) { "oauth_token" }
+
+ it "displays the oauth scheme but not the oauth token" do
+ install_gemfile <<-G
+ git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do
+ gem "foo"
+ end
+ G
+
+ bundle :install
+ expect(last_command.stdboth).to_not include("oauth_token")
+ expect(last_command.stdout).to include("Fetching https://x-oauth-basic@github.com/company/private-repo")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb
new file mode 100644
index 0000000000..19c379e188
--- /dev/null
+++ b/spec/bundler/install/gemfile/groups_spec.rb
@@ -0,0 +1,384 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with groups" do
+ describe "installing with no options" do
+ before :each do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ group :emo do
+ gem "activesupport", "2.3.5"
+ end
+ gem "thin", :groups => [:emo]
+ G
+ end
+
+ it "installs gems in the default group" do
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs gems in a group block into that group" do
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+
+ load_error_run <<-R, "activesupport", :default
+ require 'activesupport'
+ puts ACTIVESUPPORT
+ R
+
+ expect(err).to eq_err("ZOMG LOAD ERROR")
+ end
+
+ it "installs gems with inline :groups into those groups" do
+ expect(the_bundle).to include_gems "thin 1.0"
+
+ load_error_run <<-R, "thin", :default
+ require 'thin'
+ puts THIN
+ R
+
+ expect(err).to eq_err("ZOMG LOAD ERROR")
+ end
+
+ it "sets up everything if Bundler.setup is used with no groups" do
+ output = run("require 'rack'; puts RACK")
+ expect(output).to eq("1.0.0")
+
+ output = run("require 'activesupport'; puts ACTIVESUPPORT")
+ expect(output).to eq("2.3.5")
+
+ output = run("require 'thin'; puts THIN")
+ expect(output).to eq("1.0")
+ end
+
+ it "removes old groups when new groups are set up" do
+ load_error_run <<-RUBY, "thin", :emo
+ Bundler.setup(:default)
+ require 'thin'
+ puts THIN
+ RUBY
+
+ expect(err).to eq_err("ZOMG LOAD ERROR")
+ end
+
+ it "sets up old groups when they have previously been removed" do
+ output = run <<-RUBY, :emo
+ Bundler.setup(:default)
+ Bundler.setup(:default, :emo)
+ require 'thin'; puts THIN
+ RUBY
+ expect(output).to eq("1.0")
+ end
+ end
+
+ describe "installing --without" do
+ describe "with gems assigned to a single group" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ group :emo do
+ gem "activesupport", "2.3.5"
+ end
+ group :debugging, :optional => true do
+ gem "thin"
+ end
+ G
+ end
+
+ it "installs gems in the default group" do
+ bundle! :install, forgotten_command_line_options(:without => "emo")
+ expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default]
+ end
+
+ it "does not install gems from the excluded group" do
+ bundle :install, :without => "emo"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default]
+ end
+
+ it "does not install gems from the previously excluded group" do
+ bundle :install, forgotten_command_line_options(:without => "emo")
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ bundle :install
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+
+ it "does not say it installed gems from the excluded group" do
+ bundle! :install, forgotten_command_line_options(:without => "emo")
+ expect(out).not_to include("activesupport")
+ end
+
+ it "allows Bundler.setup for specific groups" do
+ bundle :install, forgotten_command_line_options(:without => "emo")
+ run!("require 'rack'; puts RACK", :default)
+ expect(out).to eq("1.0.0")
+ end
+
+ it "does not effect the resolve" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+ group :emo do
+ gem "rails", "2.3.2"
+ end
+ G
+
+ bundle :install, forgotten_command_line_options(:without => "emo")
+ expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => [:default]
+ end
+
+ it "still works on a different machine and excludes gems" do
+ bundle :install, forgotten_command_line_options(:without => "emo")
+
+ simulate_new_machine
+ bundle :install, forgotten_command_line_options(:without => "emo")
+
+ expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default]
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default]
+ end
+
+ it "still works when BUNDLE_WITHOUT is set" do
+ ENV["BUNDLE_WITHOUT"] = "emo"
+
+ bundle :install
+ expect(out).not_to include("activesupport")
+
+ expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default]
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default]
+
+ ENV["BUNDLE_WITHOUT"] = nil
+ end
+
+ it "clears without when passed an empty list" do
+ bundle :install, forgotten_command_line_options(:without => "emo")
+
+ bundle :install, forgotten_command_line_options(:without => "")
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+
+ it "doesn't clear without when nothing is passed" do
+ bundle :install, forgotten_command_line_options(:without => "emo")
+
+ bundle :install
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+
+ it "does not install gems from the optional group" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "thin 1.0"
+ end
+
+ it "does install gems from the optional group when requested" do
+ bundle :install, forgotten_command_line_options(:with => "debugging")
+ expect(the_bundle).to include_gems "thin 1.0"
+ end
+
+ it "does install gems from the previously requested group" do
+ bundle :install, forgotten_command_line_options(:with => "debugging")
+ expect(the_bundle).to include_gems "thin 1.0"
+ bundle :install
+ expect(the_bundle).to include_gems "thin 1.0"
+ end
+
+ it "does install gems from the optional groups requested with BUNDLE_WITH" do
+ ENV["BUNDLE_WITH"] = "debugging"
+ bundle :install
+ expect(the_bundle).to include_gems "thin 1.0"
+ ENV["BUNDLE_WITH"] = nil
+ end
+
+ it "clears with when passed an empty list" do
+ bundle :install, forgotten_command_line_options(:with => "debugging")
+ bundle :install, forgotten_command_line_options(:with => "")
+ expect(the_bundle).not_to include_gems "thin 1.0"
+ end
+
+ it "does remove groups from without when passed at --with", :bundler => "< 2" do
+ bundle :install, forgotten_command_line_options(:without => "emo")
+ bundle :install, forgotten_command_line_options(:with => "emo")
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+
+ it "does remove groups from with when passed at --without", :bundler => "< 2" do
+ bundle :install, forgotten_command_line_options(:with => "debugging")
+ bundle :install, forgotten_command_line_options(:without => "debugging")
+ expect(the_bundle).not_to include_gem "thin 1.0"
+ end
+
+ it "errors out when passing a group to with and without via CLI flags", :bundler => "< 2" do
+ bundle :install, forgotten_command_line_options(:with => "emo debugging", :without => "emo")
+ expect(last_command).to be_failure
+ expect(out).to include("The offending groups are: emo")
+ end
+
+ it "allows the BUNDLE_WITH setting to override BUNDLE_WITHOUT" do
+ ENV["BUNDLE_WITH"] = "debugging"
+
+ bundle! :install
+ expect(the_bundle).to include_gem "thin 1.0"
+
+ ENV["BUNDLE_WITHOUT"] = "debugging"
+ expect(the_bundle).to include_gem "thin 1.0"
+
+ bundle! :install
+ expect(the_bundle).to include_gem "thin 1.0"
+ end
+
+ it "can add and remove a group at the same time" do
+ bundle :install, forgotten_command_line_options(:with => "debugging", :without => "emo")
+ expect(the_bundle).to include_gems "thin 1.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+
+ it "does have no effect when listing a not optional group in with" do
+ bundle :install, forgotten_command_line_options(:with => "emo")
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+
+ it "does have no effect when listing an optional group in without" do
+ bundle :install, forgotten_command_line_options(:without => "debugging")
+ expect(the_bundle).not_to include_gems "thin 1.0"
+ end
+ end
+
+ describe "with gems assigned to multiple groups" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ group :emo, :lolercoaster do
+ gem "activesupport", "2.3.5"
+ end
+ G
+ end
+
+ it "installs gems in the default group" do
+ bundle! :install, forgotten_command_line_options(:without => "emo lolercoaster")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs the gem if any of its groups are installed" do
+ bundle! :install, forgotten_command_line_options(:without => "emo")
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ end
+
+ describe "with a gem defined multiple times in different groups" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ group :emo do
+ gem "activesupport", "2.3.5"
+ end
+
+ group :lolercoaster do
+ gem "activesupport", "2.3.5"
+ end
+ G
+ end
+
+ it "installs the gem w/ option --without emo" do
+ bundle :install, forgotten_command_line_options(:without => "emo")
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+
+ it "installs the gem w/ option --without lolercoaster" do
+ bundle :install, forgotten_command_line_options(:without => "lolercoaster")
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+
+ it "does not install the gem w/ option --without emo lolercoaster" do
+ bundle :install, forgotten_command_line_options(:without => "emo lolercoaster")
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+
+ it "does not install the gem w/ option --without 'emo lolercoaster'" do
+ bundle :install, forgotten_command_line_options(:without => "'emo lolercoaster'")
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+ end
+ end
+
+ describe "nesting groups" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ group :emo do
+ group :lolercoaster do
+ gem "activesupport", "2.3.5"
+ end
+ end
+ G
+ end
+
+ it "installs gems in the default group" do
+ bundle! :install, forgotten_command_line_options(:without => "emo lolercoaster")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs the gem if any of its groups are installed" do
+ bundle! :install, forgotten_command_line_options(:without => "emo")
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ end
+ end
+ end
+
+ describe "when loading only the default group" do
+ it "should not load all groups" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :groups => :development
+ G
+
+ ruby <<-R
+ require "bundler"
+ Bundler.setup :default
+ Bundler.require :default
+ puts RACK
+ begin
+ require "activesupport"
+ rescue LoadError
+ puts "no activesupport"
+ end
+ R
+
+ expect(out).to include("1.0")
+ expect(out).to include("no activesupport")
+ end
+ end
+
+ describe "when locked and installed with --without" do
+ before(:each) do
+ build_repo2
+ system_gems "rack-0.9.1" do
+ install_gemfile <<-G, forgotten_command_line_options(:without => "rack")
+ source "file://#{gem_repo2}"
+ gem "rack"
+
+ group :rack do
+ gem "rack_middleware"
+ end
+ G
+ end
+ end
+
+ it "uses the correct versions even if --without was used on the original" do
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ expect(the_bundle).not_to include_gems "rack_middleware 1.0"
+ simulate_new_machine
+
+ bundle :install
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ expect(the_bundle).to include_gems "rack_middleware 1.0"
+ end
+
+ it "does not hit the remote a second time" do
+ FileUtils.rm_rf gem_repo2
+ bundle! :install, forgotten_command_line_options(:without => "rack").merge(:verbose => true)
+ expect(last_command.stdboth).not_to match(/fetching/i)
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/install_if.rb b/spec/bundler/install/gemfile/install_if.rb
new file mode 100644
index 0000000000..1319051fdb
--- /dev/null
+++ b/spec/bundler/install/gemfile/install_if.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+describe "bundle install with install_if conditionals" do
+ it "follows the install_if DSL" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ install_if(lambda { true }) do
+ gem "activesupport", "2.3.5"
+ end
+ gem "thin", :install_if => false
+ install_if(lambda { false }) do
+ gem "foo"
+ end
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems("rack 1.0", "activesupport 2.3.5")
+ expect(the_bundle).not_to include_gems("thin")
+ expect(the_bundle).not_to include_gems("foo")
+
+ lockfile_should_be <<-L
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ activesupport (2.3.5)
+ foo (1.0)
+ rack (1.0.0)
+ thin (1.0)
+ rack
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ activesupport (= 2.3.5)
+ foo
+ rack
+ thin
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+end
diff --git a/spec/bundler/install/gemfile/lockfile_spec.rb b/spec/bundler/install/gemfile/lockfile_spec.rb
new file mode 100644
index 0000000000..dc1baca6ea
--- /dev/null
+++ b/spec/bundler/install/gemfile/lockfile_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with a lockfile present" do
+ let(:gf) { <<-G }
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ G
+
+ subject do
+ install_gemfile(gf)
+ end
+
+ context "gemfile evaluation" do
+ let(:gf) { super() + "\n\n File.open('evals', 'a') {|f| f << %(1\n) } unless ENV['BUNDLER_SPEC_NO_APPEND']" }
+
+ context "with plugins disabled" do
+ before do
+ bundle! "config plugins false"
+ subject
+ end
+
+ it "does not evaluate the gemfile twice" do
+ bundle! :install
+
+ with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "rack 1.0.0" }
+
+ # The first eval is from the initial install, we're testing that the
+ # second install doesn't double-eval
+ expect(bundled_app("evals").read.lines.to_a.size).to eq(2)
+ end
+
+ context "when the gem is not installed" do
+ before { FileUtils.rm_rf ".bundle" }
+
+ it "does not evaluate the gemfile twice" do
+ bundle! :install
+
+ with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "rack 1.0.0" }
+
+ # The first eval is from the initial install, we're testing that the
+ # second install doesn't double-eval
+ expect(bundled_app("evals").read.lines.to_a.size).to eq(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb
new file mode 100644
index 0000000000..f7789e7ea5
--- /dev/null
+++ b/spec/bundler/install/gemfile/path_spec.rb
@@ -0,0 +1,630 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with explicit source paths" do
+ it "fetches gems with a global path source", :bundler => "< 2" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ path "#{lib_path("foo-1.0")}"
+ gem 'foo'
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "fetches gems" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ path "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "supports pinned paths" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem 'foo', :path => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "supports relative paths" do
+ build_lib "foo"
+
+ relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new(Dir.pwd))
+
+ install_gemfile <<-G
+ gem 'foo', :path => "#{relative_path}"
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "expands paths" do
+ build_lib "foo"
+
+ relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("~").expand_path)
+
+ install_gemfile <<-G
+ gem 'foo', :path => "~/#{relative_path}"
+ G
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "expands paths raise error with not existing user's home dir" do
+ build_lib "foo"
+ username = "some_unexisting_user"
+ relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("/home/#{username}").expand_path)
+
+ install_gemfile <<-G
+ gem 'foo', :path => "~#{username}/#{relative_path}"
+ G
+ expect(out).to match("There was an error while trying to use the path `~#{username}/#{relative_path}`.")
+ expect(out).to match("user #{username} doesn't exist")
+ end
+
+ it "expands paths relative to Bundler.root" do
+ build_lib "foo", :path => bundled_app("foo-1.0")
+
+ install_gemfile <<-G
+ gem 'foo', :path => "./foo-1.0"
+ G
+
+ bundled_app("subdir").mkpath
+ Dir.chdir(bundled_app("subdir")) do
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+ end
+
+ it "expands paths when comparing locked paths to Gemfile paths" do
+ build_lib "foo", :path => bundled_app("foo-1.0")
+
+ install_gemfile <<-G
+ gem 'foo', :path => File.expand_path("../foo-1.0", __FILE__)
+ G
+
+ bundle! :install, forgotten_command_line_options(:frozen => true)
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ it "installs dependencies from the path even if a newer gem is available elsewhere" do
+ system_gems "rack-1.0.0"
+
+ build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s|
+ s.write "lib/rack.rb", "puts 'WIN OVERRIDE'"
+ end
+
+ build_lib "foo", :path => lib_path("nested") do |s|
+ s.add_dependency "rack", "= 1.0"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :path => "#{lib_path("nested")}"
+ G
+
+ run "require 'rack'"
+ expect(out).to eq("WIN OVERRIDE")
+ end
+
+ it "works" do
+ build_gem "foo", "1.0.0", :to_system => true do |s|
+ s.write "lib/foo.rb", "puts 'FAIL'"
+ end
+
+ build_lib "omg", "1.0", :path => lib_path("omg") do |s|
+ s.add_dependency "foo"
+ end
+
+ build_lib "foo", "1.0.0", :path => lib_path("omg/foo")
+
+ install_gemfile <<-G
+ gem "omg", :path => "#{lib_path("omg")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "prefers gemspecs closer to the path root" do
+ build_lib "premailer", "1.0.0", :path => lib_path("premailer") do |s|
+ s.write "gemfiles/ruby187.gemspec", <<-G
+ Gem::Specification.new do |s|
+ s.name = 'premailer'
+ s.version = '1.0.0'
+ s.summary = 'Hi'
+ s.authors = 'Me'
+ end
+ G
+ end
+
+ install_gemfile <<-G
+ gem "premailer", :path => "#{lib_path("premailer")}"
+ G
+
+ # Installation of the 'gemfiles' gemspec would fail since it will be unable
+ # to require 'premailer.rb'
+ expect(the_bundle).to include_gems "premailer 1.0.0"
+ end
+
+ it "warns on invalid specs", :rubygems => "1.7" do
+ build_lib "foo"
+
+ gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s
+ File.open(gemspec, "w") do |f|
+ f.write <<-G
+ Gem::Specification.new do |s|
+ s.name = "foo"
+ end
+ G
+ end
+
+ install_gemfile <<-G
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(out).to_not include("ERROR REPORT")
+ expect(out).to_not include("Your Gemfile has no gem server sources.")
+ expect(out).to match(/is not valid. Please fix this gemspec./)
+ expect(out).to match(/The validation error was 'missing value for attribute version'/)
+ expect(out).to match(/You have one or more invalid gemspecs that need to be fixed/)
+ end
+
+ it "supports gemspec syntax" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ end
+
+ gemfile = <<-G
+ source "file://#{gem_repo1}"
+ gemspec
+ G
+
+ File.open(lib_path("foo/Gemfile"), "w") {|f| f.puts gemfile }
+
+ Dir.chdir(lib_path("foo")) do
+ bundle "install"
+ expect(the_bundle).to include_gems "foo 1.0"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ it "supports gemspec syntax with an alternative path" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", "1.0"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gemspec :path => "#{lib_path("foo")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "doesn't automatically unlock dependencies when using the gemspec syntax" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", ">= 1.0"
+ end
+
+ Dir.chdir lib_path("foo")
+
+ install_gemfile lib_path("foo/Gemfile"), <<-G
+ source "file://#{gem_repo1}"
+ gemspec
+ G
+
+ build_gem "rack", "1.0.1", :to_system => true
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "doesn't automatically unlock dependencies when using the gemspec syntax and the gem has development dependencies" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack", ">= 1.0"
+ s.add_development_dependency "activesupport"
+ end
+
+ Dir.chdir lib_path("foo")
+
+ install_gemfile lib_path("foo/Gemfile"), <<-G
+ source "file://#{gem_repo1}"
+ gemspec
+ G
+
+ build_gem "rack", "1.0.1", :to_system => true
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "raises if there are multiple gemspecs" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.write "bar.gemspec", build_spec("bar", "1.0").first.to_ruby
+ end
+
+ install_gemfile <<-G
+ gemspec :path => "#{lib_path("foo")}"
+ G
+
+ expect(exitstatus).to eq(15) if exitstatus
+ expect(out).to match(/There are multiple gemspecs/)
+ end
+
+ it "allows :name to be specified to resolve ambiguity" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.write "bar.gemspec"
+ end
+
+ install_gemfile <<-G
+ gemspec :path => "#{lib_path("foo")}", :name => "foo"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "sets up executables" do
+ build_lib "foo" do |s|
+ s.executables = "foobar"
+ end
+
+ install_gemfile <<-G
+ path "#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+ expect(the_bundle).to include_gems "foo 1.0"
+
+ bundle "exec foobar"
+ expect(out).to eq("1.0")
+ end
+
+ it "handles directories in bin/" do
+ build_lib "foo"
+ lib_path("foo-1.0").join("foo.gemspec").rmtree
+ lib_path("foo-1.0").join("bin/performance").mkpath
+
+ install_gemfile <<-G
+ gem 'foo', '1.0', :path => "#{lib_path("foo-1.0")}"
+ G
+ expect(err).to lack_errors
+ end
+
+ it "removes the .gem file after installing" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem 'foo', :path => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(lib_path("foo-1.0").join("foo-1.0.gem")).not_to exist
+ end
+
+ describe "block syntax" do
+ it "pulls all gems from a path block" do
+ build_lib "omg"
+ build_lib "hi2u"
+
+ install_gemfile <<-G
+ path "#{lib_path}" do
+ gem "omg"
+ gem "hi2u"
+ end
+ G
+
+ expect(the_bundle).to include_gems "omg 1.0", "hi2u 1.0"
+ end
+ end
+
+ it "keeps source pinning" do
+ build_lib "foo", "1.0", :path => lib_path("foo")
+ build_lib "omg", "1.0", :path => lib_path("omg")
+ build_lib "foo", "1.0", :path => lib_path("omg/foo") do |s|
+ s.write "lib/foo.rb", "puts 'FAIL'"
+ end
+
+ install_gemfile <<-G
+ gem "foo", :path => "#{lib_path("foo")}"
+ gem "omg", :path => "#{lib_path("omg")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "works when the path does not have a gemspec" do
+ build_lib "foo", :gemspec => false
+
+ gemfile <<-G
+ gem "foo", "1.0", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0"
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "works when the path does not have a gemspec but there is a lockfile" do
+ lockfile <<-L
+ PATH
+ remote: vendor/bar
+ specs:
+
+ GEM
+ remote: http://rubygems.org
+ L
+
+ in_app_root { FileUtils.mkdir_p("vendor/bar") }
+
+ install_gemfile <<-G
+ gem "bar", "1.0.0", path: "vendor/bar", require: "bar/nyard"
+ G
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ context "existing lockfile" do
+ it "rubygems gems don't re-resolve without changes" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack-obama', '1.0'
+ gem 'net-ssh', '1.0'
+ G
+
+ bundle :check, :env => { "DEBUG" => 1 }
+ expect(out).to match(/using resolution from the lockfile/)
+ expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0"
+ end
+
+ it "source path gems w/deps don't re-resolve without changes" do
+ build_lib "rack-obama", "1.0", :path => lib_path("omg") do |s|
+ s.add_dependency "yard"
+ end
+
+ build_lib "net-ssh", "1.0", :path => lib_path("omg") do |s|
+ s.add_dependency "yard"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack-obama', :path => "#{lib_path("omg")}"
+ gem 'net-ssh', :path => "#{lib_path("omg")}"
+ G
+
+ bundle :check, :env => { "DEBUG" => 1 }
+ expect(out).to match(/using resolution from the lockfile/)
+ expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0"
+ end
+ end
+
+ it "installs executable stubs" do
+ build_lib "foo" do |s|
+ s.executables = ["foo"]
+ end
+
+ install_gemfile <<-G
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle "exec foo"
+ expect(out).to eq("1.0")
+ end
+
+ describe "when the gem version in the path is updated" do
+ before :each do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "bar"
+ end
+ build_lib "bar", "1.0", :path => lib_path("foo/bar")
+
+ install_gemfile <<-G
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+ end
+
+ it "unlocks all gems when the top level gem is updated" do
+ build_lib "foo", "2.0", :path => lib_path("foo") do |s|
+ s.add_dependency "bar"
+ end
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "foo 2.0", "bar 1.0"
+ end
+
+ it "unlocks all gems when a child dependency gem is updated" do
+ build_lib "bar", "2.0", :path => lib_path("foo/bar")
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "foo 1.0", "bar 2.0"
+ end
+ end
+
+ describe "when dependencies in the path are updated" do
+ before :each do
+ build_lib "foo", "1.0", :path => lib_path("foo")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :path => "#{lib_path("foo")}"
+ G
+ end
+
+ it "gets dependencies that are updated in the path" do
+ build_lib "foo", "1.0", :path => lib_path("foo") do |s|
+ s.add_dependency "rack"
+ end
+
+ bundle "install"
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ describe "switching sources" do
+ it "doesn't switch pinned git sources to rubygems when pinning the parent gem to a path source" do
+ build_gem "foo", "1.0", :to_system => true do |s|
+ s.write "lib/foo.rb", "raise 'fail'"
+ end
+ build_lib "foo", "1.0", :path => lib_path("bar/foo")
+ build_git "bar", "1.0", :path => lib_path("bar") do |s|
+ s.add_dependency "foo"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "bar", :git => "#{lib_path("bar")}"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "bar", :path => "#{lib_path("bar")}"
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0", "bar 1.0"
+ end
+
+ it "switches the source when the gem existed in rubygems and the path was already being used for another gem" do
+ build_lib "foo", "1.0", :path => lib_path("foo")
+ build_gem "bar", "1.0", :to_system => true do |s|
+ s.write "lib/bar.rb", "raise 'fail'"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "bar"
+ path "#{lib_path("foo")}" do
+ gem "foo"
+ end
+ G
+
+ build_lib "bar", "1.0", :path => lib_path("foo/bar")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ path "#{lib_path("foo")}" do
+ gem "foo"
+ gem "bar"
+ end
+ G
+
+ expect(the_bundle).to include_gems "bar 1.0"
+ end
+ end
+
+ describe "when there are both a gemspec and remote gems" do
+ it "doesn't query rubygems for local gemspec name" do
+ build_lib "private_lib", "2.2", :path => lib_path("private_lib")
+ gemfile = <<-G
+ source "http://localgemserver.test"
+ gemspec
+ gem 'rack'
+ G
+ File.open(lib_path("private_lib/Gemfile"), "w") {|f| f.puts gemfile }
+
+ Dir.chdir(lib_path("private_lib")) do
+ bundle :install, :env => { "DEBUG" => 1 }, :artifice => "endpoint"
+ expect(out).to match(%r{^HTTP GET http://localgemserver\.test/api/v1/dependencies\?gems=rack$})
+ expect(out).not_to match(/^HTTP GET.*private_lib/)
+ expect(the_bundle).to include_gems "private_lib 2.2"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+ end
+
+ describe "gem install hooks" do
+ it "runs pre-install hooks" do
+ build_git "foo"
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ require 'rubygems'
+ Gem.pre_install_hooks << lambda do |inst|
+ STDERR.puts "Ran pre-install hook: \#{inst.spec.full_name}"
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(err).to eq_err("Ran pre-install hook: foo-1.0")
+ end
+
+ it "runs post-install hooks" do
+ build_git "foo"
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ require 'rubygems'
+ Gem.post_install_hooks << lambda do |inst|
+ STDERR.puts "Ran post-install hook: \#{inst.spec.full_name}"
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(err).to eq_err("Ran post-install hook: foo-1.0")
+ end
+
+ it "complains if the install hook fails" do
+ build_git "foo"
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ File.open(lib_path("install_hooks.rb"), "w") do |h|
+ h.write <<-H
+ require 'rubygems'
+ Gem.pre_install_hooks << lambda do |inst|
+ false
+ end
+ H
+ end
+
+ bundle :install,
+ :requires => [lib_path("install_hooks.rb")]
+ expect(out).to include("failed for foo-1.0")
+ end
+
+ it "loads plugins from the path gem" do
+ foo_file = home("foo_plugin_loaded")
+ bar_file = home("bar_plugin_loaded")
+ expect(foo_file).not_to be_file
+ expect(bar_file).not_to be_file
+
+ build_lib "foo" do |s|
+ s.write("lib/rubygems_plugin.rb", "FileUtils.touch('#{foo_file}')")
+ end
+
+ build_git "bar" do |s|
+ s.write("lib/rubygems_plugin.rb", "FileUtils.touch('#{bar_file}')")
+ end
+
+ install_gemfile! <<-G
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ gem "bar", :path => "#{lib_path("bar-1.0")}"
+ G
+
+ expect(foo_file).to be_file
+ expect(bar_file).to be_file
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb
new file mode 100644
index 0000000000..bfdf9b68c8
--- /dev/null
+++ b/spec/bundler/install/gemfile/platform_spec.rb
@@ -0,0 +1,426 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install across platforms" do
+ it "maintains the same lockfile if all gems are compatible across platforms" do
+ lockfile <<-G
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{not_local}
+
+ DEPENDENCIES
+ rack
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ end
+
+ it "pulls in the correct platform specific gem" do
+ lockfile <<-G
+ GEM
+ remote: file:#{gem_repo1}
+ specs:
+ platform_specific (1.0)
+ platform_specific (1.0-java)
+ platform_specific (1.0-x86-mswin32)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ platform_specific
+ G
+
+ simulate_platform "java"
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 JAVA"
+ end
+
+ it "works with gems that have different dependencies" do
+ simulate_platform "java"
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "nokogiri"
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3"
+
+ simulate_new_machine
+
+ simulate_platform "ruby"
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "nokogiri"
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ expect(the_bundle).not_to include_gems "weakling"
+ end
+
+ it "does not keep unneeded platforms for gems that are used" do
+ build_repo4 do
+ build_gem "empyrean", "0.1.0"
+ build_gem "coderay", "1.1.2"
+ build_gem "method_source", "0.9.0"
+ build_gem("spoon", "0.0.6") {|s| s.add_runtime_dependency "ffi" }
+ build_gem "pry", "0.11.3" do |s|
+ s.platform = "java"
+ s.add_runtime_dependency "coderay", "~> 1.1.0"
+ s.add_runtime_dependency "method_source", "~> 0.9.0"
+ s.add_runtime_dependency "spoon", "~> 0.0"
+ end
+ build_gem "pry", "0.11.3" do |s|
+ s.add_runtime_dependency "coderay", "~> 1.1.0"
+ s.add_runtime_dependency "method_source", "~> 0.9.0"
+ end
+ build_gem("ffi", "1.9.23") {|s| s.platform = "java" }
+ build_gem("ffi", "1.9.23")
+ end
+
+ simulate_platform java
+
+ install_gemfile! <<-G
+ source "file://localhost/#{gem_repo4}"
+
+ gem "empyrean", "0.1.0"
+ gem "pry"
+ G
+
+ expect(the_bundle.lockfile).to read_as normalize_uri_file(strip_whitespace(<<-L))
+ GEM
+ remote: file://localhost/#{gem_repo4}/
+ specs:
+ coderay (1.1.2)
+ empyrean (0.1.0)
+ ffi (1.9.23-java)
+ method_source (0.9.0)
+ pry (0.11.3-java)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ spoon (~> 0.0)
+ spoon (0.0.6)
+ ffi
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ empyrean (= 0.1.0)
+ pry
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle! "lock --add-platform ruby"
+
+ good_lockfile = strip_whitespace(<<-L)
+ GEM
+ remote: file://localhost/#{gem_repo4}/
+ specs:
+ coderay (1.1.2)
+ empyrean (0.1.0)
+ ffi (1.9.23-java)
+ method_source (0.9.0)
+ pry (0.11.3)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ pry (0.11.3-java)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ spoon (~> 0.0)
+ spoon (0.0.6)
+ ffi
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ empyrean (= 0.1.0)
+ pry
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile)
+
+ bad_lockfile = strip_whitespace <<-L
+ GEM
+ remote: file://localhost/#{gem_repo4}/
+ specs:
+ coderay (1.1.2)
+ empyrean (0.1.0)
+ ffi (1.9.23)
+ ffi (1.9.23-java)
+ method_source (0.9.0)
+ pry (0.11.3)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ pry (0.11.3-java)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ spoon (~> 0.0)
+ spoon (0.0.6)
+ ffi
+
+ PLATFORMS
+ java
+ ruby
+
+ DEPENDENCIES
+ empyrean (= 0.1.0)
+ pry
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ aggregate_failures do
+ lockfile bad_lockfile
+ bundle! :install
+ expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile)
+
+ lockfile bad_lockfile
+ bundle! :update, :all => true
+ expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile)
+
+ lockfile bad_lockfile
+ bundle! "update ffi"
+ expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile)
+
+ lockfile bad_lockfile
+ bundle! "update empyrean"
+ expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile)
+
+ lockfile bad_lockfile
+ bundle! :lock
+ expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile)
+ end
+ end
+
+ it "works the other way with gems that have different dependencies" do
+ simulate_platform "ruby"
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "nokogiri"
+ G
+
+ simulate_platform "java"
+ bundle "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3"
+ end
+
+ it "works with gems that have extra platform-specific runtime dependencies", :bundler => "< 2" do
+ simulate_platform x64_mac
+
+ update_repo2 do
+ build_gem "facter", "2.4.6"
+ build_gem "facter", "2.4.6" do |s|
+ s.platform = "universal-darwin"
+ s.add_runtime_dependency "CFPropertyList"
+ end
+ build_gem "CFPropertyList"
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo2}"
+
+ gem "facter"
+ G
+
+ expect(out).to include "Unable to use the platform-specific (universal-darwin) version of facter (2.4.6) " \
+ "because it has different dependencies from the ruby version. " \
+ "To use the platform-specific version of the gem, run `bundle config specific_platform true` and install again."
+
+ expect(the_bundle).to include_gem "facter 2.4.6"
+ expect(the_bundle).not_to include_gem "CFPropertyList"
+ end
+
+ it "fetches gems again after changing the version of Ruby" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", "1.0.0"
+ G
+
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle")
+
+ new_version = Gem::ConfigMap[:ruby_version] == "1.8" ? "1.9.1" : "1.8"
+ FileUtils.mv(vendored_gems, bundled_app("vendor/bundle", Gem.ruby_engine, new_version))
+
+ bundle! :install
+ expect(vendored_gems("gems/rack-1.0.0")).to exist
+ end
+end
+
+RSpec.describe "bundle install with platform conditionals" do
+ it "installs gems tagged w/ the current platforms" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ platforms :#{local_tag} do
+ gem "nokogiri"
+ end
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ end
+
+ it "does not install gems tagged w/ another platforms" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ platforms :#{not_local_tag} do
+ gem "nokogiri"
+ end
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).not_to include_gems "nokogiri 1.4.2"
+ end
+
+ it "installs gems tagged w/ the current platforms inline" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "nokogiri", :platforms => :#{local_tag}
+ G
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ end
+
+ it "does not install gems tagged w/ another platforms inline" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "nokogiri", :platforms => :#{not_local_tag}
+ G
+ expect(the_bundle).to include_gems "rack 1.0"
+ expect(the_bundle).not_to include_gems "nokogiri 1.4.2"
+ end
+
+ it "installs gems tagged w/ the current platform inline" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "nokogiri", :platform => :#{local_tag}
+ G
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ end
+
+ it "doesn't install gems tagged w/ another platform inline" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "nokogiri", :platform => :#{not_local_tag}
+ G
+ expect(the_bundle).not_to include_gems "nokogiri 1.4.2"
+ end
+
+ it "does not blow up on sources with all platform-excluded specs" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ platform :#{not_local_tag} do
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ end
+ G
+
+ bundle :list
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ it "does not attempt to install gems from :rbx when using --local" do
+ simulate_platform "ruby"
+ simulate_ruby_engine "ruby"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "some_gem", :platform => :rbx
+ G
+
+ bundle "install --local"
+ expect(out).not_to match(/Could not find gem 'some_gem/)
+ end
+
+ it "does not attempt to install gems from other rubies when using --local" do
+ simulate_platform "ruby"
+ simulate_ruby_engine "ruby"
+ other_ruby_version_tag = RUBY_VERSION =~ /^1\.8/ ? :ruby_19 : :ruby_18
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "some_gem", platform: :#{other_ruby_version_tag}
+ G
+
+ bundle "install --local"
+ expect(out).not_to match(/Could not find gem 'some_gem/)
+ end
+
+ it "prints a helpful warning when a dependency is unused on any platform" do
+ simulate_platform "ruby"
+ simulate_ruby_engine "ruby"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", :platform => [:mingw, :mswin, :x64_mingw, :jruby]
+ G
+
+ bundle! "install"
+
+ expect(out).to include <<-O.strip
+The dependency #{Gem::Dependency.new("rack", ">= 0")} will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
+ O
+ end
+
+ context "when disable_platform_warnings is true" do
+ before { bundle! "config disable_platform_warnings true" }
+
+ it "does not print the warning when a dependency is unused on any platform" do
+ simulate_platform "ruby"
+ simulate_ruby_engine "ruby"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", :platform => [:mingw, :mswin, :x64_mingw, :jruby]
+ G
+
+ bundle! "install"
+
+ expect(out).not_to match(/The dependency (.*) will be unused/)
+ end
+ end
+end
+
+RSpec.describe "when a gem has no architecture" do
+ it "still installs correctly" do
+ simulate_platform mswin
+
+ gemfile <<-G
+ # Try to install gem with nil arch
+ source "http://localgemserver.test/"
+ gem "rcov"
+ G
+
+ bundle :install, :artifice => "windows"
+ expect(the_bundle).to include_gems "rcov 1.0.0"
+ end
+end
diff --git a/spec/bundler/install/gemfile/ruby_spec.rb b/spec/bundler/install/gemfile/ruby_spec.rb
new file mode 100644
index 0000000000..24fe021fa3
--- /dev/null
+++ b/spec/bundler/install/gemfile/ruby_spec.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+RSpec.describe "ruby requirement" do
+ def locked_ruby_version
+ Bundler::RubyVersion.from_string(Bundler::LockfileParser.new(lockfile).ruby_version)
+ end
+
+ # As discovered by https://github.com/bundler/bundler/issues/4147, there is
+ # no test coverage to ensure that adding a gem is possible with a ruby
+ # requirement. This test verifies the fix, committed in bfbad5c5.
+ it "allows adding gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "#{RUBY_VERSION}"
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "#{RUBY_VERSION}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(the_bundle).to include_gems "rack-obama 1.0"
+ end
+
+ it "allows removing the ruby version requirement" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "~> #{RUBY_VERSION}"
+ gem "rack"
+ G
+
+ expect(lockfile).to include("RUBY VERSION")
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(lockfile).not_to include("RUBY VERSION")
+ end
+
+ it "allows changing the ruby version requirement to something compatible" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby ">= 1.0.0"
+ gem "rack"
+ G
+
+ expect(locked_ruby_version).to eq(Bundler::RubyVersion.system)
+
+ simulate_ruby_version "5100"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby ">= 1.0.1"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(locked_ruby_version).to eq(Bundler::RubyVersion.system)
+ end
+
+ it "allows changing the ruby version requirement to something incompatible" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby ">= 1.0.0"
+ gem "rack"
+ G
+
+ expect(locked_ruby_version).to eq(Bundler::RubyVersion.system)
+
+ simulate_ruby_version "5100"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby ">= 5000.0"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(locked_ruby_version.versions).to eq(["5100"])
+ end
+
+ it "allows requirements with trailing whitespace" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ ruby "#{RUBY_VERSION}\\n \t\\n"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "fails gracefully with malformed requirements" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby ">= 0", "-.\\0"
+ gem "rack"
+ G
+
+ expect(out).to include("There was an error parsing") # i.e. DSL error, not error template
+ end
+end
diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb
new file mode 100644
index 0000000000..c814d0de76
--- /dev/null
+++ b/spec/bundler/install/gemfile/sources_spec.rb
@@ -0,0 +1,619 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with gems on multiple sources" do
+ # repo1 is built automatically before all of the specs run
+ # it contains rack-obama 1.0.0 and rack 0.9.1 & 1.0.0 amongst other gems
+
+ context "without source affinity" do
+ before do
+ # Oh no! Someone evil is trying to hijack rack :(
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo3 do
+ build_gem "rack", repo3_rack_version do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+ end
+
+ context "with multiple toplevel sources" do
+ let(:repo3_rack_version) { "1.0.0" }
+
+ before do
+ gemfile <<-G
+ source "file://localhost#{gem_repo3}"
+ source "file://localhost#{gem_repo1}"
+ gem "rack-obama"
+ gem "rack"
+ G
+ bundle "config major_deprecations true"
+ end
+
+ it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 2" do
+ bundle :install
+
+ expect(out).to have_major_deprecation a_string_including("Your Gemfile contains multiple primary sources.")
+ expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(out).to include(normalize_uri_file("Installed from: file://localhost#{gem_repo1}"))
+ expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1")
+ end
+
+ it "errors when disable_multisource is set" do
+ bundle "config disable_multisource true"
+ bundle :install
+ expect(out).to include("Each source after the first must include a block")
+ expect(exitstatus).to eq(4) if exitstatus
+ end
+ end
+
+ context "when different versions of the same gem are in multiple sources" do
+ let(:repo3_rack_version) { "1.2" }
+
+ before do
+ gemfile <<-G
+ source "file://localhost#{gem_repo3}"
+ source "file://localhost#{gem_repo1}"
+ gem "rack-obama"
+ gem "rack", "1.0.0" # force it to install the working version in repo1
+ G
+ bundle "config major_deprecations true"
+ end
+
+ it "warns about ambiguous gems, but installs anyway", :bundler => "< 2" do
+ bundle :install
+
+ expect(out).to have_major_deprecation a_string_including("Your Gemfile contains multiple primary sources.")
+ expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(out).to include(normalize_uri_file("Installed from: file://localhost#{gem_repo1}"))
+ expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1")
+ end
+ end
+ end
+
+ context "with source affinity" do
+ context "with sources given by a block" do
+ before do
+ # Oh no! Someone evil is trying to hijack rack :(
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo3 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ build_gem "rack-obama" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo3}"
+ source "file://#{gem_repo1}" do
+ gem "thin" # comes first to test name sorting
+ gem "rack"
+ end
+ gem "rack-obama" # shoud come from repo3!
+ G
+ end
+
+ it "installs the gems without any warning" do
+ bundle! :install
+ expect(out).not_to include("Warning")
+ expect(the_bundle).to include_gems("rack-obama 1.0.0")
+ expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote1")
+ end
+
+ it "can cache and deploy" do
+ bundle! :package
+
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ expect(bundled_app("vendor/cache/rack-obama-1.0.gem")).to exist
+
+ bundle! :install, forgotten_command_line_options(:deployment => true)
+
+ expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0")
+ end
+ end
+
+ context "with sources set by an option" do
+ before do
+ # Oh no! Someone evil is trying to hijack rack :(
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo3 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ build_gem "rack-obama" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo3}"
+ gem "rack-obama" # should come from repo3!
+ gem "rack", :source => "file://#{gem_repo1}"
+ G
+ end
+
+ it "installs the gems without any warning" do
+ bundle :install
+ expect(out).not_to include("Warning")
+ expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0")
+ end
+ end
+
+ context "when a pinned gem has an indirect dependency" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "depends_on_rack", "1.0.1" do |s|
+ s.add_dependency "rack"
+ end
+ end
+ end
+
+ context "when the indirect dependency is in the pinned source" do
+ before do
+ # we need a working rack gem in repo3
+ update_repo gem_repo3 do
+ build_gem "rack", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ source "file://#{gem_repo3}" do
+ gem "depends_on_rack"
+ end
+ G
+ end
+
+ context "and not in any other sources" do
+ before do
+ build_repo(gem_repo2) {}
+ end
+
+ it "installs from the same source without any warning" do
+ bundle :install
+ expect(out).not_to include("Warning")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ end
+ end
+
+ context "and in another source" do
+ before do
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo2 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+ end
+
+ context "when lockfile_uses_separate_rubygems_sources is set" do
+ before do
+ bundle! "config lockfile_uses_separate_rubygems_sources true"
+ bundle! "config disable_multisource true"
+ end
+
+ it "installs from the same source without any warning" do
+ bundle! :install
+
+ expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+
+ # when there is already a lock file, and the gems are missing, so try again
+ system_gems []
+ bundle! :install
+
+ expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ end
+ end
+ end
+ end
+
+ context "when the indirect dependency is in a different source" do
+ before do
+ # In these tests, we need a working rack gem in repo2 and not repo3
+ build_repo gem_repo2 do
+ build_gem "rack", "1.0.0"
+ end
+ end
+
+ context "and not in any other sources" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ source "file://#{gem_repo3}" do
+ gem "depends_on_rack"
+ end
+ G
+ end
+
+ it "installs from the other source without any warning" do
+ bundle :install
+ expect(out).not_to include("Warning")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ end
+ end
+
+ context "and in yet another source" do
+ before do
+ gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ source "file://localhost#{gem_repo2}"
+ source "file://localhost#{gem_repo3}" do
+ gem "depends_on_rack"
+ end
+ G
+ end
+
+ it "installs from the other source and warns about ambiguous gems", :bundler => "< 2" do
+ bundle "config major_deprecations true"
+ bundle :install
+ expect(out).to have_major_deprecation a_string_including("Your Gemfile contains multiple primary sources.")
+ expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(out).to include(normalize_uri_file("Installed from: file://localhost#{gem_repo2}"))
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ end
+ end
+
+ context "and only the dependency is pinned" do
+ before do
+ # need this to be broken to check for correct source ordering
+ build_repo gem_repo2 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo3}" # contains depends_on_rack
+ source "file://#{gem_repo2}" # contains broken rack
+
+ gem "depends_on_rack" # installed from gem_repo3
+ gem "rack", :source => "file://#{gem_repo1}"
+ G
+ end
+
+ it "installs the dependency from the pinned source without warning", :bundler => "< 2" do
+ bundle :install
+
+ expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+
+ # In https://github.com/bundler/bundler/issues/3585 this failed
+ # when there is already a lock file, and the gems are missing, so try again
+ system_gems []
+ bundle :install
+
+ expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
+ end
+ end
+ end
+ end
+
+ context "when a top-level gem has an indirect dependency" do
+ context "when lockfile_uses_separate_rubygems_sources is set" do
+ before do
+ bundle! "config lockfile_uses_separate_rubygems_sources true"
+ bundle! "config disable_multisource true"
+ end
+
+ before do
+ build_repo gem_repo2 do
+ build_gem "depends_on_rack", "1.0.1" do |s|
+ s.add_dependency "rack"
+ end
+ end
+
+ build_repo gem_repo3 do
+ build_gem "unrelated_gem", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "depends_on_rack"
+
+ source "file://#{gem_repo3}" do
+ gem "unrelated_gem"
+ end
+ G
+ end
+
+ context "and the dependency is only in the top-level source" do
+ before do
+ update_repo gem_repo2 do
+ build_gem "rack", "1.0.0"
+ end
+ end
+
+ it "installs all gems without warning" do
+ bundle :install
+ expect(out).not_to include("Warning")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
+ end
+ end
+
+ context "and the dependency is only in a pinned source" do
+ before do
+ update_repo gem_repo3 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+ end
+
+ it "does not find the dependency" do
+ bundle :install
+ expect(out).to include("Could not find gem 'rack', which is required by gem 'depends_on_rack', in any of the relevant sources")
+ end
+ end
+
+ context "and the dependency is in both the top-level and a pinned source" do
+ before do
+ update_repo gem_repo2 do
+ build_gem "rack", "1.0.0"
+ end
+
+ update_repo gem_repo3 do
+ build_gem "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+ end
+ end
+
+ it "installs the dependency from the top-level source without warning" do
+ bundle :install
+ expect(out).not_to include("Warning")
+ expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
+ end
+ end
+ end
+ end
+
+ context "with a gem that is only found in the wrong source" do
+ before do
+ build_repo gem_repo3 do
+ build_gem "not_in_repo1", "1.0.0"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo3}"
+ gem "not_in_repo1", :source => "file://#{gem_repo1}"
+ G
+ end
+
+ it "does not install the gem" do
+ bundle :install
+ expect(out).to include("Could not find gem 'not_in_repo1'")
+ end
+ end
+
+ context "with an existing lockfile" do
+ before do
+ system_gems "rack-0.9.1", "rack-1.0.0", :path => :bundle_path
+
+ lockfile <<-L
+ GEM
+ remote: file:#{gem_repo1}
+ remote: file:#{gem_repo3}
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack!
+ L
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ source "file://#{gem_repo3}" do
+ gem 'rack'
+ end
+ G
+ end
+
+ # Reproduction of https://github.com/bundler/bundler/issues/3298
+ it "does not unlock the installed gem on exec" do
+ expect(the_bundle).to include_gems("rack 0.9.1")
+ end
+ end
+
+ context "with a path gem in the same Gemfile" do
+ before do
+ build_lib "foo"
+
+ gemfile <<-G
+ gem "rack", :source => "file://#{gem_repo1}"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+ end
+
+ it "does not unlock the non-path gem after install" do
+ bundle! :install
+
+ bundle! %(exec ruby -e 'puts "OK"'), :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+
+ expect(out).to include("OK")
+ end
+ end
+ end
+
+ context "when an older version of the same gem also ships with Ruby" do
+ before do
+ system_gems "rack-0.9.1"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack" # shoud come from repo1!
+ G
+ end
+
+ it "installs the gems without any warning" do
+ bundle :install
+ expect(out).not_to include("Warning")
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ end
+ end
+
+ context "when a single source contains multiple locked gems" do
+ before do
+ # 1. With these gems,
+ build_repo4 do
+ build_gem "foo", "0.1"
+ build_gem "bar", "0.1"
+ end
+
+ # 2. Installing this gemfile will produce...
+ gemfile <<-G
+ source 'file://#{gem_repo1}'
+ gem 'rack'
+ gem 'foo', '~> 0.1', :source => 'file://#{gem_repo4}'
+ gem 'bar', '~> 0.1', :source => 'file://#{gem_repo4}'
+ G
+
+ # 3. this lockfile.
+ lockfile <<-L
+ GEM
+ remote: file:/Users/andre/src/bundler/bundler/tmp/gems/remote1/
+ remote: file:/Users/andre/src/bundler/bundler/tmp/gems/remote4/
+ specs:
+ bar (0.1)
+ foo (0.1)
+ rack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ bar (~> 0.1)!
+ foo (~> 0.1)!
+ rack
+ L
+
+ bundle! :install, forgotten_command_line_options(:path => "../gems/system")
+
+ # 4. Then we add some new versions...
+ update_repo4 do
+ build_gem "foo", "0.2"
+ build_gem "bar", "0.3"
+ end
+ end
+
+ it "allows them to be unlocked separately" do
+ # 5. and install this gemfile, updating only foo.
+ install_gemfile <<-G
+ source 'file://#{gem_repo1}'
+ gem 'rack'
+ gem 'foo', '~> 0.2', :source => 'file://#{gem_repo4}'
+ gem 'bar', '~> 0.1', :source => 'file://#{gem_repo4}'
+ G
+
+ # 6. Which should update foo to 0.2, but not the (locked) bar 0.1
+ expect(the_bundle).to include_gems("foo 0.2", "bar 0.1")
+ end
+ end
+
+ context "re-resolving" do
+ context "when there is a mix of sources in the gemfile" do
+ before do
+ build_repo3
+ build_lib "path1"
+ build_lib "path2"
+ build_git "git1"
+ build_git "git2"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+
+ source "file://#{gem_repo3}" do
+ gem "rack"
+ end
+
+ gem "path1", :path => "#{lib_path("path1-1.0")}"
+ gem "path2", :path => "#{lib_path("path2-1.0")}"
+ gem "git1", :git => "#{lib_path("git1-1.0")}"
+ gem "git2", :git => "#{lib_path("git2-1.0")}"
+ G
+ end
+
+ it "does not re-resolve" do
+ bundle :install, :verbose => true
+ expect(out).to include("using resolution from the lockfile")
+ expect(out).not_to include("re-resolving dependencies")
+ end
+ end
+ end
+
+ context "when a gem is installed to system gems" do
+ before do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ context "and the gemfile changes" do
+ it "is still able to find that gem from remote sources" do
+ source_uri = "file://#{gem_repo1}"
+ second_uri = "file://#{gem_repo4}"
+
+ build_repo4 do
+ build_gem "rack", "2.0.1.1.forked"
+ build_gem "thor", "0.19.1.1.forked"
+ end
+
+ # When this gemfile is installed...
+ gemfile <<-G
+ source "#{source_uri}"
+
+ source "#{second_uri}" do
+ gem "rack", "2.0.1.1.forked"
+ gem "thor"
+ end
+ gem "rack-obama"
+ G
+
+ # It creates this lockfile.
+ lockfile <<-L
+ GEM
+ remote: #{source_uri}/
+ remote: #{second_uri}/
+ specs:
+ rack (2.0.1.1.forked)
+ rack-obama (1.0)
+ rack
+ thor (0.19.1.1.forked)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack (= 2.0.1.1.forked)!
+ rack-obama
+ thor!
+ L
+
+ # Then we change the Gemfile by adding a version to thor
+ gemfile <<-G
+ source "#{source_uri}"
+
+ source "#{second_uri}" do
+ gem "rack", "2.0.1.1.forked"
+ gem "thor", "0.19.1.1.forked"
+ end
+ gem "rack-obama"
+ G
+
+ # But we should still be able to find rack 2.0.1.1.forked and install it
+ bundle! :install
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb
new file mode 100644
index 0000000000..9c725416d5
--- /dev/null
+++ b/spec/bundler/install/gemfile/specific_platform_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with specific_platform enabled" do
+ before do
+ bundle "config specific_platform true"
+
+ build_repo2 do
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1")
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "universal-darwin" }
+
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5")
+
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "universal-darwin" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4")
+
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3")
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "universal-darwin" }
+
+ build_gem("google-protobuf", "3.0.0.alpha.4.0")
+ build_gem("google-protobuf", "3.0.0.alpha.3.1.pre")
+ build_gem("google-protobuf", "3.0.0.alpha.3")
+ build_gem("google-protobuf", "3.0.0.alpha.2.0")
+ build_gem("google-protobuf", "3.0.0.alpha.1.1")
+ build_gem("google-protobuf", "3.0.0.alpha.1.0")
+
+ build_gem("facter", "2.4.6")
+ build_gem("facter", "2.4.6") do |s|
+ s.platform = "universal-darwin"
+ s.add_runtime_dependency "CFPropertyList"
+ end
+ build_gem("CFPropertyList")
+ end
+ end
+
+ let(:google_protobuf) { <<-G }
+ source "file:#{gem_repo2}"
+ gem "google-protobuf"
+ G
+
+ context "when on a darwin machine" do
+ before { simulate_platform "x86_64-darwin-15" }
+
+ it "locks to both the specific darwin platform and ruby" do
+ install_gemfile!(google_protobuf)
+ expect(the_bundle.locked_gems.platforms).to eq([pl("ruby"), pl("x86_64-darwin-15")])
+ expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin")
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[
+ google-protobuf-3.0.0.alpha.5.0.5.1
+ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin
+ ])
+ end
+
+ it "caches both the universal-darwin and ruby gems when --all-platforms is passed" do
+ gemfile(google_protobuf)
+ bundle! "package --all-platforms"
+ expect([cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1"), cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin")]).
+ to all(exist)
+ end
+
+ it "uses the platform-specific gem with extra dependencies" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo2}"
+ gem "facter"
+ G
+
+ expect(the_bundle.locked_gems.platforms).to eq([pl("ruby"), pl("x86_64-darwin-15")])
+ expect(the_bundle).to include_gems("facter 2.4.6 universal-darwin", "CFPropertyList 1.0")
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(["CFPropertyList-1.0",
+ "facter-2.4.6",
+ "facter-2.4.6-universal-darwin"])
+ end
+
+ context "when adding a platform via lock --add_platform" do
+ it "adds the foreign platform" do
+ install_gemfile!(google_protobuf)
+ bundle! "lock --add-platform=#{x64_mingw}"
+
+ expect(the_bundle.locked_gems.platforms).to eq([rb, x64_mingw, pl("x86_64-darwin-15")])
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[
+ google-protobuf-3.0.0.alpha.5.0.5.1
+ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin
+ google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw32
+ ])
+ end
+
+ it "falls back on plain ruby when that version doesnt have a platform-specific gem" do
+ install_gemfile!(google_protobuf)
+ bundle! "lock --add-platform=#{java}"
+
+ expect(the_bundle.locked_gems.platforms).to eq([java, rb, pl("x86_64-darwin-15")])
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[
+ google-protobuf-3.0.0.alpha.5.0.5.1
+ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin
+ ])
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb
new file mode 100644
index 0000000000..e74c5ffe59
--- /dev/null
+++ b/spec/bundler/install/gemfile_spec.rb
@@ -0,0 +1,145 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ context "with duplicated gems" do
+ it "will display a warning" do
+ install_gemfile <<-G
+ gem 'rails', '~> 4.0.0'
+ gem 'rails', '~> 4.0.0'
+ G
+ expect(out).to include("more than once")
+ end
+ end
+
+ context "with --gemfile" do
+ it "finds the gemfile" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ bundle :install, :gemfile => bundled_app("NotGemfile")
+
+ # Specify BUNDLE_GEMFILE for `the_bundle`
+ # to retrieve the proper Gemfile
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with gemfile set via config" do
+ before do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ bundle "config --local gemfile #{bundled_app("NotGemfile")}"
+ end
+ it "uses the gemfile to install" do
+ bundle "install"
+ bundle "list"
+
+ expect(out).to include("rack (1.0.0)")
+ end
+ it "uses the gemfile while in a subdirectory" do
+ bundled_app("subdir").mkpath
+ Dir.chdir(bundled_app("subdir")) do
+ bundle "install"
+ bundle "list"
+
+ expect(out).to include("rack (1.0.0)")
+ end
+ end
+ end
+
+ context "with deprecated features" do
+ before :each do
+ in_app_root
+ end
+
+ it "reports that lib is an invalid option" do
+ gemfile <<-G
+ gem "rack", :lib => "rack"
+ G
+
+ bundle :install
+ expect(out).to match(/You passed :lib as an option for gem 'rack', but it is invalid/)
+ end
+ end
+
+ context "with prefer_gems_rb set" do
+ before { bundle! "config prefer_gems_rb true" }
+
+ it "prefers gems.rb to Gemfile" do
+ create_file("gems.rb", "gem 'bundler'")
+ create_file("Gemfile", "raise 'wrong Gemfile!'")
+
+ bundle! :install
+
+ expect(bundled_app("gems.rb")).to be_file
+ expect(bundled_app("Gemfile.lock")).not_to be_file
+
+ expect(the_bundle).to include_gem "bundler #{Bundler::VERSION}"
+ end
+ end
+
+ context "with engine specified in symbol" do
+ it "does not raise any error parsing Gemfile" do
+ simulate_ruby_version "2.3.0" do
+ simulate_ruby_engine "jruby", "9.1.2.0" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0"
+ G
+
+ expect(out).to match(/Bundle complete!/)
+ end
+ end
+ end
+
+ it "installation succeeds" do
+ simulate_ruby_version "2.3.0" do
+ simulate_ruby_engine "jruby", "9.1.2.0" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+ end
+
+ context "with a Gemfile containing non-US-ASCII characters" do
+ it "reads the Gemfile with the UTF-8 encoding by default" do
+ skip "Ruby 1.8 has no encodings" if RUBY_VERSION < "1.9"
+
+ install_gemfile <<-G
+ str = "Il était une fois ..."
+ puts "The source encoding is: " + str.encoding.name
+ G
+
+ expect(out).to include("The source encoding is: UTF-8")
+ expect(out).not_to include("The source encoding is: ASCII-8BIT")
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "respects the magic encoding comment" do
+ skip "Ruby 1.8 has no encodings" if RUBY_VERSION < "1.9"
+
+ # NOTE: This works thanks to #eval interpreting the magic encoding comment
+ install_gemfile <<-G
+ # encoding: iso-8859-1
+ str = "Il #{"\xE9".dup.force_encoding("binary")}tait une fois ..."
+ puts "The source encoding is: " + str.encoding.name
+ G
+
+ expect(out).to include("The source encoding is: ISO-8859-1")
+ expect(out).to include("Bundle complete!")
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb
new file mode 100644
index 0000000000..02a37a77d5
--- /dev/null
+++ b/spec/bundler/install/gems/compact_index_spec.rb
@@ -0,0 +1,940 @@
+# frozen_string_literal: true
+
+RSpec.describe "compact index api" do
+ let(:source_hostname) { "localgemserver.test" }
+ let(:source_uri) { "http://#{source_hostname}" }
+
+ it "should use the API" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle! :install, :artifice => "compact_index"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should URI encode gem names" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem " sinatra"
+ G
+
+ bundle :install, :artifice => "compact_index"
+ expect(out).to include("' sinatra' is not a valid gem name because it contains whitespace.")
+ end
+
+ it "should handle nested dependencies" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rails"
+ G
+
+ bundle! :install, :artifice => "compact_index"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems(
+ "rails 2.3.2",
+ "actionpack 2.3.2",
+ "activerecord 2.3.2",
+ "actionmailer 2.3.2",
+ "activeresource 2.3.2",
+ "activesupport 2.3.2"
+ )
+ end
+
+ it "should handle case sensitivity conflicts" do
+ build_repo4 do
+ build_gem "rack", "1.0" do |s|
+ s.add_runtime_dependency("Rack", "0.1")
+ end
+ build_gem "Rack", "0.1"
+ end
+
+ install_gemfile! <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4 }
+ source "#{source_uri}"
+ gem "rack", "1.0"
+ gem "Rack", "0.1"
+ G
+
+ # can't use `include_gems` here since the `require` will conflict on a
+ # case-insensitive FS
+ run! "Bundler.require; puts Gem.loaded_specs.values_at('rack', 'Rack').map(&:full_name)"
+ expect(last_command.stdout).to eq("rack-1.0\nRack-0.1")
+ end
+
+ it "should handle multiple gem dependencies on the same gem" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "net-sftp"
+ G
+
+ bundle! :install, :artifice => "compact_index"
+ expect(the_bundle).to include_gems "net-sftp 1.1.1"
+ end
+
+ it "should use the endpoint when using --deployment" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+ bundle! :install, :artifice => "compact_index"
+
+ bundle! :install, forgotten_command_line_options(:deployment => true, :path => "vendor/bundle").merge(:artifice => "compact_index")
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles git dependencies that are in rubygems" do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ s.add_dependency "rails", "2.3.2"
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ git "file:///#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+
+ bundle! :install, :artifice => "compact_index"
+
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "handles git dependencies that are in rubygems using --deployment" do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ s.add_dependency "rails", "2.3.2"
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'foo', :git => "file:///#{lib_path("foo-1.0")}"
+ G
+
+ bundle! :install, :artifice => "compact_index"
+
+ bundle "install --deployment", :artifice => "compact_index"
+
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "doesn't fail if you only have a git gem with no deps when using --deployment" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'foo', :git => "file:///#{lib_path("foo-1.0")}"
+ G
+
+ bundle "install", :artifice => "compact_index"
+ bundle! :install, forgotten_command_line_options(:deployment => true).merge(:artifice => "compact_index")
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "falls back when the API errors out" do
+ simulate_platform mswin
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rcov"
+ G
+
+ bundle! :install, :artifice => "windows"
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rcov 1.0.0"
+ end
+
+ it "falls back when the API URL returns 403 Forbidden" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle! :install, :verbose => true, :artifice => "compact_index_forbidden"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "falls back when the versions endpoint has a checksum mismatch" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle! :install, :verbose => true, :artifice => "compact_index_checksum_mismatch"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(out).to include <<-'WARN'
+The checksum of /versions does not match the checksum provided by the server! Something is wrong (local checksum is "\"d41d8cd98f00b204e9800998ecf8427e\"", was expecting "\"123\"").
+ WARN
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "falls back when the user's home directory does not exist or is not writable" do
+ ENV["HOME"] = tmp("missing_home").to_s
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle! :install, :artifice => "compact_index"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles host redirects" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle! :install, :artifice => "compact_index_host_redirect"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles host redirects without Net::HTTP::Persistent" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ FileUtils.mkdir_p lib_path
+ File.open(lib_path("disable_net_http_persistent.rb"), "w") do |h|
+ h.write <<-H
+ module Kernel
+ alias require_without_disabled_net_http require
+ def require(*args)
+ raise LoadError, 'simulated' if args.first == 'openssl' && !caller.grep(/vendored_persistent/).empty?
+ require_without_disabled_net_http(*args)
+ end
+ end
+ H
+ end
+
+ bundle! :install, :artifice => "compact_index_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")]
+ expect(out).to_not match(/Too many redirects/)
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "times out when Bundler::Fetcher redirects too much" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "compact_index_redirects"
+ expect(out).to match(/Too many redirects/)
+ end
+
+ context "when --full-index is specified" do
+ it "should use the modern index for install" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --full-index", :artifice => "compact_index"
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should use the modern index for update" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle! "update --full-index", :artifice => "compact_index", :all => bundle_update_requires_all?
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ it "does not double check for gems that are only installed locally" do
+ system_gems %w[rack-1.0.0 thin-1.0 net_a-1.0]
+ bundle! "config --local path.system true"
+ ENV["BUNDLER_SPEC_ALL_REQUESTS"] = strip_whitespace(<<-EOS).strip
+ #{source_uri}/versions
+ #{source_uri}/info/rack
+ EOS
+
+ install_gemfile! <<-G, :artifice => "compact_index", :verbose => true
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ expect(last_command.stdboth).not_to include "Double checking"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 2" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ bundle! :install, :artifice => "compact_index_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources with source blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ install_gemfile! <<-G, :artifice => "compact_index_extra", :verbose => true
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0"
+ end
+
+ it "fetches gem versions even when those gems are already installed" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack", "1.0.0"
+ G
+ bundle! :install, :artifice => "compact_index_extra_api"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+
+ build_repo4 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}" do; end
+ source "#{source_uri}/extra"
+ gem "rack", "1.2"
+ G
+ bundle! :install, :artifice => "compact_index_extra_api"
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+
+ it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 2" do
+ # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that
+ # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
+ # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
+ # repo and installs it.
+ build_repo4 do
+ build_gem "activesupport", "1.2.0"
+ build_gem "somegem", "1.0.0" do |s|
+ s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem 'somegem', '1.0.0'
+ G
+
+ bundle! :install, :artifice => "compact_index_extra_api"
+
+ expect(the_bundle).to include_gems "somegem 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 1.2.3"
+ end
+
+ it "considers all possible versions of dependencies from all api gem sources when using blocks", :bundler => "< 2" do
+ # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that
+ # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
+ # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
+ # repo and installs it.
+ build_repo4 do
+ build_gem "activesupport", "1.2.0"
+ build_gem "somegem", "1.0.0" do |s|
+ s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem 'somegem', '1.0.0'
+ end
+ G
+
+ bundle! :install, :artifice => "compact_index_extra_api"
+
+ expect(the_bundle).to include_gems "somegem 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 1.2.3"
+ end
+
+ it "prints API output properly with back deps" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle! :install, :artifice => "compact_index_extra"
+
+ expect(out).to include("Fetching gem metadata from http://localgemserver.test/")
+ expect(out).to include("Fetching source index from http://localgemserver.test/extra")
+ end
+
+ it "does not fetch every spec if the index of gems is large when doing back deps" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ build_gem "missing"
+ # need to hit the limit
+ 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i|
+ build_gem "gem#{i}"
+ end
+
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle! :install, :artifice => "compact_index_extra_missing"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "does not fetch every spec if the index of gems is large when doing back deps & everything is the compact index" do
+ build_repo4 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ build_gem "missing"
+ # need to hit the limit
+ 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i|
+ build_gem "gem#{i}"
+ end
+
+ FileUtils.rm_rf Dir[gem_repo4("gems/foo-*.gem")]
+ end
+
+ install_gemfile! <<-G, :artifice => "compact_index_extra_api_missing"
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ expect(the_bundle).to include_gem "back_deps 1.0"
+ end
+
+ it "uses the endpoint if all sources support it" do
+ gemfile <<-G
+ source "#{source_uri}"
+
+ gem 'foo'
+ G
+
+ bundle! :install, :artifice => "compact_index_api_missing"
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using --deployment", :bundler => "< 2" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ bundle! :install, :artifice => "compact_index_extra"
+
+ bundle "install --deployment", :artifice => "compact_index_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using --deployment with blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle! :install, :artifice => "compact_index_extra"
+
+ bundle "install --deployment", :artifice => "compact_index_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "does not refetch if the only unmet dependency is bundler" do
+ gemfile <<-G
+ source "#{source_uri}"
+
+ gem "bundler_dep"
+ G
+
+ bundle! :install, :artifice => "compact_index"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ end
+
+ it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do
+ sudo "mkdir -p #{system_gem_path("bin")}"
+ sudo "chown -R root #{system_gem_path("bin")}"
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rails"
+ G
+ bundle! :install, :artifice => "compact_index"
+ expect(the_bundle).to include_gems "rails 2.3.2"
+ end
+
+ it "installs the binstubs", :bundler => "< 2" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --binstubs", :artifice => "compact_index"
+
+ gembin "rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "installs the bins when using --path and uses autoclean", :bundler => "< 2" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --path vendor/bundle", :artifice => "compact_index"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "installs the bins when using --path and uses bundle clean", :bundler => "< 2" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --path vendor/bundle --no-clean", :artifice => "compact_index"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "prints post_install_messages" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack-obama'
+ G
+
+ bundle! :install, :artifice => "compact_index"
+ expect(out).to include("Post-install message from rack:")
+ end
+
+ it "should display the post install message for a dependency" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack_middleware'
+ G
+
+ bundle! :install, :artifice => "compact_index"
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ end
+
+ context "when using basic authentication" do
+ let(:user) { "user" }
+ let(:password) { "pass" }
+ let(:basic_auth_source_uri) do
+ uri = URI.parse(source_uri)
+ uri.user = user
+ uri.password = password
+
+ uri
+ end
+
+ it "passes basic authentication details and strips out creds" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle! :install, :artifice => "compact_index_basic_authentication"
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "strips http basic authentication creds for modern index" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle! :install, :artifice => "endopint_marshal_fail_basic_authentication"
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "strips http basic auth creds when it can't reach the server" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_500"
+ expect(out).not_to include("#{user}:#{password}")
+ end
+
+ it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 2" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle! :install, :artifice => "compact_index_basic_authentication"
+ expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not pass the user / password to different hosts on redirect" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle! :install, :artifice => "compact_index_creds_diff_host"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ describe "with authentication details in bundle config" do
+ before do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+
+ it "reads authentication details by host name from bundle config" do
+ bundle "config #{source_hostname} #{user}:#{password}"
+
+ bundle! :install, :artifice => "compact_index_strict_basic_authentication"
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "reads authentication details by full url from bundle config" do
+ # The trailing slash is necessary here; Fetcher canonicalizes the URI.
+ bundle "config #{source_uri}/ #{user}:#{password}"
+
+ bundle! :install, :artifice => "compact_index_strict_basic_authentication"
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should use the API" do
+ bundle "config #{source_hostname} #{user}:#{password}"
+ bundle! :install, :artifice => "compact_index_strict_basic_authentication"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "prefers auth supplied in the source uri" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle "config #{source_hostname} otheruser:wrong"
+
+ bundle! :install, :artifice => "compact_index_strict_basic_authentication"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "shows instructions if auth is not provided for the source" do
+ bundle :install, :artifice => "compact_index_strict_basic_authentication"
+ expect(out).to include("bundle config #{source_hostname} username:password")
+ end
+
+ it "fails if authentication has already been provided, but failed" do
+ bundle "config #{source_hostname} #{user}:wrong"
+
+ bundle :install, :artifice => "compact_index_strict_basic_authentication"
+ expect(out).to include("Bad username or password")
+ end
+ end
+
+ describe "with no password" do
+ let(:password) { nil }
+
+ it "passes basic authentication details" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle! :install, :artifice => "compact_index_basic_authentication"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+
+ context "when ruby is compiled without openssl" do
+ before do
+ # Install a monkeypatch that reproduces the effects of openssl being
+ # missing when the fetcher runs, as happens in real life. The reason
+ # we can't just overwrite openssl.rb is that Artifice uses it.
+ bundled_app("broken_ssl").mkpath
+ bundled_app("broken_ssl/openssl.rb").open("w") do |f|
+ f.write <<-RUBY
+ raise LoadError, "cannot load such file -- openssl"
+ RUBY
+ end
+ end
+
+ it "explains what to do to get it" do
+ gemfile <<-G
+ source "#{source_uri.gsub(/http/, "https")}"
+ gem "rack"
+ G
+
+ bundle :install, :env => { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }
+ expect(out).to include("OpenSSL")
+ end
+ end
+
+ context "when SSL certificate verification fails" do
+ it "explains what happened" do
+ # Install a monkeypatch that reproduces the effects of openssl raising
+ # a certificate validation error when RubyGems tries to connect.
+ gemfile <<-G
+ class Net::HTTP
+ def start
+ raise OpenSSL::SSL::SSLError, "certificate verify failed"
+ end
+ end
+
+ source "#{source_uri.gsub(/http/, "https")}"
+ gem "rack"
+ G
+
+ bundle :install
+ expect(out).to match(/could not verify the SSL certificate/i)
+ end
+ end
+
+ context ".gemrc with sources is present" do
+ before do
+ File.open(home(".gemrc"), "w") do |file|
+ file.puts({ :sources => ["https://rubygems.org"] }.to_yaml)
+ end
+ end
+
+ after do
+ home(".gemrc").rmtree
+ end
+
+ it "uses other sources declared in the Gemfile" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack'
+ G
+
+ bundle! :install, :artifice => "compact_index_forbidden"
+ end
+ end
+
+ it "performs partial update with a non-empty range" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack', '0.9.1'
+ G
+
+ # Initial install creates the cached versions file
+ bundle! :install, :artifice => "compact_index"
+
+ # Update the Gemfile so we can check subsequent install was successful
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack', '1.0.0'
+ G
+
+ # Second install should make only a partial request to /versions
+ bundle! :install, :artifice => "compact_index_partial_update"
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "performs partial update while local cache is updated by another process" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack'
+ G
+
+ # Create an empty file to trigger a partial download
+ versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions")
+ FileUtils.mkdir_p(File.dirname(versions))
+ FileUtils.touch(versions)
+
+ bundle! :install, :artifice => "compact_index_concurrent_download"
+
+ expect(File.read(versions)).to start_with("created_at")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "performs full update of compact index info cache if range is not satisfiable" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack', '0.9.1'
+ G
+
+ rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack")
+
+ bundle! :install, :artifice => "compact_index"
+
+ expected_rack_info_content = File.read(rake_info_path)
+
+ # Modify the cache files. We expect them to be reset to the normal ones when we re-run :install
+ File.open(rake_info_path, "w") {|f| f << (expected_rack_info_content + "this is different") }
+
+ # Update the Gemfile so the next install does its normal things
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack', '1.0.0'
+ G
+
+ # The cache files now being longer means the requested range is going to be not satisfiable
+ # Bundler must end up requesting the whole file to fix things up.
+ bundle! :install, :artifice => "compact_index_range_not_satisfiable"
+
+ resulting_rack_info_content = File.read(rake_info_path)
+
+ expect(resulting_rack_info_content).to eq(expected_rack_info_content)
+ end
+
+ it "fails gracefully when the source URI has an invalid scheme" do
+ install_gemfile <<-G
+ source "htps://rubygems.org"
+ gem "rack"
+ G
+ expect(exitstatus).to eq(15) if exitstatus
+ expect(out).to end_with(<<-E.strip)
+ The request uri `htps://index.rubygems.org/versions` has an invalid scheme (`htps`). Did you mean `http` or `https`?
+ E
+ end
+
+ describe "checksum validation", :rubygems => ">= 2.3.0" do
+ it "raises when the checksum does not match" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum"
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ expect(exitstatus).to eq(19) if exitstatus
+ expect(out).
+ to include("Bundler cannot continue installing rack (1.0.0).").
+ and include("The checksum for the downloaded `rack-1.0.0.gem` does not match the checksum given by the server.").
+ and include("This means the contents of the downloaded gem is different from what was uploaded to the server, and could be a potential security issue.").
+ and include("To resolve this issue:").
+ and include("1. delete the downloaded gem located at: `#{default_bundle_path}/gems/rack-1.0.0/rack-1.0.0.gem`").
+ and include("2. run `bundle install`").
+ and include("If you wish to continue installing the downloaded gem, and are certain it does not pose a security issue despite the mismatching checksum, do the following:").
+ and include("1. run `bundle config disable_checksum_validation true` to turn off checksum verification").
+ and include("2. run `bundle install`").
+ and match(/\(More info: The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/)
+ end
+
+ it "raises when the checksum is the wrong length" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!" }
+ source "#{source_uri}"
+ gem "rack"
+ G
+ expect(exitstatus).to eq(5) if exitstatus
+ expect(out).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest")
+ end
+
+ it "does not raise when disable_checksum_validation is set" do
+ bundle! "config disable_checksum_validation true"
+ install_gemfile! <<-G, :artifice => "compact_index_wrong_gem_checksum"
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+ end
+
+ it "works when cache dir is world-writable" do
+ install_gemfile! <<-G, :artifice => "compact_index"
+ File.umask(0000)
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+
+ it "doesn't explode when the API dependencies are wrong" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_dependencies", :env => { "DEBUG" => "true" }
+ source "#{source_uri}"
+ gem "rails"
+ G
+ deps = [Gem::Dependency.new("rake", "= 10.0.2"),
+ Gem::Dependency.new("actionpack", "= 2.3.2"),
+ Gem::Dependency.new("activerecord", "= 2.3.2"),
+ Gem::Dependency.new("actionmailer", "= 2.3.2"),
+ Gem::Dependency.new("activeresource", "= 2.3.2")]
+ expect(out).to include(<<-E.strip).and include("rails-2.3.2 from rubygems remote at #{source_uri}/ has either corrupted API or lockfile dependencies")
+Bundler::APIResponseMismatchError: Downloading rails-2.3.2 revealed dependencies not in the API or the lockfile (#{deps.map(&:to_s).join(", ")}).
+Either installing with `--full-index` or running `bundle update rails` should fix the problem.
+ E
+ end
+
+ it "does not duplicate specs in the lockfile when updating and a dependency is not installed" do
+ install_gemfile! <<-G, :artifice => "compact_index"
+ source "#{source_uri}" do
+ gem "rails"
+ gem "activemerchant"
+ end
+ G
+ gem_command! :uninstall, "activemerchant"
+ bundle! "update rails", :artifice => "compact_index"
+ expect(lockfile.scan(/activemerchant \(/).size).to eq(1)
+ end
+end
diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb
new file mode 100644
index 0000000000..2ffe4b62d7
--- /dev/null
+++ b/spec/bundler/install/gems/dependency_api_spec.rb
@@ -0,0 +1,760 @@
+# frozen_string_literal: true
+
+RSpec.describe "gemcutter's dependency API" do
+ let(:source_hostname) { "localgemserver.test" }
+ let(:source_uri) { "http://#{source_hostname}" }
+
+ it "should use the API" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should URI encode gem names" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem " sinatra"
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("' sinatra' is not a valid gem name because it contains whitespace.")
+ end
+
+ it "should handle nested dependencies" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rails"
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Fetching gem metadata from #{source_uri}/...")
+ expect(the_bundle).to include_gems(
+ "rails 2.3.2",
+ "actionpack 2.3.2",
+ "activerecord 2.3.2",
+ "actionmailer 2.3.2",
+ "activeresource 2.3.2",
+ "activesupport 2.3.2"
+ )
+ end
+
+ it "should handle multiple gem dependencies on the same gem" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "net-sftp"
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(the_bundle).to include_gems "net-sftp 1.1.1"
+ end
+
+ it "should use the endpoint when using --deployment" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+ bundle :install, :artifice => "endpoint"
+
+ bundle! :install, forgotten_command_line_options(:deployment => true, :path => "vendor/bundle").merge(:artifice => "endpoint")
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles git dependencies that are in rubygems" do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ s.add_dependency "rails", "2.3.2"
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ git "file:///#{lib_path("foo-1.0")}" do
+ gem 'foo'
+ end
+ G
+
+ bundle :install, :artifice => "endpoint"
+
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "handles git dependencies that are in rubygems using --deployment" do
+ build_git "foo" do |s|
+ s.executables = "foobar"
+ s.add_dependency "rails", "2.3.2"
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'foo', :git => "file:///#{lib_path("foo-1.0")}"
+ G
+
+ bundle :install, :artifice => "endpoint"
+
+ bundle "install --deployment", :artifice => "endpoint"
+
+ expect(the_bundle).to include_gems("rails 2.3.2")
+ end
+
+ it "doesn't fail if you only have a git gem with no deps when using --deployment" do
+ build_git "foo"
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'foo', :git => "file:///#{lib_path("foo-1.0")}"
+ G
+
+ bundle "install", :artifice => "endpoint"
+ bundle! :install, forgotten_command_line_options(:deployment => true).merge(:artifice => "endpoint")
+
+ expect(the_bundle).to include_gems("foo 1.0")
+ end
+
+ it "falls back when the API errors out" do
+ simulate_platform mswin
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rcov"
+ G
+
+ bundle :install, :artifice => "windows"
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rcov 1.0.0"
+ end
+
+ it "falls back when hitting the Gemcutter Dependency Limit" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "activesupport"
+ gem "actionpack"
+ gem "actionmailer"
+ gem "activeresource"
+ gem "thin"
+ gem "rack"
+ gem "rails"
+ G
+ bundle :install, :artifice => "endpoint_fallback"
+ expect(out).to include("Fetching source index from #{source_uri}")
+
+ expect(the_bundle).to include_gems(
+ "activesupport 2.3.2",
+ "actionpack 2.3.2",
+ "actionmailer 2.3.2",
+ "activeresource 2.3.2",
+ "activesupport 2.3.2",
+ "thin 1.0.0",
+ "rack 1.0.0",
+ "rails 2.3.2"
+ )
+ end
+
+ it "falls back when Gemcutter API doesn't return proper Marshal format" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :verbose => true, :artifice => "endpoint_marshal_fail"
+ expect(out).to include("could not fetch from the dependency API, trying the full index")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "falls back when the API URL returns 403 Forbidden" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :verbose => true, :artifice => "endpoint_api_forbidden"
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles host redirects" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_host_redirect"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles host redirects without Net::HTTP::Persistent" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ FileUtils.mkdir_p lib_path
+ File.open(lib_path("disable_net_http_persistent.rb"), "w") do |h|
+ h.write <<-H
+ module Kernel
+ alias require_without_disabled_net_http require
+ def require(*args)
+ raise LoadError, 'simulated' if args.first == 'openssl' && !caller.grep(/vendored_persistent/).empty?
+ require_without_disabled_net_http(*args)
+ end
+ end
+ H
+ end
+
+ bundle :install, :artifice => "endpoint_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")]
+ expect(out).to_not match(/Too many redirects/)
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "timeouts when Bundler::Fetcher redirects too much" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_redirect"
+ expect(out).to match(/Too many redirects/)
+ end
+
+ context "when --full-index is specified" do
+ it "should use the modern index for install" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --full-index", :artifice => "endpoint"
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should use the modern index for update" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle! "update --full-index", :artifice => "endpoint", :all => bundle_update_requires_all?
+ expect(out).to include("Fetching source index from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 2" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0"
+ end
+
+ it "fetches gem versions even when those gems are already installed" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack", "1.0.0"
+ G
+ bundle :install, :artifice => "endpoint_extra_api"
+
+ build_repo4 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}" do; end
+ source "#{source_uri}/extra"
+ gem "rack", "1.2"
+ G
+ bundle :install, :artifice => "endpoint_extra_api"
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+
+ it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 2" do
+ # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that
+ # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
+ # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
+ # repo and installs it.
+ build_repo4 do
+ build_gem "activesupport", "1.2.0"
+ build_gem "somegem", "1.0.0" do |s|
+ s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem 'somegem', '1.0.0'
+ G
+
+ bundle! :install, :artifice => "endpoint_extra_api"
+
+ expect(the_bundle).to include_gems "somegem 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 1.2.3"
+ end
+
+ it "considers all possible versions of dependencies from all api gem sources using blocks" do
+ # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that
+ # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
+ # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
+ # repo and installs it.
+ build_repo4 do
+ build_gem "activesupport", "1.2.0"
+ build_gem "somegem", "1.0.0" do |s|
+ s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1
+ end
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem 'somegem', '1.0.0'
+ end
+ G
+
+ bundle :install, :artifice => "endpoint_extra_api"
+
+ expect(the_bundle).to include_gems "somegem 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 1.2.3"
+ end
+
+ it "prints API output properly with back deps" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+
+ expect(out).to include("Fetching gem metadata from http://localgemserver.test/.")
+ expect(out).to include("Fetching source index from http://localgemserver.test/extra")
+ end
+
+ it "does not fetch every spec if the index of gems is large when doing back deps", :bundler => "< 2" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ build_gem "missing"
+ # need to hit the limit
+ 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i|
+ build_gem "gem#{i}"
+ end
+
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ bundle :install, :artifice => "endpoint_extra_missing"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "does not fetch every spec if the index of gems is large when doing back deps using blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ build_gem "missing"
+ # need to hit the limit
+ 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i|
+ build_gem "gem#{i}"
+ end
+
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle :install, :artifice => "endpoint_extra_missing"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "uses the endpoint if all sources support it" do
+ gemfile <<-G
+ source "#{source_uri}"
+
+ gem 'foo'
+ G
+
+ bundle :install, :artifice => "endpoint_api_missing"
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using --deployment", :bundler => "< 2" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra"
+ gem "back_deps"
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+
+ bundle "install --deployment", :artifice => "endpoint_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "fetches again when more dependencies are found in subsequent sources using --deployment with blocks" do
+ build_repo2 do
+ build_gem "back_deps" do |s|
+ s.add_dependency "foo"
+ end
+ FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")]
+ end
+
+ gemfile <<-G
+ source "#{source_uri}"
+ source "#{source_uri}/extra" do
+ gem "back_deps"
+ end
+ G
+
+ bundle :install, :artifice => "endpoint_extra"
+
+ bundle "install --deployment", :artifice => "endpoint_extra"
+ expect(the_bundle).to include_gems "back_deps 1.0"
+ end
+
+ it "does not refetch if the only unmet dependency is bundler" do
+ gemfile <<-G
+ source "#{source_uri}"
+
+ gem "bundler_dep"
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ end
+
+ it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do
+ sudo "mkdir -p #{system_gem_path("bin")}"
+ sudo "chown -R root #{system_gem_path("bin")}"
+
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rails"
+ G
+ bundle :install, :artifice => "endpoint"
+ expect(the_bundle).to include_gems "rails 2.3.2"
+ end
+
+ it "installs the binstubs", :bundler => "< 2" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --binstubs", :artifice => "endpoint"
+
+ gembin "rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "installs the bins when using --path and uses autoclean", :bundler => "< 2" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --path vendor/bundle", :artifice => "endpoint"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "installs the bins when using --path and uses bundle clean", :bundler => "< 2" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+
+ bundle "install --path vendor/bundle --no-clean", :artifice => "endpoint"
+
+ expect(vendored_gems("bin/rackup")).to exist
+ end
+
+ it "prints post_install_messages" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack-obama'
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Post-install message from rack:")
+ end
+
+ it "should display the post install message for a dependency" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack_middleware'
+ G
+
+ bundle :install, :artifice => "endpoint"
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ end
+
+ context "when using basic authentication" do
+ let(:user) { "user" }
+ let(:password) { "pass" }
+ let(:basic_auth_source_uri) do
+ uri = URI.parse(source_uri)
+ uri.user = user
+ uri.password = password
+
+ uri
+ end
+
+ it "passes basic authentication details and strips out creds" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_basic_authentication"
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "strips http basic authentication creds for modern index" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endopint_marshal_fail_basic_authentication"
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "strips http basic auth creds when it can't reach the server" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_500"
+ expect(out).not_to include("#{user}:#{password}")
+ end
+
+ it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 2" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_basic_authentication"
+ expect(out).to include("Warning: the gem 'rack' was found in multiple sources.")
+ expect(out).not_to include("#{user}:#{password}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not pass the user / password to different hosts on redirect" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_creds_diff_host"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ describe "with authentication details in bundle config" do
+ before do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+
+ it "reads authentication details by host name from bundle config" do
+ bundle "config #{source_hostname} #{user}:#{password}"
+
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "reads authentication details by full url from bundle config" do
+ # The trailing slash is necessary here; Fetcher canonicalizes the URI.
+ bundle "config #{source_uri}/ #{user}:#{password}"
+
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "should use the API" do
+ bundle "config #{source_hostname} #{user}:#{password}"
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ expect(out).to include("Fetching gem metadata from #{source_uri}")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "prefers auth supplied in the source uri" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle "config #{source_hostname} otheruser:wrong"
+
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "shows instructions if auth is not provided for the source" do
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ expect(out).to include("bundle config #{source_hostname} username:password")
+ end
+
+ it "fails if authentication has already been provided, but failed" do
+ bundle "config #{source_hostname} #{user}:wrong"
+
+ bundle :install, :artifice => "endpoint_strict_basic_authentication"
+ expect(out).to include("Bad username or password")
+ end
+ end
+
+ describe "with no password" do
+ let(:password) { nil }
+
+ it "passes basic authentication details" do
+ gemfile <<-G
+ source "#{basic_auth_source_uri}"
+ gem "rack"
+ G
+
+ bundle :install, :artifice => "endpoint_basic_authentication"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+
+ context "when ruby is compiled without openssl" do
+ before do
+ # Install a monkeypatch that reproduces the effects of openssl being
+ # missing when the fetcher runs, as happens in real life. The reason
+ # we can't just overwrite openssl.rb is that Artifice uses it.
+ bundled_app("broken_ssl").mkpath
+ bundled_app("broken_ssl/openssl.rb").open("w") do |f|
+ f.write <<-RUBY
+ raise LoadError, "cannot load such file -- openssl"
+ RUBY
+ end
+ end
+
+ it "explains what to do to get it" do
+ gemfile <<-G
+ source "#{source_uri.gsub(/http/, "https")}"
+ gem "rack"
+ G
+
+ bundle :install, :env => { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }
+ expect(out).to include("OpenSSL")
+ end
+ end
+
+ context "when SSL certificate verification fails" do
+ it "explains what happened" do
+ # Install a monkeypatch that reproduces the effects of openssl raising
+ # a certificate validation error when RubyGems tries to connect.
+ gemfile <<-G
+ class Net::HTTP
+ def start
+ raise OpenSSL::SSL::SSLError, "certificate verify failed"
+ end
+ end
+
+ source "#{source_uri.gsub(/http/, "https")}"
+ gem "rack"
+ G
+
+ bundle :install
+ expect(out).to match(/could not verify the SSL certificate/i)
+ end
+ end
+
+ context ".gemrc with sources is present" do
+ before do
+ File.open(home(".gemrc"), "w") do |file|
+ file.puts({ :sources => ["https://rubygems.org"] }.to_yaml)
+ end
+ end
+
+ after do
+ home(".gemrc").rmtree
+ end
+
+ it "uses other sources declared in the Gemfile" do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem 'rack'
+ G
+
+ bundle "install", :artifice => "endpoint_marshal_fail"
+
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/env_spec.rb b/spec/bundler/install/gems/env_spec.rb
new file mode 100644
index 0000000000..0dccbbfd24
--- /dev/null
+++ b/spec/bundler/install/gems/env_spec.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with ENV conditionals" do
+ describe "when just setting an ENV key as a string" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ env "BUNDLER_TEST" do
+ gem "rack"
+ end
+ G
+ end
+
+ it "excludes the gems when the ENV variable is not set" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "includes the gems when the ENV variable is set" do
+ ENV["BUNDLER_TEST"] = "1"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when just setting an ENV key as a symbol" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ env :BUNDLER_TEST do
+ gem "rack"
+ end
+ G
+ end
+
+ it "excludes the gems when the ENV variable is not set" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "includes the gems when the ENV variable is set" do
+ ENV["BUNDLER_TEST"] = "1"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when setting a string to match the env" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ env "BUNDLER_TEST" => "foo" do
+ gem "rack"
+ end
+ G
+ end
+
+ it "excludes the gems when the ENV variable is not set" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "excludes the gems when the ENV variable is set but does not match the condition" do
+ ENV["BUNDLER_TEST"] = "1"
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "includes the gems when the ENV variable is set and matches the condition" do
+ ENV["BUNDLER_TEST"] = "foo"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when setting a regex to match the env" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ env "BUNDLER_TEST" => /foo/ do
+ gem "rack"
+ end
+ G
+ end
+
+ it "excludes the gems when the ENV variable is not set" do
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "excludes the gems when the ENV variable is set but does not match the condition" do
+ ENV["BUNDLER_TEST"] = "fo"
+ bundle :install
+ expect(the_bundle).not_to include_gems "rack"
+ end
+
+ it "includes the gems when the ENV variable is set and matches the condition" do
+ ENV["BUNDLER_TEST"] = "foobar"
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb
new file mode 100644
index 0000000000..37d2e4958a
--- /dev/null
+++ b/spec/bundler/install/gems/flex_spec.rb
@@ -0,0 +1,351 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle flex_install" do
+ it "installs the gems as expected" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to be_locked
+ end
+
+ it "installs even when the lockfile is invalid" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to be_locked
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack', '1.0'
+ G
+
+ bundle :install
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to be_locked
+ end
+
+ it "keeps child dependencies at the same version" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack-obama"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0"
+
+ update_repo2
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack-obama", "1.0"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0"
+ end
+
+ describe "adding new gems" do
+ it "installs added gems without updating previously installed gems" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'rack'
+ G
+
+ update_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'rack'
+ gem 'activesupport', '2.3.5'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5"
+ end
+
+ it "keeps child dependencies pinned" do
+ build_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack-obama"
+ G
+
+ update_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack-obama"
+ gem "thin"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0", "thin 1.0"
+ end
+ end
+
+ describe "removing gems" do
+ it "removes gems without changing the versions of remaining gems" do
+ build_repo2
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'rack'
+ gem 'activesupport', '2.3.5'
+ G
+
+ update_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'rack'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'rack'
+ gem 'activesupport', '2.3.2'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2"
+ end
+
+ it "removes top level dependencies when removed from the Gemfile while leaving other dependencies intact" do
+ build_repo2
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'rack'
+ gem 'activesupport', '2.3.5'
+ G
+
+ update_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'rack'
+ G
+
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ end
+
+ it "removes child dependencies" do
+ build_repo2
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'rack-obama'
+ gem 'activesupport'
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0", "activesupport 2.3.5"
+
+ update_repo2
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'activesupport'
+ G
+
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ expect(the_bundle).not_to include_gems "rack-obama", "rack"
+ end
+ end
+
+ describe "when Gemfile conflicts with lockfile" do
+ before(:each) do
+ build_repo2
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack_middleware"
+ G
+
+ expect(the_bundle).to include_gems "rack_middleware 1.0", "rack 0.9.1"
+
+ build_repo2
+ update_repo2 do
+ build_gem "rack-obama", "2.0" do |s|
+ s.add_dependency "rack", "=1.2"
+ end
+ build_gem "rack_middleware", "2.0" do |s|
+ s.add_dependency "rack", ">=1.0"
+ end
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rack-obama", "2.0"
+ gem "rack_middleware"
+ G
+ end
+
+ it "does not install gems whose dependencies are not met" do
+ bundle :install
+ ruby <<-RUBY
+ require 'bundler/setup'
+ RUBY
+ expect(err).to match(/could not find gem 'rack-obama/i)
+ end
+
+ it "suggests bundle update when the Gemfile requires different versions than the lock" do
+ nice_error = <<-E.strip.gsub(/^ {8}/, "")
+ Bundler could not find compatible versions for gem "rack":
+ In snapshot (Gemfile.lock):
+ rack (= 0.9.1)
+
+ In Gemfile:
+ rack-obama (= 2.0) was resolved to 2.0, which depends on
+ rack (= 1.2)
+
+ rack_middleware was resolved to 1.0, which depends on
+ rack (= 0.9.1)
+
+ Running `bundle update` will rebuild your snapshot from scratch, using only
+ the gems in your Gemfile, which may resolve the conflict.
+ E
+
+ bundle :install, :retry => 0
+ expect(last_command.bundler_err).to end_with(nice_error)
+ end
+ end
+
+ describe "subtler cases" do
+ before :each do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rack-obama"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "0.9.1"
+ gem "rack-obama"
+ G
+ end
+
+ it "does something" do
+ expect do
+ bundle "install"
+ end.not_to change { File.read(bundled_app("Gemfile.lock")) }
+
+ expect(out).to include("rack = 0.9.1")
+ expect(out).to include("locked at 1.0.0")
+ expect(out).to include("bundle update rack")
+ end
+
+ it "should work when you update" do
+ bundle "update rack"
+ end
+ end
+
+ describe "when adding a new source" do
+ it "updates the lockfile", :bundler => "< 2" do
+ build_repo2
+ install_gemfile! <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ G
+ install_gemfile! <<-G
+ source "file://localhost#{gem_repo1}"
+ source "file://localhost#{gem_repo2}"
+ gem "rack"
+ G
+
+ lockfile_should_be <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "updates the lockfile", :bundler => "2" do
+ build_repo2
+ install_gemfile! <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ G
+
+ install_gemfile! <<-G
+ source "file://localhost#{gem_repo1}"
+ source "file://localhost#{gem_repo2}" do
+ end
+ gem "rack"
+ G
+
+ lockfile_should_be <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+ end
+
+ # This was written to test github issue #636
+ describe "when a locked child dependency conflicts" do
+ before(:each) do
+ build_repo2 do
+ build_gem "capybara", "0.3.9" do |s|
+ s.add_dependency "rack", ">= 1.0.0"
+ end
+
+ build_gem "rack", "1.1.0"
+ build_gem "rails", "3.0.0.rc4" do |s|
+ s.add_dependency "rack", "~> 1.1.0"
+ end
+
+ build_gem "rack", "1.2.1"
+ build_gem "rails", "3.0.0" do |s|
+ s.add_dependency "rack", "~> 1.2.1"
+ end
+ end
+ end
+
+ it "prints the correct error message" do
+ # install Rails 3.0.0.rc
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rails", "3.0.0.rc4"
+ gem "capybara", "0.3.9"
+ G
+
+ # upgrade Rails to 3.0.0 and then install again
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "rails", "3.0.0"
+ gem "capybara", "0.3.9"
+ G
+
+ expect(out).to include("Gemfile.lock")
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/mirror_spec.rb b/spec/bundler/install/gems/mirror_spec.rb
new file mode 100644
index 0000000000..4c35b8f206
--- /dev/null
+++ b/spec/bundler/install/gems/mirror_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with a mirror configured" do
+ describe "when the mirror does not match the gem source" do
+ before :each do
+ gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+ bundle "config --local mirror.http://gems.example.org http://gem-mirror.example.org"
+ end
+
+ it "installs from the normal location" do
+ bundle :install
+ expect(out).to include(normalize_uri_file("Fetching source index from file://localhost#{gem_repo1}"))
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ describe "when the gem source matches a configured mirror" do
+ before :each do
+ gemfile <<-G
+ # This source is bogus and doesn't have the gem we're looking for
+ source "file://localhost#{gem_repo2}"
+
+ gem "rack"
+ G
+ bundle "config --local mirror.file://localhost#{gem_repo2} file://localhost#{gem_repo1}"
+ end
+
+ it "installs the gem from the mirror" do
+ bundle :install
+ expect(out).to include(normalize_uri_file("Fetching source index from file://localhost#{gem_repo1}"))
+ expect(out).not_to include(normalize_uri_file("Fetching source index from file://localhost#{gem_repo2}"))
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb
new file mode 100644
index 0000000000..ea616f60d3
--- /dev/null
+++ b/spec/bundler/install/gems/native_extensions_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+RSpec.describe "installing a gem with native extensions", :ruby_repo do
+ it "installs" do
+ build_repo2 do
+ build_gem "c_extension" do |s|
+ s.extensions = ["ext/extconf.rb"]
+ s.write "ext/extconf.rb", <<-E
+ require "mkmf"
+ name = "c_extension_bundle"
+ dir_config(name)
+ raise "OMG" unless with_config("c_extension") == "hello"
+ create_makefile(name)
+ E
+
+ s.write "ext/c_extension.c", <<-C
+ #include "ruby.h"
+
+ VALUE c_extension_true(VALUE self) {
+ return Qtrue;
+ }
+
+ void Init_c_extension_bundle() {
+ VALUE c_Extension = rb_define_class("CExtension", rb_cObject);
+ rb_define_method(c_Extension, "its_true", c_extension_true, 0);
+ }
+ C
+
+ s.write "lib/c_extension.rb", <<-C
+ require "c_extension_bundle"
+ C
+ end
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "c_extension"
+ G
+
+ bundle "config build.c_extension --with-c_extension=hello"
+ bundle "install"
+
+ expect(out).not_to include("extconf.rb failed")
+ expect(out).to include("Installing c_extension 1.0 with native extensions")
+
+ run "Bundler.require; puts CExtension.new.its_true"
+ expect(out).to eq("true")
+ end
+
+ it "installs from git" do
+ build_git "c_extension" do |s|
+ s.extensions = ["ext/extconf.rb"]
+ s.write "ext/extconf.rb", <<-E
+ require "mkmf"
+ name = "c_extension_bundle"
+ dir_config(name)
+ raise "OMG" unless with_config("c_extension") == "hello"
+ create_makefile(name)
+ E
+
+ s.write "ext/c_extension.c", <<-C
+ #include "ruby.h"
+
+ VALUE c_extension_true(VALUE self) {
+ return Qtrue;
+ }
+
+ void Init_c_extension_bundle() {
+ VALUE c_Extension = rb_define_class("CExtension", rb_cObject);
+ rb_define_method(c_Extension, "its_true", c_extension_true, 0);
+ }
+ C
+
+ s.write "lib/c_extension.rb", <<-C
+ require "c_extension_bundle"
+ C
+ end
+
+ bundle! "config build.c_extension --with-c_extension=hello"
+
+ install_gemfile! <<-G
+ gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump}
+ G
+
+ expect(out).not_to include("extconf.rb failed")
+
+ run! "Bundler.require; puts CExtension.new.its_true"
+ expect(out).to eq("true")
+ end
+end
diff --git a/spec/bundler/install/gems/post_install_spec.rb b/spec/bundler/install/gems/post_install_spec.rb
new file mode 100644
index 0000000000..c6e348fb65
--- /dev/null
+++ b/spec/bundler/install/gems/post_install_spec.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ context "with gem sources" do
+ context "when gems include post install messages" do
+ it "should display the post-install messages after installing" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ gem 'thin'
+ gem 'rack-obama'
+ G
+
+ bundle :install
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ expect(out).to include("Post-install message from thin:")
+ expect(out).to include("Thin's post install message")
+ expect(out).to include("Post-install message from rack-obama:")
+ expect(out).to include("Rack-obama's post install message")
+ end
+ end
+
+ context "when gems do not include post install messages" do
+ it "should not display any post-install messages" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+ G
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+
+ context "when a dependecy includes a post install message" do
+ it "should display the post install message" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack_middleware'
+ G
+
+ bundle :install
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ end
+ end
+ end
+
+ context "with git sources" do
+ context "when gems include post install messages" do
+ it "should display the post-install messages after installing" do
+ build_git "foo" do |s|
+ s.post_install_message = "Foo's post install message"
+ end
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'foo', :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :install
+ expect(out).to include("Post-install message from foo:")
+ expect(out).to include("Foo's post install message")
+ end
+
+ it "should display the post-install messages if repo is updated" do
+ build_git "foo" do |s|
+ s.post_install_message = "Foo's post install message"
+ end
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'foo', :git => '#{lib_path("foo-1.0")}'
+ G
+ bundle :install
+
+ build_git "foo", "1.1" do |s|
+ s.post_install_message = "Foo's 1.1 post install message"
+ end
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'foo', :git => '#{lib_path("foo-1.1")}'
+ G
+ bundle :install
+
+ expect(out).to include("Post-install message from foo:")
+ expect(out).to include("Foo's 1.1 post install message")
+ end
+
+ it "should not display the post-install messages if repo is not updated" do
+ build_git "foo" do |s|
+ s.post_install_message = "Foo's post install message"
+ end
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'foo', :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :install
+ expect(out).to include("Post-install message from foo:")
+ expect(out).to include("Foo's post install message")
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+
+ context "when gems do not include post install messages" do
+ it "should not display any post-install messages" do
+ build_git "foo" do |s|
+ s.post_install_message = nil
+ end
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'foo', :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+ end
+
+ context "when ignore post-install messages for gem is set" do
+ it "doesn't display any post-install messages" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "config ignore_messages.rack true"
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+
+ context "when ignore post-install messages for all gems" do
+ it "doesn't display any post-install messages" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "config ignore_messages true"
+
+ bundle :install
+ expect(out).not_to include("Post-install message")
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb
new file mode 100644
index 0000000000..01c03ac793
--- /dev/null
+++ b/spec/bundler/install/gems/resolving_spec.rb
@@ -0,0 +1,195 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with install-time dependencies" do
+ it "installs gems with implicit rake dependencies", :ruby_repo do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "with_implicit_rake_dep"
+ gem "another_implicit_rake_dep"
+ gem "rake"
+ G
+
+ run <<-R
+ require 'implicit_rake_dep'
+ require 'another_implicit_rake_dep'
+ puts IMPLICIT_RAKE_DEP
+ puts ANOTHER_IMPLICIT_RAKE_DEP
+ R
+ expect(out).to eq("YES\nYES")
+ end
+
+ it "installs gems with a dependency with no type" do
+ build_repo2
+
+ path = "#{gem_repo2}/#{Gem::MARSHAL_SPEC_DIR}/actionpack-2.3.2.gemspec.rz"
+ spec = Marshal.load(Bundler.rubygems.inflate(File.read(path)))
+ spec.dependencies.each do |d|
+ d.instance_variable_set(:@type, :fail)
+ end
+ File.open(path, "w") do |f|
+ f.write Gem.deflate(Marshal.dump(spec))
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "actionpack", "2.3.2"
+ G
+
+ expect(the_bundle).to include_gems "actionpack 2.3.2", "activesupport 2.3.2"
+ end
+
+ describe "with crazy rubygem plugin stuff" do
+ it "installs plugins" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "net_b"
+ G
+
+ expect(the_bundle).to include_gems "net_b 1.0"
+ end
+
+ it "installs plugins depended on by other plugins", :ruby_repo do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "net_a"
+ G
+
+ expect(the_bundle).to include_gems "net_a 1.0", "net_b 1.0"
+ end
+
+ it "installs multiple levels of dependencies", :ruby_repo do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "net_c"
+ gem "net_e"
+ G
+
+ expect(the_bundle).to include_gems "net_a 1.0", "net_b 1.0", "net_c 1.0", "net_d 1.0", "net_e 1.0"
+ end
+
+ context "with ENV['DEBUG_RESOLVER'] set" do
+ it "produces debug output" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "net_c"
+ gem "net_e"
+ G
+
+ bundle :install, :env => { "DEBUG_RESOLVER" => "1" }
+
+ expect(err).to include("Creating possibility state for net_c")
+ end
+ end
+
+ context "with ENV['DEBUG_RESOLVER_TREE'] set" do
+ it "produces debug output" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "net_c"
+ gem "net_e"
+ G
+
+ bundle :install, :env => { "DEBUG_RESOLVER_TREE" => "1" }
+
+ expect(err).to include(" net_b").
+ and include("Starting resolution").
+ and include("Finished resolution").
+ and include("Attempting to activate")
+ end
+ end
+ end
+
+ describe "when a required ruby version" do
+ context "allows only an older version" do
+ it "installs the older version" do
+ build_repo2 do
+ build_gem "rack", "9001.0.0" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ end
+
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2 }
+ ruby "#{RUBY_VERSION}"
+ source "http://localgemserver.test/"
+ gem 'rack'
+ G
+
+ expect(out).to_not include("rack-9001.0.0 requires ruby version > 9000")
+ expect(the_bundle).to include_gems("rack 1.2")
+ end
+ end
+
+ context "allows no gems" do
+ before do
+ build_repo2 do
+ build_gem "require_ruby" do |s|
+ s.required_ruby_version = "> 9000"
+ end
+ end
+ end
+
+ let(:ruby_requirement) { %("#{RUBY_VERSION}") }
+ let(:error_message_requirement) { "~> #{RUBY_VERSION}.0" }
+
+ shared_examples_for "ruby version conflicts" do
+ it "raises an error during resolution" do
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2 }
+ source "http://localgemserver.test/"
+ ruby #{ruby_requirement}
+ gem 'require_ruby'
+ G
+
+ expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000")
+
+ nice_error = strip_whitespace(<<-E).strip
+ Bundler could not find compatible versions for gem "ruby\0":
+ In Gemfile:
+ ruby\0 (#{error_message_requirement})
+
+ require_ruby was resolved to 1.0, which depends on
+ ruby\0 (> 9000)
+
+ Could not find gem 'ruby\0 (> 9000)', which is required by gem 'require_ruby', in any of the relevant sources:
+ the local ruby installation
+ E
+ expect(last_command.bundler_err).to end_with(nice_error)
+ end
+ end
+
+ it_behaves_like "ruby version conflicts"
+
+ describe "with a < requirement" do
+ let(:ruby_requirement) { %("< 5000") }
+ let(:error_message_requirement) { "< 5000" }
+
+ it_behaves_like "ruby version conflicts"
+ end
+
+ describe "with a compound requirement" do
+ let(:reqs) { ["> 0.1", "< 5000"] }
+ let(:ruby_requirement) { reqs.map(&:dump).join(", ") }
+ let(:error_message_requirement) { Gem::Requirement.new(reqs).to_s }
+
+ it_behaves_like "ruby version conflicts"
+ end
+ end
+ end
+
+ describe "when a required rubygems version disallows a gem" do
+ it "does not try to install those gems" do
+ build_repo2 do
+ build_gem "require_rubygems" do |s|
+ s.required_rubygems_version = "> 9000"
+ end
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem 'require_rubygems'
+ G
+
+ expect(out).to_not include("Gem::InstallError: require_rubygems requires RubyGems version > 9000")
+ expect(out).to include("require_rubygems-1.0 requires rubygems version > 9000, which is incompatible with the current version, #{Gem::VERSION}")
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb
new file mode 100644
index 0000000000..10ce589eef
--- /dev/null
+++ b/spec/bundler/install/gems/standalone_spec.rb
@@ -0,0 +1,337 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "bundle install --standalone" do
+ shared_examples "common functionality" do
+ it "still makes the gems available to normal bundler" do
+ args = expected_gems.map {|k, v| "#{k} #{v}" }
+ expect(the_bundle).to include_gems(*args)
+ end
+
+ it "generates a bundle/bundler/setup.rb" do
+ expect(bundled_app("bundle/bundler/setup.rb")).to exist
+ end
+
+ it "makes the gems available without bundler" do
+ testrb = String.new <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ RUBY
+ expected_gems.each do |k, _|
+ testrb << "\nrequire \"#{k}\""
+ testrb << "\nputs #{k.upcase}"
+ end
+ Dir.chdir(bundled_app) do
+ ruby testrb, :no_lib => true
+ end
+
+ expect(out).to eq(expected_gems.values.join("\n"))
+ end
+
+ it "works on a different system" do
+ FileUtils.mv(bundled_app, "#{bundled_app}2")
+
+ testrb = String.new <<-RUBY
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ RUBY
+ expected_gems.each do |k, _|
+ testrb << "\nrequire \"#{k}\""
+ testrb << "\nputs #{k.upcase}"
+ end
+ Dir.chdir("#{bundled_app}2") do
+ ruby testrb, :no_lib => true
+ end
+
+ expect(out).to eq(expected_gems.values.join("\n"))
+ end
+ end
+
+ describe "with simple gems" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+ bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true)
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+ end
+
+ describe "with gems with native extension", :ruby_repo do
+ before do
+ install_gemfile <<-G, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true)
+ source "file://#{gem_repo1}"
+ gem "very_simple_binary"
+ G
+ end
+
+ it "generates a bundle/bundler/setup.rb with the proper paths", :rubygems => "2.4" do
+ expected_path = bundled_app("bundle/bundler/setup.rb")
+ extension_line = File.read(expected_path).each_line.find {|line| line.include? "/extensions/" }.strip
+ expect(extension_line).to start_with '$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/'
+ expect(extension_line).to end_with '/very_simple_binary-1.0"'
+ end
+ end
+
+ describe "with gem that has an invalid gemspec" do
+ before do
+ build_git "bar", :gemspec => false do |s|
+ s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0')
+ s.write "bar.gemspec", <<-G
+ lib = File.expand_path('../lib/', __FILE__)
+ $:.unshift lib unless $:.include?(lib)
+ require 'bar/version'
+
+ Gem::Specification.new do |s|
+ s.name = 'bar'
+ s.version = BAR_VERSION
+ s.summary = 'Bar'
+ s.files = Dir["lib/**/*.rb"]
+ s.author = 'Anonymous'
+ s.require_path = [1,2]
+ end
+ G
+ end
+ install_gemfile <<-G, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true)
+ gem "bar", :git => "#{lib_path("bar-1.0")}"
+ G
+ end
+
+ it "outputs a helpful error message" do
+ expect(out).to include("You have one or more invalid gemspecs that need to be fixed.")
+ expect(out).to include("bar 1.0 has an invalid gemspec")
+ end
+ end
+
+ describe "with a combination of gems and git repos" do
+ before do
+ build_git "devise", "1.0"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ gem "devise", :git => "#{lib_path("devise-1.0")}"
+ G
+ bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true)
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "devise" => "1.0",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+ end
+
+ describe "with groups" do
+ before do
+ build_git "devise", "1.0"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+
+ group :test do
+ gem "rspec"
+ gem "rack-test"
+ end
+ G
+ bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true)
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+
+ it "allows creating a standalone file with limited groups" do
+ bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => "default")
+
+ Dir.chdir(bundled_app) do
+ load_error_ruby <<-RUBY, "spec", :no_lib => true
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ require "actionpack"
+ puts ACTIONPACK
+ require "spec"
+ RUBY
+ end
+
+ expect(last_command.stdout).to eq("2.3.2")
+ expect(last_command.stderr).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "allows --without to limit the groups used in a standalone" do
+ bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle"), :without => "test").merge(:standalone => true)
+
+ Dir.chdir(bundled_app) do
+ load_error_ruby <<-RUBY, "spec", :no_lib => true
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ require "actionpack"
+ puts ACTIONPACK
+ require "spec"
+ RUBY
+ end
+
+ expect(last_command.stdout).to eq("2.3.2")
+ expect(last_command.stderr).to eq("ZOMG LOAD ERROR")
+ end
+
+ it "allows --path to change the location of the standalone bundle", :bundler => "< 2" do
+ bundle! "install", forgotten_command_line_options(:path => "path/to/bundle").merge(:standalone => true)
+
+ Dir.chdir(bundled_app) do
+ ruby <<-RUBY, :no_lib => true
+ $:.unshift File.expand_path("path/to/bundle")
+ require "bundler/setup"
+
+ require "actionpack"
+ puts ACTIONPACK
+ RUBY
+ end
+
+ expect(last_command.stdout).to eq("2.3.2")
+ end
+
+ it "allows --path to change the location of the standalone bundle", :bundler => "2" do
+ bundle! "install", forgotten_command_line_options(:path => "path/to/bundle").merge(:standalone => true)
+ path = File.expand_path("path/to/bundle")
+
+ Dir.chdir(bundled_app) do
+ ruby <<-RUBY, :no_lib => true
+ $:.unshift File.expand_path(#{path.dump})
+ require "bundler/setup"
+
+ require "actionpack"
+ puts ACTIONPACK
+ RUBY
+ end
+
+ expect(last_command.stdout).to eq("2.3.2")
+ end
+
+ it "allows remembered --without to limit the groups used in a standalone" do
+ bundle! :install, forgotten_command_line_options(:without => "test")
+ bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true)
+
+ Dir.chdir(bundled_app) do
+ load_error_ruby <<-RUBY, "spec", :no_lib => true
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ require "actionpack"
+ puts ACTIONPACK
+ require "spec"
+ RUBY
+ end
+
+ expect(last_command.stdout).to eq("2.3.2")
+ expect(last_command.stderr).to eq("ZOMG LOAD ERROR")
+ end
+ end
+
+ describe "with gemcutter's dependency API" do
+ let(:source_uri) { "http://localgemserver.test" }
+
+ describe "simple gems" do
+ before do
+ gemfile <<-G
+ source "#{source_uri}"
+ gem "rails"
+ G
+ bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true, :artifice => "endpoint")
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+ end
+ end
+
+ describe "with --binstubs", :bundler => "< 2" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+ bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true, :binstubs => true)
+ end
+
+ let(:expected_gems) do
+ {
+ "actionpack" => "2.3.2",
+ "rails" => "2.3.2",
+ }
+ end
+
+ include_examples "common functionality"
+
+ it "creates stubs that use the standalone load path" do
+ Dir.chdir(bundled_app) do
+ expect(`bin/rails -v`.chomp).to eql "2.3.2"
+ end
+ end
+
+ it "creates stubs that can be executed from anywhere" do
+ require "tmpdir"
+ Dir.chdir(Dir.tmpdir) do
+ sys_exec!(%(#{bundled_app("bin/rails")} -v))
+ expect(out).to eq("2.3.2")
+ end
+ end
+
+ it "creates stubs that can be symlinked" do
+ pending "File.symlink is unsupported on Windows" if Bundler::WINDOWS
+
+ symlink_dir = tmp("symlink")
+ FileUtils.mkdir_p(symlink_dir)
+ symlink = File.join(symlink_dir, "rails")
+
+ File.symlink(bundled_app("bin/rails"), symlink)
+ sys_exec!("#{symlink} -v")
+ expect(out).to eq("2.3.2")
+ end
+
+ it "creates stubs with the correct load path" do
+ extension_line = File.read(bundled_app("bin/rails")).each_line.find {|line| line.include? "$:.unshift" }.strip
+ expect(extension_line).to eq %($:.unshift File.expand_path "../../bundle", path.realpath)
+ end
+ end
+end
+
+RSpec.describe "bundle install --standalone" do
+ include_examples("bundle install --standalone")
+end
+
+RSpec.describe "bundle install --standalone run in a subdirectory" do
+ before do
+ Dir.chdir(bundled_app("bob").tap(&:mkpath))
+ end
+
+ include_examples("bundle install --standalone")
+end
diff --git a/spec/bundler/install/gems/sudo_spec.rb b/spec/bundler/install/gems/sudo_spec.rb
new file mode 100644
index 0000000000..1781451c98
--- /dev/null
+++ b/spec/bundler/install/gems/sudo_spec.rb
@@ -0,0 +1,178 @@
+# frozen_string_literal: true
+
+RSpec.describe "when using sudo", :sudo => true do
+ describe "and BUNDLE_PATH is writable" do
+ context "but BUNDLE_PATH/build_info is not writable" do
+ before do
+ bundle! "config path.system true"
+ subdir = system_gem_path("cache")
+ subdir.mkpath
+ sudo "chmod u-w #{subdir}"
+ end
+
+ it "installs" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ expect(out).to_not match(/an error occurred/i)
+ expect(system_gem_path("cache/rack-1.0.0.gem")).to exist
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+ end
+
+ describe "and GEM_HOME is owned by root" do
+ before :each do
+ bundle! "config path.system true"
+ chown_system_gems_to_root
+ end
+
+ it "installs" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", '1.0'
+ gem "thin"
+ G
+
+ expect(system_gem_path("gems/rack-1.0.0")).to exist
+ expect(system_gem_path("gems/rack-1.0.0").stat.uid).to eq(0)
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "installs rake and a gem dependent on rake in the same session" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rake"
+ gem "another_implicit_rake_dep"
+ G
+ bundle "install"
+ expect(system_gem_path("gems/another_implicit_rake_dep-1.0")).to exist
+ end
+
+ it "installs when BUNDLE_PATH is owned by root" do
+ bundle! "config global_path_appends_ruby_scope false" # consistency in tests between 1.x and 2.x modes
+
+ bundle_path = tmp("owned_by_root")
+ FileUtils.mkdir_p bundle_path
+ sudo "chown -R root #{bundle_path}"
+
+ ENV["BUNDLE_PATH"] = bundle_path.to_s
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", '1.0'
+ G
+
+ expect(bundle_path.join("gems/rack-1.0.0")).to exist
+ expect(bundle_path.join("gems/rack-1.0.0").stat.uid).to eq(0)
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "installs when BUNDLE_PATH does not exist" do
+ bundle! "config global_path_appends_ruby_scope false" # consistency in tests between 1.x and 2.x modes
+
+ root_path = tmp("owned_by_root")
+ FileUtils.mkdir_p root_path
+ sudo "chown -R root #{root_path}"
+ bundle_path = root_path.join("does_not_exist")
+
+ ENV["BUNDLE_PATH"] = bundle_path.to_s
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", '1.0'
+ G
+
+ expect(bundle_path.join("gems/rack-1.0.0")).to exist
+ expect(bundle_path.join("gems/rack-1.0.0").stat.uid).to eq(0)
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "installs extensions/ compiled by RubyGems 2.2", :rubygems => "2.2" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "very_simple_binary"
+ G
+
+ expect(system_gem_path("gems/very_simple_binary-1.0")).to exist
+ binary_glob = system_gem_path("extensions/*/*/very_simple_binary-1.0")
+ expect(Dir.glob(binary_glob).first).to be
+ end
+ end
+
+ describe "and BUNDLE_PATH is not writable" do
+ before do
+ sudo "chmod ugo-w #{default_bundle_path}"
+ end
+
+ it "installs" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", '1.0'
+ G
+
+ expect(default_bundle_path("gems/rack-1.0.0")).to exist
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ it "cleans up the tmpdirs generated" do
+ require "tmpdir"
+ Dir.glob("#{Dir.tmpdir}/bundler*").each do |tmpdir|
+ FileUtils.remove_entry_secure(tmpdir)
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ tmpdirs = Dir.glob("#{Dir.tmpdir}/bundler*")
+
+ expect(tmpdirs).to be_empty
+ end
+ end
+
+ describe "and GEM_HOME is not writable" do
+ it "installs" do
+ bundle! "config path.system true"
+ gem_home = tmp("sudo_gem_home")
+ sudo "mkdir -p #{gem_home}"
+ sudo "chmod ugo-w #{gem_home}"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", '1.0'
+ G
+
+ bundle :install, :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil }
+ expect(gem_home.join("bin/rackup")).to exist
+ expect(the_bundle).to include_gems "rack 1.0", :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil }
+ end
+ end
+
+ describe "and root runs install" do
+ let(:warning) { "Don't run Bundler as root." }
+
+ before do
+ gemfile %(source "file://#{gem_repo1}")
+ end
+
+ it "warns against that" do
+ bundle :install, :sudo => true
+ expect(out).to include(warning)
+ end
+
+ context "when ENV['BUNDLE_SILENCE_ROOT_WARNING'] is set" do
+ it "skips the warning" do
+ bundle :install, :sudo => :preserve_env, :env => { "BUNDLE_SILENCE_ROOT_WARNING" => true }
+ expect(out).to_not include(warning)
+ end
+ end
+
+ context "when silence_root_warning = false" do
+ it "warns against that" do
+ bundle :install, :sudo => true, :env => { "BUNDLE_SILENCE_ROOT_WARNING" => "false" }
+ expect(out).to include(warning)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/gems/win32_spec.rb b/spec/bundler/install/gems/win32_spec.rb
new file mode 100644
index 0000000000..ad758b94fa
--- /dev/null
+++ b/spec/bundler/install/gems/win32_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install with win32-generated lockfile" do
+ it "should read lockfile" do
+ File.open(bundled_app("Gemfile.lock"), "wb") do |f|
+ f << "GEM\r\n"
+ f << " remote: file:#{gem_repo1}/\r\n"
+ f << " specs:\r\n"
+ f << "\r\n"
+ f << " rack (1.0.0)\r\n"
+ f << "\r\n"
+ f << "PLATFORMS\r\n"
+ f << " ruby\r\n"
+ f << "\r\n"
+ f << "DEPENDENCIES\r\n"
+ f << " rack\r\n"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack"
+ G
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+end
diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb
new file mode 100644
index 0000000000..666707b295
--- /dev/null
+++ b/spec/bundler/install/gemspecs_spec.rb
@@ -0,0 +1,154 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ describe "when a gem has a YAML gemspec" do
+ before :each do
+ build_repo2 do
+ build_gem "yaml_spec", :gemspec => :yaml
+ end
+ end
+
+ it "still installs correctly" do
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "yaml_spec"
+ G
+ bundle :install
+ expect(err).to lack_errors
+ end
+
+ it "still installs correctly when using path" do
+ build_lib "yaml_spec", :gemspec => :yaml
+
+ install_gemfile <<-G
+ gem 'yaml_spec', :path => "#{lib_path("yaml_spec-1.0")}"
+ G
+ expect(err).to lack_errors
+ end
+ end
+
+ it "should use gemspecs in the system cache when available" do
+ gemfile <<-G
+ source "http://localtestserver.gem"
+ gem 'rack'
+ G
+
+ FileUtils.mkdir_p "#{default_bundle_path}/specifications"
+ File.open("#{default_bundle_path}/specifications/rack-1.0.0.gemspec", "w+") do |f|
+ spec = Gem::Specification.new do |s|
+ s.name = "rack"
+ s.version = "1.0.0"
+ s.add_runtime_dependency "activesupport", "2.3.2"
+ end
+ f.write spec.to_ruby
+ end
+ bundle :install, :artifice => "endpoint_marshal_fail" # force gemspec load
+ expect(the_bundle).to include_gems "activesupport 2.3.2"
+ end
+
+ it "does not hang when gemspec has incompatible encoding" do
+ create_file "foo.gemspec", <<-G
+ Gem::Specification.new do |gem|
+ gem.name = "pry-byebug"
+ gem.version = "3.4.2"
+ gem.author = "David Rodriguez"
+ gem.summary = "Good stuff"
+ end
+ G
+
+ install_gemfile <<-G, :env => { "LANG" => "C" }
+ gemspec
+ G
+
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "reads gemspecs respecting their encoding" do
+ skip "Unicode is not supported on Ruby 1.x without extra work" if RUBY_VERSION < "2.0"
+
+ create_file "version.rb", <<-RUBY
+ module Persistent💎
+ VERSION = "0.0.1"
+ end
+ RUBY
+
+ create_file "persistent-dmnd.gemspec", <<-G
+ require_relative "version"
+
+ Gem::Specification.new do |gem|
+ gem.name = "persistent-dmnd"
+ gem.version = Persistent💎::VERSION
+ gem.author = "Ivo Anjo"
+ gem.summary = "Unscratchable stuff"
+ end
+ G
+
+ install_gemfile <<-G
+ gemspec
+ G
+
+ expect(out).to include("Bundle complete!")
+ end
+
+ context "when ruby version is specified in gemspec and gemfile" do
+ it "installs when patch level is not specified and the version matches" do
+ build_lib("foo", :path => bundled_app) do |s|
+ s.required_ruby_version = "~> #{RUBY_VERSION}.0"
+ end
+
+ install_gemfile <<-G
+ ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby'
+ gemspec
+ G
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "installs when patch level is specified and the version still matches the current version",
+ :if => RUBY_PATCHLEVEL >= 0 do
+ build_lib("foo", :path => bundled_app) do |s|
+ s.required_ruby_version = "#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
+ end
+
+ install_gemfile <<-G
+ ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{RUBY_PATCHLEVEL}'
+ gemspec
+ G
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "fails and complains about patchlevel on patchlevel mismatch",
+ :if => RUBY_PATCHLEVEL >= 0 do
+ patchlevel = RUBY_PATCHLEVEL.to_i + 1
+ build_lib("foo", :path => bundled_app) do |s|
+ s.required_ruby_version = "#{RUBY_VERSION}.#{patchlevel}"
+ end
+
+ install_gemfile <<-G
+ ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{patchlevel}'
+ gemspec
+ G
+
+ expect(out).to include("Ruby patchlevel")
+ expect(out).to include("but your Gemfile specified")
+ expect(exitstatus).to eq(18) if exitstatus
+ end
+
+ it "fails and complains about version on version mismatch" do
+ version = Gem::Requirement.create(RUBY_VERSION).requirements.first.last.bump.version
+
+ build_lib("foo", :path => bundled_app) do |s|
+ s.required_ruby_version = version
+ end
+
+ install_gemfile <<-G
+ ruby '#{version}', :engine_version => '#{version}', :engine => 'ruby'
+ gemspec
+ G
+
+ expect(out).to include("Ruby version")
+ expect(out).to include("but your Gemfile specified")
+ expect(exitstatus).to eq(18) if exitstatus
+ end
+ end
+end
diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb
new file mode 100644
index 0000000000..6ae718c2a4
--- /dev/null
+++ b/spec/bundler/install/git_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ context "git sources" do
+ it "displays the revision hash of the gem repository", :bundler => "< 2" do
+ build_git "foo", "1.0", :path => lib_path("foo")
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo")}"
+ G
+
+ bundle! :install
+ expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at master@#{revision_for(lib_path("foo"))[0..6]})")
+ expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}"
+ end
+
+ it "displays the ref of the gem repository when using branch~num as a ref", :bundler => "< 2" do
+ build_git "foo", "1.0", :path => lib_path("foo")
+ rev = revision_for(lib_path("foo"))[0..6]
+ update_git "foo", "2.0", :path => lib_path("foo"), :gemspec => true
+ rev2 = revision_for(lib_path("foo"))[0..6]
+ update_git "foo", "3.0", :path => lib_path("foo"), :gemspec => true
+
+ install_gemfile! <<-G
+ gem "foo", :git => "#{lib_path("foo")}", :ref => "master~2"
+ G
+
+ bundle! :install
+ expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at master~2@#{rev})")
+ expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}"
+
+ update_git "foo", "4.0", :path => lib_path("foo"), :gemspec => true
+
+ bundle! :update, :all => bundle_update_requires_all?
+ expect(out).to include("Using foo 2.0 (was 1.0) from #{lib_path("foo")} (at master~2@#{rev2})")
+ expect(the_bundle).to include_gems "foo 2.0", :source => "git@#{lib_path("foo")}"
+ end
+
+ it "should allows git repos that are missing but not being installed" do
+ revision = build_git("foo").ref_for("HEAD")
+
+ gemfile <<-G
+ gem "foo", :git => "file://#{lib_path("foo-1.0")}", :group => :development
+ G
+
+ lockfile <<-L
+ GIT
+ remote: file://#{lib_path("foo-1.0")}
+ revision: #{revision}
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ foo!
+ L
+
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle", :without => "development")
+
+ expect(out).to include("Bundle complete!")
+ end
+ end
+end
diff --git a/spec/bundler/install/global_cache_spec.rb b/spec/bundler/install/global_cache_spec.rb
new file mode 100644
index 0000000000..e41e7e0157
--- /dev/null
+++ b/spec/bundler/install/global_cache_spec.rb
@@ -0,0 +1,235 @@
+# frozen_string_literal: true
+
+RSpec.describe "global gem caching" do
+ before { bundle! "config global_gem_cache true" }
+
+ describe "using the cross-application user cache" do
+ let(:source) { "http://localgemserver.test" }
+ let(:source2) { "http://gemserver.example.org" }
+
+ def source_global_cache(*segments)
+ home(".bundle", "cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", *segments)
+ end
+
+ def source2_global_cache(*segments)
+ home(".bundle", "cache", "gems", "gemserver.example.org.80.1ae1663619ffe0a3c9d97712f44c705b", *segments)
+ end
+
+ it "caches gems into the global cache on download" do
+ install_gemfile! <<-G, :artifice => "compact_index"
+ source "#{source}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ end
+
+ it "uses globally cached gems if they exist" do
+ source_global_cache.mkpath
+ FileUtils.cp(gem_repo1("gems/rack-1.0.0.gem"), source_global_cache("rack-1.0.0.gem"))
+
+ install_gemfile! <<-G, :artifice => "compact_index_no_gem"
+ source "#{source}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ describe "when the same gem from different sources is installed" do
+ it "should use the appropriate one from the global cache" do
+ install_gemfile! <<-G, :artifice => "compact_index"
+ source "#{source}"
+ gem "rack"
+ G
+
+ FileUtils.rm_r(default_bundle_path)
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ # rack 1.0.0 is not installed and it is in the global cache
+
+ install_gemfile! <<-G, :artifice => "compact_index"
+ source "#{source2}"
+ gem "rack", "0.9.1"
+ G
+
+ FileUtils.rm_r(default_bundle_path)
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+ expect(source2_global_cache("rack-0.9.1.gem")).to exist
+ # rack 0.9.1 is not installed and it is in the global cache
+
+ gemfile <<-G
+ source "#{source}"
+ gem "rack", "1.0.0"
+ G
+
+ bundle! :install, :artifice => "compact_index_no_gem"
+ # rack 1.0.0 is installed and rack 0.9.1 is not
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+ FileUtils.rm_r(default_bundle_path)
+
+ gemfile <<-G
+ source "#{source2}"
+ gem "rack", "0.9.1"
+ G
+
+ bundle! :install, :artifice => "compact_index_no_gem"
+ # rack 0.9.1 is installed and rack 1.0.0 is not
+ expect(the_bundle).to include_gems "rack 0.9.1"
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ end
+
+ it "should not install if the wrong source is provided" do
+ gemfile <<-G
+ source "#{source}"
+ gem "rack"
+ G
+
+ bundle! :install, :artifice => "compact_index"
+ FileUtils.rm_r(default_bundle_path)
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ # rack 1.0.0 is not installed and it is in the global cache
+
+ gemfile <<-G
+ source "#{source2}"
+ gem "rack", "0.9.1"
+ G
+
+ bundle! :install, :artifice => "compact_index"
+ FileUtils.rm_r(default_bundle_path)
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+ expect(source2_global_cache("rack-0.9.1.gem")).to exist
+ # rack 0.9.1 is not installed and it is in the global cache
+
+ gemfile <<-G
+ source "#{source2}"
+ gem "rack", "1.0.0"
+ G
+
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source2_global_cache("rack-0.9.1.gem")).to exist
+ bundle :install, :artifice => "compact_index_no_gem"
+ expect(out).to include("Internal Server Error 500")
+ # rack 1.0.0 is not installed and rack 0.9.1 is not
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+
+ gemfile <<-G
+ source "#{source}"
+ gem "rack", "0.9.1"
+ G
+
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source2_global_cache("rack-0.9.1.gem")).to exist
+ bundle :install, :artifice => "compact_index_no_gem"
+ expect(out).to include("Internal Server Error 500")
+ # rack 0.9.1 is not installed and rack 1.0.0 is not
+ expect(the_bundle).not_to include_gems "rack 0.9.1"
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ end
+ end
+
+ describe "when installing gems from a different directory" do
+ it "uses the global cache as a source" do
+ install_gemfile! <<-G, :artifice => "compact_index"
+ source "#{source}"
+ gem "rack"
+ gem "activesupport"
+ G
+
+ # Both gems are installed and in the global cache
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source_global_cache("activesupport-2.3.5.gem")).to exist
+ FileUtils.rm_r(default_bundle_path)
+ # Both gems are now only in the global cache
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+
+ install_gemfile! <<-G, :artifice => "compact_index_no_gem"
+ source "#{source}"
+ gem "rack"
+ G
+
+ # rack is installed and both are in the global cache
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source_global_cache("activesupport-2.3.5.gem")).to exist
+
+ Dir.chdir bundled_app2 do
+ create_file bundled_app2("gems.rb"), <<-G
+ source "#{source}"
+ gem "activesupport"
+ G
+
+ # Neither gem is installed and both are in the global cache
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(the_bundle).not_to include_gems "activesupport 2.3.5"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source_global_cache("activesupport-2.3.5.gem")).to exist
+
+ # Install using the global cache instead of by downloading the .gem
+ # from the server
+ bundle! :install, :artifice => "compact_index_no_gem"
+
+ # activesupport is installed and both are in the global cache
+ expect(the_bundle).not_to include_gems "rack 1.0.0"
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ expect(source_global_cache("rack-1.0.0.gem")).to exist
+ expect(source_global_cache("activesupport-2.3.5.gem")).to exist
+ end
+ end
+ end
+ end
+
+ describe "extension caching", :ruby_repo, :rubygems => "2.2" do
+ it "works" do
+ build_git "very_simple_git_binary", &:add_c_extension
+ build_lib "very_simple_path_binary", &:add_c_extension
+ revision = revision_for(lib_path("very_simple_git_binary-1.0"))[0, 12]
+
+ install_gemfile! <<-G
+ source "file:#{gem_repo1}"
+
+ gem "very_simple_binary"
+ gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}"
+ gem "very_simple_path_binary", :path => "#{lib_path("very_simple_path_binary-1.0")}"
+ G
+
+ gem_binary_cache = home(".bundle", "cache", "extensions", specific_local_platform.to_s, Bundler.ruby_scope,
+ Digest(:MD5).hexdigest("#{gem_repo1}/"), "very_simple_binary-1.0")
+ git_binary_cache = home(".bundle", "cache", "extensions", specific_local_platform.to_s, Bundler.ruby_scope,
+ "very_simple_git_binary-1.0-#{revision}", "very_simple_git_binary-1.0")
+
+ cached_extensions = Pathname.glob(home(".bundle", "cache", "extensions", "*", "*", "*", "*", "*")).sort
+ expect(cached_extensions).to eq [gem_binary_cache, git_binary_cache].sort
+
+ run! <<-R
+ require 'very_simple_binary_c'; puts ::VERY_SIMPLE_BINARY_IN_C
+ require 'very_simple_git_binary_c'; puts ::VERY_SIMPLE_GIT_BINARY_IN_C
+ R
+ expect(out).to eq "VERY_SIMPLE_BINARY_IN_C\nVERY_SIMPLE_GIT_BINARY_IN_C"
+
+ FileUtils.rm Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")]
+
+ gem_binary_cache.join("very_simple_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" }
+ git_binary_cache.join("very_simple_git_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" }
+
+ bundle! "config --local path different_path"
+ bundle! :install
+
+ expect(Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")]).to all(end_with(".rb"))
+
+ run! <<-R
+ require 'very_simple_binary_c'
+ require 'very_simple_git_binary_c'
+ R
+ expect(out).to eq "very_simple_binary_c.rb\nvery_simple_git_binary_c.rb"
+ end
+ end
+end
diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb
new file mode 100644
index 0000000000..44439c275e
--- /dev/null
+++ b/spec/bundler/install/path_spec.rb
@@ -0,0 +1,256 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ describe "with --path" do
+ before :each do
+ build_gem "rack", "1.0.0", :to_system => true do |s|
+ s.write "lib/rack.rb", "puts 'FAIL'"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ it "does not use available system gems with bundle --path vendor/bundle", :bundler => "< 2" do
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "handles paths with regex characters in them" do
+ dir = bundled_app("bun++dle")
+ dir.mkpath
+
+ Dir.chdir(dir) do
+ bundle! :install, forgotten_command_line_options(:path => dir.join("vendor/bundle"))
+ expect(out).to include("installed into `./vendor/bundle`")
+ end
+
+ dir.rmtree
+ end
+
+ it "prints a warning to let the user know what has happened with bundle --path vendor/bundle" do
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle")
+ expect(out).to include("gems are installed into `./vendor/bundle`")
+ end
+
+ it "disallows --path vendor/bundle --system", :bundler => "< 2" do
+ bundle "install --path vendor/bundle --system"
+ expect(out).to include("Please choose only one option.")
+ expect(exitstatus).to eq(15) if exitstatus
+ end
+
+ it "remembers to disable system gems after the first time with bundle --path vendor/bundle", :bundler => "< 2" do
+ bundle "install --path vendor/bundle"
+ FileUtils.rm_rf bundled_app("vendor")
+ bundle "install"
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ context "with path_relative_to_cwd set to true" do
+ before { bundle! "config path_relative_to_cwd true" }
+
+ it "installs the bundle relatively to current working directory", :bundler => "< 2" do
+ Dir.chdir(bundled_app.parent) do
+ bundle! "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle"
+ expect(out).to include("installed into `./vendor/bundle`")
+ expect(bundled_app("../vendor/bundle")).to be_directory
+ end
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs the standalone bundle relative to the cwd" do
+ Dir.chdir(bundled_app.parent) do
+ bundle! :install, :gemfile => bundled_app("Gemfile"), :standalone => true
+ expect(out).to include("installed into `./bundled_app/bundle`")
+ expect(bundled_app("bundle")).to be_directory
+ expect(bundled_app("bundle/ruby")).to be_directory
+ end
+
+ bundle! "config unset path"
+
+ Dir.chdir(bundled_app("subdir").tap(&:mkpath)) do
+ bundle! :install, :gemfile => bundled_app("Gemfile"), :standalone => true
+ expect(out).to include("installed into `../bundle`")
+ expect(bundled_app("bundle")).to be_directory
+ expect(bundled_app("bundle/ruby")).to be_directory
+ end
+ end
+ end
+ end
+
+ describe "when BUNDLE_PATH or the global path config is set" do
+ before :each do
+ build_lib "rack", "1.0.0", :to_system => true do |s|
+ s.write "lib/rack.rb", "raise 'FAIL'"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ def set_bundle_path(type, location)
+ if type == :env
+ ENV["BUNDLE_PATH"] = location
+ elsif type == :global
+ bundle! "config path #{location}", "no-color" => nil
+ end
+ end
+
+ [:env, :global].each do |type|
+ context "when set via #{type}" do
+ it "installs gems to a path if one is specified" do
+ set_bundle_path(type, bundled_app("vendor2").to_s)
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle")
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(bundled_app("vendor2")).not_to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ context "with global_path_appends_ruby_scope set", :bundler => "2" do
+ it "installs gems to ." do
+ set_bundle_path(type, ".")
+ bundle! "config --global disable_shared_gems true"
+
+ bundle! :install
+
+ paths_to_exist = %w[cache/rack-1.0.0.gem gems/rack-1.0.0 specifications/rack-1.0.0.gemspec].map {|path| bundled_app(Bundler.ruby_scope, path) }
+ expect(paths_to_exist).to all exist
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs gems to the path" do
+ set_bundle_path(type, bundled_app("vendor").to_s)
+
+ bundle! :install
+
+ expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs gems to the path relative to root when relative" do
+ set_bundle_path(type, "vendor")
+
+ FileUtils.mkdir_p bundled_app("lol")
+ Dir.chdir(bundled_app("lol")) do
+ bundle! :install
+ end
+
+ expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with global_path_appends_ruby_scope unset", :bundler => "< 2" do
+ it "installs gems to ." do
+ set_bundle_path(type, ".")
+ bundle! "config --global disable_shared_gems true"
+
+ bundle! :install
+
+ expect([bundled_app("cache/rack-1.0.0.gem"), bundled_app("gems/rack-1.0.0"), bundled_app("specifications/rack-1.0.0.gemspec")]).to all exist
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs gems to BUNDLE_PATH with #{type}" do
+ set_bundle_path(type, bundled_app("vendor").to_s)
+
+ bundle :install
+
+ expect(bundled_app("vendor/gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "installs gems to BUNDLE_PATH relative to root when relative" do
+ set_bundle_path(type, "vendor")
+
+ FileUtils.mkdir_p bundled_app("lol")
+ Dir.chdir(bundled_app("lol")) do
+ bundle :install
+ end
+
+ expect(bundled_app("vendor/gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+ end
+
+ it "installs gems to BUNDLE_PATH from .bundle/config" do
+ config "BUNDLE_PATH" => bundled_app("vendor/bundle").to_s
+
+ bundle :install
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "sets BUNDLE_PATH as the first argument to bundle install" do
+ bundle! :install, forgotten_command_line_options(:path => "./vendor/bundle")
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "disables system gems when passing a path to install" do
+ # This is so that vendored gems can be distributed to others
+ build_gem "rack", "1.1.0", :to_system => true
+ bundle! :install, forgotten_command_line_options(:path => "./vendor/bundle")
+
+ expect(vendored_gems("gems/rack-1.0.0")).to be_directory
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "re-installs gems whose extensions have been deleted", :ruby_repo, :rubygems => ">= 2.3" do
+ build_lib "very_simple_binary", "1.0.0", :to_system => true do |s|
+ s.write "lib/very_simple_binary.rb", "raise 'FAIL'"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "very_simple_binary"
+ G
+
+ bundle! :install, forgotten_command_line_options(:path => "./vendor/bundle")
+
+ expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory
+ expect(vendored_gems("extensions")).to be_directory
+ expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1"
+
+ vendored_gems("extensions").rmtree
+
+ run "require 'very_simple_binary_c'"
+ expect(err).to include("Bundler::GemNotFound")
+
+ bundle :install, forgotten_command_line_options(:path => "./vendor/bundle")
+
+ expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory
+ expect(vendored_gems("extensions")).to be_directory
+ expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1"
+ end
+ end
+
+ describe "to a file" do
+ before do
+ in_app_root do
+ FileUtils.touch "bundle"
+ end
+ end
+
+ it "reports the file exists" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle :install, forgotten_command_line_options(:path => "bundle")
+ expect(out).to include("file already exists")
+ end
+ end
+end
diff --git a/spec/bundler/install/post_bundle_message_spec.rb b/spec/bundler/install/post_bundle_message_spec.rb
new file mode 100644
index 0000000000..eadc8a4d85
--- /dev/null
+++ b/spec/bundler/install/post_bundle_message_spec.rb
@@ -0,0 +1,206 @@
+# frozen_string_literal: true
+
+RSpec.describe "post bundle message" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", "2.3.5", :group => [:emo, :test]
+ group :test do
+ gem "rspec"
+ end
+ gem "rack-obama", :group => :obama
+ G
+ end
+
+ let(:bundle_path) { "./.bundle" }
+ let(:bundle_show_system_message) { "Use `bundle info [gemname]` to see where a bundled gem is installed." }
+ let(:bundle_show_path_message) { "Bundled gems are installed into `#{bundle_path}`" }
+ let(:bundle_complete_message) { "Bundle complete!" }
+ let(:bundle_updated_message) { "Bundle updated!" }
+ let(:installed_gems_stats) { "4 Gemfile dependencies, 5 gems now installed." }
+ let(:bundle_show_message) { Bundler::VERSION.split(".").first.to_i < 2 ? bundle_show_system_message : bundle_show_path_message }
+
+ describe "for fresh bundle install" do
+ it "without any options" do
+ bundle :install
+ expect(out).to include(bundle_show_message)
+ expect(out).not_to include("Gems in the group")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include(installed_gems_stats)
+ end
+
+ it "with --without one group" do
+ bundle! :install, forgotten_command_line_options(:without => "emo")
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the group emo were not installed")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include(installed_gems_stats)
+ end
+
+ it "with --without two groups" do
+ bundle! :install, forgotten_command_line_options(:without => "emo test")
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the groups emo and test were not installed")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include("4 Gemfile dependencies, 3 gems now installed.")
+ end
+
+ it "with --without more groups" do
+ bundle! :install, forgotten_command_line_options(:without => "emo obama test")
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the groups emo, obama and test were not installed")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include("4 Gemfile dependencies, 2 gems now installed.")
+ end
+
+ describe "with --path and" do
+ let(:bundle_path) { "./vendor" }
+
+ it "without any options" do
+ bundle! :install, forgotten_command_line_options(:path => "vendor")
+ expect(out).to include(bundle_show_path_message)
+ expect(out).to_not include("Gems in the group")
+ expect(out).to include(bundle_complete_message)
+ end
+
+ it "with --without one group" do
+ bundle! :install, forgotten_command_line_options(:without => "emo", :path => "vendor")
+ expect(out).to include(bundle_show_path_message)
+ expect(out).to include("Gems in the group emo were not installed")
+ expect(out).to include(bundle_complete_message)
+ end
+
+ it "with --without two groups" do
+ bundle! :install, forgotten_command_line_options(:without => "emo test", :path => "vendor")
+ expect(out).to include(bundle_show_path_message)
+ expect(out).to include("Gems in the groups emo and test were not installed")
+ expect(out).to include(bundle_complete_message)
+ end
+
+ it "with --without more groups" do
+ bundle! :install, forgotten_command_line_options(:without => "emo obama test", :path => "vendor")
+ expect(out).to include(bundle_show_path_message)
+ expect(out).to include("Gems in the groups emo, obama and test were not installed")
+ expect(out).to include(bundle_complete_message)
+ end
+
+ it "with an absolute --path inside the cwd" do
+ bundle! :install, forgotten_command_line_options(:path => bundled_app("cache"))
+ expect(out).to include("Bundled gems are installed into `./cache`")
+ expect(out).to_not include("Gems in the group")
+ expect(out).to include(bundle_complete_message)
+ end
+
+ it "with an absolute --path outside the cwd" do
+ bundle! :install, forgotten_command_line_options(:path => tmp("not_bundled_app"))
+ expect(out).to include("Bundled gems are installed into `#{tmp("not_bundled_app")}`")
+ expect(out).to_not include("Gems in the group")
+ expect(out).to include(bundle_complete_message)
+ end
+ end
+
+ describe "with misspelled or non-existent gem name" do
+ it "should report a helpful error message", :bundler => "< 2" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ gem "not-a-gem", :group => :development
+ G
+ expect(out).to include("Could not find gem 'not-a-gem' in any of the gem sources listed in your Gemfile.")
+ end
+
+ it "should report a helpful error message", :bundler => "2" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ gem "not-a-gem", :group => :development
+ G
+ expect(out).to include normalize_uri_file(<<-EOS.strip)
+Could not find gem 'not-a-gem' in rubygems repository file://localhost#{gem_repo1}/ or installed locally.
+The source does not contain any versions of 'not-a-gem'
+ EOS
+ end
+
+ it "should report a helpful error message with reference to cache if available" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ G
+ bundle :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ gem "not-a-gem", :group => :development
+ G
+ expect(out).to include("Could not find gem 'not-a-gem' in").
+ and include("or in gems cached in vendor/cache.")
+ end
+ end
+ end
+
+ describe "for second bundle install run" do
+ it "without any options" do
+ 2.times { bundle :install }
+ expect(out).to include(bundle_show_message)
+ expect(out).to_not include("Gems in the groups")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include(installed_gems_stats)
+ end
+
+ it "with --without one group" do
+ bundle! :install, forgotten_command_line_options(:without => "emo")
+ bundle! :install
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the group emo were not installed")
+ expect(out).to include(bundle_complete_message)
+ expect(out).to include(installed_gems_stats)
+ end
+
+ it "with --without two groups" do
+ bundle! :install, forgotten_command_line_options(:without => "emo test")
+ bundle! :install
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the groups emo and test were not installed")
+ expect(out).to include(bundle_complete_message)
+ end
+
+ it "with --without more groups" do
+ bundle! :install, forgotten_command_line_options(:without => "emo obama test")
+ bundle :install
+ expect(out).to include(bundle_show_message)
+ expect(out).to include("Gems in the groups emo, obama and test were not installed")
+ expect(out).to include(bundle_complete_message)
+ end
+ end
+
+ describe "for bundle update" do
+ it "without any options" do
+ bundle! :update, :all => bundle_update_requires_all?
+ expect(out).not_to include("Gems in the groups")
+ expect(out).to include(bundle_updated_message)
+ end
+
+ it "with --without one group" do
+ bundle! :install, forgotten_command_line_options(:without => "emo")
+ bundle! :update, :all => bundle_update_requires_all?
+ expect(out).to include("Gems in the group emo were not installed")
+ expect(out).to include(bundle_updated_message)
+ end
+
+ it "with --without two groups" do
+ bundle! :install, forgotten_command_line_options(:without => "emo test")
+ bundle! :update, :all => bundle_update_requires_all?
+ expect(out).to include("Gems in the groups emo and test were not installed")
+ expect(out).to include(bundle_updated_message)
+ end
+
+ it "with --without more groups" do
+ bundle! :install, forgotten_command_line_options(:without => "emo obama test")
+ bundle! :update, :all => bundle_update_requires_all?
+ expect(out).to include("Gems in the groups emo, obama and test were not installed")
+ expect(out).to include(bundle_updated_message)
+ end
+ end
+end
diff --git a/spec/bundler/install/prereleases_spec.rb b/spec/bundler/install/prereleases_spec.rb
new file mode 100644
index 0000000000..7af8c3b304
--- /dev/null
+++ b/spec/bundler/install/prereleases_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install" do
+ describe "when prerelease gems are available" do
+ it "finds prereleases" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "not_released"
+ G
+ expect(the_bundle).to include_gems "not_released 1.0.pre"
+ end
+
+ it "uses regular releases if available" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "has_prerelease"
+ G
+ expect(the_bundle).to include_gems "has_prerelease 1.0"
+ end
+
+ it "uses prereleases if requested" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "has_prerelease", "1.1.pre"
+ G
+ expect(the_bundle).to include_gems "has_prerelease 1.1.pre"
+ end
+ end
+
+ describe "when prerelease gems are not available" do
+ it "still works" do
+ build_repo3
+ install_gemfile <<-G
+ source "file://#{gem_repo3}"
+ gem "rack"
+ G
+
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+end
diff --git a/spec/bundler/install/process_lock_spec.rb b/spec/bundler/install/process_lock_spec.rb
new file mode 100644
index 0000000000..be8fd04fdd
--- /dev/null
+++ b/spec/bundler/install/process_lock_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.describe "process lock spec" do
+ describe "when an install operation is already holding a process lock" do
+ before { FileUtils.mkdir_p(default_bundle_path) }
+
+ it "will not run a second concurrent bundle install until the lock is released" do
+ thread = Thread.new do
+ Bundler::ProcessLock.lock(default_bundle_path) do
+ sleep 1 # ignore quality_spec
+ expect(the_bundle).not_to include_gems "rack 1.0"
+ end
+ end
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ thread.join
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ context "when creating a lock raises Errno::ENOTSUP", :ruby => ">= 1.9" do
+ before { allow(File).to receive(:open).and_raise(Errno::ENOTSUP) }
+
+ it "skips creating the lock file and yields" do
+ processed = false
+ Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
+
+ expect(processed).to eq true
+ end
+ end
+ end
+end
diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/redownload_spec.rb
new file mode 100644
index 0000000000..665c64d49a
--- /dev/null
+++ b/spec/bundler/install/redownload_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle install", :bundler => "< 2", :ruby => ">= 2.0" do
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ before { bundle "config major_deprecations yes" }
+
+ shared_examples_for "an option to force redownloading gems" do
+ it "re-installs installed gems" do
+ rack_lib = default_bundle_path("gems/rack-1.0.0/lib/rack.rb")
+
+ bundle! :install
+ rack_lib.open("w") {|f| f.write("blah blah blah") }
+ bundle! :install, flag => true
+
+ expect(out).to include "Installing rack 1.0.0"
+ expect(rack_lib.open(&:read)).to eq("RACK = '1.0.0'\n")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "works on first bundle install" do
+ bundle! :install, flag => true
+
+ expect(out).to include "Installing rack 1.0.0"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ context "with a git gem" do
+ let!(:ref) { build_git("foo", "1.0").ref_for("HEAD", 11) }
+
+ before do
+ gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+ end
+
+ it "re-installs installed gems" do
+ foo_lib = default_bundle_path("bundler/gems/foo-1.0-#{ref}/lib/foo.rb")
+
+ bundle! :install
+ foo_lib.open("w") {|f| f.write("blah blah blah") }
+ bundle! :install, flag => true
+
+ expect(foo_lib.open(&:read)).to eq("FOO = '1.0'\n")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
+ it "works on first bundle install" do
+ bundle! :install, flag => true
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+ end
+ end
+
+ describe "with --force" do
+ it_behaves_like "an option to force redownloading gems" do
+ let(:flag) { "force" }
+ end
+
+ it "shows a deprecation when single flag passed" do
+ bundle! "install --force"
+ expect(out).to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`"
+ end
+
+ it "shows a deprecation when multiple flags passed" do
+ bundle! "install --no-color --force"
+ expect(out).to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`"
+ end
+ end
+
+ describe "with --redownload" do
+ it_behaves_like "an option to force redownloading gems" do
+ let(:flag) { "redownload" }
+ end
+
+ it "does not show a deprecation when single flag passed" do
+ bundle! "install --redownload"
+ expect(out).not_to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`"
+ end
+
+ it "does not show a deprecation when single multiple flags passed" do
+ bundle! "install --no-color --redownload"
+ expect(out).not_to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`"
+ end
+ end
+end
diff --git a/spec/bundler/install/security_policy_spec.rb b/spec/bundler/install/security_policy_spec.rb
new file mode 100644
index 0000000000..7be09d6bd4
--- /dev/null
+++ b/spec/bundler/install/security_policy_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require "rubygems/security"
+
+# unfortunately, testing signed gems with a provided CA is extremely difficult
+# as 'gem cert' is currently the only way to add CAs to the system.
+
+RSpec.describe "policies with unsigned gems" do
+ before do
+ build_security_repo
+ gemfile <<-G
+ source "file://#{security_repo}"
+ gem "rack"
+ gem "signed_gem"
+ G
+ end
+
+ it "will work after you try to deploy without a lock" do
+ bundle "install --deployment"
+ bundle :install
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(the_bundle).to include_gems "rack 1.0", "signed_gem 1.0"
+ end
+
+ it "will fail when given invalid security policy" do
+ bundle "install --trust-policy=InvalidPolicyName"
+ expect(out).to include("RubyGems doesn't know about trust policy")
+ end
+
+ it "will fail with High Security setting due to presence of unsigned gem" do
+ bundle "install --trust-policy=HighSecurity"
+ expect(out).to include("security policy didn't allow")
+ end
+
+ # This spec will fail on RubyGems 2 rc1 due to a bug in policy.rb. the bug is fixed in rc3.
+ it "will fail with Medium Security setting due to presence of unsigned gem", :unless => ENV["RGV"] == "v2.0.0.rc.1" do
+ bundle "install --trust-policy=MediumSecurity"
+ expect(out).to include("security policy didn't allow")
+ end
+
+ it "will succeed with no policy" do
+ bundle "install"
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+end
+
+RSpec.describe "policies with signed gems and no CA" do
+ before do
+ build_security_repo
+ gemfile <<-G
+ source "file://#{security_repo}"
+ gem "signed_gem"
+ G
+ end
+
+ it "will fail with High Security setting, gem is self-signed" do
+ bundle "install --trust-policy=HighSecurity"
+ expect(out).to include("security policy didn't allow")
+ end
+
+ it "will fail with Medium Security setting, gem is self-signed" do
+ bundle "install --trust-policy=MediumSecurity"
+ expect(out).to include("security policy didn't allow")
+ end
+
+ it "will succeed with Low Security setting, low security accepts self signed gem" do
+ bundle "install --trust-policy=LowSecurity"
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(the_bundle).to include_gems "signed_gem 1.0"
+ end
+
+ it "will succeed with no policy" do
+ bundle "install"
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(the_bundle).to include_gems "signed_gem 1.0"
+ end
+end
diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb
new file mode 100644
index 0000000000..7c4b98bfdf
--- /dev/null
+++ b/spec/bundler/install/yanked_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+RSpec.context "when installing a bundle that includes yanked gems" do
+ before(:each) do
+ build_repo4 do
+ build_gem "foo", "9.0.0"
+ end
+ end
+
+ it "throws an error when the original gem version is yanked" do
+ lockfile <<-L
+ GEM
+ remote: file://#{gem_repo4}
+ specs:
+ foo (10.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ foo (= 10.0.0)
+
+ L
+
+ install_gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem "foo", "10.0.0"
+ G
+
+ expect(out).to include("Your bundle is locked to foo (10.0.0)")
+ end
+
+ it "throws the original error when only the Gemfile specifies a gem version that doesn't exist" do
+ install_gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem "foo", "10.0.0"
+ G
+
+ expect(out).not_to include("Your bundle is locked to foo (10.0.0)")
+ expect(out).to include("Could not find gem 'foo (= 10.0.0)' in")
+ end
+end
+
+RSpec.context "when using gem before installing" do
+ it "does not suggest the author has yanked the gem" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "0.9.1"
+ G
+
+ lockfile <<-L
+ GEM
+ remote: file://#{gem_repo1}
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack (= 0.9.1)
+ L
+
+ bundle :list
+
+ expect(out).to include("Could not find rack-0.9.1 in any of the sources")
+ expect(out).to_not include("Your bundle is locked to rack (0.9.1), but that version could not be found in any of the sources listed in your Gemfile.")
+ expect(out).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.")
+ expect(out).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.")
+ end
+end
diff --git a/spec/bundler/lock/git_spec.rb b/spec/bundler/lock/git_spec.rb
new file mode 100644
index 0000000000..14b80483ee
--- /dev/null
+++ b/spec/bundler/lock/git_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle lock with git gems" do
+ before :each do
+ build_git "foo"
+
+ install_gemfile <<-G
+ gem 'foo', :git => "#{lib_path("foo-1.0")}"
+ G
+ end
+
+ it "doesn't break right after running lock" do
+ expect(the_bundle).to include_gems "foo 1.0.0"
+ end
+
+ it "locks a git source to the current ref" do
+ update_git "foo"
+ bundle :install
+
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" unless defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+
+ it "provides correct #full_gem_path" do
+ run <<-RUBY
+ puts Bundler.rubygems.find_name('foo').first.full_gem_path
+ RUBY
+ expect(out).to eq(bundle("info foo --path"))
+ end
+end
diff --git a/spec/bundler/lock/lockfile_bundler_1_spec.rb b/spec/bundler/lock/lockfile_bundler_1_spec.rb
new file mode 100644
index 0000000000..fcdf6ebf0d
--- /dev/null
+++ b/spec/bundler/lock/lockfile_bundler_1_spec.rb
@@ -0,0 +1,1386 @@
+# frozen_string_literal: true
+
+RSpec.describe "the lockfile format", :bundler => "< 2" do
+ include Bundler::GemHelpers
+
+ before { ENV["BUNDLER_SPEC_IGNORE_COMPATIBILITY_GUARD"] = "TRUE" }
+
+ it "generates a simple lockfile for a single source, gem" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "updates the lockfile's bundler version if current ver. is newer" do
+ lockfile <<-L
+ GIT
+ remote: git://github.com/nex3/haml.git
+ revision: 8a2271f
+ specs:
+
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ omg!
+ rack
+
+ BUNDLED WITH
+ 1.8.2
+ L
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not update the lockfile's bundler version if nothing changed during bundle install", :ruby_repo do
+ version = "#{Bundler::VERSION.split(".").first}.0.0.0.a"
+
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{version}
+ L
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{version}
+ G
+ end
+
+ it "updates the lockfile's bundler version if not present" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+ L
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack", "> 0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack (> 0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "outputs a warning if the current is older than lockfile's bundler version" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 9999999.1.0
+ L
+
+ simulate_bundler_version "9999999.0.0" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+ end
+
+ warning_message = "the running version of Bundler (9999999.0.0) is older " \
+ "than the version that created the lockfile (9999999.1.0)"
+ expect(out.scan(warning_message).size).to eq(1)
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+ #{specific_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 9999999.1.0
+ G
+ end
+
+ it "errors if the current is a major version older than lockfile's bundler version" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 9999999.0.0
+ L
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ expect(exitstatus > 0) if exitstatus
+ expect(out).to include("You must use Bundler 9999999 or greater with this lockfile.")
+ end
+
+ it "shows a friendly error when running with a new bundler 2 lockfile" do
+ lockfile <<-L
+ GEM
+ remote: https://rails-assets.org/
+ specs:
+ rails-assets-bootstrap (3.3.4)
+ rails-assets-jquery (>= 1.9.1)
+ rails-assets-jquery (2.1.4)
+
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ rake (10.4.2)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rails-assets-bootstrap!
+ rake
+
+ BUNDLED WITH
+ 9999999.0.0
+ L
+
+ install_gemfile <<-G
+ source 'https://rubygems.org'
+ gem 'rake'
+
+ source 'https://rails-assets.org' do
+ gem 'rails-assets-bootstrap'
+ end
+ G
+
+ expect(exitstatus > 0) if exitstatus
+ expect(out).to include("You must use Bundler 9999999 or greater with this lockfile.")
+ end
+
+ it "warns when updating bundler major version" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 1.10.0
+ L
+
+ simulate_bundler_version "9999999.0.0" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+ end
+
+ expect(out).to include("Warning: the lockfile is being updated to Bundler " \
+ "9999999, after which you will be unable to return to Bundler 1.")
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+ #{specific_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 9999999.0.0
+ G
+ end
+
+ it "generates a simple lockfile for a single source, gem with dependencies" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack-obama"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack-obama
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a simple lockfile for a single source, gem with a version requirement" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack-obama", ">= 1.0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a lockfile wihout credentials for a configured source" do
+ bundle "config http://localgemserver.test/ user:pass"
+
+ install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true)
+ source "http://localgemserver.test/"
+ source "http://user:pass@othergemserver.test/"
+
+ gem "rack-obama", ">= 1.0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: http://localgemserver.test/
+ remote: http://user:pass@othergemserver.test/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates lockfiles with multiple requirements" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "net-sftp"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ net-sftp (1.1.1)
+ net-ssh (>= 1.0.0, < 1.99.0)
+ net-ssh (1.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ net-sftp
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ expect(the_bundle).to include_gems "net-sftp 1.1.1", "net-ssh 1.0.0"
+ end
+
+ it "generates a simple lockfile for a single pinned source, gem with a version requirement" do
+ git = build_git "foo"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ lockfile_should_be <<-G
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("master")}
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not asplode when a platform specific dependency is present and the Gemfile has not been resolved on that platform" do
+ build_lib "omg", :path => lib_path("omg")
+
+ gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ platforms :#{not_local_tag} do
+ gem "omg", :path => "#{lib_path("omg")}"
+ end
+
+ gem "rack"
+ G
+
+ lockfile <<-L
+ GIT
+ remote: git://github.com/nex3/haml.git
+ revision: 8a2271f
+ specs:
+
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{not_local}
+
+ DEPENDENCIES
+ omg!
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle "install"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "serializes global git sources" do
+ git = build_git "foo"
+
+ install_gemfile <<-G
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ lockfile_should_be <<-G
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("master")}
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a lockfile with a ref for a single pinned source, git gem with a branch requirement" do
+ git = build_git "foo"
+ update_git "foo", :branch => "omg"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg"
+ G
+
+ lockfile_should_be <<-G
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("omg")}
+ branch: omg
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a lockfile with a ref for a single pinned source, git gem with a tag requirement" do
+ git = build_git "foo"
+ update_git "foo", :tag => "omg"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :tag => "omg"
+ G
+
+ lockfile_should_be <<-G
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("omg")}
+ tag: omg
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "serializes pinned path sources to the lockfile" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ lockfile_should_be <<-G
+ PATH
+ remote: #{lib_path("foo-1.0")}
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "serializes pinned path sources to the lockfile even when packaging" do
+ build_lib "foo"
+
+ install_gemfile! <<-G
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle! :package, forgotten_command_line_options([:all, :cache_all] => true)
+ bundle! :install, :local => true
+
+ lockfile_should_be <<-G
+ PATH
+ remote: #{lib_path("foo-1.0")}
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "sorts serialized sources by type" do
+ build_lib "foo"
+ bar = build_git "bar"
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ gem "bar", :git => "#{lib_path("bar-1.0")}"
+ G
+
+ lockfile_should_be <<-G
+ GIT
+ remote: #{lib_path("bar-1.0")}
+ revision: #{bar.ref_for("master")}
+ specs:
+ bar (1.0)
+
+ PATH
+ remote: #{lib_path("foo-1.0")}
+ specs:
+ foo (1.0)
+
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ bar!
+ foo!
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "lists gems alphabetically" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "thin"
+ gem "actionpack"
+ gem "rack-obama"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ actionpack (2.3.2)
+ activesupport (= 2.3.2)
+ activesupport (2.3.2)
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+ thin (1.0)
+ rack
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ actionpack
+ rack-obama
+ thin
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "orders dependencies' dependencies in alphabetical order" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rails"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ actionmailer (2.3.2)
+ activesupport (= 2.3.2)
+ actionpack (2.3.2)
+ activesupport (= 2.3.2)
+ activerecord (2.3.2)
+ activesupport (= 2.3.2)
+ activeresource (2.3.2)
+ activesupport (= 2.3.2)
+ activesupport (2.3.2)
+ rails (2.3.2)
+ actionmailer (= 2.3.2)
+ actionpack (= 2.3.2)
+ activerecord (= 2.3.2)
+ activeresource (= 2.3.2)
+ rake (= 10.0.2)
+ rake (10.0.2)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rails
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "orders dependencies by version" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem 'double_deps'
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ double_deps (1.0)
+ net-ssh
+ net-ssh (>= 1.0.0)
+ net-ssh (1.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ double_deps
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add the :require option to the lockfile" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack-obama", ">= 1.0", :require => "rack/obama"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add the :group option to the lockfile" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack-obama", ">= 1.0", :group => :test
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided in a relative fashion and in Gemfile dir" do
+ build_lib "foo", :path => bundled_app("foo")
+
+ install_gemfile <<-G
+ path "foo"
+ gem "foo"
+ G
+
+ lockfile_should_be <<-G
+ PATH
+ remote: foo
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided in a relative fashion and is above Gemfile dir" do
+ build_lib "foo", :path => bundled_app(File.join("..", "foo"))
+
+ install_gemfile <<-G
+ path "../foo"
+ gem "foo"
+ G
+
+ lockfile_should_be <<-G
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided in an absolute fashion but is relative" do
+ build_lib "foo", :path => bundled_app("foo")
+
+ install_gemfile <<-G
+ path File.expand_path("../foo", __FILE__)
+ gem "foo"
+ G
+
+ lockfile_should_be <<-G
+ PATH
+ remote: foo
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided for gemspec" do
+ build_lib("foo", :path => tmp.join("foo"))
+
+ install_gemfile <<-G
+ gemspec :path => "../foo"
+ G
+
+ lockfile_should_be <<-G
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "keeps existing platforms in the lockfile" do
+ lockfile <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ platforms = ["java", generic_local_platform.to_s].sort
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{platforms[0]}
+ #{platforms[1]}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "persists the spec's platform to the lockfile" do
+ build_gem "platform_specific", "1.0.0", :to_system => true do |s|
+ s.platform = Gem::Platform.new("universal-java-16")
+ end
+
+ simulate_platform "universal-java-16"
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "platform_specific"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ platform_specific (1.0-java)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate gems" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ gem "activesupport"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ activesupport (2.3.5)
+ rack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ activesupport
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ gem "rack"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies with versions" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack", "1.0"
+ gem "rack", "1.0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack (= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies in different groups" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack", "1.0", :group => :one
+ gem "rack", "1.0", :group => :two
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack (= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "raises if two different versions are used" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack", "1.0"
+ gem "rack", "1.1"
+ G
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ expect(out).to include "rack (= 1.0) and rack (= 1.1)"
+ end
+
+ it "raises if two different sources are used" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ gem "rack", :git => "git://hubz.com"
+ G
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ expect(out).to include "rack (>= 0) should come from an unspecified source and git://hubz.com (at master)"
+ end
+
+ it "works correctly with multiple version dependencies" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack", "> 0.9", "< 1.0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack (> 0.9, < 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "captures the Ruby version in the lockfile" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ ruby '#{RUBY_VERSION}'
+ gem "rack", "> 0.9", "< 1.0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack (> 0.9, < 1.0)
+
+ RUBY VERSION
+ ruby #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ # Some versions of the Bundler 1.1 RC series introduced corrupted
+ # lockfiles. There were two major problems:
+ #
+ # * multiple copies of the same GIT section appeared in the lockfile
+ # * when this happened, those sections got multiple copies of gems
+ # in those sections.
+ it "fixes corrupted lockfiles" do
+ build_git "omg", :path => lib_path("omg")
+ revision = revision_for(lib_path("omg"))
+
+ gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "omg", :git => "#{lib_path("omg")}", :branch => 'master'
+ G
+
+ bundle "install --path vendor"
+ expect(the_bundle).to include_gems "omg 1.0"
+
+ # Create a Gemfile.lock that has duplicate GIT sections
+ lockfile <<-L
+ GIT
+ remote: #{lib_path("omg")}
+ revision: #{revision}
+ branch: master
+ specs:
+ omg (1.0)
+
+ GIT
+ remote: #{lib_path("omg")}
+ revision: #{revision}
+ branch: master
+ specs:
+ omg (1.0)
+
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+
+ PLATFORMS
+ #{local}
+
+ DEPENDENCIES
+ omg!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ FileUtils.rm_rf(bundled_app("vendor"))
+ bundle "install"
+ expect(the_bundle).to include_gems "omg 1.0"
+
+ # Confirm that duplicate specs do not appear
+ lockfile_should_be(<<-L)
+ GIT
+ remote: #{lib_path("omg")}
+ revision: #{revision}
+ branch: master
+ specs:
+ omg (1.0)
+
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+
+ PLATFORMS
+ #{local}
+
+ DEPENDENCIES
+ omg!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "raises a helpful error message when the lockfile is missing deps" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack_middleware (1.0)
+
+ PLATFORMS
+ #{local}
+
+ DEPENDENCIES
+ rack_middleware
+ L
+
+ install_gemfile <<-G
+ source "file:#{gem_repo1}"
+ gem "rack_middleware"
+ G
+
+ expect(out).to include("Downloading rack_middleware-1.0 revealed dependencies not in the API or the lockfile (#{Gem::Dependency.new("rack", "= 0.9.1")}).").
+ and include("Either installing with `--full-index` or running `bundle update rack_middleware` should fix the problem.")
+ end
+
+ describe "a line ending" do
+ def set_lockfile_mtime_to_known_value
+ time = Time.local(2000, 1, 1, 0, 0, 0)
+ File.utime(time, time, bundled_app("Gemfile.lock"))
+ end
+ before(:each) do
+ build_repo2
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo2}"
+ gem "rack"
+ G
+ set_lockfile_mtime_to_known_value
+ end
+
+ it "generates Gemfile.lock with \\n line endings" do
+ expect(File.read(bundled_app("Gemfile.lock"))).not_to match("\r\n")
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ context "during updates" do
+ it "preserves Gemfile.lock \\n line endings" do
+ update_repo2
+
+ expect { bundle "update", :all => true }.to change { File.mtime(bundled_app("Gemfile.lock")) }
+ expect(File.read(bundled_app("Gemfile.lock"))).not_to match("\r\n")
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+
+ it "preserves Gemfile.lock \\n\\r line endings" do
+ update_repo2
+ win_lock = File.read(bundled_app("Gemfile.lock")).gsub(/\n/, "\r\n")
+ File.open(bundled_app("Gemfile.lock"), "wb") {|f| f.puts(win_lock) }
+ set_lockfile_mtime_to_known_value
+
+ expect { bundle "update", :all => true }.to change { File.mtime(bundled_app("Gemfile.lock")) }
+ expect(File.read(bundled_app("Gemfile.lock"))).to match("\r\n")
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+ end
+
+ context "when nothing changes" do
+ it "preserves Gemfile.lock \\n line endings" do
+ expect do
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup
+ RUBY
+ end.not_to change { File.mtime(bundled_app("Gemfile.lock")) }
+ end
+
+ it "preserves Gemfile.lock \\n\\r line endings" do
+ win_lock = File.read(bundled_app("Gemfile.lock")).gsub(/\n/, "\r\n")
+ File.open(bundled_app("Gemfile.lock"), "wb") {|f| f.puts(win_lock) }
+ set_lockfile_mtime_to_known_value
+
+ expect do
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup
+ RUBY
+ end.not_to change { File.mtime(bundled_app("Gemfile.lock")) }
+ end
+ end
+ end
+
+ it "refuses to install if Gemfile.lock contains conflict markers" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ <<<<<<<
+ rack (1.0.0)
+ =======
+ rack (1.0.1)
+ >>>>>>>
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ install_gemfile(<<-G)
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ G
+
+ expect(last_command.bundler_err).to match(/your Gemfile.lock contains merge conflicts/i)
+ expect(last_command.bundler_err).to match(/git checkout HEAD -- Gemfile.lock/i)
+ end
+end
diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb
new file mode 100644
index 0000000000..53c832445f
--- /dev/null
+++ b/spec/bundler/lock/lockfile_spec.rb
@@ -0,0 +1,1425 @@
+# frozen_string_literal: true
+
+RSpec.describe "the lockfile format", :bundler => "2" do
+ include Bundler::GemHelpers
+
+ before { ENV["BUNDLER_SPEC_IGNORE_COMPATIBILITY_GUARD"] = "TRUE" }
+
+ it "generates a simple lockfile for a single source, gem" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "updates the lockfile's bundler version if current ver. is newer" do
+ lockfile <<-L
+ GIT
+ remote: git://github.com/nex3/haml.git
+ revision: 8a2271f
+ specs:
+
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ omg!
+ rack
+
+ BUNDLED WITH
+ 1.8.2
+ L
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not update the lockfile's bundler version if nothing changed during bundle install" do
+ version = "#{Bundler::VERSION.split(".").first}.0.0.0.a"
+
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{version}
+ L
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{version}
+ G
+ end
+
+ it "updates the lockfile's bundler version if not present" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+ L
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack", "> 0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (> 0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "outputs a warning if the current is older than lockfile's bundler version" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 9999999.1.0
+ L
+
+ simulate_bundler_version "9999999.0.0" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+
+ gem "rack"
+ G
+ end
+
+ warning_message = "the running version of Bundler (9999999.0.0) is older " \
+ "than the version that created the lockfile (9999999.1.0)"
+ expect(last_command.bundler_err).to include warning_message
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 9999999.1.0
+ G
+ end
+
+ it "errors if the current is a major version older than lockfile's bundler version" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 9999999.0.0
+ L
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "rack"
+ G
+
+ expect(last_command).to be_failure
+ expect(last_command.bundler_err).to include("You must use Bundler 9999999 or greater with this lockfile.")
+ end
+
+ it "shows a friendly error when running with a new bundler 2 lockfile" do
+ lockfile <<-L
+ GEM
+ remote: https://rails-assets.org/
+ specs:
+ rails-assets-bootstrap (3.3.4)
+ rails-assets-jquery (>= 1.9.1)
+ rails-assets-jquery (2.1.4)
+
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ rake (10.4.2)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rails-assets-bootstrap!
+ rake
+
+ BUNDLED WITH
+ 9999999.0.0
+ L
+
+ install_gemfile <<-G
+ source 'https://rubygems.org'
+ gem 'rake'
+
+ source 'https://rails-assets.org' do
+ gem 'rails-assets-bootstrap'
+ end
+ G
+
+ expect(last_command).to be_failure
+ expect(out).to include("You must use Bundler 9999999 or greater with this lockfile.")
+ end
+
+ it "warns when updating bundler major version" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 1.10.0
+ L
+
+ simulate_bundler_version "9999999.0.0" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "rack"
+ G
+ end
+
+ expect(out).to include("Warning: the lockfile is being updated to Bundler " \
+ "9999999, after which you will be unable to return to Bundler 1.")
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ 9999999.0.0
+ G
+ end
+
+ it "generates a simple lockfile for a single source, gem with dependencies" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "rack-obama"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a simple lockfile for a single source, gem with a version requirement" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "rack-obama", ">= 1.0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a lockfile without credentials for a configured source" do
+ bundle "config http://localgemserver.test/ user:pass"
+
+ install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true)
+ source "http://localgemserver.test/" do
+
+ end
+
+ source "http://user:pass@othergemserver.test/" do
+ gem "rack-obama", ">= 1.0"
+ end
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ GEM
+ remote: http://localgemserver.test/
+ specs:
+
+ GEM
+ remote: http://user:pass@othergemserver.test/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates lockfiles with multiple requirements" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "net-sftp"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ net-sftp (1.1.1)
+ net-ssh (>= 1.0.0, < 1.99.0)
+ net-ssh (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ net-sftp
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ expect(the_bundle).to include_gems "net-sftp 1.1.1", "net-ssh 1.0.0"
+ end
+
+ it "generates a simple lockfile for a single pinned source, gem with a version requirement", :bundler => "< 2" do
+ git = build_git "foo"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ lockfile_should_be <<-G
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("master")}
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a simple lockfile for a single pinned source, gem with a version requirement" do
+ git = build_git "foo"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("master")}
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not asplode when a platform specific dependency is present and the Gemfile has not been resolved on that platform" do
+ build_lib "omg", :path => lib_path("omg")
+
+ gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ platforms :#{not_local_tag} do
+ gem "omg", :path => "#{lib_path("omg")}"
+ end
+
+ gem "rack"
+ G
+
+ lockfile <<-L
+ GIT
+ remote: git://github.com/nex3/haml.git
+ revision: 8a2271f
+ specs:
+
+ GEM
+ remote: file://localhost#{gem_repo1}//
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{not_local}
+
+ DEPENDENCIES
+ omg!
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle! "install"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "serializes global git sources" do
+ git = build_git "foo"
+
+ install_gemfile <<-G
+ git "#{lib_path("foo-1.0")}" do
+ gem "foo"
+ end
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("master")}
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a lockfile with a ref for a single pinned source, git gem with a branch requirement" do
+ git = build_git "foo"
+ update_git "foo", :branch => "omg"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("omg")}
+ branch: omg
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "generates a lockfile with a ref for a single pinned source, git gem with a tag requirement" do
+ git = build_git "foo"
+ update_git "foo", :tag => "omg"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}", :tag => "omg"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ GIT
+ remote: #{lib_path("foo-1.0")}
+ revision: #{git.ref_for("omg")}
+ tag: omg
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "serializes pinned path sources to the lockfile" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ PATH
+ remote: #{lib_path("foo-1.0")}
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "serializes pinned path sources to the lockfile even when packaging" do
+ build_lib "foo"
+
+ install_gemfile! <<-G
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ G
+
+ bundle! :package, forgotten_command_line_options([:all, :cache_all] => true)
+ bundle! :install, :local => true
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ PATH
+ remote: #{lib_path("foo-1.0")}
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "sorts serialized sources by type" do
+ build_lib "foo"
+ bar = build_git "bar"
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "rack"
+ gem "foo", :path => "#{lib_path("foo-1.0")}"
+ gem "bar", :git => "#{lib_path("bar-1.0")}"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ GIT
+ remote: #{lib_path("bar-1.0")}
+ revision: #{bar.ref_for("master")}
+ specs:
+ bar (1.0)
+
+ PATH
+ remote: #{lib_path("foo-1.0")}
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ bar!
+ foo!
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "lists gems alphabetically" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "thin"
+ gem "actionpack"
+ gem "rack-obama"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ actionpack (2.3.2)
+ activesupport (= 2.3.2)
+ activesupport (2.3.2)
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+ thin (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ actionpack
+ rack-obama
+ thin
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "orders dependencies' dependencies in alphabetical order" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "rails"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ actionmailer (2.3.2)
+ activesupport (= 2.3.2)
+ actionpack (2.3.2)
+ activesupport (= 2.3.2)
+ activerecord (2.3.2)
+ activesupport (= 2.3.2)
+ activeresource (2.3.2)
+ activesupport (= 2.3.2)
+ activesupport (2.3.2)
+ rails (2.3.2)
+ actionmailer (= 2.3.2)
+ actionpack (= 2.3.2)
+ activerecord (= 2.3.2)
+ activeresource (= 2.3.2)
+ rake (= 10.0.2)
+ rake (10.0.2)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rails
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "orders dependencies by version" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem 'double_deps'
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ double_deps (1.0)
+ net-ssh
+ net-ssh (>= 1.0.0)
+ net-ssh (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ double_deps
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add the :require option to the lockfile" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "rack-obama", ">= 1.0", :require => "rack/obama"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add the :group option to the lockfile" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "rack-obama", ">= 1.0", :group => :test
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+ rack-obama (1.0)
+ rack
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack-obama (>= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided in a relative fashion and in Gemfile dir" do
+ build_lib "foo", :path => bundled_app("foo")
+
+ install_gemfile <<-G
+ path "foo" do
+ gem "foo"
+ end
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ PATH
+ remote: foo
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided in a relative fashion and is above Gemfile dir" do
+ build_lib "foo", :path => bundled_app(File.join("..", "foo"))
+
+ install_gemfile <<-G
+ path "../foo" do
+ gem "foo"
+ end
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided in an absolute fashion but is relative" do
+ build_lib "foo", :path => bundled_app("foo")
+
+ install_gemfile <<-G
+ path File.expand_path("../foo", __FILE__) do
+ gem "foo"
+ end
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ PATH
+ remote: foo
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "stores relative paths when the path is provided for gemspec" do
+ build_lib("foo", :path => tmp.join("foo"))
+
+ install_gemfile <<-G
+ gemspec :path => "../foo"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ specs:
+
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "keeps existing platforms in the lockfile" do
+ lockfile <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+
+ gem "rack"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms "java", generic_local_platform, specific_local_platform}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "persists the spec's platform to the lockfile" do
+ build_repo2 do
+ build_gem "platform_specific", "1.0" do |s|
+ s.platform = Gem::Platform.new("universal-java-16")
+ end
+ end
+
+ simulate_platform "universal-java-16"
+
+ install_gemfile! <<-G
+ source "file://localhost#{gem_repo2}"
+ gem "platform_specific"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ platform_specific (1.0-java)
+ platform_specific (1.0-universal-java-16)
+
+ PLATFORMS
+ java
+ universal-java-16
+
+ DEPENDENCIES
+ platform_specific
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate gems" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "rack"
+ G
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "rack"
+ gem "activesupport"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ activesupport (2.3.5)
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ activesupport
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "rack"
+ gem "rack"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies with versions" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "rack", "1.0"
+ gem "rack", "1.0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "does not add duplicate dependencies in different groups" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "rack", "1.0", :group => :one
+ gem "rack", "1.0", :group => :two
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (= 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "raises if two different versions are used" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "rack", "1.0"
+ gem "rack", "1.1"
+ G
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ expect(out).to include "rack (= 1.0) and rack (= 1.1)"
+ end
+
+ it "raises if two different sources are used" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "rack"
+ gem "rack", :git => "git://hubz.com"
+ G
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ expect(out).to include "rack (>= 0) should come from an unspecified source and git://hubz.com (at master)"
+ end
+
+ it "works correctly with multiple version dependencies" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "rack", "> 0.9", "< 1.0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (> 0.9, < 1.0)
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "captures the Ruby version in the lockfile" do
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ ruby '#{RUBY_VERSION}'
+ gem "rack", "> 0.9", "< 1.0"
+ G
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (0.9.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack (> 0.9, < 1.0)
+
+ RUBY VERSION
+ ruby #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ # Some versions of the Bundler 1.1 RC series introduced corrupted
+ # lockfiles. There were two major problems:
+ #
+ # * multiple copies of the same GIT section appeared in the lockfile
+ # * when this happened, those sections got multiple copies of gems
+ # in those sections.
+ it "fixes corrupted lockfiles" do
+ build_git "omg", :path => lib_path("omg")
+ revision = revision_for(lib_path("omg"))
+
+ gemfile <<-G
+ source "file://localhost#{gem_repo1}/"
+ gem "omg", :git => "#{lib_path("omg")}", :branch => 'master'
+ G
+
+ bundle! :install, forgotten_command_line_options(:path => "vendor")
+ expect(the_bundle).to include_gems "omg 1.0"
+
+ # Create a Gemfile.lock that has duplicate GIT sections
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+
+ GIT
+ remote: #{lib_path("omg")}
+ revision: #{revision}
+ branch: master
+ specs:
+ omg (1.0)
+
+ GIT
+ remote: #{lib_path("omg")}
+ revision: #{revision}
+ branch: master
+ specs:
+ omg (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ omg!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ FileUtils.rm_rf(bundled_app("vendor"))
+ bundle "install"
+ expect(the_bundle).to include_gems "omg 1.0"
+
+ # Confirm that duplicate specs do not appear
+ lockfile_should_be(<<-L)
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+
+ GIT
+ remote: #{lib_path("omg")}
+ revision: #{revision}
+ branch: master
+ specs:
+ omg (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ omg!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "raises a helpful error message when the lockfile is missing deps" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack_middleware
+ L
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo1}"
+ gem "rack_middleware"
+ G
+
+ expect(out).to include("Downloading rack_middleware-1.0 revealed dependencies not in the API or the lockfile (#{Gem::Dependency.new("rack", "= 0.9.1")}).").
+ and include("Either installing with `--full-index` or running `bundle update rack_middleware` should fix the problem.")
+ end
+
+ describe "a line ending" do
+ def set_lockfile_mtime_to_known_value
+ time = Time.local(2000, 1, 1, 0, 0, 0)
+ File.utime(time, time, bundled_app("Gemfile.lock"))
+ end
+ before(:each) do
+ build_repo2
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo2}"
+ gem "rack"
+ G
+ set_lockfile_mtime_to_known_value
+ end
+
+ it "generates Gemfile.lock with \\n line endings" do
+ expect(File.read(bundled_app("Gemfile.lock"))).not_to match("\r\n")
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+
+ context "during updates" do
+ it "preserves Gemfile.lock \\n line endings" do
+ update_repo2
+
+ expect { bundle "update", :all => true }.to change { File.mtime(bundled_app("Gemfile.lock")) }
+ expect(File.read(bundled_app("Gemfile.lock"))).not_to match("\r\n")
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+
+ it "preserves Gemfile.lock \\n\\r line endings" do
+ update_repo2
+ win_lock = File.read(bundled_app("Gemfile.lock")).gsub(/\n/, "\r\n")
+ File.open(bundled_app("Gemfile.lock"), "wb") {|f| f.puts(win_lock) }
+ set_lockfile_mtime_to_known_value
+
+ expect { bundle "update", :all => true }.to change { File.mtime(bundled_app("Gemfile.lock")) }
+ expect(File.read(bundled_app("Gemfile.lock"))).to match("\r\n")
+ expect(the_bundle).to include_gems "rack 1.2"
+ end
+ end
+
+ context "when nothing changes" do
+ it "preserves Gemfile.lock \\n line endings" do
+ expect do
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup
+ RUBY
+ end.not_to change { File.mtime(bundled_app("Gemfile.lock")) }
+ end
+
+ it "preserves Gemfile.lock \\n\\r line endings" do
+ win_lock = File.read(bundled_app("Gemfile.lock")).gsub(/\n/, "\r\n")
+ File.open(bundled_app("Gemfile.lock"), "wb") {|f| f.puts(win_lock) }
+ set_lockfile_mtime_to_known_value
+
+ expect do
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup
+ RUBY
+ end.not_to change { File.mtime(bundled_app("Gemfile.lock")) }
+ end
+ end
+ end
+
+ it "refuses to install if Gemfile.lock contains conflict markers" do
+ lockfile <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}//
+ specs:
+ <<<<<<<
+ rack (1.0.0)
+ =======
+ rack (1.0.1)
+ >>>>>>>
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ install_gemfile(<<-G)
+ source "file://localhost#{gem_repo1}/"
+ gem "rack"
+ G
+
+ expect(last_command.bundler_err).to match(/your Gemfile.lock contains merge conflicts/i)
+ expect(last_command.bundler_err).to match(/git checkout HEAD -- Gemfile.lock/i)
+ end
+end
diff --git a/spec/bundler/other/bundle_ruby_spec.rb b/spec/bundler/other/bundle_ruby_spec.rb
new file mode 100644
index 0000000000..a7da9cbec9
--- /dev/null
+++ b/spec/bundler/other/bundle_ruby_spec.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle_ruby", :bundler => "< 2" do
+ context "without patchlevel" do
+ it "returns the ruby version" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.9.3", :engine => 'ruby', :engine_version => '1.9.3'
+
+ gem "foo"
+ G
+
+ bundle_ruby
+
+ expect(out).to include("ruby 1.9.3")
+ end
+
+ it "engine defaults to MRI" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.9.3"
+
+ gem "foo"
+ G
+
+ bundle_ruby
+
+ expect(out).to include("ruby 1.9.3")
+ end
+
+ it "handles jruby" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine => 'jruby', :engine_version => '1.6.5'
+
+ gem "foo"
+ G
+
+ bundle_ruby
+
+ expect(out).to include("ruby 1.8.7 (jruby 1.6.5)")
+ end
+
+ it "handles rbx" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine => 'rbx', :engine_version => '1.2.4'
+
+ gem "foo"
+ G
+
+ bundle_ruby
+
+ expect(out).to include("ruby 1.8.7 (rbx 1.2.4)")
+ end
+
+ it "handles truffleruby", :rubygems => ">= 2.1.0" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "2.5.1", :engine => 'truffleruby', :engine_version => '1.0.0-rc6'
+
+ gem "foo"
+ G
+
+ bundle_ruby
+
+ expect(out).to include("ruby 2.5.1 (truffleruby 1.0.0-rc6)")
+ end
+
+ it "raises an error if engine is used but engine version is not" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine => 'rbx'
+
+ gem "foo"
+ G
+
+ bundle_ruby
+ expect(exitstatus).not_to eq(0) if exitstatus
+
+ bundle_ruby
+ expect(out).to include("Please define :engine_version")
+ end
+
+ it "raises an error if engine_version is used but engine is not" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine_version => '1.2.4'
+
+ gem "foo"
+ G
+
+ bundle_ruby
+ expect(exitstatus).not_to eq(0) if exitstatus
+
+ bundle_ruby
+ expect(out).to include("Please define :engine")
+ end
+
+ it "raises an error if engine version doesn't match ruby version for MRI" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine => 'ruby', :engine_version => '1.2.4'
+
+ gem "foo"
+ G
+
+ bundle_ruby
+ expect(exitstatus).not_to eq(0) if exitstatus
+
+ bundle_ruby
+ expect(out).to include("ruby_version must match the :engine_version for MRI")
+ end
+
+ it "should print if no ruby version is specified" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "foo"
+ G
+
+ bundle_ruby
+
+ expect(out).to include("No ruby version specified")
+ end
+ end
+
+ context "when using patchlevel" do
+ it "returns the ruby version" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.9.3", :patchlevel => '429', :engine => 'ruby', :engine_version => '1.9.3'
+
+ gem "foo"
+ G
+
+ bundle_ruby
+
+ expect(out).to include("ruby 1.9.3p429")
+ end
+
+ it "handles an engine" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.9.3", :patchlevel => '392', :engine => 'jruby', :engine_version => '1.7.4'
+
+ gem "foo"
+ G
+
+ bundle_ruby
+
+ expect(out).to include("ruby 1.9.3p392 (jruby 1.7.4)")
+ end
+ end
+end
diff --git a/spec/bundler/other/cli_dispatch_spec.rb b/spec/bundler/other/cli_dispatch_spec.rb
new file mode 100644
index 0000000000..d17819b394
--- /dev/null
+++ b/spec/bundler/other/cli_dispatch_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle command names" do
+ it "work when given fully" do
+ bundle "install"
+ expect(last_command.bundler_err).to eq("Could not locate Gemfile")
+ expect(last_command.stdboth).not_to include("Ambiguous command")
+ end
+
+ it "work when not ambiguous" do
+ bundle "ins"
+ expect(last_command.bundler_err).to eq("Could not locate Gemfile")
+ expect(last_command.stdboth).not_to include("Ambiguous command")
+ end
+
+ it "print a friendly error when ambiguous" do
+ bundle "in"
+ expect(last_command.bundler_err).to eq("Ambiguous command in matches [info, init, inject, install]")
+ end
+
+ context "when cache_command_is_package is set" do
+ before { bundle! "config cache_command_is_package true" }
+
+ it "dispatches `bundle cache` to the package command" do
+ bundle "cache --verbose"
+ expect(last_command.stdout).to start_with "Running `bundle package --verbose`"
+ end
+ end
+end
diff --git a/spec/bundler/other/compatibility_guard_spec.rb b/spec/bundler/other/compatibility_guard_spec.rb
new file mode 100644
index 0000000000..ac05ebd918
--- /dev/null
+++ b/spec/bundler/other/compatibility_guard_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundler compatibility guard" do
+ context "when the bundler version is 2+" do
+ before { simulate_bundler_version "2.0.a" }
+
+ context "when running on Ruby < 2.3", :ruby => "< 2.3" do
+ before { simulate_rubygems_version "2.6.11" }
+
+ it "raises a friendly error" do
+ bundle :version
+ expect(err).to eq("Bundler 2 requires Ruby 2.3 or later. Either install bundler 1 or update to a supported Ruby version.")
+ end
+ end
+
+ context "when running on RubyGems < 2.5", :ruby => ">= 2.5" do
+ before { simulate_rubygems_version "1.3.6" }
+
+ it "raises a friendly error" do
+ bundle :version
+ expect(err).to eq("Bundler 2 requires RubyGems 2.5 or later. Either install bundler 1 or update to a supported RubyGems version.")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/other/ext_spec.rb b/spec/bundler/other/ext_spec.rb
new file mode 100644
index 0000000000..3f6f8b4928
--- /dev/null
+++ b/spec/bundler/other/ext_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+RSpec.describe "Gem::Specification#match_platform" do
+ it "does not match platforms other than the gem platform" do
+ darwin = gem "lol", "1.0", "platform_specific-1.0-x86-darwin-10"
+ expect(darwin.match_platform(pl("java"))).to eq(false)
+ end
+
+ context "when platform is a string" do
+ it "matches when platform is a string" do
+ lazy_spec = Bundler::LazySpecification.new("lol", "1.0", "universal-mingw32")
+ expect(lazy_spec.match_platform(pl("x86-mingw32"))).to eq(true)
+ expect(lazy_spec.match_platform(pl("x64-mingw32"))).to eq(true)
+ end
+ end
+end
+
+RSpec.describe "Bundler::GemHelpers#generic" do
+ include Bundler::GemHelpers
+
+ it "converts non-windows platforms into ruby" do
+ expect(generic(pl("x86-darwin-10"))).to eq(pl("ruby"))
+ expect(generic(pl("ruby"))).to eq(pl("ruby"))
+ end
+
+ it "converts java platform variants into java" do
+ expect(generic(pl("universal-java-17"))).to eq(pl("java"))
+ expect(generic(pl("java"))).to eq(pl("java"))
+ end
+
+ it "converts mswin platform variants into x86-mswin32" do
+ expect(generic(pl("mswin32"))).to eq(pl("x86-mswin32"))
+ expect(generic(pl("i386-mswin32"))).to eq(pl("x86-mswin32"))
+ expect(generic(pl("x86-mswin32"))).to eq(pl("x86-mswin32"))
+ end
+
+ it "converts 32-bit mingw platform variants into x86-mingw32" do
+ expect(generic(pl("mingw32"))).to eq(pl("x86-mingw32"))
+ expect(generic(pl("i386-mingw32"))).to eq(pl("x86-mingw32"))
+ expect(generic(pl("x86-mingw32"))).to eq(pl("x86-mingw32"))
+ end
+
+ it "converts 64-bit mingw platform variants into x64-mingw32" do
+ expect(generic(pl("x64-mingw32"))).to eq(pl("x64-mingw32"))
+ expect(generic(pl("x86_64-mingw32"))).to eq(pl("x64-mingw32"))
+ end
+end
+
+RSpec.describe "Gem::SourceIndex#refresh!" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ it "does not explode when called", :rubygems => "1.7" do
+ run "Gem.source_index.refresh!"
+ run "Gem::SourceIndex.new([]).refresh!"
+ end
+
+ it "does not explode when called", :rubygems => "< 1.7" do
+ run "Gem.source_index.refresh!"
+ run "Gem::SourceIndex.from_gems_in([]).refresh!"
+ end
+end
diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb
new file mode 100644
index 0000000000..fba177b497
--- /dev/null
+++ b/spec/bundler/other/major_deprecation_spec.rb
@@ -0,0 +1,282 @@
+# frozen_string_literal: true
+
+RSpec.describe "major deprecations", :bundler => "< 2" do
+ let(:warnings) { last_command.bundler_err } # change to err in 2.0
+ let(:warnings_without_version_messages) { warnings.gsub(/#{Spec::Matchers::MAJOR_DEPRECATION}Bundler will only support ruby(gems)? >= .*/, "") }
+
+ context "in a .99 version" do
+ before do
+ simulate_bundler_version "1.99.1"
+ bundle "config --delete major_deprecations"
+ end
+
+ it "prints major deprecations without being configured" do
+ ruby <<-R
+ require "bundler"
+ Bundler::SharedHelpers.major_deprecation(Bundler::VERSION)
+ R
+
+ expect(warnings).to have_major_deprecation("1.99.1")
+ end
+ end
+
+ before do
+ bundle "config major_deprecations true"
+
+ create_file "gems.rb", <<-G
+ source "file:#{gem_repo1}"
+ ruby #{RUBY_VERSION.dump}
+ gem "rack"
+ G
+ bundle! "install"
+ end
+
+ describe "bundle_ruby" do
+ it "prints a deprecation" do
+ bundle_ruby
+ warnings.gsub! "\nruby #{RUBY_VERSION}", ""
+ expect(warnings).to have_major_deprecation "the bundle_ruby executable has been removed in favor of `bundle platform --ruby`"
+ end
+ end
+
+ describe "Bundler" do
+ describe ".clean_env" do
+ it "is deprecated in favor of .original_env" do
+ source = "Bundler.clean_env"
+ bundle "exec ruby -e #{source.dump}"
+ expect(warnings).to have_major_deprecation "`Bundler.clean_env` has weird edge cases, use `.original_env` instead"
+ end
+ end
+
+ describe ".environment" do
+ it "is deprecated in favor of .load" do
+ source = "Bundler.environment"
+ bundle "exec ruby -e #{source.dump}"
+ expect(warnings).to have_major_deprecation "Bundler.environment has been removed in favor of Bundler.load"
+ end
+ end
+
+ shared_examples_for "environmental deprecations" do |trigger|
+ describe "ruby version", :ruby => "< 2.0" do
+ it "requires a newer ruby version" do
+ instance_eval(&trigger)
+ expect(warnings).to have_major_deprecation "Bundler will only support ruby >= 2.0, you are running #{RUBY_VERSION}"
+ end
+ end
+
+ describe "rubygems version", :rubygems => "< 2.0" do
+ it "requires a newer rubygems version" do
+ instance_eval(&trigger)
+ expect(warnings).to have_major_deprecation "Bundler will only support rubygems >= 2.0, you are running #{Gem::VERSION}"
+ end
+ end
+ end
+
+ describe "-rbundler/setup" do
+ it_behaves_like "environmental deprecations", proc { ruby "require 'bundler/setup'" }
+ end
+
+ describe "Bundler.setup" do
+ it_behaves_like "environmental deprecations", proc { ruby "require 'bundler'; Bundler.setup" }
+ end
+
+ describe "bundle check" do
+ it_behaves_like "environmental deprecations", proc { bundle :check }
+ end
+
+ describe "bundle update --quiet" do
+ it "does not print any deprecations" do
+ bundle :update, :quiet => true
+ expect(warnings_without_version_messages).not_to have_major_deprecation
+ end
+ end
+
+ describe "bundle update" do
+ before do
+ create_file("gems.rb", "")
+ bundle! "install"
+ end
+
+ it "warns when no options are given" do
+ bundle! "update"
+ expect(warnings).to have_major_deprecation a_string_including("Pass --all to `bundle update` to update everything")
+ end
+
+ it "does not warn when --all is passed" do
+ bundle! "update --all"
+ expect(warnings_without_version_messages).not_to have_major_deprecation
+ end
+ end
+
+ describe "bundle install --binstubs" do
+ it "should output a deprecation warning" do
+ gemfile <<-G
+ gem 'rack'
+ G
+
+ bundle :install, :binstubs => true
+ expect(warnings).to have_major_deprecation a_string_including("The --binstubs option will be removed")
+ end
+ end
+ end
+
+ context "when bundle is run" do
+ it "should not warn about gems.rb" do
+ create_file "gems.rb", <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle :install
+ expect(warnings_without_version_messages).not_to have_major_deprecation
+ end
+
+ it "should print a Gemfile deprecation warning" do
+ create_file "gems.rb"
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ expect(the_bundle).to include_gem "rack 1.0"
+
+ expect(warnings).to have_major_deprecation a_string_including("gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock.")
+ end
+
+ context "with flags" do
+ it "should print a deprecation warning about autoremembering flags" do
+ install_gemfile <<-G, :path => "vendor/bundle"
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ expect(warnings).to have_major_deprecation a_string_including(
+ "flags passed to commands will no longer be automatically remembered."
+ )
+ end
+ end
+ end
+
+ context "when Bundler.setup is run in a ruby script" do
+ it "should print a single deprecation warning" do
+ create_file "gems.rb"
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :group => :test
+ G
+
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ require 'bundler/vendored_thor'
+
+ Bundler.ui = Bundler::UI::Shell.new
+ Bundler.setup
+ Bundler.setup
+ RUBY
+
+ expect(warnings_without_version_messages).to have_major_deprecation("gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock.")
+ end
+ end
+
+ context "when `bundler/deployment` is required in a ruby script" do
+ it "should print a capistrano deprecation warning" do
+ ruby(<<-RUBY)
+ require 'bundler/deployment'
+ RUBY
+
+ expect(warnings).to have_major_deprecation("Bundler no longer integrates " \
+ "with Capistrano, but Capistrano provides " \
+ "its own integration with Bundler via the " \
+ "capistrano-bundler gem. Use it instead.")
+ end
+ end
+
+ describe Bundler::Dsl do
+ before do
+ @rubygems = double("rubygems")
+ allow(Bundler::Source::Rubygems).to receive(:new) { @rubygems }
+ end
+
+ context "with github gems" do
+ it "warns about the https change" do
+ msg = <<-EOS
+The :github git source is deprecated, and will be removed in Bundler 2.0. Change any "reponame" :github sources to "username/reponame". Add this code to the top of your Gemfile to ensure it continues to work:
+
+ git_source(:github) {|repo_name| "https://github.com/\#{repo_name}.git" }
+
+ EOS
+ expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, msg)
+ subject.gem("sparks", :github => "indirect/sparks")
+ end
+
+ it "upgrades to https on request" do
+ Bundler.settings.temporary "github.https" => true
+ msg = <<-EOS
+The :github git source is deprecated, and will be removed in Bundler 2.0. Change any "reponame" :github sources to "username/reponame". Add this code to the top of your Gemfile to ensure it continues to work:
+
+ git_source(:github) {|repo_name| "https://github.com/\#{repo_name}.git" }
+
+ EOS
+ expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, msg)
+ expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, "The `github.https` setting will be removed")
+ subject.gem("sparks", :github => "indirect/sparks")
+ github_uri = "https://github.com/indirect/sparks.git"
+ expect(subject.dependencies.first.source.uri).to eq(github_uri)
+ end
+ end
+
+ context "with bitbucket gems" do
+ it "warns about removal" do
+ allow(Bundler.ui).to receive(:deprecate)
+ msg = <<-EOS
+The :bitbucket git source is deprecated, and will be removed in Bundler 2.0. Add this code to the top of your Gemfile to ensure it continues to work:
+
+ git_source(:bitbucket) do |repo_name|
+ user_name, repo_name = repo_name.split("/")
+ repo_name ||= user_name
+ "https://\#{user_name}@bitbucket.org/\#{user_name}/\#{repo_name}.git"
+ end
+
+ EOS
+ expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, msg)
+ subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails")
+ end
+ end
+
+ context "with gist gems" do
+ it "warns about removal" do
+ allow(Bundler.ui).to receive(:deprecate)
+ msg = "The :gist git source is deprecated, and will be removed " \
+ "in Bundler 2.0. Add this code to the top of your Gemfile to ensure it " \
+ "continues to work:\n\n git_source(:gist) {|repo_name| " \
+ "\"https://gist.github.com/\#{repo_name}.git\" }\n\n"
+ expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, msg)
+ subject.gem("not-really-a-gem", :gist => "1234")
+ end
+ end
+ end
+
+ context "bundle show" do
+ it "prints a deprecation warning" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle! :show
+
+ warnings.gsub!(/gems included.*?\[DEPRECATED/im, "[DEPRECATED")
+
+ expect(warnings).to have_major_deprecation a_string_including("use `bundle list` instead of `bundle show`")
+ end
+ end
+
+ context "bundle console" do
+ it "prints a deprecation warning" do
+ bundle "console"
+
+ expect(warnings).to have_major_deprecation \
+ a_string_including("bundle console will be replaced by `bin/console` generated by `bundle gem <name>`")
+ end
+ end
+end
diff --git a/spec/bundler/other/platform_spec.rb b/spec/bundler/other/platform_spec.rb
new file mode 100644
index 0000000000..ca74945563
--- /dev/null
+++ b/spec/bundler/other/platform_spec.rb
@@ -0,0 +1,1312 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle platform" do
+ context "without flags" do
+ let(:bundle_platform_platforms_string) do
+ platforms = [rb]
+ platforms.unshift(specific_local_platform) if Bundler.feature_flag.bundler_2_mode?
+ platforms.map {|pl| "* #{pl}" }.join("\n")
+ end
+
+ it "returns all the output" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ #{ruby_version_correct}
+
+ gem "foo"
+ G
+
+ bundle "platform"
+ expect(out).to eq(<<-G.chomp)
+Your platform is: #{RUBY_PLATFORM}
+
+Your app has gems that work on these platforms:
+#{bundle_platform_platforms_string}
+
+Your Gemfile specifies a Ruby version requirement:
+* ruby #{RUBY_VERSION}
+
+Your current platform satisfies the Ruby version requirement.
+G
+ end
+
+ it "returns all the output including the patchlevel" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ #{ruby_version_correct_patchlevel}
+
+ gem "foo"
+ G
+
+ bundle "platform"
+ expect(out).to eq(<<-G.chomp)
+Your platform is: #{RUBY_PLATFORM}
+
+Your app has gems that work on these platforms:
+#{bundle_platform_platforms_string}
+
+Your Gemfile specifies a Ruby version requirement:
+* ruby #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
+
+Your current platform satisfies the Ruby version requirement.
+G
+ end
+
+ it "doesn't print ruby version requirement if it isn't specified" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "foo"
+ G
+
+ bundle "platform"
+ expect(out).to eq(<<-G.chomp)
+Your platform is: #{RUBY_PLATFORM}
+
+Your app has gems that work on these platforms:
+#{bundle_platform_platforms_string}
+
+Your Gemfile does not specify a Ruby version requirement.
+G
+ end
+
+ it "doesn't match the ruby version requirement" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ #{ruby_version_incorrect}
+
+ gem "foo"
+ G
+
+ bundle "platform"
+ expect(out).to eq(<<-G.chomp)
+Your platform is: #{RUBY_PLATFORM}
+
+Your app has gems that work on these platforms:
+#{bundle_platform_platforms_string}
+
+Your Gemfile specifies a Ruby version requirement:
+* ruby #{not_local_ruby_version}
+
+Your Ruby version is #{RUBY_VERSION}, but your Gemfile specified #{not_local_ruby_version}
+G
+ end
+ end
+
+ context "--ruby" do
+ it "returns ruby version when explicit" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.9.3", :engine => 'ruby', :engine_version => '1.9.3'
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 1.9.3")
+ end
+
+ it "defaults to MRI" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.9.3"
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 1.9.3")
+ end
+
+ it "handles jruby" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine => 'jruby', :engine_version => '1.6.5'
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 1.8.7 (jruby 1.6.5)")
+ end
+
+ it "handles rbx" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine => 'rbx', :engine_version => '1.2.4'
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 1.8.7 (rbx 1.2.4)")
+ end
+
+ it "handles truffleruby", :rubygems => ">= 2.1.0" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "2.5.1", :engine => 'truffleruby', :engine_version => '1.0.0-rc6'
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("ruby 2.5.1 (truffleruby 1.0.0-rc6)")
+ end
+
+ it "raises an error if engine is used but engine version is not" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine => 'rbx'
+
+ gem "foo"
+ G
+
+ bundle "platform"
+
+ expect(exitstatus).not_to eq(0) if exitstatus
+ end
+
+ it "raises an error if engine_version is used but engine is not" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine_version => '1.2.4'
+
+ gem "foo"
+ G
+
+ bundle "platform"
+
+ expect(exitstatus).not_to eq(0) if exitstatus
+ end
+
+ it "raises an error if engine version doesn't match ruby version for MRI" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ ruby "1.8.7", :engine => 'ruby', :engine_version => '1.2.4'
+
+ gem "foo"
+ G
+
+ bundle "platform"
+
+ expect(exitstatus).not_to eq(0) if exitstatus
+ end
+
+ it "should print if no ruby version is specified" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "foo"
+ G
+
+ bundle "platform --ruby"
+
+ expect(out).to eq("No ruby version specified")
+ end
+
+ it "handles when there is a locked requirement" do
+ gemfile <<-G
+ ruby "< 1.8.7"
+ G
+
+ lockfile <<-L
+ GEM
+ specs:
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+
+ RUBY VERSION
+ ruby 1.0.0p127
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ bundle! "platform --ruby"
+ expect(out).to eq("ruby 1.0.0p127")
+ end
+
+ it "handles when there is a requirement in the gemfile" do
+ gemfile <<-G
+ ruby ">= 1.8.7"
+ G
+
+ bundle! "platform --ruby"
+ expect(out).to eq("ruby 1.8.7")
+ end
+
+ it "handles when there are multiple requirements in the gemfile" do
+ gemfile <<-G
+ ruby ">= 1.8.7", "< 2.0.0"
+ G
+
+ bundle! "platform --ruby"
+ expect(out).to eq("ruby 1.8.7")
+ end
+ end
+
+ let(:ruby_version_correct) { "ruby \"#{RUBY_VERSION}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{local_engine_version}\"" }
+ let(:ruby_version_correct_engineless) { "ruby \"#{RUBY_VERSION}\"" }
+ let(:ruby_version_correct_patchlevel) { "#{ruby_version_correct}, :patchlevel => '#{RUBY_PATCHLEVEL}'" }
+ let(:ruby_version_incorrect) { "ruby \"#{not_local_ruby_version}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{not_local_ruby_version}\"" }
+ let(:engine_incorrect) { "ruby \"#{RUBY_VERSION}\", :engine => \"#{not_local_tag}\", :engine_version => \"#{RUBY_VERSION}\"" }
+ let(:engine_version_incorrect) { "ruby \"#{RUBY_VERSION}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{not_local_engine_version}\"" }
+ let(:patchlevel_incorrect) { "#{ruby_version_correct}, :patchlevel => '#{not_local_patchlevel}'" }
+ let(:patchlevel_fixnum) { "#{ruby_version_correct}, :patchlevel => #{RUBY_PATCHLEVEL}1" }
+
+ def should_be_ruby_version_incorrect
+ expect(exitstatus).to eq(18) if exitstatus
+ expect(out).to be_include("Your Ruby version is #{RUBY_VERSION}, but your Gemfile specified #{not_local_ruby_version}")
+ end
+
+ def should_be_engine_incorrect
+ expect(exitstatus).to eq(18) if exitstatus
+ expect(out).to be_include("Your Ruby engine is #{local_ruby_engine}, but your Gemfile specified #{not_local_tag}")
+ end
+
+ def should_be_engine_version_incorrect
+ expect(exitstatus).to eq(18) if exitstatus
+ expect(out).to be_include("Your #{local_ruby_engine} version is #{local_engine_version}, but your Gemfile specified #{local_ruby_engine} #{not_local_engine_version}")
+ end
+
+ def should_be_patchlevel_incorrect
+ expect(exitstatus).to eq(18) if exitstatus
+ expect(out).to be_include("Your Ruby patchlevel is #{RUBY_PATCHLEVEL}, but your Gemfile specified #{not_local_patchlevel}")
+ end
+
+ def should_be_patchlevel_fixnum
+ expect(exitstatus).to eq(18) if exitstatus
+ expect(out).to be_include("The Ruby patchlevel in your Gemfile must be a string")
+ end
+
+ context "bundle install" do
+ it "installs fine when the ruby version matches" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{ruby_version_correct}
+ G
+
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+
+ it "installs fine with any engine" do
+ simulate_ruby_engine "jruby" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+ end
+
+ it "installs fine when the patchlevel matches" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{ruby_version_correct_patchlevel}
+ G
+
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+
+ it "doesn't install when the ruby version doesn't match" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{ruby_version_incorrect}
+ G
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ should_be_ruby_version_incorrect
+ end
+
+ it "doesn't install when engine doesn't match" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{engine_incorrect}
+ G
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ should_be_engine_incorrect
+ end
+
+ it "doesn't install when engine version doesn't match" do
+ simulate_ruby_engine "jruby" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{engine_version_incorrect}
+ G
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ should_be_engine_version_incorrect
+ end
+ end
+
+ it "doesn't install when patchlevel doesn't match" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle check" do
+ it "checks fine when the ruby version matches" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{ruby_version_correct}
+ G
+
+ bundle :check
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(out).to eq("Resolving dependencies...\nThe Gemfile's dependencies are satisfied")
+ end
+
+ it "checks fine with any engine" do
+ simulate_ruby_engine "jruby" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle :check
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(out).to eq("Resolving dependencies...\nThe Gemfile's dependencies are satisfied")
+ end
+ end
+
+ it "fails when ruby version doesn't match" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle :check
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when engine doesn't match" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{engine_incorrect}
+ G
+
+ bundle :check
+ should_be_engine_incorrect
+ end
+
+ it "fails when engine version doesn't match" do
+ simulate_ruby_engine "ruby" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{engine_version_incorrect}
+ G
+
+ bundle :check
+ should_be_engine_version_incorrect
+ end
+ end
+
+ it "fails when patchlevel doesn't match" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle :check
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle update" do
+ before do
+ build_repo2
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ gem "rack-obama"
+ G
+ end
+
+ it "updates successfully when the ruby version matches" do
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{ruby_version_correct}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", :all => bundle_update_requires_all?
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
+ end
+
+ it "updates fine with any engine" do
+ simulate_ruby_engine "jruby" do
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{ruby_version_correct_engineless}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "update", :all => bundle_update_requires_all?
+ expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0"
+ end
+ end
+
+ it "fails when ruby version doesn't match" do
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{ruby_version_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle :update, :all => bundle_update_requires_all?
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when ruby engine doesn't match" do
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{engine_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle :update, :all => bundle_update_requires_all?
+ should_be_engine_incorrect
+ end
+
+ it "fails when ruby engine version doesn't match" do
+ simulate_ruby_engine "jruby" do
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport"
+ gem "rack-obama"
+
+ #{engine_version_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle :update, :all => bundle_update_requires_all?
+ should_be_engine_version_incorrect
+ end
+ end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle :update, :all => bundle_update_requires_all?
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle info" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+ end
+
+ it "prints path if ruby version is correct" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+
+ #{ruby_version_correct}
+ G
+
+ bundle "info rails --path"
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+
+ it "prints path if ruby version is correct for any engine" do
+ simulate_ruby_engine "jruby" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle "info rails --path"
+ expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
+ end
+ end
+
+ it "fails if ruby version doesn't match", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle "show rails"
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails if engine doesn't match", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+
+ #{engine_incorrect}
+ G
+
+ bundle "show rails"
+ should_be_engine_incorrect
+ end
+
+ it "fails if engine version doesn't match", :bundler => "< 2" do
+ simulate_ruby_engine "jruby" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+
+ #{engine_version_incorrect}
+ G
+
+ bundle "show rails"
+ should_be_engine_version_incorrect
+ end
+ end
+
+ it "fails when patchlevel doesn't match", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ end
+
+ bundle "show rails"
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle cache" do
+ before do
+ install_gemfile <<-G
+ source "file:#{gem_repo1}"
+ gem 'rack'
+ G
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches" do
+ gemfile <<-G
+ gem 'rack'
+
+ #{ruby_version_correct}
+ G
+
+ bundle :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches for any engine" do
+ simulate_ruby_engine "jruby" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo1}"
+ gem 'rack'
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle! :cache
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+ end
+
+ it "fails if the ruby version doesn't match" do
+ gemfile <<-G
+ gem 'rack'
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle :cache
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails if the engine doesn't match" do
+ gemfile <<-G
+ gem 'rack'
+
+ #{engine_incorrect}
+ G
+
+ bundle :cache
+ should_be_engine_incorrect
+ end
+
+ it "fails if the engine version doesn't match" do
+ simulate_ruby_engine "jruby" do
+ gemfile <<-G
+ gem 'rack'
+
+ #{engine_version_incorrect}
+ G
+
+ bundle :cache
+ should_be_engine_version_incorrect
+ end
+ end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle :cache
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle pack" do
+ before do
+ install_gemfile! <<-G
+ source "file:#{gem_repo1}"
+ gem 'rack'
+ G
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches" do
+ gemfile <<-G
+ gem 'rack'
+
+ #{ruby_version_correct}
+ G
+
+ bundle :pack
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+
+ it "copies the .gem file to vendor/cache when ruby version matches any engine" do
+ simulate_ruby_engine "jruby" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo1}"
+ gem 'rack'
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle :pack
+ expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
+ end
+ end
+
+ it "fails if the ruby version doesn't match" do
+ gemfile <<-G
+ gem 'rack'
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle :pack
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails if the engine doesn't match" do
+ gemfile <<-G
+ gem 'rack'
+
+ #{engine_incorrect}
+ G
+
+ bundle :pack
+ should_be_engine_incorrect
+ end
+
+ it "fails if the engine version doesn't match" do
+ simulate_ruby_engine "jruby" do
+ gemfile <<-G
+ gem 'rack'
+
+ #{engine_version_incorrect}
+ G
+
+ bundle :pack
+ should_be_engine_version_incorrect
+ end
+ end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle :pack
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle exec" do
+ before do
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ system_gems "rack-1.0.0", "rack-0.9.1", :path => :bundle_path
+ end
+
+ it "activates the correct gem when ruby version matches" do
+ gemfile <<-G
+ gem "rack", "0.9.1"
+
+ #{ruby_version_correct}
+ G
+
+ bundle "exec rackup"
+ expect(out).to eq("0.9.1")
+ end
+
+ it "activates the correct gem when ruby version matches any engine" do
+ simulate_ruby_engine "jruby" do
+ system_gems "rack-1.0.0", "rack-0.9.1", :path => :bundle_path
+ gemfile <<-G
+ gem "rack", "0.9.1"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle "exec rackup"
+ expect(out).to eq("0.9.1")
+ end
+ end
+
+ it "fails when the ruby version doesn't match" do
+ gemfile <<-G
+ gem "rack", "0.9.1"
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle "exec rackup"
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when the engine doesn't match" do
+ gemfile <<-G
+ gem "rack", "0.9.1"
+
+ #{engine_incorrect}
+ G
+
+ bundle "exec rackup"
+ should_be_engine_incorrect
+ end
+
+ # it "fails when the engine version doesn't match" do
+ # simulate_ruby_engine "jruby" do
+ # gemfile <<-G
+ # gem "rack", "0.9.1"
+ #
+ # #{engine_version_incorrect}
+ # G
+ #
+ # bundle "exec rackup"
+ # should_be_engine_version_incorrect
+ # end
+ # end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle "exec rackup"
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle console", :bundler => "< 2" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+ G
+ end
+
+ it "starts IRB with the default group loaded when ruby version matches" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{ruby_version_correct}
+ G
+
+ bundle "console" do |input, _, _|
+ input.puts("puts RACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+
+ it "starts IRB with the default group loaded when ruby version matches any engine" do
+ simulate_ruby_engine "jruby" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle "console" do |input, _, _|
+ input.puts("puts RACK")
+ input.puts("exit")
+ end
+ expect(out).to include("0.9.1")
+ end
+ end
+
+ it "fails when ruby version doesn't match" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle "console"
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when engine doesn't match" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{engine_incorrect}
+ G
+
+ bundle "console"
+ should_be_engine_incorrect
+ end
+
+ it "fails when engine version doesn't match" do
+ simulate_ruby_engine "jruby" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{engine_version_incorrect}
+ G
+
+ bundle "console"
+ should_be_engine_version_incorrect
+ end
+ end
+
+ it "fails when patchlevel doesn't match" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ gem "rack_middleware", :group => :development
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle "console"
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "Bundler.setup" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "yard"
+ gem "rack", :group => :test
+ G
+
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ end
+
+ it "makes a Gemfile.lock if setup succeeds" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "yard"
+ gem "rack"
+
+ #{ruby_version_correct}
+ G
+
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ run "1"
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+
+ it "makes a Gemfile.lock if setup succeeds for any engine" do
+ simulate_ruby_engine "jruby" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "yard"
+ gem "rack"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ run "1"
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+ end
+
+ it "fails when ruby version doesn't match" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "yard"
+ gem "rack"
+
+ #{ruby_version_incorrect}
+ G
+
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ ruby <<-R
+ require 'rubygems'
+ require 'bundler/setup'
+ R
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when engine doesn't match" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "yard"
+ gem "rack"
+
+ #{engine_incorrect}
+ G
+
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ ruby <<-R
+ require 'rubygems'
+ require 'bundler/setup'
+ R
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ should_be_engine_incorrect
+ end
+
+ it "fails when engine version doesn't match" do
+ simulate_ruby_engine "jruby" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "yard"
+ gem "rack"
+
+ #{engine_version_incorrect}
+ G
+
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ ruby <<-R
+ require 'rubygems'
+ require 'bundler/setup'
+ R
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ should_be_engine_version_incorrect
+ end
+ end
+
+ it "fails when patchlevel doesn't match" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "yard"
+ gem "rack"
+
+ #{patchlevel_incorrect}
+ G
+
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ ruby <<-R
+ require 'rubygems'
+ require 'bundler/setup'
+ R
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ context "bundle outdated" do
+ before do
+ build_repo2 do
+ build_git "foo", :path => lib_path("foo")
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+ G
+ end
+
+ it "returns list of outdated gems when the ruby version matches" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{ruby_version_correct}
+ G
+
+ bundle "outdated"
+ expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5")
+ expect(out).to include("foo (newest 1.0")
+ end
+
+ it "returns list of outdated gems when the ruby version matches for any engine" do
+ simulate_ruby_engine "jruby" do
+ bundle! :install
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{ruby_version_correct_engineless}
+ G
+
+ bundle "outdated"
+ expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)")
+ expect(out).to include("foo (newest 1.0")
+ end
+ end
+
+ it "fails when the ruby version doesn't match" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{ruby_version_incorrect}
+ G
+
+ bundle "outdated"
+ should_be_ruby_version_incorrect
+ end
+
+ it "fails when the engine doesn't match" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{engine_incorrect}
+ G
+
+ bundle "outdated"
+ should_be_engine_incorrect
+ end
+
+ it "fails when the engine version doesn't match" do
+ simulate_ruby_engine "jruby" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{engine_version_incorrect}
+ G
+
+ bundle "outdated"
+ should_be_engine_version_incorrect
+ end
+ end
+
+ it "fails when the patchlevel doesn't match" do
+ simulate_ruby_engine "jruby" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{patchlevel_incorrect}
+ G
+
+ bundle "outdated"
+ should_be_patchlevel_incorrect
+ end
+ end
+
+ it "fails when the patchlevel is a fixnum" do
+ simulate_ruby_engine "jruby" do
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ update_git "foo", :path => lib_path("foo")
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "activesupport", "2.3.5"
+ gem "foo", :git => "#{lib_path("foo")}"
+
+ #{patchlevel_fixnum}
+ G
+
+ bundle "outdated"
+ should_be_patchlevel_fixnum
+ end
+ end
+ end
+end
diff --git a/spec/bundler/other/ssl_cert_spec.rb b/spec/bundler/other/ssl_cert_spec.rb
new file mode 100644
index 0000000000..6d957276fc
--- /dev/null
+++ b/spec/bundler/other/ssl_cert_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require "bundler/ssl_certs/certificate_manager"
+
+RSpec.describe "SSL Certificates", :rubygems_master do
+ hosts = %w[
+ rubygems.org
+ index.rubygems.org
+ rubygems.global.ssl.fastly.net
+ staging.rubygems.org
+ ]
+
+ hosts.each do |host|
+ it "can securely connect to #{host}", :realworld do
+ Bundler::SSLCerts::CertificateManager.new.connect_to(host)
+ end
+ end
+end
diff --git a/spec/bundler/plugins/command_spec.rb b/spec/bundler/plugins/command_spec.rb
new file mode 100644
index 0000000000..999d8b722b
--- /dev/null
+++ b/spec/bundler/plugins/command_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+RSpec.describe "command plugins" do
+ before do
+ build_repo2 do
+ build_plugin "command-mah" do |s|
+ s.write "plugins.rb", <<-RUBY
+ module Mah
+ class Plugin < Bundler::Plugin::API
+ command "mahcommand" # declares the command
+
+ def exec(command, args)
+ puts "MahHello"
+ end
+ end
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install command-mah --source file://#{gem_repo2}"
+ end
+
+ it "executes without arguments" do
+ expect(out).to include("Installed plugin command-mah")
+
+ bundle "mahcommand"
+ expect(out).to eq("MahHello")
+ end
+
+ it "accepts the arguments" do
+ build_repo2 do
+ build_plugin "the-echoer" do |s|
+ s.write "plugins.rb", <<-RUBY
+ module Resonance
+ class Echoer
+ # Another method to declare the command
+ Bundler::Plugin::API.command "echo", self
+
+ def exec(command, args)
+ puts "You gave me \#{args.join(", ")}"
+ end
+ end
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install the-echoer --source file://#{gem_repo2}"
+ expect(out).to include("Installed plugin the-echoer")
+
+ bundle "echo tacos tofu lasange"
+ expect(out).to eq("You gave me tacos, tofu, lasange")
+ end
+
+ it "raises error on redeclaration of command" do
+ build_repo2 do
+ build_plugin "copycat" do |s|
+ s.write "plugins.rb", <<-RUBY
+ module CopyCat
+ class Cheater < Bundler::Plugin::API
+ command "mahcommand", self
+
+ def exec(command, args)
+ end
+ end
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install copycat --source file://#{gem_repo2}"
+
+ expect(out).not_to include("Installed plugin copycat")
+
+ expect(out).to include("Failed to install plugin")
+
+ expect(out).to include("Command(s) `mahcommand` declared by copycat are already registered.")
+ end
+end
diff --git a/spec/bundler/plugins/hook_spec.rb b/spec/bundler/plugins/hook_spec.rb
new file mode 100644
index 0000000000..53062095e2
--- /dev/null
+++ b/spec/bundler/plugins/hook_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+RSpec.describe "hook plugins" do
+ context "before-install-all hook" do
+ before do
+ build_repo2 do
+ build_plugin "before-install-all-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_INSTALL_ALL do |deps|
+ puts "gems to be installed \#{deps.map(&:name).join(", ")}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install before-install-all-plugin --source file://#{gem_repo2}"
+ end
+
+ it "runs before all rubygems are installed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rake"
+ gem "rack"
+ G
+
+ expect(out).to include "gems to be installed rake, rack"
+ end
+ end
+
+ context "before-install hook" do
+ before do
+ build_repo2 do
+ build_plugin "before-install-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_INSTALL do |spec_install|
+ puts "installing gem \#{spec_install.name}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install before-install-plugin --source file://#{gem_repo2}"
+ end
+
+ it "runs before each rubygem is installed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rake"
+ gem "rack"
+ G
+
+ expect(out).to include "installing gem rake"
+ expect(out).to include "installing gem rack"
+ end
+ end
+
+ context "after-install-all hook" do
+ before do
+ build_repo2 do
+ build_plugin "after-install-all-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_INSTALL_ALL do |deps|
+ puts "installed gems \#{deps.map(&:name).join(", ")}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install after-install-all-plugin --source file://#{gem_repo2}"
+ end
+
+ it "runs after each rubygem is installed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rake"
+ gem "rack"
+ G
+
+ expect(out).to include "installed gems rake, rack"
+ end
+ end
+
+ context "after-install hook" do
+ before do
+ build_repo2 do
+ build_plugin "after-install-plugin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_INSTALL do |spec_install|
+ puts "installed gem \#{spec_install.name} : \#{spec_install.state}"
+ end
+ RUBY
+ end
+ end
+
+ bundle "plugin install after-install-plugin --source file://#{gem_repo2}"
+ end
+
+ it "runs after each rubygem is installed" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rake"
+ gem "rack"
+ G
+
+ expect(out).to include "installed gem rake : installed"
+ expect(out).to include "installed gem rack : installed"
+ end
+ end
+end
diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb
new file mode 100644
index 0000000000..9304d78062
--- /dev/null
+++ b/spec/bundler/plugins/install_spec.rb
@@ -0,0 +1,257 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundler plugin install" do
+ before do
+ build_repo2 do
+ build_plugin "foo"
+ build_plugin "kung-foo"
+ end
+ end
+
+ it "shows proper message when gem in not found in the source" do
+ bundle "plugin install no-foo --source file://#{gem_repo1}"
+
+ expect(out).to include("Could not find")
+ plugin_should_not_be_installed("no-foo")
+ end
+
+ it "installs from rubygems source" do
+ bundle "plugin install foo --source file://#{gem_repo2}"
+
+ expect(out).to include("Installed plugin foo")
+ plugin_should_be_installed("foo")
+ end
+
+ it "installs multiple plugins" do
+ bundle "plugin install foo kung-foo --source file://#{gem_repo2}"
+
+ expect(out).to include("Installed plugin foo")
+ expect(out).to include("Installed plugin kung-foo")
+
+ plugin_should_be_installed("foo", "kung-foo")
+ end
+
+ it "uses the same version for multiple plugins" do
+ update_repo2 do
+ build_plugin "foo", "1.1"
+ build_plugin "kung-foo", "1.1"
+ end
+
+ bundle "plugin install foo kung-foo --version '1.0' --source file://#{gem_repo2}"
+
+ expect(out).to include("Installing foo 1.0")
+ expect(out).to include("Installing kung-foo 1.0")
+ plugin_should_be_installed("foo", "kung-foo")
+ end
+
+ it "works with different load paths" do
+ build_repo2 do
+ build_plugin "testing" do |s|
+ s.write "plugins.rb", <<-RUBY
+ require "fubar"
+ class Test < Bundler::Plugin::API
+ command "check2"
+
+ def exec(command, args)
+ puts "mate"
+ end
+ end
+ RUBY
+ s.require_paths = %w[lib src]
+ s.write("src/fubar.rb")
+ end
+ end
+ bundle "plugin install testing --source file://#{gem_repo2}"
+
+ bundle "check2", "no-color" => false
+ expect(out).to eq("mate")
+ end
+
+ context "malformatted plugin" do
+ it "fails when plugins.rb is missing" do
+ update_repo2 do
+ build_plugin "foo", "1.1"
+ build_plugin "kung-foo", "1.1"
+ end
+
+ bundle "plugin install foo kung-foo --version '1.0' --source file://#{gem_repo2}"
+
+ expect(out).to include("Installing foo 1.0")
+ expect(out).to include("Installing kung-foo 1.0")
+ plugin_should_be_installed("foo", "kung-foo")
+
+ build_repo2 do
+ build_gem "charlie"
+ end
+
+ bundle "plugin install charlie --source file://#{gem_repo2}"
+
+ expect(out).to include("plugins.rb was not found")
+
+ expect(global_plugin_gem("charlie-1.0")).not_to be_directory
+
+ plugin_should_be_installed("foo", "kung-foo")
+ plugin_should_not_be_installed("charlie")
+ end
+
+ it "fails when plugins.rb throws exception on load" do
+ build_repo2 do
+ build_plugin "chaplin" do |s|
+ s.write "plugins.rb", <<-RUBY
+ raise "I got you man"
+ RUBY
+ end
+ end
+
+ bundle "plugin install chaplin --source file://#{gem_repo2}"
+
+ expect(global_plugin_gem("chaplin-1.0")).not_to be_directory
+
+ plugin_should_not_be_installed("chaplin")
+ end
+ end
+
+ context "git plugins" do
+ it "installs form a git source" do
+ build_git "foo" do |s|
+ s.write "plugins.rb"
+ end
+
+ bundle "plugin install foo --git file://#{lib_path("foo-1.0")}"
+
+ expect(out).to include("Installed plugin foo")
+ plugin_should_be_installed("foo")
+ end
+ end
+
+ context "Gemfile eval" do
+ it "installs plugins listed in gemfile" do
+ gemfile <<-G
+ source 'file://#{gem_repo2}'
+ plugin 'foo'
+ gem 'rack', "1.0.0"
+ G
+
+ bundle "install"
+
+ expect(out).to include("Installed plugin foo")
+
+ expect(out).to include("Bundle complete!")
+
+ expect(the_bundle).to include_gems("rack 1.0.0")
+ plugin_should_be_installed("foo")
+ end
+
+ it "accepts plugin version" do
+ update_repo2 do
+ build_plugin "foo", "1.1.0"
+ end
+
+ install_gemfile <<-G
+ source 'file://#{gem_repo2}'
+ plugin 'foo', "1.0"
+ G
+
+ bundle "install"
+
+ expect(out).to include("Installing foo 1.0")
+
+ plugin_should_be_installed("foo")
+
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "accepts git sources" do
+ build_git "ga-plugin" do |s|
+ s.write "plugins.rb"
+ end
+
+ install_gemfile <<-G
+ plugin 'ga-plugin', :git => "#{lib_path("ga-plugin-1.0")}"
+ G
+
+ expect(out).to include("Installed plugin ga-plugin")
+ plugin_should_be_installed("ga-plugin")
+ end
+ end
+
+ context "inline gemfiles" do
+ it "installs the listed plugins" do
+ code = <<-RUBY
+ require "bundler/inline"
+
+ gemfile do
+ source 'file://#{gem_repo2}'
+ plugin 'foo'
+ end
+ RUBY
+
+ ruby code
+ expect(local_plugin_gem("foo-1.0", "plugins.rb")).to exist
+ end
+ end
+
+ describe "local plugin" do
+ it "is installed when inside an app" do
+ gemfile ""
+ bundle "plugin install foo --source file://#{gem_repo2}"
+
+ plugin_should_be_installed("foo")
+ expect(local_plugin_gem("foo-1.0")).to be_directory
+ end
+
+ context "conflict with global plugin" do
+ before do
+ update_repo2 do
+ build_plugin "fubar" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class Fubar < Bundler::Plugin::API
+ command "shout"
+
+ def exec(command, args)
+ puts "local_one"
+ end
+ end
+ RUBY
+ end
+ end
+
+ # inside the app
+ gemfile "source 'file://#{gem_repo2}'\nplugin 'fubar'"
+ bundle "install"
+
+ update_repo2 do
+ build_plugin "fubar", "1.1" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class Fubar < Bundler::Plugin::API
+ command "shout"
+
+ def exec(command, args)
+ puts "global_one"
+ end
+ end
+ RUBY
+ end
+ end
+
+ # outside the app
+ Dir.chdir tmp
+ bundle "plugin install fubar --source file://#{gem_repo2}"
+ end
+
+ it "inside the app takes precedence over global plugin" do
+ Dir.chdir bundled_app
+
+ bundle "shout"
+ expect(out).to eq("local_one")
+ end
+
+ it "outside the app global plugin is used" do
+ Dir.chdir tmp
+
+ bundle "shout"
+ expect(out).to eq("global_one")
+ end
+ end
+ end
+end
diff --git a/spec/bundler/plugins/source/example_spec.rb b/spec/bundler/plugins/source/example_spec.rb
new file mode 100644
index 0000000000..fd30892f63
--- /dev/null
+++ b/spec/bundler/plugins/source/example_spec.rb
@@ -0,0 +1,505 @@
+# frozen_string_literal: true
+
+RSpec.describe "real source plugins" do
+ context "with a minimal source plugin" do
+ before do
+ build_repo2 do
+ build_plugin "bundler-source-mpath" do |s|
+ s.write "plugins.rb", <<-RUBY
+ require "bundler/vendored_fileutils"
+ require "bundler-source-mpath"
+
+ class MPath < Bundler::Plugin::API
+ source "mpath"
+
+ attr_reader :path
+
+ def initialize(opts)
+ super
+
+ @path = Pathname.new options["uri"]
+ end
+
+ def fetch_gemspec_files
+ @spec_files ||= begin
+ glob = "{,*,*/*}.gemspec"
+ if installed?
+ search_path = install_path
+ else
+ search_path = path
+ end
+ Dir["\#{search_path.to_s}/\#{glob}"]
+ end
+ end
+
+ def install(spec, opts)
+ mkdir_p(install_path.parent)
+ FileUtils.cp_r(path, install_path)
+
+ spec_path = install_path.join("\#{spec.full_name}.gemspec")
+ spec_path.open("wb") {|f| f.write spec.to_ruby }
+ spec.loaded_from = spec_path.to_s
+
+ post_install(spec)
+
+ nil
+ end
+ end
+ RUBY
+ end # build_plugin
+ end
+
+ build_lib "a-path-gem"
+
+ gemfile <<-G
+ source "file://localhost#{gem_repo2}" # plugin source
+ source "#{lib_path("a-path-gem-1.0")}", :type => :mpath do
+ gem "a-path-gem"
+ end
+ G
+ end
+
+ it "installs" do
+ bundle "install"
+
+ expect(out).to include("Bundle complete!")
+
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+
+ it "writes to lock file", :bundler => "< 2" do
+ bundle "install"
+
+ lockfile_should_be <<-G
+ PLUGIN SOURCE
+ remote: #{lib_path("a-path-gem-1.0")}
+ type: mpath
+ specs:
+ a-path-gem (1.0)
+
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ a-path-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "writes to lock file", :bundler => "2" do
+ bundle "install"
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+
+ PLUGIN SOURCE
+ remote: #{lib_path("a-path-gem-1.0")}
+ type: mpath
+ specs:
+ a-path-gem (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ a-path-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "provides correct #full_gem_path" do
+ bundle "install"
+ run <<-RUBY
+ puts Bundler.rubygems.find_name('a-path-gem').first.full_gem_path
+ RUBY
+ expect(out).to eq(bundle("info a-path-gem --path"))
+ end
+
+ it "installs the gem executables" do
+ build_lib "gem-with-bin" do |s|
+ s.executables = ["foo"]
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}" # plugin source
+ source "#{lib_path("gem-with-bin-1.0")}", :type => :mpath do
+ gem "gem-with-bin"
+ end
+ G
+
+ bundle "exec foo"
+ expect(out).to eq("1.0")
+ end
+
+ describe "bundle cache/package" do
+ let(:uri_hash) { Digest(:SHA1).hexdigest(lib_path("a-path-gem-1.0").to_s) }
+ it "copies repository to vendor cache and uses it" do
+ bundle "install"
+ bundle :cache, forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.git")).not_to exist
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.bundlecache")).to be_file
+
+ FileUtils.rm_rf lib_path("a-path-gem-1.0")
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+
+ it "copies repository to vendor cache and uses it even when installed with bundle --path" do
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle")
+ bundle! :cache, forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
+
+ FileUtils.rm_rf lib_path("a-path-gem-1.0")
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+
+ it "bundler package copies repository to vendor cache" do
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle")
+ bundle! :package, forgotten_command_line_options([:all, :cache_all] => true)
+
+ expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
+
+ FileUtils.rm_rf lib_path("a-path-gem-1.0")
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+ end
+
+ context "with lockfile" do
+ before do
+ lockfile <<-G
+ PLUGIN SOURCE
+ remote: #{lib_path("a-path-gem-1.0")}
+ type: mpath
+ specs:
+ a-path-gem (1.0)
+
+ GEM
+ remote: file:#{gem_repo2}/
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ a-path-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "installs" do
+ bundle! "install"
+
+ expect(the_bundle).to include_gems("a-path-gem 1.0")
+ end
+ end
+ end
+
+ context "with a more elaborate source plugin" do
+ before do
+ build_repo2 do
+ build_plugin "bundler-source-gitp" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class SPlugin < Bundler::Plugin::API
+ source "gitp"
+
+ attr_reader :ref
+
+ def initialize(opts)
+ super
+
+ @ref = options["ref"] || options["branch"] || options["tag"] || "master"
+ @unlocked = false
+ end
+
+ def eql?(other)
+ other.is_a?(self.class) && uri == other.uri && ref == other.ref
+ end
+
+ alias_method :==, :eql?
+
+ def fetch_gemspec_files
+ @spec_files ||= begin
+ glob = "{,*,*/*}.gemspec"
+ if !cached?
+ cache_repo
+ end
+
+ if installed? && !@unlocked
+ path = install_path
+ else
+ path = cache_path
+ end
+
+ Dir["\#{path}/\#{glob}"]
+ end
+ end
+
+ def install(spec, opts)
+ mkdir_p(install_path.dirname)
+ rm_rf(install_path)
+ `git clone --no-checkout --quiet "\#{cache_path}" "\#{install_path}"`
+ Dir.chdir install_path do
+ `git reset --hard \#{revision}`
+ end
+
+ spec_path = install_path.join("\#{spec.full_name}.gemspec")
+ spec_path.open("wb") {|f| f.write spec.to_ruby }
+ spec.loaded_from = spec_path.to_s
+
+ post_install(spec)
+
+ nil
+ end
+
+ def options_to_lock
+ opts = {"revision" => revision}
+ opts["ref"] = ref if ref != "master"
+ opts
+ end
+
+ def unlock!
+ @unlocked = true
+ @revision = latest_revision
+ end
+
+ def app_cache_dirname
+ "\#{base_name}-\#{shortref_for_path(revision)}"
+ end
+
+ private
+
+ def cache_path
+ @cache_path ||= cache_dir.join("gitp", base_name)
+ end
+
+ def cache_repo
+ `git clone --quiet \#{@options["uri"]} \#{cache_path}`
+ end
+
+ def cached?
+ File.directory?(cache_path)
+ end
+
+ def locked_revision
+ options["revision"]
+ end
+
+ def revision
+ @revision ||= locked_revision || latest_revision
+ end
+
+ def latest_revision
+ if !cached? || @unlocked
+ rm_rf(cache_path)
+ cache_repo
+ end
+
+ Dir.chdir cache_path do
+ `git rev-parse --verify \#{@ref}`.strip
+ end
+ end
+
+ def base_name
+ File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*}, ""), ".git")
+ end
+
+ def shortref_for_path(ref)
+ ref[0..11]
+ end
+
+ def install_path
+ @install_path ||= begin
+ git_scope = "\#{base_name}-\#{shortref_for_path(revision)}"
+
+ path = gem_install_dir.join(git_scope)
+
+ if !path.exist? && requires_sudo?
+ user_bundle_path.join(ruby_scope).join(git_scope)
+ else
+ path
+ end
+ end
+ end
+
+ def installed?
+ File.directory?(install_path)
+ end
+ end
+ RUBY
+ end
+ end
+
+ build_git "ma-gitp-gem"
+
+ gemfile <<-G
+ source "file://localhost#{gem_repo2}" # plugin source
+ source "file://#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do
+ gem "ma-gitp-gem"
+ end
+ G
+ end
+
+ it "handles the source option" do
+ bundle "install"
+ expect(out).to include("Bundle complete!")
+ expect(the_bundle).to include_gems("ma-gitp-gem 1.0")
+ end
+
+ it "writes to lock file", :bundler => "< 2" do
+ revision = revision_for(lib_path("ma-gitp-gem-1.0"))
+ bundle "install"
+
+ lockfile_should_be <<-G
+ PLUGIN SOURCE
+ remote: file://#{lib_path("ma-gitp-gem-1.0")}
+ type: gitp
+ revision: #{revision}
+ specs:
+ ma-gitp-gem (1.0)
+
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ ma-gitp-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "writes to lock file", :bundler => "2" do
+ revision = revision_for(lib_path("ma-gitp-gem-1.0"))
+ bundle "install"
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+
+ PLUGIN SOURCE
+ remote: file://#{lib_path("ma-gitp-gem-1.0")}
+ type: gitp
+ revision: #{revision}
+ specs:
+ ma-gitp-gem (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ ma-gitp-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ context "with lockfile" do
+ before do
+ revision = revision_for(lib_path("ma-gitp-gem-1.0"))
+ lockfile <<-G
+ PLUGIN SOURCE
+ remote: file://#{lib_path("ma-gitp-gem-1.0")}
+ type: gitp
+ revision: #{revision}
+ specs:
+ ma-gitp-gem (1.0)
+
+ GEM
+ remote: file:#{gem_repo2}/
+ specs:
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ ma-gitp-gem!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "installs" do
+ bundle "install"
+ expect(the_bundle).to include_gems("ma-gitp-gem 1.0")
+ end
+
+ it "uses the locked ref" do
+ update_git "ma-gitp-gem"
+ bundle "install"
+
+ run <<-RUBY
+ require 'ma-gitp-gem'
+ puts "WIN" unless defined?(MAGITPGEM_PREV_REF)
+ RUBY
+ expect(out).to eq("WIN")
+ end
+
+ it "updates the deps on bundler update" do
+ update_git "ma-gitp-gem"
+ bundle "update ma-gitp-gem"
+
+ run <<-RUBY
+ require 'ma-gitp-gem'
+ puts "WIN" if defined?(MAGITPGEM_PREV_REF)
+ RUBY
+ expect(out).to eq("WIN")
+ end
+
+ it "updates the deps on change in gemfile" do
+ update_git "ma-gitp-gem", "1.1", :path => lib_path("ma-gitp-gem-1.0"), :gemspec => true
+ gemfile <<-G
+ source "file://#{gem_repo2}" # plugin source
+ source "file://#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do
+ gem "ma-gitp-gem", "1.1"
+ end
+ G
+ bundle "install"
+
+ expect(the_bundle).to include_gems("ma-gitp-gem 1.1")
+ end
+ end
+
+ describe "bundle cache with gitp" do
+ it "copies repository to vendor cache and uses it" do
+ git = build_git "foo"
+ ref = git.ref_for("master", 11)
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}" # plugin source
+ source '#{lib_path("foo-1.0")}', :type => :gitp do
+ gem "foo"
+ end
+ G
+
+ bundle :cache, forgotten_command_line_options([:all, :cache_all] => true)
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.bundlecache")).to be_file
+
+ FileUtils.rm_rf lib_path("foo-1.0")
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/plugins/source_spec.rb b/spec/bundler/plugins/source_spec.rb
new file mode 100644
index 0000000000..543e90eb60
--- /dev/null
+++ b/spec/bundler/plugins/source_spec.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundler source plugin" do
+ describe "plugins dsl eval for #source with :type option" do
+ before do
+ update_repo2 do
+ build_plugin "bundler-source-psource" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class OPSource < Bundler::Plugin::API
+ source "psource"
+ end
+ RUBY
+ end
+ end
+ end
+
+ it "installs bundler-source-* gem when no handler for source is present" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ source "file://#{lib_path("gitp")}", :type => :psource do
+ end
+ G
+
+ plugin_should_be_installed("bundler-source-psource")
+ end
+
+ it "enables the plugin to require a lib path" do
+ update_repo2 do
+ build_plugin "bundler-source-psource" do |s|
+ s.write "plugins.rb", <<-RUBY
+ require "bundler-source-psource"
+ class PSource < Bundler::Plugin::API
+ source "psource"
+ end
+ RUBY
+ end
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ source "file://#{lib_path("gitp")}", :type => :psource do
+ end
+ G
+
+ expect(out).to include("Bundle complete!")
+ end
+
+ context "with an explicit handler" do
+ before do
+ update_repo2 do
+ build_plugin "another-psource" do |s|
+ s.write "plugins.rb", <<-RUBY
+ class Cheater < Bundler::Plugin::API
+ source "psource"
+ end
+ RUBY
+ end
+ end
+ end
+
+ context "explicit presence in gemfile" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ plugin "another-psource"
+
+ source "file://#{lib_path("gitp")}", :type => :psource do
+ end
+ G
+ end
+
+ it "completes successfully" do
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "installs the explicit one" do
+ plugin_should_be_installed("another-psource")
+ end
+
+ it "doesn't install the default one" do
+ plugin_should_not_be_installed("bundler-source-psource")
+ end
+ end
+
+ context "explicit default source" do
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ plugin "bundler-source-psource"
+
+ source "file://#{lib_path("gitp")}", :type => :psource do
+ end
+ G
+ end
+
+ it "completes successfully" do
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "installs the default one" do
+ plugin_should_be_installed("bundler-source-psource")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/bundler/quality_spec.rb b/spec/bundler/quality_spec.rb
new file mode 100644
index 0000000000..14d6bb99b9
--- /dev/null
+++ b/spec/bundler/quality_spec.rb
@@ -0,0 +1,266 @@
+# frozen_string_literal: true
+
+if defined?(Encoding) && Encoding.default_external.name != "UTF-8"
+ # Poor man's ruby -E UTF-8, since it works on 1.8.7
+ Encoding.default_external = Encoding.find("UTF-8")
+end
+
+RSpec.describe "The library itself" do
+ def check_for_debugging_mechanisms(filename)
+ debugging_mechanisms_regex = /
+ (binding\.pry)|
+ (debugger)|
+ (sleep\s*\(?\d+)|
+ (fit\s*\(?("|\w))
+ /x
+
+ failing_lines = []
+ File.readlines(filename).each_with_index do |line, number|
+ if line =~ debugging_mechanisms_regex && !line.end_with?("# ignore quality_spec\n")
+ failing_lines << number + 1
+ end
+ end
+
+ return if failing_lines.empty?
+ "#{filename} has debugging mechanisms (like binding.pry, sleep, debugger, rspec focusing, etc.) on lines #{failing_lines.join(", ")}"
+ end
+
+ def check_for_git_merge_conflicts(filename)
+ merge_conflicts_regex = /
+ <<<<<<<|
+ =======|
+ >>>>>>>
+ /x
+
+ failing_lines = []
+ File.readlines(filename).each_with_index do |line, number|
+ failing_lines << number + 1 if line =~ merge_conflicts_regex
+ end
+
+ return if failing_lines.empty?
+ "#{filename} has unresolved git merge conflicts on lines #{failing_lines.join(", ")}"
+ end
+
+ def check_for_tab_characters(filename)
+ failing_lines = []
+ File.readlines(filename).each_with_index do |line, number|
+ failing_lines << number + 1 if line =~ /\t/
+ end
+
+ return if failing_lines.empty?
+ "#{filename} has tab characters on lines #{failing_lines.join(", ")}"
+ end
+
+ def check_for_extra_spaces(filename)
+ failing_lines = []
+ File.readlines(filename).each_with_index do |line, number|
+ next if line =~ /^\s+#.*\s+\n$/
+ next if %w[LICENCE.md].include?(line)
+ failing_lines << number + 1 if line =~ /\s+\n$/
+ end
+
+ return if failing_lines.empty?
+ "#{filename} has spaces on the EOL on lines #{failing_lines.join(", ")}"
+ end
+
+ def check_for_expendable_words(filename)
+ failing_line_message = []
+ useless_words = %w[
+ actually
+ basically
+ clearly
+ just
+ obviously
+ really
+ simply
+ ]
+ pattern = /\b#{Regexp.union(useless_words)}\b/i
+
+ File.readlines(filename).each_with_index do |line, number|
+ next unless word_found = pattern.match(line)
+ failing_line_message << "#{filename}:#{number.succ} has '#{word_found}'. Avoid using these kinds of weak modifiers."
+ end
+
+ failing_line_message unless failing_line_message.empty?
+ end
+
+ def check_for_specific_pronouns(filename)
+ failing_line_message = []
+ specific_pronouns = /\b(he|she|his|hers|him|her|himself|herself)\b/i
+
+ File.readlines(filename).each_with_index do |line, number|
+ next unless word_found = specific_pronouns.match(line)
+ failing_line_message << "#{filename}:#{number.succ} has '#{word_found}'. Use more generic pronouns in documentation."
+ end
+
+ failing_line_message unless failing_line_message.empty?
+ end
+
+ RSpec::Matchers.define :be_well_formed do
+ match(&:empty?)
+
+ failure_message do |actual|
+ actual.join("\n")
+ end
+ end
+
+ it "has no malformed whitespace" do
+ exempt = /\.gitmodules|\.marshal|fixtures|vendor|ssl_certs|LICENSE|vcr_cassettes/
+ error_messages = []
+ Dir.chdir(root) do
+ lib_files = ruby_core? ? `git ls-files -z -- lib/bundler lib/bundler.rb spec/bundler` : `git ls-files -z -- lib`
+ lib_files.split("\x0").each do |filename|
+ next if filename =~ exempt
+ error_messages << check_for_tab_characters(filename)
+ error_messages << check_for_extra_spaces(filename)
+ end
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "does not include any leftover debugging or development mechanisms" do
+ exempt = %r{quality_spec.rb|support/helpers|vcr_cassettes|\.md|\.ronn}
+ error_messages = []
+ Dir.chdir(root) do
+ lib_files = ruby_core? ? `git ls-files -z -- lib/bundler lib/bundler.rb spec/bundler` : `git ls-files -z -- lib`
+ lib_files.split("\x0").each do |filename|
+ next if filename =~ exempt
+ error_messages << check_for_debugging_mechanisms(filename)
+ end
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "does not include any unresolved merge conflicts" do
+ error_messages = []
+ exempt = %r{lock/lockfile_(bundler_1_)?spec|quality_spec|vcr_cassettes|\.ronn|lockfile_parser\.rb}
+ Dir.chdir(root) do
+ lib_files = ruby_core? ? `git ls-files -z -- lib/bundler lib/bundler.rb spec/bundler` : `git ls-files -z -- lib`
+ lib_files.split("\x0").each do |filename|
+ next if filename =~ exempt
+ error_messages << check_for_git_merge_conflicts(filename)
+ end
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "maintains language quality of the documentation" do
+ included = /ronn/
+ error_messages = []
+ Dir.chdir(root) do
+ `git ls-files -z -- man`.split("\x0").each do |filename|
+ next unless filename =~ included
+ error_messages << check_for_expendable_words(filename)
+ error_messages << check_for_specific_pronouns(filename)
+ end
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "maintains language quality of sentences used in source code" do
+ error_messages = []
+ exempt = /vendor/
+ Dir.chdir(root) do
+ lib_files = ruby_core? ? `git ls-files -z -- lib/bundler lib/bundler.rb` : `git ls-files -z -- lib`
+ lib_files.split("\x0").each do |filename|
+ next if filename =~ exempt
+ error_messages << check_for_expendable_words(filename)
+ error_messages << check_for_specific_pronouns(filename)
+ end
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
+ it "documents all used settings" do
+ exemptions = %w[
+ auto_config_jobs
+ cache_command_is_package
+ console_command
+ deployment_means_frozen
+ forget_cli_options
+ gem.coc
+ gem.mit
+ inline
+ lockfile_uses_separate_rubygems_sources
+ use_gem_version_promoter_for_major_updates
+ viz_command
+ ]
+
+ all_settings = Hash.new {|h, k| h[k] = [] }
+ documented_settings = []
+
+ Bundler::Settings::BOOL_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::BOOL_KEYS" }
+ Bundler::Settings::NUMBER_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::NUMBER_KEYS" }
+ Bundler::Settings::ARRAY_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::ARRAY_KEYS" }
+
+ Dir.chdir(root) do
+ key_pattern = /([a-z\._-]+)/i
+ lib_files = ruby_core? ? `git ls-files -z -- lib/bundler lib/bundler.rb` : `git ls-files -z -- lib`
+ lib_files.split("\x0").each do |filename|
+ File.readlines(filename).each_with_index do |line, number|
+ line.scan(/Bundler\.settings\[:#{key_pattern}\]/).flatten.each {|s| all_settings[s] << "referenced at `#{filename}:#{number.succ}`" }
+ end
+ end
+ documented_settings = File.read("man/bundle-config.ronn")[/LIST OF AVAILABLE KEYS.*/m].scan(/^\* `#{key_pattern}`/).flatten
+ end
+
+ documented_settings.each do |s|
+ all_settings.delete(s)
+ expect(exemptions.delete(s)).to be_nil, "setting #{s} was exempted but was actually documented"
+ end
+
+ exemptions.each do |s|
+ expect(all_settings.delete(s)).to be_truthy, "setting #{s} was exempted but unused"
+ end
+ error_messages = all_settings.map do |setting, refs|
+ "The `#{setting}` setting is undocumented\n\t- #{refs.join("\n\t- ")}\n"
+ end
+
+ expect(error_messages.sort).to be_well_formed
+
+ expect(documented_settings).to be_sorted
+ end
+
+ it "can still be built" do
+ Dir.chdir(root) do
+ begin
+ gem_command! :build, gemspec
+ if Bundler.rubygems.provides?(">= 2.4")
+ # there's no way aroudn this warning
+ last_command.stderr.sub!(/^YAML safe loading.*/, "")
+
+ # older rubygems have weird warnings, and we won't actually be using them
+ # to build the gem for releases anyways
+ expect(last_command.stderr).to be_empty, "bundler should build as a gem without warnings, but\n#{err}"
+ end
+ ensure
+ # clean up the .gem generated
+ path_prefix = ruby_core? ? "lib/" : "./"
+ FileUtils.rm("#{path_prefix}bundler-#{Bundler::VERSION}.gem")
+ end
+ end
+ end
+
+ it "does not contain any warnings" do
+ Dir.chdir(root) do
+ exclusions = %w[
+ lib/bundler/capistrano.rb
+ lib/bundler/deployment.rb
+ lib/bundler/gem_tasks.rb
+ lib/bundler/vlad.rb
+ lib/bundler/templates/gems.rb
+ ]
+ lib_files = ruby_core? ? `git ls-files -z -- lib/bundler lib/bundler.rb` : `git ls-files -z -- lib`
+ lib_files = lib_files.split("\x0").grep(/\.rb$/) - exclusions
+ lib_files.reject! {|f| f.start_with?("lib/bundler/vendor") }
+ lib_files.map! {|f| f.chomp(".rb") }
+ sys_exec!("ruby -w -Ilib") do |input, _, _|
+ lib_files.each do |f|
+ input.puts "require '#{f.sub(%r{\Alib/}, "")}'"
+ end
+ end
+
+ expect(last_command.stdboth.split("\n")).to be_well_formed
+ end
+ end
+end
diff --git a/spec/bundler/realworld/dependency_api_spec.rb b/spec/bundler/realworld/dependency_api_spec.rb
new file mode 100644
index 0000000000..13527ce5d1
--- /dev/null
+++ b/spec/bundler/realworld/dependency_api_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+RSpec.describe "gemcutter's dependency API", :realworld => true do
+ context "when Gemcutter API takes too long to respond" do
+ before do
+ require_rack
+
+ port = find_unused_port
+ @server_uri = "http://127.0.0.1:#{port}"
+
+ require File.expand_path("../../support/artifice/endpoint_timeout", __FILE__)
+ require "thread"
+ @t = Thread.new do
+ server = Rack::Server.start(:app => EndpointTimeout,
+ :Host => "0.0.0.0",
+ :Port => port,
+ :server => "webrick",
+ :AccessLog => [],
+ :Logger => Spec::SilentLogger.new)
+ server.start
+ end
+ @t.run
+
+ wait_for_server("127.0.0.1", port)
+ bundle! "config timeout 1"
+ end
+
+ after do
+ Artifice.deactivate
+ @t.kill
+ @t.join
+ end
+
+ it "times out and falls back on the modern index" do
+ install_gemfile! <<-G, :artifice => nil
+ source "#{@server_uri}"
+ gem "rack"
+ G
+
+ expect(out).to include("Fetching source index from #{@server_uri}/")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+end
diff --git a/spec/bundler/realworld/double_check_spec.rb b/spec/bundler/realworld/double_check_spec.rb
new file mode 100644
index 0000000000..94ab49ba2a
--- /dev/null
+++ b/spec/bundler/realworld/double_check_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+RSpec.describe "double checking sources", :realworld => true do
+ it "finds already-installed gems", :ruby => ">= 2.2" do
+ create_file("rails.gemspec", <<-RUBY)
+ Gem::Specification.new do |s|
+ s.name = "rails"
+ s.version = "5.1.4"
+ s.summary = ""
+ s.description = ""
+ s.author = ""
+ s.add_dependency "actionpack", "5.1.4"
+ end
+ RUBY
+
+ create_file("actionpack.gemspec", <<-RUBY)
+ Gem::Specification.new do |s|
+ s.name = "actionpack"
+ s.version = "5.1.4"
+ s.summary = ""
+ s.description = ""
+ s.author = ""
+ s.add_dependency "rack", "~> 2.0.0"
+ end
+ RUBY
+
+ cmd = <<-RUBY
+ require "bundler"
+ require #{File.expand_path("../../support/artifice/vcr.rb", __FILE__).dump}
+ require "bundler/inline"
+ gemfile(true) do
+ source "https://rubygems.org"
+ gem "rails", path: "."
+ end
+ RUBY
+
+ ruby! cmd
+ ruby! cmd
+ end
+end
diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb
new file mode 100644
index 0000000000..aa60e20b8a
--- /dev/null
+++ b/spec/bundler/realworld/edgecases_spec.rb
@@ -0,0 +1,382 @@
+# frozen_string_literal: true
+
+RSpec.describe "real world edgecases", :realworld => true, :sometimes => true do
+ def rubygems_version(name, requirement)
+ ruby! <<-RUBY
+ require #{File.expand_path("../../support/artifice/vcr.rb", __FILE__).dump}
+ require "bundler"
+ require "bundler/source/rubygems/remote"
+ require "bundler/fetcher"
+ source = Bundler::Source::Rubygems::Remote.new(URI("https://rubygems.org"))
+ fetcher = Bundler::Fetcher.new(source)
+ index = fetcher.specs([#{name.dump}], nil)
+ rubygem = index.search(Gem::Dependency.new(#{name.dump}, #{requirement.dump})).last
+ if rubygem.nil?
+ raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \
+ "Found specs:\n\#{index.send(:specs).inspect}"
+ end
+ "#{name} (\#{rubygem.version})"
+ RUBY
+ end
+
+ # there is no rbx-relative-require gem that will install on 1.9
+ it "ignores extra gems with bad platforms", :ruby => "~> 1.8.7" do
+ gemfile <<-G
+ source "https://rubygems.org"
+ gem "linecache", "0.46"
+ G
+ bundle :lock
+ expect(err).to lack_errors
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ # https://github.com/bundler/bundler/issues/1202
+ it "bundle cache works with rubygems 1.3.7 and pre gems",
+ :ruby => "~> 1.8.7", :rubygems => "~> 1.3.7" do
+ install_gemfile <<-G
+ source "https://rubygems.org"
+ gem "rack", "1.3.0.beta2"
+ gem "will_paginate", "3.0.pre2"
+ G
+ bundle :cache
+ expect(out).not_to include("Removing outdated .gem files from vendor/cache")
+ end
+
+ # https://github.com/bundler/bundler/issues/1486
+ # this is a hash collision that only manifests on 1.8.7
+ it "finds the correct child versions", :ruby => "~> 1.8.7" do
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem 'i18n', '~> 0.6.0'
+ gem 'activesupport', '~> 3.0.5'
+ gem 'activerecord', '~> 3.0.5'
+ gem 'builder', '~> 2.1.2'
+ G
+ bundle :lock
+ expect(lockfile).to include("activemodel (3.0.5)")
+ end
+
+ it "resolves dependencies correctly", :ruby => "1.9.3" do
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem 'rails', '~> 3.0'
+ gem 'capybara', '~> 2.2.0'
+ gem 'rack-cache', '1.2.0' # last version that works on Ruby 1.9
+ G
+ bundle! :lock
+ expect(lockfile).to include(rubygems_version("rails", "~> 3.0"))
+ expect(lockfile).to include("capybara (2.2.1)")
+ end
+
+ it "installs the latest version of gxapi_rails", :ruby => "1.9.3" do
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem "sass-rails"
+ gem "rails", "~> 3"
+ gem "gxapi_rails", "< 0.1.0" # 0.1.0 was released way after the test was written
+ gem 'rack-cache', '1.2.0' # last version that works on Ruby 1.9
+ G
+ bundle! :lock
+ expect(lockfile).to include("gxapi_rails (0.0.6)")
+ end
+
+ it "installs the latest version of i18n" do
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem "i18n", "~> 0.6.0"
+ gem "activesupport", "~> 3.0"
+ gem "activerecord", "~> 3.0"
+ gem "builder", "~> 2.1.2"
+ G
+ bundle! :lock
+ expect(lockfile).to include(rubygems_version("i18n", "~> 0.6.0"))
+ expect(lockfile).to include(rubygems_version("activesupport", "~> 3.0"))
+ end
+
+ it "is able to update a top-level dependency when there is a conflict on a shared transitive child", :ruby => "2.1" do
+ # from https://github.com/bundler/bundler/issues/5031
+
+ gemfile <<-G
+ source "https://rubygems.org"
+ gem 'rails', '~> 4.2.7.1'
+ gem 'paperclip', '~> 5.1.0'
+ G
+
+ lockfile <<-L
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ actionmailer (4.2.7.1)
+ actionpack (= 4.2.7.1)
+ actionview (= 4.2.7.1)
+ activejob (= 4.2.7.1)
+ mail (~> 2.5, >= 2.5.4)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ actionpack (4.2.7.1)
+ actionview (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
+ rack (~> 1.6)
+ rack-test (~> 0.6.2)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ actionview (4.2.7.1)
+ activesupport (= 4.2.7.1)
+ builder (~> 3.1)
+ erubis (~> 2.7.0)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ activejob (4.2.7.1)
+ activesupport (= 4.2.7.1)
+ globalid (>= 0.3.0)
+ activemodel (4.2.7.1)
+ activesupport (= 4.2.7.1)
+ builder (~> 3.1)
+ activerecord (4.2.7.1)
+ activemodel (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
+ arel (~> 6.0)
+ activesupport (4.2.7.1)
+ i18n (~> 0.7)
+ json (~> 1.7, >= 1.7.7)
+ minitest (~> 5.1)
+ thread_safe (~> 0.3, >= 0.3.4)
+ tzinfo (~> 1.1)
+ arel (6.0.3)
+ builder (3.2.2)
+ climate_control (0.0.3)
+ activesupport (>= 3.0)
+ cocaine (0.5.8)
+ climate_control (>= 0.0.3, < 1.0)
+ concurrent-ruby (1.0.2)
+ erubis (2.7.0)
+ globalid (0.3.7)
+ activesupport (>= 4.1.0)
+ i18n (0.7.0)
+ json (1.8.3)
+ loofah (2.0.3)
+ nokogiri (>= 1.5.9)
+ mail (2.6.4)
+ mime-types (>= 1.16, < 4)
+ mime-types (3.1)
+ mime-types-data (~> 3.2015)
+ mime-types-data (3.2016.0521)
+ mimemagic (0.3.2)
+ mini_portile2 (2.1.0)
+ minitest (5.9.1)
+ nokogiri (1.6.8)
+ mini_portile2 (~> 2.1.0)
+ pkg-config (~> 1.1.7)
+ paperclip (5.1.0)
+ activemodel (>= 4.2.0)
+ activesupport (>= 4.2.0)
+ cocaine (~> 0.5.5)
+ mime-types
+ mimemagic (~> 0.3.0)
+ pkg-config (1.1.7)
+ rack (1.6.4)
+ rack-test (0.6.3)
+ rack (>= 1.0)
+ rails (4.2.7.1)
+ actionmailer (= 4.2.7.1)
+ actionpack (= 4.2.7.1)
+ actionview (= 4.2.7.1)
+ activejob (= 4.2.7.1)
+ activemodel (= 4.2.7.1)
+ activerecord (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
+ bundler (>= 1.3.0, < 2.0)
+ railties (= 4.2.7.1)
+ sprockets-rails
+ rails-deprecated_sanitizer (1.0.3)
+ activesupport (>= 4.2.0.alpha)
+ rails-dom-testing (1.0.7)
+ activesupport (>= 4.2.0.beta, < 5.0)
+ nokogiri (~> 1.6.0)
+ rails-deprecated_sanitizer (>= 1.0.1)
+ rails-html-sanitizer (1.0.3)
+ loofah (~> 2.0)
+ railties (4.2.7.1)
+ actionpack (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
+ rake (>= 0.8.7)
+ thor (>= 0.18.1, < 2.0)
+ rake (11.3.0)
+ sprockets (3.7.0)
+ concurrent-ruby (~> 1.0)
+ rack (> 1, < 3)
+ sprockets-rails (3.2.0)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0)
+ thor (0.19.1)
+ thread_safe (0.3.5)
+ tzinfo (1.2.2)
+ thread_safe (~> 0.1)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ paperclip (~> 5.1.0)
+ rails (~> 4.2.7.1)
+ L
+
+ bundle! "lock --update paperclip"
+
+ expect(lockfile).to include(rubygems_version("paperclip", "~> 5.1.0"))
+ end
+
+ # https://github.com/bundler/bundler/issues/1500
+ it "does not fail install because of gem plugins" do
+ realworld_system_gems("open_gem --version 1.4.2", "rake --version 0.9.2")
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem 'rack', '1.0.1'
+ G
+
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle")
+ expect(err).not_to include("Could not find rake")
+ expect(err).to lack_errors
+ end
+
+ it "checks out git repos when the lockfile is corrupted" do
+ gemfile <<-G
+ source "https://rubygems.org"
+ git_source(:github) {|repo| "https://github.com/\#{repo}.git" }
+
+ gem 'activerecord', :github => 'carlhuda/rails-bundler-test', :branch => 'master'
+ gem 'activesupport', :github => 'carlhuda/rails-bundler-test', :branch => 'master'
+ gem 'actionpack', :github => 'carlhuda/rails-bundler-test', :branch => 'master'
+ G
+
+ lockfile <<-L
+ GIT
+ remote: https://github.com/carlhuda/rails-bundler-test.git
+ revision: 369e28a87419565f1940815219ea9200474589d4
+ branch: master
+ specs:
+ actionpack (3.2.2)
+ activemodel (= 3.2.2)
+ activesupport (= 3.2.2)
+ builder (~> 3.0.0)
+ erubis (~> 2.7.0)
+ journey (~> 1.0.1)
+ rack (~> 1.4.0)
+ rack-cache (~> 1.2)
+ rack-test (~> 0.6.1)
+ sprockets (~> 2.1.2)
+ activemodel (3.2.2)
+ activesupport (= 3.2.2)
+ builder (~> 3.0.0)
+ activerecord (3.2.2)
+ activemodel (= 3.2.2)
+ activesupport (= 3.2.2)
+ arel (~> 3.0.2)
+ tzinfo (~> 0.3.29)
+ activesupport (3.2.2)
+ i18n (~> 0.6)
+ multi_json (~> 1.0)
+
+ GIT
+ remote: https://github.com/carlhuda/rails-bundler-test.git
+ revision: 369e28a87419565f1940815219ea9200474589d4
+ branch: master
+ specs:
+ actionpack (3.2.2)
+ activemodel (= 3.2.2)
+ activesupport (= 3.2.2)
+ builder (~> 3.0.0)
+ erubis (~> 2.7.0)
+ journey (~> 1.0.1)
+ rack (~> 1.4.0)
+ rack-cache (~> 1.2)
+ rack-test (~> 0.6.1)
+ sprockets (~> 2.1.2)
+ activemodel (3.2.2)
+ activesupport (= 3.2.2)
+ builder (~> 3.0.0)
+ activerecord (3.2.2)
+ activemodel (= 3.2.2)
+ activesupport (= 3.2.2)
+ arel (~> 3.0.2)
+ tzinfo (~> 0.3.29)
+ activesupport (3.2.2)
+ i18n (~> 0.6)
+ multi_json (~> 1.0)
+
+ GIT
+ remote: https://github.com/carlhuda/rails-bundler-test.git
+ revision: 369e28a87419565f1940815219ea9200474589d4
+ branch: master
+ specs:
+ actionpack (3.2.2)
+ activemodel (= 3.2.2)
+ activesupport (= 3.2.2)
+ builder (~> 3.0.0)
+ erubis (~> 2.7.0)
+ journey (~> 1.0.1)
+ rack (~> 1.4.0)
+ rack-cache (~> 1.2)
+ rack-test (~> 0.6.1)
+ sprockets (~> 2.1.2)
+ activemodel (3.2.2)
+ activesupport (= 3.2.2)
+ builder (~> 3.0.0)
+ activerecord (3.2.2)
+ activemodel (= 3.2.2)
+ activesupport (= 3.2.2)
+ arel (~> 3.0.2)
+ tzinfo (~> 0.3.29)
+ activesupport (3.2.2)
+ i18n (~> 0.6)
+ multi_json (~> 1.0)
+
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ arel (3.0.2)
+ builder (3.0.0)
+ erubis (2.7.0)
+ hike (1.2.1)
+ i18n (0.6.0)
+ journey (1.0.3)
+ multi_json (1.1.0)
+ rack (1.4.1)
+ rack-cache (1.2)
+ rack (>= 0.4)
+ rack-test (0.6.1)
+ rack (>= 1.0)
+ sprockets (2.1.2)
+ hike (~> 1.2)
+ rack (~> 1.0)
+ tilt (~> 1.1, != 1.3.0)
+ tilt (1.3.3)
+ tzinfo (0.3.32)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ actionpack!
+ activerecord!
+ activesupport!
+ L
+
+ bundle! :lock
+ expect(last_command.stderr).to lack_errors
+ end
+
+ it "outputs a helpful error message when gems have invalid gemspecs" do
+ install_gemfile <<-G, :standalone => true
+ source 'https://rubygems.org'
+ gem "resque-scheduler", "2.2.0"
+ G
+ expect(out).to include("You have one or more invalid gemspecs that need to be fixed.")
+ expect(out).to include("resque-scheduler 2.2.0 has an invalid gemspec")
+ end
+end
diff --git a/spec/bundler/realworld/gemfile_source_header_spec.rb b/spec/bundler/realworld/gemfile_source_header_spec.rb
new file mode 100644
index 0000000000..59c1916874
--- /dev/null
+++ b/spec/bundler/realworld/gemfile_source_header_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require "thread"
+
+RSpec.describe "fetching dependencies with a mirrored source", :realworld => true, :rubygems => ">= 2.0" do
+ let(:mirror) { "https://server.example.org" }
+ let(:original) { "http://127.0.0.1:#{@port}" }
+
+ before do
+ setup_server
+ bundle "config --local mirror.#{mirror} #{original}"
+ end
+
+ after do
+ Artifice.deactivate
+ @t.kill
+ @t.join
+ end
+
+ it "sets the 'X-Gemfile-Source' header and bundles successfully" do
+ gemfile <<-G
+ source "#{mirror}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil
+
+ expect(out).to include("Installing weakling")
+ expect(out).to include("Bundle complete")
+ expect(the_bundle).to include_gems "weakling 0.0.3"
+ end
+
+ private
+
+ def setup_server
+ require_rack
+ @port = find_unused_port
+ @server_uri = "http://127.0.0.1:#{@port}"
+
+ require File.expand_path("../../support/artifice/endpoint_mirror_source", __FILE__)
+
+ @t = Thread.new do
+ Rack::Server.start(:app => EndpointMirrorSource,
+ :Host => "0.0.0.0",
+ :Port => @port,
+ :server => "webrick",
+ :AccessLog => [],
+ :Logger => Spec::SilentLogger.new)
+ end.run
+
+ wait_for_server("127.0.0.1", @port)
+ end
+end
diff --git a/spec/bundler/realworld/mirror_probe_spec.rb b/spec/bundler/realworld/mirror_probe_spec.rb
new file mode 100644
index 0000000000..ab74886329
--- /dev/null
+++ b/spec/bundler/realworld/mirror_probe_spec.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+require "thread"
+
+RSpec.describe "fetching dependencies with a not available mirror", :realworld => true do
+ let(:mirror) { @mirror_uri }
+ let(:original) { @server_uri }
+ let(:server_port) { @server_port }
+ let(:host) { "127.0.0.1" }
+
+ before do
+ require_rack
+ setup_server
+ setup_mirror
+ end
+
+ after do
+ Artifice.deactivate
+ @server_thread.kill
+ @server_thread.join
+ end
+
+ context "with a specific fallback timeout" do
+ before do
+ global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/__FALLBACK_TIMEOUT/" => "true",
+ "BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror)
+ end
+
+ it "install a gem using the original uri when the mirror is not responding" do
+ gemfile <<-G
+ source "#{original}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil
+
+ expect(out).to include("Installing weakling")
+ expect(out).to include("Bundle complete")
+ expect(the_bundle).to include_gems "weakling 0.0.3"
+ end
+ end
+
+ context "with a global fallback timeout" do
+ before do
+ global_config("BUNDLE_MIRROR__ALL__FALLBACK_TIMEOUT/" => "1",
+ "BUNDLE_MIRROR__ALL" => mirror)
+ end
+
+ it "install a gem using the original uri when the mirror is not responding" do
+ gemfile <<-G
+ source "#{original}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil
+
+ expect(out).to include("Installing weakling")
+ expect(out).to include("Bundle complete")
+ expect(the_bundle).to include_gems "weakling 0.0.3"
+ end
+ end
+
+ context "with a specific mirror without a fallback timeout" do
+ before do
+ global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror)
+ end
+
+ it "fails to install the gem with a timeout error" do
+ gemfile <<-G
+ source "#{original}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil
+
+ expect(out).to include("Fetching source index from #{mirror}")
+ expect(out).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
+ expect(out).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
+ expect(out).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
+ expect(out).to include("Could not fetch specs from #{mirror}")
+ end
+
+ it "prints each error and warning on a new line" do
+ gemfile <<-G
+ source "#{original}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil
+
+ expect(last_command.stdout).to include "Fetching source index from #{mirror}/"
+ expect(last_command.bundler_err).to include <<-EOS.strip
+Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/
+Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/
+Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/
+Could not fetch specs from #{mirror}/
+ EOS
+ end
+ end
+
+ context "with a global mirror without a fallback timeout" do
+ before do
+ global_config("BUNDLE_MIRROR__ALL" => mirror)
+ end
+
+ it "fails to install the gem with a timeout error" do
+ gemfile <<-G
+ source "#{original}"
+ gem 'weakling'
+ G
+
+ bundle :install, :artifice => nil
+
+ expect(out).to include("Fetching source index from #{mirror}")
+ expect(out).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
+ expect(out).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
+ expect(out).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}")
+ expect(out).to include("Could not fetch specs from #{mirror}")
+ end
+ end
+
+ def setup_server
+ @server_port = find_unused_port
+ @server_uri = "http://#{host}:#{@server_port}"
+
+ require File.expand_path("../../support/artifice/endpoint", __FILE__)
+
+ @server_thread = Thread.new do
+ Rack::Server.start(:app => Endpoint,
+ :Host => host,
+ :Port => @server_port,
+ :server => "webrick",
+ :AccessLog => [],
+ :Logger => Spec::SilentLogger.new)
+ end.run
+
+ wait_for_server(host, @server_port)
+ end
+
+ def setup_mirror
+ mirror_port = find_unused_port
+ @mirror_uri = "http://#{host}:#{mirror_port}"
+ end
+end
diff --git a/spec/bundler/realworld/parallel_spec.rb b/spec/bundler/realworld/parallel_spec.rb
new file mode 100644
index 0000000000..ed4430c68b
--- /dev/null
+++ b/spec/bundler/realworld/parallel_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+RSpec.describe "parallel", :realworld => true, :sometimes => true do
+ it "installs" do
+ gemfile <<-G
+ source "https://rubygems.org"
+ gem 'activesupport', '~> 3.2.13'
+ gem 'faker', '~> 1.1.2'
+ gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+
+ G
+
+ bundle :install, :jobs => 4, :env => { "DEBUG" => "1" }
+
+ if Bundler.rubygems.provides?(">= 2.1.0")
+ expect(out).to match(/[1-3]: /)
+ else
+ expect(out).to include("is not threadsafe")
+ end
+
+ bundle "info activesupport --path"
+ expect(out).to match(/activesupport/)
+
+ bundle "info faker --path"
+ expect(out).to match(/faker/)
+ end
+
+ it "updates" do
+ install_gemfile <<-G
+ source "https://rubygems.org"
+ gem 'activesupport', '3.2.12'
+ gem 'faker', '~> 1.1.2'
+ G
+
+ gemfile <<-G
+ source "https://rubygems.org"
+ gem 'activesupport', '~> 3.2.12'
+ gem 'faker', '~> 1.1.2'
+ gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+
+ G
+
+ bundle :update, :jobs => 4, :env => { "DEBUG" => "1" }, :all => bundle_update_requires_all?
+
+ if Bundler.rubygems.provides?(">= 2.1.0")
+ expect(out).to match(/[1-3]: /)
+ else
+ expect(out).to include("is not threadsafe")
+ end
+
+ bundle "info activesupport --path"
+ expect(out).to match(/activesupport-3\.2\.\d+/)
+
+ bundle "info faker --path"
+ expect(out).to match(/faker/)
+ end
+
+ it "works with --standalone" do
+ gemfile <<-G, :standalone => true
+ source "https://rubygems.org"
+ gem "diff-lcs"
+ G
+
+ bundle :install, :standalone => true, :jobs => 4
+
+ ruby <<-RUBY, :no_lib => true
+ $:.unshift File.expand_path("bundle")
+ require "bundler/setup"
+
+ require "diff/lcs"
+ puts Diff::LCS
+ RUBY
+
+ expect(out).to eq("Diff::LCS")
+ end
+end
diff --git a/spec/bundler/resolver/basic_spec.rb b/spec/bundler/resolver/basic_spec.rb
new file mode 100644
index 0000000000..c023f5d7aa
--- /dev/null
+++ b/spec/bundler/resolver/basic_spec.rb
@@ -0,0 +1,308 @@
+# frozen_string_literal: true
+
+RSpec.describe "Resolving" do
+ before :each do
+ @index = an_awesome_index
+ end
+
+ it "resolves a single gem" do
+ dep "rack"
+
+ should_resolve_as %w[rack-1.1]
+ end
+
+ it "resolves a gem with dependencies" do
+ dep "actionpack"
+
+ should_resolve_as %w[actionpack-2.3.5 activesupport-2.3.5 rack-1.0]
+ end
+
+ it "resolves a conflicting index" do
+ @index = a_conflict_index
+ dep "my_app"
+ should_resolve_as %w[activemodel-3.2.11 builder-3.0.4 grape-0.2.6 my_app-1.0.0]
+ end
+
+ it "resolves a complex conflicting index" do
+ @index = a_complex_conflict_index
+ dep "my_app"
+ should_resolve_as %w[a-1.4.0 b-0.3.5 c-3.2 d-0.9.8 my_app-1.1.0]
+ end
+
+ it "resolves a index with conflict on child" do
+ @index = index_with_conflict_on_child
+ dep "chef_app"
+ should_resolve_as %w[berkshelf-2.0.7 chef-10.26 chef_app-1.0.0 json-1.7.7]
+ end
+
+ it "prefers expicitly requested dependencies when resolving an index which would otherwise be ambiguous" do
+ @index = an_ambiguous_index
+ dep "a"
+ dep "b"
+ should_resolve_as %w[a-1.0.0 b-2.0.0 c-1.0.0 d-1.0.0]
+ end
+
+ it "prefers non-prerelease resolutions in sort order" do
+ @index = optional_prereleases_index
+ dep "a"
+ dep "b"
+ should_resolve_as %w[a-1.0.0 b-1.5.0]
+ end
+
+ it "resolves a index with root level conflict on child" do
+ @index = a_index_with_root_conflict_on_child
+ dep "i18n", "~> 0.4"
+ dep "activesupport", "~> 3.0"
+ dep "activerecord", "~> 3.0"
+ dep "builder", "~> 2.1.2"
+ should_resolve_as %w[activesupport-3.0.5 i18n-0.4.2 builder-2.1.2 activerecord-3.0.5 activemodel-3.0.5]
+ end
+
+ it "resolves a gem specified with a pre-release version" do
+ dep "activesupport", "~> 3.0.0.beta"
+ dep "activemerchant"
+ should_resolve_as %w[activemerchant-2.3.5 activesupport-3.0.0.beta1]
+ end
+
+ it "doesn't select a pre-release if not specified in the Gemfile" do
+ dep "activesupport"
+ dep "reform"
+ should_resolve_as %w[reform-1.0.0 activesupport-2.3.5]
+ end
+
+ it "doesn't select a pre-release for sub-dependencies" do
+ dep "reform"
+ should_resolve_as %w[reform-1.0.0 activesupport-2.3.5]
+ end
+
+ it "selects a pre-release for sub-dependencies if it's the only option" do
+ dep "need-pre"
+ should_resolve_as %w[need-pre-1.0.0 activesupport-3.0.0.beta1]
+ end
+
+ it "selects a pre-release if it's specified in the Gemfile" do
+ dep "activesupport", "= 3.0.0.beta"
+ dep "actionpack"
+
+ should_resolve_as %w[activesupport-3.0.0.beta actionpack-3.0.0.beta rack-1.1 rack-mount-0.6]
+ end
+
+ it "prefers non-pre-releases when doing conservative updates" do
+ @index = build_index do
+ gem "mail", "2.7.0"
+ gem "mail", "2.7.1.rc1"
+ gem "RubyGems\0", Gem::VERSION
+ end
+ dep "mail"
+ @locked = locked ["mail", "2.7.0"]
+ @base = locked
+ should_conservative_resolve_and_include [:patch], [], ["mail-2.7.0"]
+ end
+
+ it "raises an exception if a child dependency is not resolved" do
+ @index = a_unresovable_child_index
+ dep "chef_app_error"
+ expect do
+ resolve
+ end.to raise_error(Bundler::VersionConflict)
+ end
+
+ it "raises an exception with the minimal set of conflicting dependencies" do
+ @index = build_index do
+ %w[0.9 1.0 2.0].each {|v| gem("a", v) }
+ gem("b", "1.0") { dep "a", ">= 2" }
+ gem("c", "1.0") { dep "a", "< 1" }
+ end
+ dep "a"
+ dep "b"
+ dep "c"
+ expect do
+ resolve
+ end.to raise_error(Bundler::VersionConflict, <<-E.strip)
+Bundler could not find compatible versions for gem "a":
+ In Gemfile:
+ b was resolved to 1.0, which depends on
+ a (>= 2)
+
+ c was resolved to 1.0, which depends on
+ a (< 1)
+ E
+ end
+
+ it "should throw error in case of circular dependencies" do
+ @index = a_circular_index
+ dep "circular_app"
+
+ expect do
+ resolve
+ end.to raise_error(Bundler::CyclicDependencyError, /please remove either gem 'bar' or gem 'foo'/i)
+ end
+
+ # Issue #3459
+ it "should install the latest possible version of a direct requirement with no constraints given" do
+ @index = a_complicated_index
+ dep "foo"
+ should_resolve_and_include %w[foo-3.0.5]
+ end
+
+ # Issue #3459
+ it "should install the latest possible version of a direct requirement with constraints given" do
+ @index = a_complicated_index
+ dep "foo", ">= 3.0.0"
+ should_resolve_and_include %w[foo-3.0.5]
+ end
+
+ it "takes into account required_ruby_version" do
+ @index = build_index do
+ gem "foo", "1.0.0" do
+ dep "bar", ">= 0"
+ end
+
+ gem "foo", "2.0.0" do |s|
+ dep "bar", ">= 0"
+ s.required_ruby_version = "~> 2.0.0"
+ end
+
+ gem "bar", "1.0.0"
+
+ gem "bar", "2.0.0" do |s|
+ s.required_ruby_version = "~> 2.0.0"
+ end
+
+ gem "ruby\0", "1.8.7"
+ end
+ dep "foo"
+ dep "ruby\0", "1.8.7"
+
+ deps = []
+ @deps.each do |d|
+ deps << Bundler::DepProxy.new(d, "ruby")
+ end
+
+ should_resolve_and_include %w[foo-1.0.0 bar-1.0.0], [[]]
+ end
+
+ context "conservative" do
+ before :each do
+ @index = build_index do
+ gem("foo", "1.3.7") { dep "bar", "~> 2.0" }
+ gem("foo", "1.3.8") { dep "bar", "~> 2.0" }
+ gem("foo", "1.4.3") { dep "bar", "~> 2.0" }
+ gem("foo", "1.4.4") { dep "bar", "~> 2.0" }
+ gem("foo", "1.4.5") { dep "bar", "~> 2.1" }
+ gem("foo", "1.5.0") { dep "bar", "~> 2.1" }
+ gem("foo", "1.5.1") { dep "bar", "~> 3.0" }
+ gem("foo", "2.0.0") { dep "bar", "~> 3.0" }
+ gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0]
+ end
+ dep "foo"
+
+ # base represents declared dependencies in the Gemfile that are still satisfied by the lockfile
+ @base = Bundler::SpecSet.new([])
+
+ # locked represents versions in lockfile
+ @locked = locked(%w[foo 1.4.3], %w[bar 2.0.3])
+ end
+
+ it "resolves all gems to latest patch" do
+ # strict is not set, so bar goes up a minor version due to dependency from foo 1.4.5
+ should_conservative_resolve_and_include :patch, [], %w[foo-1.4.5 bar-2.1.1]
+ end
+
+ it "resolves all gems to latest patch strict" do
+ # strict is set, so foo can only go up to 1.4.4 to avoid bar going up a minor version, and bar can go up to 2.0.5
+ should_conservative_resolve_and_include [:patch, :strict], [], %w[foo-1.4.4 bar-2.0.5]
+ end
+
+ it "resolves foo only to latest patch - same dependency case" do
+ @locked = locked(%w[foo 1.3.7], %w[bar 2.0.3])
+ # bar is locked, and the lock holds here because the dependency on bar doesn't change on the matching foo version.
+ should_conservative_resolve_and_include :patch, ["foo"], %w[foo-1.3.8 bar-2.0.3]
+ end
+
+ it "resolves foo only to latest patch - changing dependency not declared case" do
+ # foo is the only gem being requested for update, therefore bar is locked, but bar is NOT
+ # declared as a dependency in the Gemfile. In this case, locks don't apply to _changing_
+ # dependencies and since the dependency of the selected foo gem changes, the latest matching
+ # dependency of "bar", "~> 2.1" -- bar-2.1.1 -- is selected. This is not a bug and follows
+ # the long-standing documented Conservative Updating behavior of bundle install.
+ # http://bundler.io/v1.12/man/bundle-install.1.html#CONSERVATIVE-UPDATING
+ should_conservative_resolve_and_include :patch, ["foo"], %w[foo-1.4.5 bar-2.1.1]
+ end
+
+ it "resolves foo only to latest patch - changing dependency declared case" do
+ # bar is locked AND a declared dependency in the Gemfile, so it will not move, and therefore
+ # foo can only move up to 1.4.4.
+ @base << build_spec("bar", "2.0.3").first
+ should_conservative_resolve_and_include :patch, ["foo"], %w[foo-1.4.4 bar-2.0.3]
+ end
+
+ it "resolves foo only to latest patch strict" do
+ # adding strict helps solve the possibly unexpected behavior of bar changing in the prior test case,
+ # because no versions will be returned for bar ~> 2.1, so the engine falls back to ~> 2.0 (turn on
+ # debugging to see this happen).
+ should_conservative_resolve_and_include [:patch, :strict], ["foo"], %w[foo-1.4.4 bar-2.0.3]
+ end
+
+ it "resolves bar only to latest patch" do
+ # bar is locked, so foo can only go up to 1.4.4
+ should_conservative_resolve_and_include :patch, ["bar"], %w[foo-1.4.3 bar-2.0.5]
+ end
+
+ it "resolves all gems to latest minor" do
+ # strict is not set, so bar goes up a major version due to dependency from foo 1.4.5
+ should_conservative_resolve_and_include :minor, [], %w[foo-1.5.1 bar-3.0.0]
+ end
+
+ it "resolves all gems to latest minor strict" do
+ # strict is set, so foo can only go up to 1.5.0 to avoid bar going up a major version
+ should_conservative_resolve_and_include [:minor, :strict], [], %w[foo-1.5.0 bar-2.1.1]
+ end
+
+ it "resolves all gems to latest major" do
+ should_conservative_resolve_and_include :major, [], %w[foo-2.0.0 bar-3.0.0]
+ end
+
+ it "resolves all gems to latest major strict" do
+ should_conservative_resolve_and_include [:major, :strict], [], %w[foo-2.0.0 bar-3.0.0]
+ end
+
+ # Why would this happen in real life? If bar 2.2 has a bug that the author of foo wants to bypass
+ # by reverting the dependency, the author of foo could release a new gem with an older requirement.
+ context "revert to previous" do
+ before :each do
+ @index = build_index do
+ gem("foo", "1.4.3") { dep "bar", "~> 2.2" }
+ gem("foo", "1.4.4") { dep "bar", "~> 2.1.0" }
+ gem("foo", "1.5.0") { dep "bar", "~> 2.0.0" }
+ gem "bar", %w[2.0.5 2.1.1 2.2.3]
+ end
+ dep "foo"
+
+ # base represents declared dependencies in the Gemfile that are still satisfied by the lockfile
+ @base = Bundler::SpecSet.new([])
+
+ # locked represents versions in lockfile
+ @locked = locked(%w[foo 1.4.3], %w[bar 2.2.3])
+ end
+
+ it "could revert to a previous version level patch" do
+ should_conservative_resolve_and_include :patch, [], %w[foo-1.4.4 bar-2.1.1]
+ end
+
+ it "cannot revert to a previous version in strict mode level patch" do
+ # fall back to the locked resolution since strict means we can't regress either version
+ should_conservative_resolve_and_include [:patch, :strict], [], %w[foo-1.4.3 bar-2.2.3]
+ end
+
+ it "could revert to a previous version level minor" do
+ should_conservative_resolve_and_include :minor, [], %w[foo-1.5.0 bar-2.0.5]
+ end
+
+ it "cannot revert to a previous version in strict mode level minor" do
+ # fall back to the locked resolution since strict means we can't regress either version
+ should_conservative_resolve_and_include [:minor, :strict], [], %w[foo-1.4.3 bar-2.2.3]
+ end
+ end
+ end
+end
diff --git a/spec/bundler/resolver/platform_spec.rb b/spec/bundler/resolver/platform_spec.rb
new file mode 100644
index 0000000000..fee0cf1f1c
--- /dev/null
+++ b/spec/bundler/resolver/platform_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+RSpec.describe "Resolving platform craziness" do
+ describe "with cross-platform gems" do
+ before :each do
+ @index = an_awesome_index
+ end
+
+ it "resolves a simple multi platform gem" do
+ dep "nokogiri"
+ platforms "ruby", "java"
+
+ should_resolve_as %w[nokogiri-1.4.2 nokogiri-1.4.2-java weakling-0.0.3]
+ end
+
+ it "doesn't pull gems that don't exist for the current platform" do
+ dep "nokogiri"
+ platforms "ruby"
+
+ should_resolve_as %w[nokogiri-1.4.2]
+ end
+
+ it "doesn't pull gems when the version is available for all requested platforms" do
+ dep "nokogiri"
+ platforms "mswin32"
+
+ should_resolve_as %w[nokogiri-1.4.2.1-x86-mswin32]
+ end
+ end
+
+ describe "with mingw32" do
+ before :each do
+ @index = build_index do
+ platforms "mingw32 mswin32 x64-mingw32" do |platform|
+ gem "thin", "1.2.7", platform
+ end
+ gem "win32-api", "1.5.1", "universal-mingw32"
+ end
+ end
+
+ it "finds mswin gems" do
+ # win32 is hardcoded to get CPU x86 in rubygems
+ platforms "mswin32"
+ dep "thin"
+ should_resolve_as %w[thin-1.2.7-x86-mswin32]
+ end
+
+ it "finds mingw gems" do
+ # mingw is _not_ hardcoded to add CPU x86 in rubygems
+ platforms "x86-mingw32"
+ dep "thin"
+ should_resolve_as %w[thin-1.2.7-mingw32]
+ end
+
+ it "finds x64-mingw gems" do
+ platforms "x64-mingw32"
+ dep "thin"
+ should_resolve_as %w[thin-1.2.7-x64-mingw32]
+ end
+
+ it "finds universal-mingw gems on x86-mingw" do
+ platform "x86-mingw32"
+ dep "win32-api"
+ should_resolve_as %w[win32-api-1.5.1-universal-mingw32]
+ end
+
+ it "finds universal-mingw gems on x64-mingw" do
+ platform "x64-mingw32"
+ dep "win32-api"
+ should_resolve_as %w[win32-api-1.5.1-universal-mingw32]
+ end
+ end
+
+ describe "with conflicting cases" do
+ before :each do
+ @index = build_index do
+ gem "foo", "1.0.0" do
+ dep "bar", ">= 0"
+ end
+
+ gem "bar", "1.0.0" do
+ dep "baz", "~> 1.0.0"
+ end
+
+ gem "bar", "1.0.0", "java" do
+ dep "baz", " ~> 1.1.0"
+ end
+
+ gem "baz", %w[1.0.0 1.1.0 1.2.0]
+ end
+ end
+
+ it "reports on the conflict" do
+ platforms "ruby", "java"
+ dep "foo"
+
+ should_conflict_on "baz"
+ end
+ end
+end
diff --git a/spec/bundler/runtime/executable_spec.rb b/spec/bundler/runtime/executable_spec.rb
new file mode 100644
index 0000000000..dcee234e15
--- /dev/null
+++ b/spec/bundler/runtime/executable_spec.rb
@@ -0,0 +1,190 @@
+# frozen_string_literal: true
+
+RSpec.describe "Running bin/* commands" do
+ before :each do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ it "runs the bundled command when in the bundle" do
+ bundle! "binstubs rack"
+
+ build_gem "rack", "2.0", :to_system => true do |s|
+ s.executables = "rackup"
+ end
+
+ gembin "rackup"
+ expect(out).to eq("1.0.0")
+ end
+
+ it "allows the location of the gem stubs to be specified" do
+ bundle! "binstubs rack", :path => "gbin"
+
+ expect(bundled_app("bin")).not_to exist
+ expect(bundled_app("gbin/rackup")).to exist
+
+ gembin bundled_app("gbin/rackup")
+ expect(out).to eq("1.0.0")
+ end
+
+ it "allows absolute paths as a specification of where to install bin stubs" do
+ bundle! "binstubs rack", :path => tmp("bin")
+
+ gembin tmp("bin/rackup")
+ expect(out).to eq("1.0.0")
+ end
+
+ it "uses the default ruby install name when shebang is not specified" do
+ bundle! "binstubs rack"
+ expect(File.open("bin/rackup").gets).to eq("#!/usr/bin/env #{RbConfig::CONFIG["ruby_install_name"]}\n")
+ end
+
+ it "allows the name of the shebang executable to be specified" do
+ bundle! "binstubs rack", :shebang => "ruby-foo"
+ expect(File.open("bin/rackup").gets).to eq("#!/usr/bin/env ruby-foo\n")
+ end
+
+ it "runs the bundled command when out of the bundle" do
+ bundle! "binstubs rack"
+
+ build_gem "rack", "2.0", :to_system => true do |s|
+ s.executables = "rackup"
+ end
+
+ Dir.chdir(tmp) do
+ gembin "rackup"
+ expect(out).to eq("1.0.0")
+ end
+ end
+
+ it "works with gems in path" do
+ build_lib "rack", :path => lib_path("rack") do |s|
+ s.executables = "rackup"
+ end
+
+ gemfile <<-G
+ gem "rack", :path => "#{lib_path("rack")}"
+ G
+
+ bundle! "binstubs rack"
+
+ build_gem "rack", "2.0", :to_system => true do |s|
+ s.executables = "rackup"
+ end
+
+ gembin "rackup"
+ expect(out).to eq("1.0")
+ end
+
+ it "creates a bundle binstub" do
+ build_gem "bundler", Bundler::VERSION, :to_system => true do |s|
+ s.executables = "bundle"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "bundler"
+ G
+
+ bundle! "binstubs bundler"
+
+ expect(bundled_app("bin/bundle")).to exist
+ end
+
+ it "does not generate bin stubs if the option was not specified" do
+ bundle! "install"
+
+ expect(bundled_app("bin/rackup")).not_to exist
+ end
+
+ it "allows you to stop installing binstubs", :bundler => "< 2" do
+ bundle! "install --binstubs bin/"
+ bundled_app("bin/rackup").rmtree
+ bundle! "install --binstubs \"\""
+
+ expect(bundled_app("bin/rackup")).not_to exist
+
+ bundle! "config bin"
+ expect(out).to include("You have not configured a value for `bin`")
+ end
+
+ it "remembers that the option was specified", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+ G
+
+ bundle! :install, forgotten_command_line_options([:binstubs, :bin] => "bin")
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+ gem "rack"
+ G
+
+ bundle "install"
+
+ expect(bundled_app("bin/rackup")).to exist
+ end
+
+ it "rewrites bins on --binstubs (to maintain backwards compatibility)", :bundler => "< 2" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle! :install, forgotten_command_line_options([:binstubs, :bin] => "bin")
+
+ File.open(bundled_app("bin/rackup"), "wb") do |file|
+ file.print "OMG"
+ end
+
+ bundle "install"
+
+ expect(bundled_app("bin/rackup").read).to_not eq("OMG")
+ end
+
+ it "rewrites bins on binstubs (to maintain backwards compatibility)" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ create_file("bin/rackup", "OMG")
+
+ bundle! "binstubs rack"
+
+ expect(bundled_app("bin/rackup").read).to_not eq("OMG")
+ end
+
+ it "use BUNDLE_GEMFILE gemfile for binstub" do
+ # context with bin/bunlder w/ default Gemfile
+ bundle! "binstubs bundler"
+
+ # generate other Gemfile with executable gem
+ build_repo2 do
+ build_gem("bindir") {|s| s.executables = "foo" }
+ end
+
+ create_file("OtherGemfile", <<-G)
+ source "file://#{gem_repo2}"
+ gem 'bindir'
+ G
+
+ # generate binstub for executable from non default Gemfile (other then bin/bundler version)
+ ENV["BUNDLE_GEMFILE"] = "OtherGemfile"
+ bundle "install"
+ bundle! "binstubs bindir"
+
+ # remove user settings
+ ENV["BUNDLE_GEMFILE"] = nil
+
+ # run binstub for non default Gemfile
+ gembin "foo"
+
+ expect(exitstatus).to eq(0) if exitstatus
+ expect(out).to eq("1.0")
+ end
+end
diff --git a/spec/bundler/runtime/gem_tasks_spec.rb b/spec/bundler/runtime/gem_tasks_spec.rb
new file mode 100644
index 0000000000..0510c80632
--- /dev/null
+++ b/spec/bundler/runtime/gem_tasks_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+RSpec.describe "require 'bundler/gem_tasks'", :ruby_repo do
+ before :each do
+ bundled_app("foo.gemspec").open("w") do |f|
+ f.write <<-GEMSPEC
+ Gem::Specification.new do |s|
+ s.name = "foo"
+ end
+ GEMSPEC
+ end
+ bundled_app("Rakefile").open("w") do |f|
+ f.write <<-RAKEFILE
+ $:.unshift("#{bundler_path}")
+ require "bundler/gem_tasks"
+ RAKEFILE
+ end
+ end
+
+ it "includes the relevant tasks" do
+ with_gem_path_as(Spec::Path.base_system_gems.to_s) do
+ sys_exec "ruby -S rake -T"
+ end
+
+ expect(err).to eq("")
+ expected_tasks = [
+ "rake build",
+ "rake clean",
+ "rake clobber",
+ "rake install",
+ "rake release[remote]",
+ ]
+ tasks = out.lines.to_a.map {|s| s.split("#").first.strip }
+ expect(tasks & expected_tasks).to eq(expected_tasks)
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ it "adds 'pkg' to rake/clean's CLOBBER" do
+ with_gem_path_as(Spec::Path.base_system_gems.to_s) do
+ sys_exec! %('#{Gem.ruby}' -S rake -e 'load "Rakefile"; puts CLOBBER.inspect')
+ end
+ expect(last_command.stdout).to eq '["pkg"]'
+ end
+end
diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb
new file mode 100644
index 0000000000..18ca246199
--- /dev/null
+++ b/spec/bundler/runtime/inline_spec.rb
@@ -0,0 +1,266 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundler/inline#gemfile" do
+ def script(code, options = {})
+ requires = ["bundler/inline"]
+ requires.unshift File.expand_path("../../support/artifice/" + options.delete(:artifice) + ".rb", __FILE__) if options.key?(:artifice)
+ requires = requires.map {|r| "require '#{r}'" }.join("\n")
+ @out = ruby("#{requires}\n\n" + code, options)
+ end
+
+ before :each do
+ build_lib "one", "1.0.0" do |s|
+ s.write "lib/baz.rb", "puts 'baz'"
+ s.write "lib/qux.rb", "puts 'qux'"
+ end
+
+ build_lib "two", "1.0.0" do |s|
+ s.write "lib/two.rb", "puts 'two'"
+ s.add_dependency "three", "= 1.0.0"
+ end
+
+ build_lib "three", "1.0.0" do |s|
+ s.write "lib/three.rb", "puts 'three'"
+ s.add_dependency "seven", "= 1.0.0"
+ end
+
+ build_lib "four", "1.0.0" do |s|
+ s.write "lib/four.rb", "puts 'four'"
+ end
+
+ build_lib "five", "1.0.0", :no_default => true do |s|
+ s.write "lib/mofive.rb", "puts 'five'"
+ end
+
+ build_lib "six", "1.0.0" do |s|
+ s.write "lib/six.rb", "puts 'six'"
+ end
+
+ build_lib "seven", "1.0.0" do |s|
+ s.write "lib/seven.rb", "puts 'seven'"
+ end
+
+ build_lib "eight", "1.0.0" do |s|
+ s.write "lib/eight.rb", "puts 'eight'"
+ end
+ end
+
+ it "requires the gems" do
+ script <<-RUBY
+ gemfile do
+ path "#{lib_path}" do
+ gem "two"
+ end
+ end
+ RUBY
+
+ expect(out).to eq("two")
+ expect(exitstatus).to be_zero if exitstatus
+
+ script <<-RUBY
+ gemfile do
+ path "#{lib_path}" do
+ gem "eleven"
+ end
+ end
+
+ puts "success"
+ RUBY
+
+ expect(err).to include "Could not find gem 'eleven'"
+ expect(out).not_to include "success"
+
+ script <<-RUBY
+ gemfile(true) do
+ source "file://#{gem_repo1}"
+ gem "rack"
+ end
+ RUBY
+
+ expect(out).to include("Rack's post install message")
+ expect(exitstatus).to be_zero if exitstatus
+
+ script <<-RUBY, :artifice => "endpoint"
+ gemfile(true) do
+ source "https://notaserver.com"
+ gem "activesupport", :require => true
+ end
+ RUBY
+
+ expect(out).to include("Installing activesupport")
+ err.gsub! %r{.*lib/sinatra/base\.rb:\d+: warning: constant ::Fixnum is deprecated$}, ""
+ err.strip!
+ expect(err).to lack_errors
+ expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "lets me use my own ui object" do
+ script <<-RUBY, :artifice => "endpoint"
+ require 'bundler'
+ class MyBundlerUI < Bundler::UI::Silent
+ def confirm(msg, newline = nil)
+ puts "CONFIRMED!"
+ end
+ end
+ gemfile(true, :ui => MyBundlerUI.new) do
+ source "https://notaserver.com"
+ gem "activesupport", :require => true
+ end
+ RUBY
+
+ expect(out).to eq("CONFIRMED!\nCONFIRMED!")
+ expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "raises an exception if passed unknown arguments" do
+ script <<-RUBY
+ gemfile(true, :arglebargle => true) do
+ path "#{lib_path}"
+ gem "two"
+ end
+
+ puts "success"
+ RUBY
+ expect(err).to include "Unknown options: arglebargle"
+ expect(out).not_to include "success"
+ end
+
+ it "does not mutate the option argument" do
+ script <<-RUBY
+ require 'bundler'
+ options = { :ui => Bundler::UI::Shell.new }
+ gemfile(false, options) do
+ path "#{lib_path}" do
+ gem "two"
+ end
+ end
+ puts "OKAY" if options.key?(:ui)
+ RUBY
+
+ expect(out).to match("OKAY")
+ expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "installs quietly if necessary when the install option is not set" do
+ script <<-RUBY
+ gemfile do
+ source "file://#{gem_repo1}"
+ gem "rack"
+ end
+
+ puts RACK
+ RUBY
+
+ expect(out).to eq("1.0.0")
+ expect(err).to be_empty
+ expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "installs quietly from git if necessary when the install option is not set" do
+ build_git "foo", "1.0.0"
+ baz_ref = build_git("baz", "2.0.0").ref_for("HEAD")
+ script <<-RUBY
+ gemfile do
+ gem "foo", :git => #{lib_path("foo-1.0.0").to_s.dump}
+ gem "baz", :git => #{lib_path("baz-2.0.0").to_s.dump}, :ref => #{baz_ref.dump}
+ end
+
+ puts FOO
+ puts BAZ
+ RUBY
+
+ expect(out).to eq("1.0.0\n2.0.0")
+ expect(err).to be_empty
+ expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "allows calling gemfile twice" do
+ script <<-RUBY
+ gemfile do
+ path "#{lib_path}" do
+ gem "two"
+ end
+ end
+
+ gemfile do
+ path "#{lib_path}" do
+ gem "four"
+ end
+ end
+ RUBY
+
+ expect(out).to eq("two\nfour")
+ expect(err).to be_empty
+ expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "installs inline gems when a Gemfile.lock is present" do
+ gemfile <<-G
+ source "https://notaserver.com"
+ gem "rake"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ rake (11.3.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ rake
+
+ BUNDLED WITH
+ 1.13.6
+ G
+
+ in_app_root do
+ script <<-RUBY
+ gemfile do
+ source "file://#{gem_repo1}"
+ gem "rack"
+ end
+
+ puts RACK
+ RUBY
+ end
+
+ expect(err).to be_empty
+ expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "installs inline gems when BUNDLE_GEMFILE is set to an empty string" do
+ ENV["BUNDLE_GEMFILE"] = ""
+
+ in_app_root do
+ script <<-RUBY
+ gemfile do
+ source "file://#{gem_repo1}"
+ gem "rack"
+ end
+
+ puts RACK
+ RUBY
+ end
+
+ expect(err).to be_empty
+ expect(exitstatus).to be_zero if exitstatus
+ end
+
+ it "installs inline gems when BUNDLE_BIN is set" do
+ ENV["BUNDLE_BIN"] = "/usr/local/bundle/bin"
+
+ script <<-RUBY
+ gemfile do
+ source "file://#{gem_repo1}"
+ gem "rack" # has the rackup executable
+ end
+
+ puts RACK
+ RUBY
+ expect(last_command).to be_success
+ expect(last_command.stdout).to eq "1.0.0"
+ end
+end
diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb
new file mode 100644
index 0000000000..b74dbde3f6
--- /dev/null
+++ b/spec/bundler/runtime/load_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+RSpec.describe "Bundler.load" do
+ describe "with a gemfile" do
+ before(:each) do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ it "provides a list of the env dependencies" do
+ expect(Bundler.load.dependencies).to have_dep("rack", ">= 0")
+ end
+
+ it "provides a list of the resolved gems" do
+ expect(Bundler.load.gems).to have_gem("rack-1.0.0", "bundler-#{Bundler::VERSION}")
+ end
+
+ it "ignores blank BUNDLE_GEMFILEs" do
+ expect do
+ ENV["BUNDLE_GEMFILE"] = ""
+ Bundler.load
+ end.not_to raise_error
+ end
+ end
+
+ describe "with a gems.rb file" do
+ before(:each) do
+ create_file "gems.rb", <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ bundle! :install
+ end
+
+ it "provides a list of the env dependencies" do
+ expect(Bundler.load.dependencies).to have_dep("rack", ">= 0")
+ end
+
+ it "provides a list of the resolved gems" do
+ expect(Bundler.load.gems).to have_gem("rack-1.0.0", "bundler-#{Bundler::VERSION}")
+ end
+ end
+
+ describe "without a gemfile" do
+ it "raises an exception if the default gemfile is not found" do
+ expect do
+ Bundler.load
+ end.to raise_error(Bundler::GemfileNotFound, /could not locate gemfile/i)
+ end
+
+ it "raises an exception if a specified gemfile is not found" do
+ expect do
+ ENV["BUNDLE_GEMFILE"] = "omg.rb"
+ Bundler.load
+ end.to raise_error(Bundler::GemfileNotFound, /omg\.rb/)
+ end
+
+ it "does not find a Gemfile above the testing directory" do
+ bundler_gemfile = tmp.join("../Gemfile")
+ unless File.exist?(bundler_gemfile)
+ FileUtils.touch(bundler_gemfile)
+ @remove_bundler_gemfile = true
+ end
+ begin
+ expect { Bundler.load }.to raise_error(Bundler::GemfileNotFound)
+ ensure
+ bundler_gemfile.rmtree if @remove_bundler_gemfile
+ end
+ end
+ end
+
+ describe "when called twice" do
+ it "doesn't try to load the runtime twice" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo1}"
+ gem "rack"
+ gem "activesupport", :group => :test
+ G
+
+ ruby! <<-RUBY
+ require "bundler"
+ Bundler.setup :default
+ Bundler.require :default
+ puts RACK
+ begin
+ require "activesupport"
+ rescue LoadError
+ puts "no activesupport"
+ end
+ RUBY
+
+ expect(out.split("\n")).to eq(["1.0.0", "no activesupport"])
+ end
+ end
+
+ describe "not hurting brittle rubygems" do
+ it "does not inject #source into the generated YAML of the gem specs" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo1}"
+ gem "activerecord"
+ G
+
+ Bundler.load.specs.each do |spec|
+ expect(spec.to_yaml).not_to match(/^\s+source:/)
+ expect(spec.to_yaml).not_to match(/^\s+groups:/)
+ end
+ end
+ end
+end
diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb
new file mode 100644
index 0000000000..eecf162427
--- /dev/null
+++ b/spec/bundler/runtime/platform_spec.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+RSpec.describe "Bundler.setup with multi platform stuff" do
+ it "raises a friendly error when gems are missing locally" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ lockfile <<-G
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ rack (1.0)
+
+ PLATFORMS
+ #{local_tag}
+
+ DEPENDENCIES
+ rack
+ G
+
+ ruby <<-R
+ begin
+ require 'bundler'
+ Bundler.setup
+ rescue Bundler::GemNotFound => e
+ puts "WIN"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "will resolve correctly on the current platform when the lockfile was targeted for a different one" do
+ lockfile <<-G
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ nokogiri (1.4.2-java)
+ weakling (= 0.0.3)
+ weakling (0.0.3)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ nokogiri
+ G
+
+ simulate_platform "x86-darwin-10"
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "nokogiri"
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2"
+ end
+
+ it "will add the resolve for the current platform" do
+ lockfile <<-G
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ nokogiri (1.4.2-java)
+ weakling (= 0.0.3)
+ weakling (0.0.3)
+
+ PLATFORMS
+ java
+
+ DEPENDENCIES
+ nokogiri
+ G
+
+ simulate_platform "x86-darwin-100"
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "nokogiri"
+ gem "platform_specific"
+ G
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 x86-darwin-100"
+ end
+
+ it "allows specifying only-ruby-platform" do
+ simulate_platform "java"
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "nokogiri"
+ gem "platform_specific"
+ G
+
+ bundle! "config force_ruby_platform true"
+
+ bundle! "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY"
+ end
+
+ it "allows specifying only-ruby-platform on windows with dependency platforms" do
+ simulate_windows do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "nokogiri", :platforms => [:mingw, :mswin, :x64_mingw, :jruby]
+ gem "platform_specific"
+ G
+
+ bundle! "config force_ruby_platform true"
+
+ bundle! "install"
+
+ expect(the_bundle).to include_gems "platform_specific 1.0 RUBY"
+ end
+ end
+
+ it "recovers when the lockfile is missing a platform-specific gem" do
+ build_repo2 do
+ build_gem "requires_platform_specific" do |s|
+ s.add_dependency "platform_specific"
+ end
+ end
+ simulate_windows x64_mingw do
+ lockfile <<-L
+ GEM
+ remote: file:#{gem_repo2}/
+ specs:
+ platform_specific (1.0-x86-mingw32)
+ requires_platform_specific (1.0)
+ platform_specific
+
+ PLATFORMS
+ x64-mingw32
+ x86-mingw32
+
+ DEPENDENCIES
+ requires_platform_specific
+ L
+
+ install_gemfile! <<-G, :verbose => true
+ source "file://#{gem_repo2}"
+ gem "requires_platform_specific"
+ G
+
+ expect(the_bundle).to include_gem "platform_specific 1.0 x64-mingw32"
+ end
+ end
+end
diff --git a/spec/bundler/runtime/require_spec.rb b/spec/bundler/runtime/require_spec.rb
new file mode 100644
index 0000000000..0484e38845
--- /dev/null
+++ b/spec/bundler/runtime/require_spec.rb
@@ -0,0 +1,452 @@
+# frozen_string_literal: true
+
+RSpec.describe "Bundler.require" do
+ before :each do
+ build_lib "one", "1.0.0" do |s|
+ s.write "lib/baz.rb", "puts 'baz'"
+ s.write "lib/qux.rb", "puts 'qux'"
+ end
+
+ build_lib "two", "1.0.0" do |s|
+ s.write "lib/two.rb", "puts 'two'"
+ s.add_dependency "three", "= 1.0.0"
+ end
+
+ build_lib "three", "1.0.0" do |s|
+ s.write "lib/three.rb", "puts 'three'"
+ s.add_dependency "seven", "= 1.0.0"
+ end
+
+ build_lib "four", "1.0.0" do |s|
+ s.write "lib/four.rb", "puts 'four'"
+ end
+
+ build_lib "five", "1.0.0", :no_default => true do |s|
+ s.write "lib/mofive.rb", "puts 'five'"
+ end
+
+ build_lib "six", "1.0.0" do |s|
+ s.write "lib/six.rb", "puts 'six'"
+ end
+
+ build_lib "seven", "1.0.0" do |s|
+ s.write "lib/seven.rb", "puts 'seven'"
+ end
+
+ build_lib "eight", "1.0.0" do |s|
+ s.write "lib/eight.rb", "puts 'eight'"
+ end
+
+ build_lib "nine", "1.0.0" do |s|
+ s.write "lib/nine.rb", "puts 'nine'"
+ end
+
+ build_lib "ten", "1.0.0" do |s|
+ s.write "lib/ten.rb", "puts 'ten'"
+ end
+
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem "one", :group => :bar, :require => %w[baz qux]
+ gem "two"
+ gem "three", :group => :not
+ gem "four", :require => false
+ gem "five"
+ gem "six", :group => "string"
+ gem "seven", :group => :not
+ gem "eight", :require => true, :group => :require_true
+ env "BUNDLER_TEST" => "nine" do
+ gem "nine", :require => true
+ end
+ gem "ten", :install_if => lambda { ENV["BUNDLER_TEST"] == "ten" }
+ end
+ G
+ end
+
+ it "requires the gems" do
+ # default group
+ run "Bundler.require"
+ expect(out).to eq("two")
+
+ # specific group
+ run "Bundler.require(:bar)"
+ expect(out).to eq("baz\nqux")
+
+ # default and specific group
+ run "Bundler.require(:default, :bar)"
+ expect(out).to eq("baz\nqux\ntwo")
+
+ # specific group given as a string
+ run "Bundler.require('bar')"
+ expect(out).to eq("baz\nqux")
+
+ # specific group declared as a string
+ run "Bundler.require(:string)"
+ expect(out).to eq("six")
+
+ # required in resolver order instead of gemfile order
+ run("Bundler.require(:not)")
+ expect(out.split("\n").sort).to eq(%w[seven three])
+
+ # test require: true
+ run "Bundler.require(:require_true)"
+ expect(out).to eq("eight")
+ end
+
+ it "allows requiring gems with non standard names explicitly" do
+ run "Bundler.require ; require 'mofive'"
+ expect(out).to eq("two\nfive")
+ end
+
+ it "allows requiring gems which are scoped by env" do
+ ENV["BUNDLER_TEST"] = "nine"
+ run "Bundler.require"
+ expect(out).to eq("two\nnine")
+ end
+
+ it "allows requiring gems which are scoped by install_if" do
+ ENV["BUNDLER_TEST"] = "ten"
+ run "Bundler.require"
+ expect(out).to eq("two\nten")
+ end
+
+ it "raises an exception if a require is specified but the file does not exist" do
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem "two", :require => 'fail'
+ end
+ G
+
+ load_error_run <<-R, "fail"
+ Bundler.require
+ R
+
+ expect(err).to eq_err("ZOMG LOAD ERROR")
+ end
+
+ it "displays a helpful message if the required gem throws an error" do
+ build_lib "faulty", "1.0.0" do |s|
+ s.write "lib/faulty.rb", "raise RuntimeError.new(\"Gem Internal Error Message\")"
+ end
+
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem "faulty"
+ end
+ G
+
+ run "Bundler.require"
+ expect(err).to match("error while trying to load the gem 'faulty'")
+ expect(err).to match("Gem Internal Error Message")
+ end
+
+ it "doesn't swallow the error when the library has an unrelated error" do
+ build_lib "loadfuuu", "1.0.0" do |s|
+ s.write "lib/loadfuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")"
+ end
+
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem "loadfuuu"
+ end
+ G
+
+ cmd = <<-RUBY
+ begin
+ Bundler.require
+ rescue LoadError => e
+ $stderr.puts "ZOMG LOAD ERROR: \#{e.message}"
+ end
+ RUBY
+ run(cmd)
+
+ expect(err).to eq_err("ZOMG LOAD ERROR: cannot load such file -- load-bar")
+ end
+
+ describe "with namespaced gems" do
+ before :each do
+ build_lib "jquery-rails", "1.0.0" do |s|
+ s.write "lib/jquery/rails.rb", "puts 'jquery/rails'"
+ end
+ lib_path("jquery-rails-1.0.0/lib/jquery-rails.rb").rmtree
+ end
+
+ it "requires gem names that are namespaced" do
+ gemfile <<-G
+ path '#{lib_path}' do
+ gem 'jquery-rails'
+ end
+ G
+
+ run "Bundler.require"
+ expect(out).to eq("jquery/rails")
+ end
+
+ it "silently passes if the require fails" do
+ build_lib "bcrypt-ruby", "1.0.0", :no_default => true do |s|
+ s.write "lib/brcrypt.rb", "BCrypt = '1.0.0'"
+ end
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem "bcrypt-ruby"
+ end
+ G
+
+ cmd = <<-RUBY
+ require 'bundler'
+ Bundler.require
+ RUBY
+ ruby(cmd)
+
+ expect(err).to lack_errors
+ end
+
+ it "does not mangle explicitly given requires" do
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem 'jquery-rails', :require => 'jquery-rails'
+ end
+ G
+
+ load_error_run <<-R, "jquery-rails"
+ Bundler.require
+ R
+ expect(err).to eq_err("ZOMG LOAD ERROR")
+ end
+
+ it "handles the case where regex fails" do
+ build_lib "load-fuuu", "1.0.0" do |s|
+ s.write "lib/load-fuuu.rb", "raise LoadError.new(\"Could not open library 'libfuuu-1.0': libfuuu-1.0: cannot open shared object file: No such file or directory.\")"
+ end
+
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem "load-fuuu"
+ end
+ G
+
+ cmd = <<-RUBY
+ begin
+ Bundler.require
+ rescue LoadError => e
+ $stderr.puts "ZOMG LOAD ERROR" if e.message.include?("Could not open library 'libfuuu-1.0'")
+ end
+ RUBY
+ run(cmd)
+
+ expect(err).to eq_err("ZOMG LOAD ERROR")
+ end
+
+ it "doesn't swallow the error when the library has an unrelated error" do
+ build_lib "load-fuuu", "1.0.0" do |s|
+ s.write "lib/load/fuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")"
+ end
+ lib_path("load-fuuu-1.0.0/lib/load-fuuu.rb").rmtree
+
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem "load-fuuu"
+ end
+ G
+
+ cmd = <<-RUBY
+ begin
+ Bundler.require
+ rescue LoadError => e
+ $stderr.puts "ZOMG LOAD ERROR: \#{e.message}"
+ end
+ RUBY
+ run(cmd)
+
+ expect(err).to eq_err("ZOMG LOAD ERROR: cannot load such file -- load-bar")
+ end
+ end
+
+ describe "using bundle exec" do
+ it "requires the locked gems" do
+ bundle "exec ruby -e 'Bundler.require'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(out).to eq("two")
+
+ bundle "exec ruby -e 'Bundler.require(:bar)'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(out).to eq("baz\nqux")
+
+ bundle "exec ruby -e 'Bundler.require(:default, :bar)'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(out).to eq("baz\nqux\ntwo")
+ end
+ end
+
+ describe "order" do
+ before(:each) do
+ build_lib "one", "1.0.0" do |s|
+ s.write "lib/one.rb", <<-ONE
+ if defined?(Two)
+ Two.two
+ else
+ puts "two_not_loaded"
+ end
+ puts 'one'
+ ONE
+ end
+
+ build_lib "two", "1.0.0" do |s|
+ s.write "lib/two.rb", <<-TWO
+ module Two
+ def self.two
+ puts 'module_two'
+ end
+ end
+ puts 'two'
+ TWO
+ end
+ end
+
+ it "works when the gems are in the Gemfile in the correct order" do
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem "two"
+ gem "one"
+ end
+ G
+
+ run "Bundler.require"
+ expect(out).to eq("two\nmodule_two\none")
+ end
+
+ describe "a gem with different requires for different envs" do
+ before(:each) do
+ build_gem "multi_gem", :to_bundle => true do |s|
+ s.write "lib/one.rb", "puts 'ONE'"
+ s.write "lib/two.rb", "puts 'TWO'"
+ end
+
+ install_gemfile <<-G
+ gem "multi_gem", :require => "one", :group => :one
+ gem "multi_gem", :require => "two", :group => :two
+ G
+ end
+
+ it "requires both with Bundler.require(both)" do
+ run "Bundler.require(:one, :two)"
+ expect(out).to eq("ONE\nTWO")
+ end
+
+ it "requires one with Bundler.require(:one)" do
+ run "Bundler.require(:one)"
+ expect(out).to eq("ONE")
+ end
+
+ it "requires :two with Bundler.require(:two)" do
+ run "Bundler.require(:two)"
+ expect(out).to eq("TWO")
+ end
+ end
+
+ it "fails when the gems are in the Gemfile in the wrong order" do
+ gemfile <<-G
+ path "#{lib_path}" do
+ gem "one"
+ gem "two"
+ end
+ G
+
+ run "Bundler.require"
+ expect(out).to eq("two_not_loaded\none\ntwo")
+ end
+
+ describe "with busted gems" do
+ it "should be busted" do
+ build_gem "busted_require", :to_bundle => true do |s|
+ s.write "lib/busted_require.rb", "require 'no_such_file_omg'"
+ end
+
+ install_gemfile <<-G
+ gem "busted_require"
+ G
+
+ load_error_run <<-R, "no_such_file_omg"
+ Bundler.require
+ R
+ expect(err).to eq_err("ZOMG LOAD ERROR")
+ end
+ end
+ end
+
+ it "does not load rubygems gemspecs that are used", :rubygems => ">= 2.5.2" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ run! <<-R
+ path = File.join(Gem.dir, "specifications", "rack-1.0.0.gemspec")
+ contents = File.read(path)
+ contents = contents.lines.to_a.insert(-2, "\n raise 'broken gemspec'\n").join
+ File.open(path, "w") do |f|
+ f.write contents
+ end
+ R
+
+ run! <<-R
+ Bundler.require
+ puts "WIN"
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "does not load git gemspecs that are used", :rubygems => ">= 2.5.2" do
+ build_git "foo"
+
+ install_gemfile! <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run! <<-R
+ path = Gem.loaded_specs["foo"].loaded_from
+ contents = File.read(path)
+ contents = contents.lines.to_a.insert(-2, "\n raise 'broken gemspec'\n").join
+ File.open(path, "w") do |f|
+ f.write contents
+ end
+ R
+
+ run! <<-R
+ Bundler.require
+ puts "WIN"
+ R
+
+ expect(out).to eq("WIN")
+ end
+end
+
+RSpec.describe "Bundler.require with platform specific dependencies" do
+ it "does not require the gems that are pinned to other platforms" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ platforms :#{not_local_tag} do
+ gem "fail", :require => "omgomg"
+ end
+
+ gem "rack", "1.0.0"
+ G
+
+ run "Bundler.require"
+ expect(err).to lack_errors
+ end
+
+ it "requires gems pinned to multiple platforms, including the current one" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ platforms :#{not_local_tag}, :#{local_tag} do
+ gem "rack", :require => "rack"
+ end
+ G
+
+ run "Bundler.require; puts RACK"
+
+ expect(out).to eq("1.0.0")
+ expect(err).to lack_errors
+ end
+end
diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb
new file mode 100644
index 0000000000..646227d931
--- /dev/null
+++ b/spec/bundler/runtime/setup_spec.rb
@@ -0,0 +1,1445 @@
+# frozen_string_literal: true
+
+RSpec.describe "Bundler.setup" do
+ describe "with no arguments" do
+ it "makes all groups available" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :group => :test
+ G
+
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup
+
+ require 'rack'
+ puts RACK
+ RUBY
+ expect(err).to lack_errors
+ expect(out).to eq("1.0.0")
+ end
+ end
+
+ describe "when called with groups" do
+ before(:each) do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "yard"
+ gem "rack", :group => :test
+ G
+ end
+
+ it "doesn't make all groups available" do
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup(:default)
+
+ begin
+ require 'rack'
+ rescue LoadError
+ puts "WIN"
+ end
+ RUBY
+ expect(err).to lack_errors
+ expect(out).to eq("WIN")
+ end
+
+ it "accepts string for group name" do
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup(:default, 'test')
+
+ require 'rack'
+ puts RACK
+ RUBY
+ expect(err).to lack_errors
+ expect(out).to eq("1.0.0")
+ end
+
+ it "leaves all groups available if they were already" do
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup
+ Bundler.setup(:default)
+
+ require 'rack'
+ puts RACK
+ RUBY
+ expect(err).to lack_errors
+ expect(out).to eq("1.0.0")
+ end
+
+ it "leaves :default available if setup is called twice" do
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup(:default)
+ Bundler.setup(:default, :test)
+
+ begin
+ require 'yard'
+ puts "WIN"
+ rescue LoadError
+ puts "FAIL"
+ end
+ RUBY
+ expect(err).to lack_errors
+ expect(out).to match("WIN")
+ end
+
+ it "handles multiple non-additive invocations" do
+ ruby <<-RUBY
+ require 'bundler'
+ Bundler.setup(:default, :test)
+ Bundler.setup(:default)
+ require 'rack'
+
+ puts "FAIL"
+ RUBY
+
+ expect(err).to match("rack")
+ expect(err).to match("LoadError")
+ expect(out).not_to match("FAIL")
+ end
+ end
+
+ context "load order" do
+ def clean_load_path(lp)
+ without_bundler_load_path = ruby!("puts $LOAD_PATH").split("\n")
+ lp = lp - [
+ bundler_path.to_s,
+ bundler_path.join("gems/bundler-#{Bundler::VERSION}/lib").to_s,
+ tmp("rubygems/lib").to_s,
+ root.join("../lib").expand_path.to_s,
+ ] - without_bundler_load_path
+ lp.map! {|p| p.sub(/^#{Regexp.union system_gem_path.to_s, default_bundle_path.to_s}/i, "") }
+ end
+
+ it "puts loaded gems after -I and RUBYLIB", :ruby_repo do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ ENV["RUBYOPT"] = "-Idash_i_dir"
+ ENV["RUBYLIB"] = "rubylib_dir"
+
+ ruby <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup
+ puts $LOAD_PATH
+ RUBY
+
+ load_path = out.split("\n")
+ rack_load_order = load_path.index {|path| path.include?("rack") }
+
+ expect(err).to eq("")
+ expect(load_path[1]).to include "dash_i_dir"
+ expect(load_path[2]).to include "rubylib_dir"
+ expect(rack_load_order).to be > 0
+ end
+
+ it "orders the load path correctly when there are dependencies" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ G
+
+ ruby! <<-RUBY
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup
+ puts $LOAD_PATH
+ RUBY
+
+ load_path = clean_load_path(out.split("\n"))
+
+ unless Bundler.load.specs["bundler"].empty?
+ load_path.delete_if {|path| path =~ /bundler/ }
+ end
+
+ expect(load_path).to start_with(
+ "/gems/rails-2.3.2/lib",
+ "/gems/activeresource-2.3.2/lib",
+ "/gems/activerecord-2.3.2/lib",
+ "/gems/actionpack-2.3.2/lib",
+ "/gems/actionmailer-2.3.2/lib",
+ "/gems/activesupport-2.3.2/lib",
+ "/gems/rake-10.0.2/lib"
+ )
+ end
+
+ it "falls back to order the load path alphabetically for backwards compatibility" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "weakling"
+ gem "duradura"
+ gem "terranova"
+ G
+
+ ruby! <<-RUBY
+ require 'rubygems'
+ require 'bundler/setup'
+ puts $LOAD_PATH
+ RUBY
+
+ load_path = clean_load_path(out.split("\n"))
+
+ expect(load_path).to start_with(
+ "/gems/weakling-0.0.3/lib",
+ "/gems/terranova-8/lib",
+ "/gems/duradura-7.0/lib"
+ )
+ end
+ end
+
+ it "raises if the Gemfile was not yet installed" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ ruby <<-R
+ require 'rubygems'
+ require 'bundler'
+
+ begin
+ Bundler.setup
+ puts "FAIL"
+ rescue Bundler::GemNotFound
+ puts "WIN"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "doesn't create a Gemfile.lock if the setup fails" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ ruby <<-R
+ require 'rubygems'
+ require 'bundler'
+
+ Bundler.setup
+ R
+
+ expect(bundled_app("Gemfile.lock")).not_to exist
+ end
+
+ it "doesn't change the Gemfile.lock if the setup fails" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ lockfile = File.read(bundled_app("Gemfile.lock"))
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "nosuchgem", "10.0"
+ G
+
+ ruby <<-R
+ require 'rubygems'
+ require 'bundler'
+
+ Bundler.setup
+ R
+
+ expect(File.read(bundled_app("Gemfile.lock"))).to eq(lockfile)
+ end
+
+ it "makes a Gemfile.lock if setup succeeds" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ File.read(bundled_app("Gemfile.lock"))
+
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ run "1"
+ expect(bundled_app("Gemfile.lock")).to exist
+ end
+
+ describe "$BUNDLE_GEMFILE" do
+ context "user provides an absolute path" do
+ it "uses BUNDLE_GEMFILE to locate the gemfile if present" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ gemfile bundled_app("4realz"), <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport", "2.3.5"
+ G
+
+ ENV["BUNDLE_GEMFILE"] = bundled_app("4realz").to_s
+ bundle :install
+
+ expect(the_bundle).to include_gems "activesupport 2.3.5"
+ end
+ end
+
+ context "an absolute path is not provided" do
+ it "uses BUNDLE_GEMFILE to locate the gemfile if present" do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ G
+
+ bundle "install"
+ bundle "install --deployment"
+
+ ENV["BUNDLE_GEMFILE"] = "Gemfile"
+ ruby <<-R
+ require 'rubygems'
+ require 'bundler'
+
+ begin
+ Bundler.setup
+ puts "WIN"
+ rescue ArgumentError => e
+ puts "FAIL"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+ end
+ end
+
+ it "prioritizes gems in BUNDLE_PATH over gems in GEM_HOME" do
+ ENV["BUNDLE_PATH"] = bundled_app(".bundle").to_s
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "1.0.0"
+ G
+
+ build_gem "rack", "1.0", :to_system => true do |s|
+ s.write "lib/rack.rb", "RACK = 'FAIL'"
+ end
+
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ describe "integrate with rubygems" do
+ describe "by replacing #gem" do
+ before :each do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "0.9.1"
+ G
+ end
+
+ it "replaces #gem but raises when the gem is missing" do
+ run <<-R
+ begin
+ gem "activesupport"
+ puts "FAIL"
+ rescue LoadError
+ puts "WIN"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "version_requirement is now deprecated in rubygems 1.4.0+ when gem is missing" do
+ run <<-R
+ begin
+ gem "activesupport"
+ puts "FAIL"
+ rescue LoadError
+ puts "WIN"
+ end
+ R
+
+ expect(err).to lack_errors
+ end
+
+ it "replaces #gem but raises when the version is wrong" do
+ run <<-R
+ begin
+ gem "rack", "1.0.0"
+ puts "FAIL"
+ rescue LoadError
+ puts "WIN"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "version_requirement is now deprecated in rubygems 1.4.0+ when the version is wrong" do
+ run <<-R
+ begin
+ gem "rack", "1.0.0"
+ puts "FAIL"
+ rescue LoadError
+ puts "WIN"
+ end
+ R
+
+ expect(err).to lack_errors
+ end
+ end
+
+ describe "by hiding system gems" do
+ before :each do
+ system_gems "activesupport-2.3.5"
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "yard"
+ G
+ end
+
+ it "removes system gems from Gem.source_index" do
+ run "require 'yard'"
+ expect(out).to eq("bundler-#{Bundler::VERSION}\nyard-1.0")
+ end
+
+ context "when the ruby stdlib is a substring of Gem.path" do
+ it "does not reject the stdlib from $LOAD_PATH" do
+ substring = "/" + $LOAD_PATH.find {|p| p =~ /vendor_ruby/ }.split("/")[2]
+ run "puts 'worked!'", :env => { "GEM_PATH" => substring }
+ expect(out).to eq("worked!")
+ end
+ end
+ end
+ end
+
+ describe "with paths" do
+ it "activates the gems in the path source" do
+ system_gems "rack-1.0.0"
+
+ build_lib "rack", "1.0.0" do |s|
+ s.write "lib/rack.rb", "puts 'WIN'"
+ end
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ path "#{lib_path("rack-1.0.0")}" do
+ gem "rack"
+ end
+ G
+
+ run "require 'rack'"
+ expect(out).to eq("WIN")
+ end
+ end
+
+ describe "with git" do
+ before do
+ build_git "rack", "1.0.0"
+
+ gemfile <<-G
+ gem "rack", :git => "#{lib_path("rack-1.0.0")}"
+ G
+ end
+
+ it "provides a useful exception when the git repo is not checked out yet" do
+ run "1"
+ expect(err).to match(/the git source #{lib_path('rack-1.0.0')} is not yet checked out. Please run `bundle install`/i)
+ end
+
+ it "does not hit the git binary if the lockfile is available and up to date" do
+ bundle "install"
+
+ break_git!
+
+ ruby <<-R
+ require 'rubygems'
+ require 'bundler'
+
+ begin
+ Bundler.setup
+ puts "WIN"
+ rescue Exception => e
+ puts "FAIL"
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "provides a good exception if the lockfile is unavailable" do
+ bundle "install"
+
+ FileUtils.rm(bundled_app("Gemfile.lock"))
+
+ break_git!
+
+ ruby <<-R
+ require "rubygems"
+ require "bundler"
+
+ begin
+ Bundler.setup
+ puts "FAIL"
+ rescue Bundler::GitError => e
+ puts e.message
+ end
+ R
+
+ run "puts 'FAIL'"
+
+ expect(err).not_to include "This is not the git you are looking for"
+ end
+
+ it "works even when the cache directory has been deleted" do
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle")
+ FileUtils.rm_rf vendored_gems("cache")
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+
+ it "does not randomly change the path when specifying --path and the bundle directory becomes read only" do
+ bundle! :install, forgotten_command_line_options(:path => "vendor/bundle")
+
+ with_read_only("**/*") do
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ it "finds git gem when default bundle path becomes read only" do
+ bundle "install"
+
+ with_read_only("#{Bundler.bundle_path}/**/*") do
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+ end
+
+ describe "when specifying local override" do
+ it "explodes if given path does not exist on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle! :install
+
+ FileUtils.rm_rf(lib_path("local-rack"))
+ run "require 'rack'"
+ expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/)
+ end
+
+ it "explodes if branch is not given on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle! :install
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}"
+ G
+
+ run "require 'rack'"
+ expect(err).to match(/because :branch is not specified in Gemfile/)
+ end
+
+ it "explodes on different branches on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle! :install
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "changed"
+ G
+
+ run "require 'rack'"
+ expect(err).to match(/is using branch master but Gemfile specifies changed/)
+ end
+
+ it "explodes on refs with different branches on runtime" do
+ build_git "rack", "0.8"
+
+ FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack"))
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "master", :branch => "master"
+ G
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "master", :branch => "nonexistant"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ run "require 'rack'"
+ expect(err).to match(/is using branch master but Gemfile specifies nonexistant/)
+ end
+ end
+
+ describe "when excluding groups" do
+ it "doesn't change the resolve if --without is used" do
+ install_gemfile <<-G, forgotten_command_line_options(:without => :rails)
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+
+ group :rails do
+ gem "rails", "2.3.2"
+ end
+ G
+
+ install_gems "activesupport-2.3.5"
+
+ expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => :default
+ end
+
+ it "remembers --without and does not bail on bare Bundler.setup" do
+ install_gemfile <<-G, forgotten_command_line_options(:without => :rails)
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+
+ group :rails do
+ gem "rails", "2.3.2"
+ end
+ G
+
+ install_gems "activesupport-2.3.5"
+
+ expect(the_bundle).to include_gems "activesupport 2.3.2"
+ end
+
+ it "remembers --without and does not include groups passed to Bundler.setup" do
+ install_gemfile <<-G, forgotten_command_line_options(:without => :rails)
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+
+ group :rack do
+ gem "rack"
+ end
+
+ group :rails do
+ gem "rails", "2.3.2"
+ end
+ G
+
+ expect(the_bundle).not_to include_gems "activesupport 2.3.2", :groups => :rack
+ expect(the_bundle).to include_gems "rack 1.0.0", :groups => :rack
+ end
+ end
+
+ # Unfortunately, gem_prelude does not record the information about
+ # activated gems, so this test cannot work on 1.9 :(
+ if RUBY_VERSION < "1.9"
+ describe "preactivated gems" do
+ it "raises an exception if a pre activated gem conflicts with the bundle" do
+ system_gems "thin-1.0", "rack-1.0.0"
+ build_gem "thin", "1.1", :to_system => true do |s|
+ s.add_dependency "rack"
+ end
+
+ gemfile <<-G
+ gem "thin", "1.0"
+ G
+
+ ruby <<-R
+ require 'rubygems'
+ gem "thin"
+ require 'bundler'
+ begin
+ Bundler.setup
+ puts "FAIL"
+ rescue Gem::LoadError => e
+ puts e.message
+ end
+ R
+
+ expect(out).to eq("You have already activated thin 1.1, but your Gemfile requires thin 1.0. Prepending `bundle exec` to your command may solve this.")
+ end
+
+ it "version_requirement is now deprecated in rubygems 1.4.0+" do
+ system_gems "thin-1.0", "rack-1.0.0"
+ build_gem "thin", "1.1", :to_system => true do |s|
+ s.add_dependency "rack"
+ end
+
+ gemfile <<-G
+ gem "thin", "1.0"
+ G
+
+ ruby <<-R
+ require 'rubygems'
+ gem "thin"
+ require 'bundler'
+ begin
+ Bundler.setup
+ puts "FAIL"
+ rescue Gem::LoadError => e
+ puts e.message
+ end
+ R
+
+ expect(err).to lack_errors
+ end
+ end
+ end
+
+ # RubyGems returns loaded_from as a string
+ it "has loaded_from as a string on all specs" do
+ build_git "foo"
+ build_git "no-gemspec", :gemspec => false
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ gem "no-gemspec", "1.0", :git => "#{lib_path("no-gemspec-1.0")}"
+ G
+
+ run <<-R
+ Gem.loaded_specs.each do |n, s|
+ puts "FAIL" unless s.loaded_from.is_a?(String)
+ end
+ R
+
+ expect(out).to be_empty
+ end
+
+ it "does not load all gemspecs", :rubygems => ">= 2.3" do
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ run! <<-R
+ File.open(File.join(Gem.dir, "specifications", "broken.gemspec"), "w") do |f|
+ f.write <<-RUBY
+# -*- encoding: utf-8 -*-
+# stub: broken 1.0.0 ruby lib
+
+Gem::Specification.new do |s|
+ s.name = "broken"
+ s.version = "1.0.0"
+ raise "BROKEN GEMSPEC"
+end
+ RUBY
+ end
+ R
+
+ run! <<-R
+ puts "WIN"
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "ignores empty gem paths" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ ENV["GEM_HOME"] = ""
+ bundle %(exec ruby -e "require 'set'"), :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+
+ expect(err).to lack_errors
+ end
+
+ describe "$MANPATH" do
+ before do
+ build_repo4 do
+ build_gem "with_man" do |s|
+ s.write("man/man1/page.1", "MANPAGE")
+ end
+ end
+ end
+
+ context "when the user has one set" do
+ before { ENV["MANPATH"] = "/foo:" }
+
+ it "adds the gem's man dir to the MANPATH" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo4}"
+ gem "with_man"
+ G
+
+ run! "puts ENV['MANPATH']"
+ expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}:/foo")
+ end
+ end
+
+ context "when the user does not have one set" do
+ before { ENV.delete("MANPATH") }
+
+ it "adds the gem's man dir to the MANPATH" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo4}"
+ gem "with_man"
+ G
+
+ run! "puts ENV['MANPATH']"
+ expect(out).to eq(default_bundle_path("gems/with_man-1.0/man").to_s)
+ end
+ end
+ end
+
+ it "should prepend gemspec require paths to $LOAD_PATH in order" do
+ update_repo2 do
+ build_gem("requirepaths") do |s|
+ s.write("lib/rq.rb", "puts 'yay'")
+ s.write("src/rq.rb", "puts 'nooo'")
+ s.require_paths = %w[lib src]
+ end
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ gem "requirepaths", :require => nil
+ G
+
+ run "require 'rq'"
+ expect(out).to eq("yay")
+ end
+
+ it "should clean $LOAD_PATH properly", :ruby_repo do
+ gem_name = "very_simple_binary"
+ full_gem_name = gem_name + "-1.0"
+ ext_dir = File.join(tmp "extenstions", full_gem_name)
+
+ install_gem full_gem_name
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ G
+
+ ruby <<-R
+ if Gem::Specification.method_defined? :extension_dir
+ s = Gem::Specification.find_by_name '#{gem_name}'
+ s.extension_dir = '#{ext_dir}'
+
+ # Don't build extensions.
+ s.class.send(:define_method, :build_extensions) { nil }
+ end
+
+ require 'bundler'
+ gem '#{gem_name}'
+
+ puts $LOAD_PATH.count {|path| path =~ /#{gem_name}/} >= 2
+
+ Bundler.setup
+
+ puts $LOAD_PATH.count {|path| path =~ /#{gem_name}/} == 0
+ R
+
+ expect(out).to eq("true\ntrue")
+ end
+
+ context "with bundler is located in symlinked GEM_HOME" do
+ let(:gem_home) { Dir.mktmpdir }
+ let(:symlinked_gem_home) { Tempfile.new("gem_home") }
+ let(:bundler_dir) { ruby_core? ? File.expand_path("../../../..", __FILE__) : File.expand_path("../../..", __FILE__) }
+ let(:bundler_lib) { File.join(bundler_dir, "lib") }
+
+ before do
+ FileUtils.ln_sf(gem_home, symlinked_gem_home.path)
+ gems_dir = File.join(gem_home, "gems")
+ specifications_dir = File.join(gem_home, "specifications")
+ Dir.mkdir(gems_dir)
+ Dir.mkdir(specifications_dir)
+
+ FileUtils.ln_s(bundler_dir, File.join(gems_dir, "bundler-#{Bundler::VERSION}"))
+
+ gemspec_file = ruby_core? ? "#{bundler_dir}/lib/bundler.gemspec" : "#{bundler_dir}/bundler.gemspec"
+ gemspec = File.read(gemspec_file).
+ sub("Bundler::VERSION", %("#{Bundler::VERSION}"))
+ gemspec = gemspec.lines.reject {|line| line =~ %r{lib/bundler/version} }.join
+
+ File.open(File.join(specifications_dir, "bundler.gemspec"), "wb") do |f|
+ f.write(gemspec)
+ end
+ end
+
+ it "should succesfully require 'bundler/setup'", :ruby_repo do
+ install_gemfile ""
+
+ ENV["GEM_PATH"] = symlinked_gem_home.path
+
+ ruby <<-R
+ if $LOAD_PATH.include?("#{bundler_lib}")
+ # We should use bundler from GEM_PATH for this test, so we should
+ # remove path to the bundler source tree
+ $LOAD_PATH.delete("#{bundler_lib}")
+ else
+ raise "We don't have #{bundler_lib} in $LOAD_PATH"
+ end
+ puts (require 'bundler/setup')
+ R
+
+ expect(out).to eql("true")
+ end
+ end
+
+ it "stubs out Gem.refresh so it does not reveal system gems" do
+ system_gems "rack-1.0.0"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "activesupport"
+ G
+
+ run <<-R
+ puts Bundler.rubygems.find_name("rack").inspect
+ Gem.refresh
+ puts Bundler.rubygems.find_name("rack").inspect
+ R
+
+ expect(out).to eq("[]\n[]")
+ end
+
+ describe "when a vendored gem specification uses the :path option" do
+ it "should resolve paths relative to the Gemfile" do
+ path = bundled_app(File.join("vendor", "foo"))
+ build_lib "foo", :path => path
+
+ # If the .gemspec exists, then Bundler handles the path differently.
+ # See Source::Path.load_spec_files for details.
+ FileUtils.rm(File.join(path, "foo.gemspec"))
+
+ install_gemfile <<-G
+ gem 'foo', '1.2.3', :path => 'vendor/foo'
+ G
+
+ Dir.chdir(bundled_app.parent) do
+ run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app("Gemfile") }
+ require 'foo'
+ R
+ end
+ expect(err).to lack_errors
+ end
+
+ it "should make sure the Bundler.root is really included in the path relative to the Gemfile" do
+ relative_path = File.join("vendor", Dir.pwd[1..-1], "foo")
+ absolute_path = bundled_app(relative_path)
+ FileUtils.mkdir_p(absolute_path)
+ build_lib "foo", :path => absolute_path
+
+ # If the .gemspec exists, then Bundler handles the path differently.
+ # See Source::Path.load_spec_files for details.
+ FileUtils.rm(File.join(absolute_path, "foo.gemspec"))
+
+ gemfile <<-G
+ gem 'foo', '1.2.3', :path => '#{relative_path}'
+ G
+
+ bundle :install
+
+ Dir.chdir(bundled_app.parent) do
+ run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app("Gemfile") }
+ require 'foo'
+ R
+ end
+
+ expect(err).to lack_errors
+ end
+ end
+
+ describe "with git gems that don't have gemspecs" do
+ before :each do
+ build_git "no-gemspec", :gemspec => false
+
+ install_gemfile <<-G
+ gem "no-gemspec", "1.0", :git => "#{lib_path("no-gemspec-1.0")}"
+ G
+ end
+
+ it "loads the library via a virtual spec" do
+ run <<-R
+ require 'no-gemspec'
+ puts NOGEMSPEC
+ R
+
+ expect(out).to eq("1.0")
+ end
+ end
+
+ describe "with bundled and system gems" do
+ before :each do
+ system_gems "rack-1.0.0"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "activesupport", "2.3.5"
+ G
+ end
+
+ it "does not pull in system gems" do
+ run <<-R
+ require 'rubygems'
+
+ begin;
+ require 'rack'
+ rescue LoadError
+ puts 'WIN'
+ end
+ R
+
+ expect(out).to eq("WIN")
+ end
+
+ it "provides a gem method" do
+ run <<-R
+ gem 'activesupport'
+ require 'activesupport'
+ puts ACTIVESUPPORT
+ R
+
+ expect(out).to eq("2.3.5")
+ end
+
+ it "raises an exception if gem is used to invoke a system gem not in the bundle" do
+ run <<-R
+ begin
+ gem 'rack'
+ rescue LoadError => e
+ puts e.message
+ end
+ R
+
+ expect(out).to eq("rack is not part of the bundle. Add it to your Gemfile.")
+ end
+
+ it "sets GEM_HOME appropriately" do
+ run "puts ENV['GEM_HOME']"
+ expect(out).to eq(default_bundle_path.to_s)
+ end
+ end
+
+ describe "with system gems in the bundle" do
+ before :each do
+ bundle! "config path.system true"
+ system_gems "rack-1.0.0"
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", "1.0.0"
+ gem "activesupport", "2.3.5"
+ G
+ end
+
+ it "sets GEM_PATH appropriately" do
+ run "puts Gem.path"
+ paths = out.split("\n")
+ expect(paths).to include(system_gem_path.to_s)
+ end
+ end
+
+ describe "with a gemspec that requires other files" do
+ before :each do
+ build_git "bar", :gemspec => false do |s|
+ s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0')
+ s.write "bar.gemspec", <<-G
+ lib = File.expand_path('../lib/', __FILE__)
+ $:.unshift lib unless $:.include?(lib)
+ require 'bar/version'
+
+ Gem::Specification.new do |s|
+ s.name = 'bar'
+ s.version = BAR_VERSION
+ s.summary = 'Bar'
+ s.files = Dir["lib/**/*.rb"]
+ s.author = 'no one'
+ end
+ G
+ end
+
+ gemfile <<-G
+ gem "bar", :git => "#{lib_path("bar-1.0")}"
+ G
+ end
+
+ it "evals each gemspec in the context of its parent directory" do
+ bundle :install
+ run "require 'bar'; puts BAR"
+ expect(out).to eq("1.0")
+ end
+
+ it "error intelligently if the gemspec has a LoadError" do
+ ref = update_git "bar", :gemspec => false do |s|
+ s.write "bar.gemspec", "require 'foobarbaz'"
+ end.ref_for("HEAD")
+ bundle :install
+
+ expect(out.lines.map(&:chomp)).to include(
+ a_string_starting_with("[!] There was an error while loading `bar.gemspec`:"),
+ RUBY_VERSION >= "1.9" ? a_string_starting_with("Does it try to require a relative path? That's been removed in Ruby 1.9.") : "",
+ " # from #{default_bundle_path "bundler", "gems", "bar-1.0-#{ref[0, 12]}", "bar.gemspec"}:1",
+ " > require 'foobarbaz'"
+ )
+ end
+
+ it "evals each gemspec with a binding from the top level" do
+ bundle "install"
+
+ ruby <<-RUBY
+ require 'bundler'
+ def Bundler.require(path)
+ raise "LOSE"
+ end
+ Bundler.load
+ RUBY
+
+ expect(err).to lack_errors
+ expect(out).to eq("")
+ end
+ end
+
+ describe "when Bundler is bundled" do
+ it "doesn't blow up" do
+ install_gemfile <<-G
+ gem "bundler", :path => "#{File.expand_path("..", lib)}"
+ G
+
+ bundle %(exec ruby -e "require 'bundler'; Bundler.setup"), :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(err).to lack_errors
+ end
+ end
+
+ describe "when BUNDLED WITH" do
+ def lock_with(bundler_version = nil)
+ lock = <<-L
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+ L
+
+ if bundler_version
+ lock += "\n BUNDLED WITH\n #{bundler_version}\n"
+ end
+
+ lock
+ end
+
+ before do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ context "is not present" do
+ it "does not change the lock" do
+ lockfile lock_with(nil)
+ ruby "require 'bundler/setup'"
+ lockfile_should_be lock_with(nil)
+ end
+ end
+
+ context "is newer" do
+ it "does not change the lock or warn" do
+ lockfile lock_with(Bundler::VERSION.succ)
+ ruby "require 'bundler/setup'"
+ expect(out).to eq("")
+ expect(err).to eq("")
+ lockfile_should_be lock_with(Bundler::VERSION.succ)
+ end
+ end
+
+ context "is older" do
+ it "does not change the lock" do
+ lockfile lock_with("1.10.1")
+ ruby "require 'bundler/setup'"
+ lockfile_should_be lock_with("1.10.1")
+ end
+ end
+ end
+
+ describe "when RUBY VERSION" do
+ let(:ruby_version) { nil }
+
+ def lock_with(ruby_version = nil)
+ lock = <<-L
+ GEM
+ remote: file://localhost#{gem_repo1}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ rack
+ L
+
+ if ruby_version
+ lock += "\n RUBY VERSION\n ruby #{ruby_version}\n"
+ end
+
+ lock += <<-L
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+
+ normalize_uri_file(lock)
+ end
+
+ before do
+ install_gemfile <<-G
+ ruby ">= 0"
+ source "file://localhost#{gem_repo1}"
+ gem "rack"
+ G
+ lockfile lock_with(ruby_version)
+ end
+
+ context "is not present" do
+ it "does not change the lock" do
+ expect { ruby! "require 'bundler/setup'" }.not_to change { lockfile }
+ end
+ end
+
+ context "is newer" do
+ let(:ruby_version) { "5.5.5" }
+ it "does not change the lock or warn" do
+ expect { ruby! "require 'bundler/setup'" }.not_to change { lockfile }
+ expect(out).to eq("")
+ expect(err).to eq("")
+ end
+ end
+
+ context "is older" do
+ let(:ruby_version) { "1.0.0" }
+ it "does not change the lock" do
+ expect { ruby! "require 'bundler/setup'" }.not_to change { lockfile }
+ end
+ end
+ end
+
+ describe "with gemified standard libraries" do
+ it "does not load Psych", :ruby => "~> 2.2" do
+ gemfile ""
+ ruby <<-RUBY
+ require 'bundler/setup'
+ puts defined?(Psych::VERSION) ? Psych::VERSION : "undefined"
+ require 'psych'
+ puts Psych::VERSION
+ RUBY
+ pre_bundler, post_bundler = out.split("\n")
+ expect(pre_bundler).to eq("undefined")
+ expect(post_bundler).to match(/\d+\.\d+\.\d+/)
+ end
+
+ it "does not load openssl" do
+ install_gemfile! ""
+ ruby! <<-RUBY
+ require "bundler/setup"
+ puts defined?(OpenSSL) || "undefined"
+ require "openssl"
+ puts defined?(OpenSSL) || "undefined"
+ RUBY
+ expect(out).to eq("undefined\nconstant")
+ end
+
+ describe "default gem activation" do
+ let(:exemptions) do
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.7") || ENV["RGV"] == "master"
+ []
+ else
+ %w[io-console openssl]
+ end << "bundler"
+ end
+
+ let(:activation_warning_hack) { strip_whitespace(<<-RUBY) }
+ require #{spec_dir.join("support/hax").to_s.dump}
+ require "rubygems"
+
+ if Gem::Specification.instance_methods.map(&:to_sym).include?(:activate)
+ Gem::Specification.send(:alias_method, :bundler_spec_activate, :activate)
+ Gem::Specification.send(:define_method, :activate) do
+ unless #{exemptions.inspect}.include?(name)
+ warn '-' * 80
+ warn "activating \#{full_name}"
+ warn *caller
+ warn '*' * 80
+ end
+ bundler_spec_activate
+ end
+ end
+ RUBY
+
+ let(:activation_warning_hack_rubyopt) do
+ create_file("activation_warning_hack.rb", activation_warning_hack)
+ "-r#{bundled_app("activation_warning_hack.rb")} #{ENV["RUBYOPT"]}"
+ end
+
+ let(:code) { strip_whitespace(<<-RUBY) }
+ require "bundler/setup"
+ require "pp"
+ loaded_specs = Gem.loaded_specs.dup
+ #{exemptions.inspect}.each {|s| loaded_specs.delete(s) }
+ pp loaded_specs
+
+ # not a default gem, but harmful to have loaded
+ open_uri = $LOADED_FEATURES.grep(/open.uri/)
+ unless open_uri.empty?
+ warn "open_uri: \#{open_uri}"
+ end
+ RUBY
+
+ it "activates no gems with -rbundler/setup" do
+ install_gemfile! ""
+ ruby! code, :env => { :RUBYOPT => activation_warning_hack_rubyopt }
+ expect(last_command.stdout).to eq("{}")
+ end
+
+ it "activates no gems with bundle exec" do
+ install_gemfile! ""
+ # ensure we clean out the default gems, bceause bundler's allowed to be activated
+ create_file("script.rb", code)
+ bundle! "exec ruby ./script.rb", :env => { :RUBYOPT => activation_warning_hack_rubyopt + " -rbundler/setup" }
+ expect(last_command.stdout).to eq("{}")
+ end
+
+ it "activates no gems with bundle exec that is loaded" do
+ # TODO: remove once https://github.com/erikhuda/thor/pull/539 is released
+ exemptions << "io-console"
+
+ install_gemfile! ""
+ create_file("script.rb", "#!/usr/bin/env ruby\n\n#{code}")
+ FileUtils.chmod(0o777, bundled_app("script.rb"))
+ bundle! "exec ./script.rb", :artifice => nil, :env => { :RUBYOPT => activation_warning_hack_rubyopt }
+ expect(last_command.stdout).to eq("{}")
+ end
+
+ let(:default_gems) do
+ ruby!(<<-RUBY).split("\n")
+ if Gem::Specification.is_a?(Enumerable)
+ puts Gem::Specification.select(&:default_gem?).map(&:name)
+ end
+ RUBY
+ end
+
+ it "activates newer versions of default gems" do
+ build_repo4 do
+ default_gems.each do |g|
+ build_gem g, "999999"
+ end
+ end
+
+ default_gems.reject! {|g| exemptions.include?(g) }
+
+ install_gemfile! <<-G
+ source "file:#{gem_repo4}"
+ #{default_gems}.each do |g|
+ gem g, "999999"
+ end
+ G
+
+ expect(the_bundle).to include_gems(*default_gems.map {|g| "#{g} 999999" })
+ end
+
+ it "activates older versions of default gems" do
+ build_repo4 do
+ default_gems.each do |g|
+ build_gem g, "0.0.0.a"
+ end
+ end
+
+ default_gems.reject! {|g| exemptions.include?(g) }
+
+ install_gemfile! <<-G
+ source "file:#{gem_repo4}"
+ #{default_gems}.each do |g|
+ gem g, "0.0.0.a"
+ end
+ G
+
+ expect(the_bundle).to include_gems(*default_gems.map {|g| "#{g} 0.0.0.a" })
+ end
+ end
+ end
+
+ describe "after setup" do
+ it "allows calling #gem on random objects", :bundler => "< 2" do
+ install_gemfile <<-G
+ source "file:#{gem_repo1}"
+ gem "rack"
+ G
+
+ ruby! <<-RUBY
+ require "bundler/setup"
+ Object.new.gem "rack"
+ puts Gem.loaded_specs["rack"].full_name
+ RUBY
+
+ expect(out).to eq("rack-1.0.0")
+ end
+
+ it "keeps Kernel#gem private", :bundler => "2" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo1}"
+ gem "rack"
+ G
+
+ ruby <<-RUBY
+ require "bundler/setup"
+ Object.new.gem "rack"
+ puts "FAIL"
+ RUBY
+
+ expect(last_command.stdboth).not_to include "FAIL"
+ expect(last_command.stderr).to include "private method `gem'"
+ end
+
+ it "keeps Kernel#require private" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo1}"
+ gem "rack"
+ G
+
+ ruby <<-RUBY
+ require "bundler/setup"
+ Object.new.require "rack"
+ puts "FAIL"
+ RUBY
+
+ expect(last_command.stdboth).not_to include "FAIL"
+ expect(last_command.stderr).to include "private method `require'"
+ end
+ end
+end
diff --git a/spec/bundler/runtime/with_clean_env_spec.rb b/spec/bundler/runtime/with_clean_env_spec.rb
new file mode 100644
index 0000000000..321f5b6415
--- /dev/null
+++ b/spec/bundler/runtime/with_clean_env_spec.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+RSpec.describe "Bundler.with_env helpers" do
+ def bundle_exec_ruby!(code, *args)
+ opts = args.last.is_a?(Hash) ? args.pop : {}
+ env = opts[:env] ||= {}
+ env[:RUBYOPT] ||= "-r#{spec_dir.join("support/hax")}"
+ args.push opts
+ bundle! "exec '#{Gem.ruby}' -e #{code}", *args
+ end
+
+ describe "Bundler.original_env" do
+ before do
+ bundle "config path vendor/bundle"
+ gemfile ""
+ bundle "install"
+ end
+
+ it "should return the PATH present before bundle was activated" do
+ code = "print Bundler.original_env['PATH']"
+ path = `getconf PATH`.strip + "#{File::PATH_SEPARATOR}/foo"
+ with_path_as(path) do
+ bundle_exec_ruby!(code.dump)
+ expect(last_command.stdboth).to eq(path)
+ end
+ end
+
+ it "should return the GEM_PATH present before bundle was activated" do
+ code = "print Bundler.original_env['GEM_PATH']"
+ gem_path = ENV["GEM_PATH"] + ":/foo"
+ with_gem_path_as(gem_path) do
+ bundle_exec_ruby!(code.dump)
+ expect(last_command.stdboth).to eq(gem_path)
+ end
+ end
+
+ it "works with nested bundle exec invocations", :ruby_repo do
+ create_file("exe.rb", <<-'RB')
+ count = ARGV.first.to_i
+ exit if count < 0
+ STDERR.puts "#{count} #{ENV["PATH"].end_with?(":/foo")}"
+ if count == 2
+ ENV["PATH"] = "#{ENV["PATH"]}:/foo"
+ end
+ exec(Gem.ruby, __FILE__, (count - 1).to_s)
+ RB
+ path = `getconf PATH`.strip + File::PATH_SEPARATOR + File.dirname(Gem.ruby)
+ with_path_as(path) do
+ bundle! "exec '#{Gem.ruby}' #{bundled_app("exe.rb")} 2", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ end
+ expect(err).to eq <<-EOS.strip
+2 false
+1 true
+0 true
+ EOS
+ end
+
+ it "removes variables that bundler added", :ruby_repo do
+ original = ruby!('puts ENV.to_a.map {|e| e.join("=") }.sort.join("\n")', :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" })
+ code = 'puts Bundler.original_env.to_a.map {|e| e.join("=") }.sort.join("\n")'
+ bundle! "exec '#{Gem.ruby}' -e #{code.dump}", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" }
+ expect(out).to eq original
+ end
+ end
+
+ describe "Bundler.clean_env", :bundler => "< 2" do
+ before do
+ bundle "config path vendor/bundle"
+ gemfile ""
+ bundle "install"
+ end
+
+ it "should delete BUNDLE_PATH" do
+ code = "print Bundler.clean_env.has_key?('BUNDLE_PATH')"
+ ENV["BUNDLE_PATH"] = "./foo"
+ bundle_exec_ruby! code.dump
+ expect(last_command.stdboth).to eq "false"
+ end
+
+ it "should remove '-rbundler/setup' from RUBYOPT" do
+ code = "print Bundler.clean_env['RUBYOPT']"
+ ENV["RUBYOPT"] = "-W2 -rbundler/setup"
+ bundle_exec_ruby! code.dump
+ expect(last_command.stdboth).not_to include("-rbundler/setup")
+ end
+
+ it "should clean up RUBYLIB", :ruby_repo do
+ code = "print Bundler.clean_env['RUBYLIB']"
+ ENV["RUBYLIB"] = root.join("lib").to_s + File::PATH_SEPARATOR + "/foo"
+ bundle_exec_ruby! code.dump
+ expect(last_command.stdboth).to eq("/foo")
+ end
+
+ it "should restore the original MANPATH" do
+ code = "print Bundler.clean_env['MANPATH']"
+ ENV["MANPATH"] = "/foo"
+ ENV["BUNDLER_ORIG_MANPATH"] = "/foo-original"
+ bundle_exec_ruby! code.dump
+ expect(last_command.stdboth).to eq("/foo-original")
+ end
+ end
+
+ describe "Bundler.with_original_env" do
+ it "should set ENV to original_env in the block" do
+ expected = Bundler.original_env
+ actual = Bundler.with_original_env { ENV.to_hash }
+ expect(actual).to eq(expected)
+ end
+
+ it "should restore the environment after execution" do
+ Bundler.with_original_env do
+ ENV["FOO"] = "hello"
+ end
+
+ expect(ENV).not_to have_key("FOO")
+ end
+ end
+
+ describe "Bundler.with_clean_env", :bundler => "< 2" do
+ it "should set ENV to clean_env in the block" do
+ expected = Bundler.clean_env
+ actual = Bundler.with_clean_env { ENV.to_hash }
+ expect(actual).to eq(expected)
+ end
+
+ it "should restore the environment after execution" do
+ Bundler.with_clean_env do
+ ENV["FOO"] = "hello"
+ end
+
+ expect(ENV).not_to have_key("FOO")
+ end
+ end
+
+ describe "Bundler.clean_system", :ruby => ">= 1.9", :bundler => "< 2" do
+ it "runs system inside with_clean_env" do
+ Bundler.clean_system(%(echo 'if [ "$BUNDLE_PATH" = "" ]; then exit 42; else exit 1; fi' | /bin/sh))
+ expect($?.exitstatus).to eq(42)
+ end
+ end
+
+ describe "Bundler.clean_exec", :ruby => ">= 1.9", :bundler => "< 2" do
+ it "runs exec inside with_clean_env" do
+ pid = Kernel.fork do
+ Bundler.clean_exec(%(echo 'if [ "$BUNDLE_PATH" = "" ]; then exit 42; else exit 1; fi' | /bin/sh))
+ end
+ Process.wait(pid)
+ expect($?.exitstatus).to eq(42)
+ end
+ end
+end
diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb
new file mode 100644
index 0000000000..d24bd4505b
--- /dev/null
+++ b/spec/bundler/spec_helper.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+$:.unshift File.expand_path("..", __FILE__)
+$:.unshift File.expand_path("../../lib", __FILE__)
+
+require "rubygems"
+module Gem
+ if defined?(@path_to_default_spec_map)
+ @path_to_default_spec_map.delete_if do |_path, spec|
+ spec.name == "bundler"
+ end
+ end
+end
+
+begin
+ require File.expand_path("../support/path.rb", __FILE__)
+ spec = Gem::Specification.load(Spec::Path.gemspec.to_s)
+ rspec = spec.dependencies.find {|d| d.name == "rspec" }
+ gem "rspec", rspec.requirement.to_s
+ require "rspec"
+ require "diff/lcs"
+rescue LoadError
+ abort "Run rake spec:deps to install development dependencies"
+end
+
+require "bundler/psyched_yaml"
+require "bundler/vendored_fileutils"
+require "uri"
+require "digest"
+
+
+# Delete the default copy of Bundler that RVM installs for us when running in CI
+require "fileutils"
+if ENV.select {|k, _v| k =~ /TRAVIS/ }.any? && Gem::Version.new(Gem::VERSION) > Gem::Version.new("2.0")
+ Dir.glob(File.join(Gem::Specification.default_specifications_dir, "bundler*.gemspec")).each do |file|
+ FileUtils.rm_rf(file)
+ end
+
+ Dir.glob(File.join(RbConfig::CONFIG["sitelibdir"], "bundler*")).each do |file|
+ FileUtils.rm_rf(file)
+ end
+end
+
+if File.expand_path(__FILE__) =~ %r{([^\w/\.:\-])}
+ abort "The bundler specs cannot be run from a path that contains special characters (particularly #{$1.inspect})"
+end
+
+require "bundler"
+
+Dir["#{File.expand_path("../support", __FILE__)}/*.rb"].each do |file|
+ file = file.gsub(%r{\A#{Regexp.escape File.expand_path("..", __FILE__)}/}, "")
+ require file unless file.end_with?("hax.rb")
+end
+
+$debug = false
+
+Spec::Manpages.setup
+Spec::Rubygems.setup
+FileUtils.rm_rf(Spec::Path.gem_repo1)
+ENV["RUBYOPT"] = "#{ENV["RUBYOPT"]} -r#{Spec::Path.spec_dir}/support/hax.rb"
+ENV["BUNDLE_SPEC_RUN"] = "true"
+
+# Don't wrap output in tests
+ENV["THOR_COLUMNS"] = "10000"
+
+Spec::CodeClimate.setup
+
+module Gem
+ def self.ruby=(ruby)
+ @ruby = ruby
+ end
+end
+
+RSpec.configure do |config|
+ config.include Spec::Builders
+ config.include Spec::Helpers
+ config.include Spec::Indexes
+ config.include Spec::Matchers
+ config.include Spec::Path
+ config.include Spec::Rubygems
+ config.include Spec::Platforms
+ config.include Spec::Sudo
+ config.include Spec::Permissions
+
+ # Enable flags like --only-failures and --next-failure
+ config.example_status_persistence_file_path = ".rspec_status"
+
+ config.disable_monkey_patching!
+
+ # Since failures cause us to keep a bunch of long strings in memory, stop
+ # once we have a large number of failures (indicative of core pieces of
+ # bundler being broken) so that running the full test suite doesn't take
+ # forever due to memory constraints
+ config.fail_fast ||= 25 if ENV["CI"]
+
+ if ENV["BUNDLER_SUDO_TESTS"] && Spec::Sudo.present?
+ config.filter_run :sudo => true
+ else
+ config.filter_run_excluding :sudo => true
+ end
+
+ if ENV["BUNDLER_REALWORLD_TESTS"]
+ config.filter_run :realworld => true
+ else
+ config.filter_run_excluding :realworld => true
+ end
+
+ git_version = Bundler::Source::Git::GitProxy.new(nil, nil, nil).version
+
+ config.filter_run_excluding :ruby => LessThanProc.with(RUBY_VERSION)
+ config.filter_run_excluding :rubygems => LessThanProc.with(Gem::VERSION)
+ config.filter_run_excluding :git => LessThanProc.with(git_version)
+ config.filter_run_excluding :rubygems_master => (ENV["RGV"] != "master")
+ config.filter_run_excluding :bundler => LessThanProc.with(Bundler::VERSION.split(".")[0, 2].join("."))
+ config.filter_run_excluding :ruby_repo => !(ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"]).nil?
+
+ config.filter_run_when_matching :focus unless ENV["CI"]
+
+ original_wd = Dir.pwd
+ original_env = ENV.to_hash.delete_if {|k, _v| k.start_with?(Bundler::EnvironmentPreserver::BUNDLER_PREFIX) }
+
+ config.expect_with :rspec do |c|
+ c.syntax = :expect
+ end
+
+ config.before :suite do
+ if ENV["BUNDLE_RUBY"]
+ @orig_ruby = Gem.ruby
+ Gem.ruby = ENV["BUNDLE_RUBY"]
+ end
+ end
+
+ config.before :all do
+ build_repo1
+ end
+
+ config.before :each do
+ reset!
+ system_gems []
+ in_app_root
+ @command_executions = []
+ end
+
+ config.after :each do |example|
+ all_output = @command_executions.map(&:to_s_verbose).join("\n\n")
+ if example.exception && !all_output.empty?
+ warn all_output unless config.formatters.grep(RSpec::Core::Formatters::DocumentationFormatter).empty?
+ message = example.exception.message + "\n\nCommands:\n#{all_output}"
+ (class << example.exception; self; end).send(:define_method, :message) do
+ message
+ end
+ end
+
+ Dir.chdir(original_wd)
+ ENV.replace(original_env)
+ end
+
+ config.after :suite do
+ Gem.ruby = @orig_ruby if ENV["BUNDLE_RUBY"]
+ end
+end
diff --git a/spec/bundler/support/artifice/compact_index.rb b/spec/bundler/support/artifice/compact_index.rb
new file mode 100644
index 0000000000..01e8eb7837
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+$LOAD_PATH.unshift Dir[base_system_gems.join("gems/compact_index*/lib")].first.to_s
+require "compact_index"
+
+class CompactIndexAPI < Endpoint
+ helpers do
+ def load_spec(name, version, platform, gem_repo)
+ full_name = "#{name}-#{version}"
+ full_name += "-#{platform}" if platform != "ruby"
+ Marshal.load(Bundler.rubygems.inflate(File.open(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz")).read))
+ end
+
+ def etag_response
+ response_body = yield
+ checksum = Digest(:MD5).hexdigest(response_body)
+ return if not_modified?(checksum)
+ headers "ETag" => quote(checksum)
+ headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
+ content_type "text/plain"
+ requested_range_for(response_body)
+ rescue => e
+ puts e
+ puts e.backtrace
+ raise
+ end
+
+ def not_modified?(checksum)
+ etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"])
+
+ return unless etags.include?(checksum)
+ headers "ETag" => quote(checksum)
+ status 304
+ body ""
+ end
+
+ def requested_range_for(response_body)
+ ranges = Rack::Utils.byte_ranges(env, response_body.bytesize)
+
+ if ranges
+ status 206
+ body ranges.map! {|range| slice_body(response_body, range) }.join
+ else
+ status 200
+ body response_body
+ end
+ end
+
+ def quote(string)
+ %("#{string}")
+ end
+
+ def parse_etags(value)
+ value ? value.split(/, ?/).select {|s| s.sub!(/"(.*)"/, '\1') } : []
+ end
+
+ def slice_body(body, range)
+ if body.respond_to?(:byteslice)
+ body.byteslice(range)
+ else # pre-1.9.3
+ body.unpack("@#{range.first}a#{range.end + 1}").first
+ end
+ end
+
+ def gems(gem_repo = GEM_REPO)
+ @gems ||= {}
+ @gems[gem_repo] ||= begin
+ specs = Bundler::Deprecate.skip_during do
+ %w[specs.4.8 prerelease_specs.4.8].map do |filename|
+ Marshal.load(File.open(gem_repo.join(filename)).read).map do |name, version, platform|
+ load_spec(name, version, platform, gem_repo)
+ end
+ end.flatten
+ end
+
+ specs.group_by(&:name).map do |name, versions|
+ gem_versions = versions.map do |spec|
+ deps = spec.dependencies.select {|d| d.type == :runtime }.map do |d|
+ reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ")
+ CompactIndex::Dependency.new(d.name, reqs)
+ end
+ checksum = begin
+ Digest::SHA256.file("#{GEM_REPO}/gems/#{spec.original_name}.gem").base64digest
+ rescue
+ nil
+ end
+ CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil,
+ deps, spec.required_ruby_version, spec.required_rubygems_version)
+ end
+ CompactIndex::Gem.new(name, gem_versions)
+ end
+ end
+ end
+ end
+
+ get "/names" do
+ etag_response do
+ CompactIndex.names(gems.map(&:name))
+ end
+ end
+
+ get "/versions" do
+ etag_response do
+ file = tmp("versions.list")
+ file.delete if file.file?
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ file.contents
+ end
+ end
+
+ get "/info/:name" do
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexAPI)
diff --git a/spec/bundler/support/artifice/compact_index_api_missing.rb b/spec/bundler/support/artifice/compact_index_api_missing.rb
new file mode 100644
index 0000000000..d4e68c38e8
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_api_missing.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexApiMissing < CompactIndexAPI
+ get "/fetch/actual/gem/:id" do
+ $stderr.puts params[:id]
+ if params[:id] == "rack-1.0.gemspec.rz"
+ halt 404
+ else
+ File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexApiMissing)
diff --git a/spec/bundler/support/artifice/compact_index_basic_authentication.rb b/spec/bundler/support/artifice/compact_index_basic_authentication.rb
new file mode 100644
index 0000000000..97aa6cbd84
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_basic_authentication.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexBasicAuthentication < CompactIndexAPI
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexBasicAuthentication)
diff --git a/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb
new file mode 100644
index 0000000000..62feb9f164
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexChecksumMismatch < CompactIndexAPI
+ get "/versions" do
+ headers "ETag" => quote("123")
+ headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
+ content_type "text/plain"
+ body ""
+ end
+end
+
+Artifice.activate_with(CompactIndexChecksumMismatch)
diff --git a/spec/bundler/support/artifice/compact_index_concurrent_download.rb b/spec/bundler/support/artifice/compact_index_concurrent_download.rb
new file mode 100644
index 0000000000..972ecb88b7
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_concurrent_download.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexConcurrentDownload < CompactIndexAPI
+ get "/versions" do
+ versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions")
+
+ # Verify the original (empty) content hasn't been deleted, e.g. on a retry
+ File.read(versions) == "" || raise("Original file should be present and empty")
+
+ # Verify this is only requested once for a partial download
+ env["HTTP_RANGE"] || raise("Missing Range header for expected partial download")
+
+ # Overwrite the file in parallel, which should be then overwritten
+ # after a successful download to prevent corruption
+ File.open(versions, "w") {|f| f.puts "another process" }
+
+ etag_response do
+ file = tmp("versions.list")
+ file.delete if file.file?
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ file.contents
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexConcurrentDownload)
diff --git a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
new file mode 100644
index 0000000000..0d349bcc1e
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexCredsDiffHost < CompactIndexAPI
+ helpers do
+ def auth
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
+ end
+
+ def authorized?
+ auth.provided? && auth.basic? && auth.credentials && auth.credentials == %w[user pass]
+ end
+
+ def protected!
+ return if authorized?
+ response["WWW-Authenticate"] = %(Basic realm="Testing HTTP Auth")
+ throw(:halt, [401, "Not authorized\n"])
+ end
+ end
+
+ before do
+ protected! unless request.path_info.include?("/no/creds/")
+ end
+
+ get "/gems/:id" do
+ redirect "http://diffhost.com/no/creds/#{params[:id]}"
+ end
+
+ get "/no/creds/:id" do
+ if request.host.include?("diffhost") && !auth.provided?
+ File.read("#{gem_repo1}/gems/#{params[:id]}")
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexCredsDiffHost)
diff --git a/spec/bundler/support/artifice/compact_index_extra.rb b/spec/bundler/support/artifice/compact_index_extra.rb
new file mode 100644
index 0000000000..84d1859235
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_extra.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexExtra < CompactIndexAPI
+ get "/extra/versions" do
+ halt 404
+ end
+
+ get "/extra/api/v1/dependencies" do
+ halt 404
+ end
+
+ get "/extra/specs.4.8.gz" do
+ File.read("#{gem_repo2}/specs.4.8.gz")
+ end
+
+ get "/extra/prerelease_specs.4.8.gz" do
+ File.read("#{gem_repo2}/prerelease_specs.4.8.gz")
+ end
+
+ get "/extra/quick/Marshal.4.8/:id" do
+ redirect "/extra/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/extra/fetch/actual/gem/:id" do
+ File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/extra/gems/:id" do
+ File.read("#{gem_repo2}/gems/#{params[:id]}")
+ end
+end
+
+Artifice.activate_with(CompactIndexExtra)
diff --git a/spec/bundler/support/artifice/compact_index_extra_api.rb b/spec/bundler/support/artifice/compact_index_extra_api.rb
new file mode 100644
index 0000000000..903aa900fb
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_extra_api.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexExtraApi < CompactIndexAPI
+ get "/extra/names" do
+ etag_response do
+ CompactIndex.names(gems(gem_repo4).map(&:name))
+ end
+ end
+
+ get "/extra/versions" do
+ etag_response do
+ file = tmp("versions.list")
+ file.delete if file.file?
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems(gem_repo4))
+ file.contents
+ end
+ end
+
+ get "/extra/info/:name" do
+ etag_response do
+ gem = gems(gem_repo4).find {|g| g.name == params[:name] }
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+
+ get "/extra/specs.4.8.gz" do
+ File.read("#{gem_repo4}/specs.4.8.gz")
+ end
+
+ get "/extra/prerelease_specs.4.8.gz" do
+ File.read("#{gem_repo4}/prerelease_specs.4.8.gz")
+ end
+
+ get "/extra/quick/Marshal.4.8/:id" do
+ redirect "/extra/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/extra/fetch/actual/gem/:id" do
+ File.read("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/extra/gems/:id" do
+ File.read("#{gem_repo4}/gems/#{params[:id]}")
+ end
+end
+
+Artifice.activate_with(CompactIndexExtraApi)
diff --git a/spec/bundler/support/artifice/compact_index_extra_api_missing.rb b/spec/bundler/support/artifice/compact_index_extra_api_missing.rb
new file mode 100644
index 0000000000..e72040f604
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_extra_api_missing.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index_extra_api", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexExtraAPIMissing < CompactIndexExtraApi
+ get "/extra/fetch/actual/gem/:id" do
+ if params[:id] == "missing-1.0.gemspec.rz"
+ halt 404
+ else
+ File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexExtraAPIMissing)
diff --git a/spec/bundler/support/artifice/compact_index_extra_missing.rb b/spec/bundler/support/artifice/compact_index_extra_missing.rb
new file mode 100644
index 0000000000..67a9d23691
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_extra_missing.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index_extra", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexExtraMissing < CompactIndexExtra
+ get "/extra/fetch/actual/gem/:id" do
+ if params[:id] == "missing-1.0.gemspec.rz"
+ halt 404
+ else
+ File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexExtraMissing)
diff --git a/spec/bundler/support/artifice/compact_index_forbidden.rb b/spec/bundler/support/artifice/compact_index_forbidden.rb
new file mode 100644
index 0000000000..0a4dfdb2e8
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_forbidden.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexForbidden < CompactIndexAPI
+ get "/versions" do
+ halt 403
+ end
+end
+
+Artifice.activate_with(CompactIndexForbidden)
diff --git a/spec/bundler/support/artifice/compact_index_host_redirect.rb b/spec/bundler/support/artifice/compact_index_host_redirect.rb
new file mode 100644
index 0000000000..ab371117de
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_host_redirect.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexHostRedirect < CompactIndexAPI
+ get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
+ redirect "http://bundler.localgemserver.test#{request.path_info}"
+ end
+
+ get "/versions" do
+ status 404
+ end
+
+ get "/api/v1/dependencies" do
+ status 404
+ end
+end
+
+Artifice.activate_with(CompactIndexHostRedirect)
diff --git a/spec/bundler/support/artifice/compact_index_no_gem.rb b/spec/bundler/support/artifice/compact_index_no_gem.rb
new file mode 100644
index 0000000000..01c5be1b3d
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_no_gem.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexNoGem < CompactIndexAPI
+ get "/gems/:id" do
+ halt 500
+ end
+end
+
+Artifice.activate_with(CompactIndexNoGem)
diff --git a/spec/bundler/support/artifice/compact_index_partial_update.rb b/spec/bundler/support/artifice/compact_index_partial_update.rb
new file mode 100644
index 0000000000..eaedff5105
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_partial_update.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexPartialUpdate < CompactIndexAPI
+ # Stub the server to never return 304s. This simulates the behaviour of
+ # Fastly / Rubygems ignoring ETag headers.
+ def not_modified?(_checksum)
+ false
+ end
+
+ get "/versions" do
+ cached_versions_path = File.join(
+ Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions"
+ )
+
+ # Verify a cached copy of the versions file exists
+ unless File.read(cached_versions_path).start_with?("created_at: ")
+ raise("Cached versions file should be present and have content")
+ end
+
+ # Verify that a partial request is made, starting from the index of the
+ # final byte of the cached file.
+ unless env["HTTP_RANGE"] == "bytes=#{File.read(cached_versions_path).bytesize - 1}-"
+ raise("Range header should be present, and start from the index of the final byte of the cache.")
+ end
+
+ etag_response do
+ # Return the exact contents of the cache.
+ File.read(cached_versions_path)
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexPartialUpdate)
diff --git a/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb b/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb
new file mode 100644
index 0000000000..487be4771a
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexRangeNotSatisfiable < CompactIndexAPI
+ get "/versions" do
+ if env["HTTP_RANGE"]
+ status 416
+ else
+ etag_response do
+ file = tmp("versions.list")
+ file.delete if file.file?
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ file.contents
+ end
+ end
+ end
+
+ get "/info/:name" do
+ if env["HTTP_RANGE"]
+ status 416
+ else
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexRangeNotSatisfiable)
diff --git a/spec/bundler/support/artifice/compact_index_redirects.rb b/spec/bundler/support/artifice/compact_index_redirects.rb
new file mode 100644
index 0000000000..e83451b5b6
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_redirects.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexRedirect < CompactIndexAPI
+ get "/fetch/actual/gem/:id" do
+ redirect "/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/versions" do
+ status 404
+ end
+
+ get "/api/v1/dependencies" do
+ status 404
+ end
+end
+
+Artifice.activate_with(CompactIndexRedirect)
diff --git a/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb
new file mode 100644
index 0000000000..abbf3258e7
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexStrictBasicAuthentication < CompactIndexAPI
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+
+ # Only accepts password == "password"
+ unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz"
+ halt 403, "Authentication failed"
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexStrictBasicAuthentication)
diff --git a/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb b/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb
new file mode 100644
index 0000000000..7e1d3686e2
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexWrongDependencies < CompactIndexAPI
+ get "/info/:name" do
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ gem.versions.each {|gv| gv.dependencies.clear } if gem
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexWrongDependencies)
diff --git a/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb
new file mode 100644
index 0000000000..db4d8e3974
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexWrongGemChecksum < CompactIndexAPI
+ get "/info/:name" do
+ etag_response do
+ name = params[:name]
+ gem = gems.find {|g| g.name == name }
+ checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "ab" * 22 }
+ versions = gem ? gem.versions : []
+ versions.each {|v| v.checksum = checksum }
+ CompactIndex.info(versions)
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexWrongGemChecksum)
diff --git a/spec/bundler/support/artifice/endopint_marshal_fail_basic_authentication.rb b/spec/bundler/support/artifice/endopint_marshal_fail_basic_authentication.rb
new file mode 100644
index 0000000000..12a6fa153f
--- /dev/null
+++ b/spec/bundler/support/artifice/endopint_marshal_fail_basic_authentication.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint_marshal_fail", __FILE__)
+
+Artifice.deactivate
+
+class EndpointMarshalFailBasicAuthentication < EndpointMarshalFail
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+ end
+end
+
+Artifice.activate_with(EndpointMarshalFailBasicAuthentication)
diff --git a/spec/bundler/support/artifice/endpoint.rb b/spec/bundler/support/artifice/endpoint.rb
new file mode 100644
index 0000000000..9a0cfae8a2
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require File.expand_path("../../path.rb", __FILE__)
+require Spec::Path.root.join("lib/bundler/deprecate")
+include Spec::Path
+
+$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{artifice,rack,tilt,sinatra}-*/lib")].map(&:to_s))
+require "artifice"
+require "sinatra/base"
+
+ALL_REQUESTS = [] # rubocop:disable Style/MutableConstant
+ALL_REQUESTS_MUTEX = Mutex.new
+
+at_exit do
+ if expected = ENV["BUNDLER_SPEC_ALL_REQUESTS"]
+ expected = expected.split("\n").sort
+ actual = ALL_REQUESTS.sort
+
+ unless expected == actual
+ raise "Unexpected requests!\nExpected:\n\t#{expected.join("\n\t")}\n\nActual:\n\t#{actual.join("\n\t")}"
+ end
+ end
+end
+
+class Endpoint < Sinatra::Base
+ def self.all_requests
+ @all_requests ||= []
+ end
+
+ GEM_REPO = Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"] || Spec::Path.gem_repo1)
+ set :raise_errors, true
+ set :show_exceptions, false
+
+ def call!(*)
+ super.tap do
+ ALL_REQUESTS_MUTEX.synchronize do
+ ALL_REQUESTS << @request.url
+ end
+ end
+ end
+
+ helpers do
+ def dependencies_for(gem_names, gem_repo = GEM_REPO)
+ return [] if gem_names.nil? || gem_names.empty?
+
+ require "rubygems"
+ require "bundler"
+ Bundler::Deprecate.skip_during do
+ all_specs = %w[specs.4.8 prerelease_specs.4.8].map do |filename|
+ Marshal.load(File.open(gem_repo.join(filename)).read)
+ end.inject(:+)
+
+ all_specs.map do |name, version, platform|
+ spec = load_spec(name, version, platform, gem_repo)
+ next unless gem_names.include?(spec.name)
+ {
+ :name => spec.name,
+ :number => spec.version.version,
+ :platform => spec.platform.to_s,
+ :dependencies => spec.dependencies.select {|dep| dep.type == :runtime }.map do |dep|
+ [dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")]
+ end
+ }
+ end.compact
+ end
+ end
+
+ def load_spec(name, version, platform, gem_repo)
+ full_name = "#{name}-#{version}"
+ full_name += "-#{platform}" if platform != "ruby"
+ Marshal.load(Bundler.rubygems.inflate(File.open(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz")).read))
+ end
+ end
+
+ get "/quick/Marshal.4.8/:id" do
+ redirect "/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/fetch/actual/gem/:id" do
+ File.read("#{GEM_REPO}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/gems/:id" do
+ File.read("#{GEM_REPO}/gems/#{params[:id]}")
+ end
+
+ get "/api/v1/dependencies" do
+ Marshal.dump(dependencies_for(params[:gems]))
+ end
+
+ get "/specs.4.8.gz" do
+ File.read("#{GEM_REPO}/specs.4.8.gz")
+ end
+
+ get "/prerelease_specs.4.8.gz" do
+ File.read("#{GEM_REPO}/prerelease_specs.4.8.gz")
+ end
+end
+
+Artifice.activate_with(Endpoint)
diff --git a/spec/bundler/support/artifice/endpoint_500.rb b/spec/bundler/support/artifice/endpoint_500.rb
new file mode 100644
index 0000000000..202ccfc829
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_500.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require File.expand_path("../../path.rb", __FILE__)
+include Spec::Path
+
+$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{artifice,rack,tilt,sinatra}-*/lib")].map(&:to_s))
+
+require "artifice"
+require "sinatra/base"
+
+Artifice.deactivate
+
+class Endpoint500 < Sinatra::Base
+ before do
+ halt 500
+ end
+end
+
+Artifice.activate_with(Endpoint500)
diff --git a/spec/bundler/support/artifice/endpoint_api_forbidden.rb b/spec/bundler/support/artifice/endpoint_api_forbidden.rb
new file mode 100644
index 0000000000..bb89747adc
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_api_forbidden.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointApiForbidden < Endpoint
+ get "/api/v1/dependencies" do
+ halt 403
+ end
+end
+
+Artifice.activate_with(EndpointApiForbidden)
diff --git a/spec/bundler/support/artifice/endpoint_api_missing.rb b/spec/bundler/support/artifice/endpoint_api_missing.rb
new file mode 100644
index 0000000000..95db8e2a7e
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_api_missing.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointApiMissing < Endpoint
+ get "/fetch/actual/gem/:id" do
+ $stderr.puts params[:id]
+ if params[:id] == "rack-1.0.gemspec.rz"
+ halt 404
+ else
+ File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+ end
+end
+
+Artifice.activate_with(EndpointApiMissing)
diff --git a/spec/bundler/support/artifice/endpoint_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_basic_authentication.rb
new file mode 100644
index 0000000000..223671bc29
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_basic_authentication.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointBasicAuthentication < Endpoint
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+ end
+end
+
+Artifice.activate_with(EndpointBasicAuthentication)
diff --git a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb
new file mode 100644
index 0000000000..925954b12d
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointCredsDiffHost < Endpoint
+ helpers do
+ def auth
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
+ end
+
+ def authorized?
+ auth.provided? && auth.basic? && auth.credentials && auth.credentials == %w[user pass]
+ end
+
+ def protected!
+ return if authorized?
+ response["WWW-Authenticate"] = %(Basic realm="Testing HTTP Auth")
+ throw(:halt, [401, "Not authorized\n"])
+ end
+ end
+
+ before do
+ protected! unless request.path_info.include?("/no/creds/")
+ end
+
+ get "/gems/:id" do
+ redirect "http://diffhost.com/no/creds/#{params[:id]}"
+ end
+
+ get "/no/creds/:id" do
+ if request.host.include?("diffhost") && !auth.provided?
+ File.read("#{gem_repo1}/gems/#{params[:id]}")
+ end
+ end
+end
+
+Artifice.activate_with(EndpointCredsDiffHost)
diff --git a/spec/bundler/support/artifice/endpoint_extra.rb b/spec/bundler/support/artifice/endpoint_extra.rb
new file mode 100644
index 0000000000..422f65401b
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_extra.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointExtra < Endpoint
+ get "/extra/api/v1/dependencies" do
+ halt 404
+ end
+
+ get "/extra/specs.4.8.gz" do
+ File.read("#{gem_repo2}/specs.4.8.gz")
+ end
+
+ get "/extra/prerelease_specs.4.8.gz" do
+ File.read("#{gem_repo2}/prerelease_specs.4.8.gz")
+ end
+
+ get "/extra/quick/Marshal.4.8/:id" do
+ redirect "/extra/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/extra/fetch/actual/gem/:id" do
+ File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/extra/gems/:id" do
+ File.read("#{gem_repo2}/gems/#{params[:id]}")
+ end
+end
+
+Artifice.activate_with(EndpointExtra)
diff --git a/spec/bundler/support/artifice/endpoint_extra_api.rb b/spec/bundler/support/artifice/endpoint_extra_api.rb
new file mode 100644
index 0000000000..62e2c2bb93
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_extra_api.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointExtraApi < Endpoint
+ get "/extra/api/v1/dependencies" do
+ deps = dependencies_for(params[:gems], gem_repo4)
+ Marshal.dump(deps)
+ end
+
+ get "/extra/specs.4.8.gz" do
+ File.read("#{gem_repo4}/specs.4.8.gz")
+ end
+
+ get "/extra/prerelease_specs.4.8.gz" do
+ File.read("#{gem_repo4}/prerelease_specs.4.8.gz")
+ end
+
+ get "/extra/quick/Marshal.4.8/:id" do
+ redirect "/extra/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/extra/fetch/actual/gem/:id" do
+ File.read("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}")
+ end
+
+ get "/extra/gems/:id" do
+ File.read("#{gem_repo4}/gems/#{params[:id]}")
+ end
+end
+
+Artifice.activate_with(EndpointExtraApi)
diff --git a/spec/bundler/support/artifice/endpoint_extra_missing.rb b/spec/bundler/support/artifice/endpoint_extra_missing.rb
new file mode 100644
index 0000000000..038a12610a
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_extra_missing.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint_extra", __FILE__)
+
+Artifice.deactivate
+
+class EndpointExtraMissing < EndpointExtra
+ get "/extra/fetch/actual/gem/:id" do
+ if params[:id] == "missing-1.0.gemspec.rz"
+ halt 404
+ else
+ File.read("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
+ end
+ end
+end
+
+Artifice.activate_with(EndpointExtraMissing)
diff --git a/spec/bundler/support/artifice/endpoint_fallback.rb b/spec/bundler/support/artifice/endpoint_fallback.rb
new file mode 100644
index 0000000000..554c08f0a2
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_fallback.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointFallback < Endpoint
+ DEPENDENCY_LIMIT = 60
+
+ get "/api/v1/dependencies" do
+ if params[:gems] && params[:gems].size <= DEPENDENCY_LIMIT
+ Marshal.dump(dependencies_for(params[:gems]))
+ else
+ halt 413, "Too many gems to resolve, please request less than #{DEPENDENCY_LIMIT} gems"
+ end
+ end
+end
+
+Artifice.activate_with(EndpointFallback)
diff --git a/spec/bundler/support/artifice/endpoint_host_redirect.rb b/spec/bundler/support/artifice/endpoint_host_redirect.rb
new file mode 100644
index 0000000000..cda5664be2
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_host_redirect.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointHostRedirect < Endpoint
+ get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
+ redirect "http://bundler.localgemserver.test#{request.path_info}"
+ end
+
+ get "/api/v1/dependencies" do
+ status 404
+ end
+end
+
+Artifice.activate_with(EndpointHostRedirect)
diff --git a/spec/bundler/support/artifice/endpoint_marshal_fail.rb b/spec/bundler/support/artifice/endpoint_marshal_fail.rb
new file mode 100644
index 0000000000..2a5dcdc2fd
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_marshal_fail.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint_fallback", __FILE__)
+
+Artifice.deactivate
+
+class EndpointMarshalFail < EndpointFallback
+ get "/api/v1/dependencies" do
+ "f0283y01hasf"
+ end
+end
+
+Artifice.activate_with(EndpointMarshalFail)
diff --git a/spec/bundler/support/artifice/endpoint_mirror_source.rb b/spec/bundler/support/artifice/endpoint_mirror_source.rb
new file mode 100644
index 0000000000..64452f198d
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_mirror_source.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+class EndpointMirrorSource < Endpoint
+ get "/gems/:id" do
+ if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/"
+ File.read("#{gem_repo1}/gems/#{params[:id]}")
+ else
+ halt 500
+ end
+ end
+end
+
+Artifice.activate_with(EndpointMirrorSource)
diff --git a/spec/bundler/support/artifice/endpoint_redirect.rb b/spec/bundler/support/artifice/endpoint_redirect.rb
new file mode 100644
index 0000000000..ebf01458ba
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_redirect.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointRedirect < Endpoint
+ get "/fetch/actual/gem/:id" do
+ redirect "/fetch/actual/gem/#{params[:id]}"
+ end
+
+ get "/api/v1/dependencies" do
+ status 404
+ end
+end
+
+Artifice.activate_with(EndpointRedirect)
diff --git a/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb
new file mode 100644
index 0000000000..905a519f3f
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint", __FILE__)
+
+Artifice.deactivate
+
+class EndpointStrictBasicAuthentication < Endpoint
+ before do
+ unless env["HTTP_AUTHORIZATION"]
+ halt 401, "Authentication info not supplied"
+ end
+
+ # Only accepts password == "password"
+ unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz"
+ halt 403, "Authentication failed"
+ end
+ end
+end
+
+Artifice.activate_with(EndpointStrictBasicAuthentication)
diff --git a/spec/bundler/support/artifice/endpoint_timeout.rb b/spec/bundler/support/artifice/endpoint_timeout.rb
new file mode 100644
index 0000000000..3f60471c90
--- /dev/null
+++ b/spec/bundler/support/artifice/endpoint_timeout.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require File.expand_path("../endpoint_fallback", __FILE__)
+
+Artifice.deactivate
+
+class EndpointTimeout < EndpointFallback
+ SLEEP_TIMEOUT = 3
+
+ get "/api/v1/dependencies" do
+ sleep(SLEEP_TIMEOUT)
+ end
+end
+
+Artifice.activate_with(EndpointTimeout)
diff --git a/spec/bundler/support/artifice/fail.rb b/spec/bundler/support/artifice/fail.rb
new file mode 100644
index 0000000000..1059c6df4e
--- /dev/null
+++ b/spec/bundler/support/artifice/fail.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require "net/http"
+begin
+ require "net/https"
+rescue LoadError
+ nil # net/https or openssl
+end
+
+# We can't use artifice here because it uses rack
+
+module Artifice; end # for < 2.0, Net::HTTP::Persistent::SSLReuse
+
+class Fail < Net::HTTP
+ # Net::HTTP uses a @newimpl instance variable to decide whether
+ # to use a legacy implementation. Since we are subclassing
+ # Net::HTTP, we must set it
+ @newimpl = true
+
+ def request(req, body = nil, &block)
+ raise(exception(req))
+ end
+
+ # Ensure we don't start a connect here
+ def connect
+ end
+
+ def exception(req)
+ name = ENV.fetch("BUNDLER_SPEC_EXCEPTION") { "Errno::ENETUNREACH" }
+ const = name.split("::").reduce(Object) {|mod, sym| mod.const_get(sym) }
+ const.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}")
+ end
+end
+
+# Replace Net::HTTP with our failing subclass
+::Net.class_eval do
+ remove_const(:HTTP)
+ const_set(:HTTP, ::Fail)
+end
diff --git a/spec/bundler/support/artifice/vcr.rb b/spec/bundler/support/artifice/vcr.rb
new file mode 100644
index 0000000000..edd2f49a91
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr.rb
@@ -0,0 +1,158 @@
+# frozen_string_literal: true
+
+require "net/http"
+if RUBY_VERSION < "1.9"
+ begin
+ require "net/https"
+ rescue LoadError
+ nil # net/https or openssl
+ end
+end # but only for 1.8
+
+CASSETTE_PATH = File.expand_path("../vcr_cassettes", __FILE__)
+CASSETTE_NAME = ENV.fetch("BUNDLER_SPEC_VCR_CASSETTE_NAME") { "realworld" }
+
+class BundlerVCRHTTP < Net::HTTP
+ class RequestHandler
+ attr_reader :http, :request, :body, :response_block
+ def initialize(http, request, body = nil, &response_block)
+ @http = http
+ @request = request
+ @body = body
+ @response_block = response_block
+ end
+
+ def handle_request
+ handler = self
+ request.instance_eval do
+ @__vcr_request_handler = handler
+ end
+
+ if recorded_response?
+ recorded_response
+ else
+ record_response
+ end
+ end
+
+ def recorded_response?
+ return true if ENV["BUNDLER_SPEC_PRE_RECORDED"]
+ return false if ENV["BUNDLER_SPEC_FORCE_RECORD"]
+ request_pair_paths.all? {|f| File.exist?(f) }
+ end
+
+ def recorded_response
+ File.open(request_pair_paths.last, "rb:ASCII-8BIT") do |response_file|
+ response_io = ::Net::BufferedIO.new(response_file)
+ ::Net::HTTPResponse.read_new(response_io).tap do |response|
+ response.decode_content = request.decode_content if request.respond_to?(:decode_content)
+ response.uri = request.uri if request.respond_to?(:uri)
+
+ response.reading_body(response_io, request.response_body_permitted?) do
+ response_block.call(response) if response_block
+ end
+ end
+ end
+ end
+
+ def record_response
+ request_path, response_path = *request_pair_paths
+
+ @recording = true
+
+ response = http.request_without_vcr(request, body, &response_block)
+ @recording = false
+ unless @recording
+ FileUtils.mkdir_p(File.dirname(request_path))
+ binwrite(request_path, request_to_string(request))
+ binwrite(response_path, response_to_string(response))
+ end
+ response
+ end
+
+ def key
+ [request["host"] || http.address, request.path, request.method].compact
+ end
+
+ def file_name_for_key(key)
+ key.join("/").gsub(/[\:*?"<>|]/, "-")
+ end
+
+ def request_pair_paths
+ %w[request response].map do |kind|
+ File.join(CASSETTE_PATH, CASSETTE_NAME, file_name_for_key(key + [kind]))
+ end
+ end
+
+ def read_stored_request(path)
+ contents = File.read(path)
+ headers = {}
+ method = nil
+ path = nil
+ contents.lines.grep(/^> /).each do |line|
+ if line =~ /^> (GET|HEAD|POST|PATCH|PUT|DELETE) (.*)/
+ method = $1
+ path = $2.strip
+ elsif line =~ /^> (.*?): (.*)/
+ headers[$1] = $2
+ end
+ end
+ body = contents =~ /^([^>].*)/m && $1
+ Net::HTTP.const_get(method.capitalize).new(path, headers).tap {|r| r.body = body if body }
+ end
+
+ def request_to_string(request)
+ request_string = []
+ request_string << "> #{request.method.upcase} #{request.path}"
+ request.to_hash.each do |key, value|
+ request_string << "> #{key}: #{Array(value).first}"
+ end
+ request << "" << request.body if request.body
+ request_string.join("\n")
+ end
+
+ def response_to_string(response)
+ headers = response.to_hash
+ body = response.body
+
+ response_string = []
+ response_string << "HTTP/1.1 #{response.code} #{response.message}"
+
+ headers["content-length"] = [body.bytesize.to_s] if body
+
+ headers.each do |header, value|
+ response_string << "#{header}: #{value.join(", ")}"
+ end
+
+ response_string << "" << body
+
+ response_string = response_string.join("\n")
+ if response_string.respond_to?(:force_encoding)
+ response_string.force_encoding("ASCII-8BIT")
+ else
+ response_string
+ end
+ end
+
+ def binwrite(path, contents)
+ File.open(path, "wb:ASCII-8BIT") {|f| f.write(contents) }
+ end
+ end
+
+ def request_with_vcr(request, *args, &block)
+ handler = request.instance_eval do
+ remove_instance_variable(:@__vcr_request_handler) if defined?(@__vcr_request_handler)
+ end || RequestHandler.new(self, request, *args, &block)
+
+ handler.handle_request
+ end
+
+ alias_method :request_without_vcr, :request
+ alias_method :request, :request_with_vcr
+end
+
+# Replace Net::HTTP with our VCR subclass
+::Net.class_eval do
+ remove_const(:HTTP)
+ const_set(:HTTP, BundlerVCRHTTP)
+end
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies-gems=bundler/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies-gems=bundler/GET/request
new file mode 100644
index 0000000000..00dcd51750
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies-gems=bundler/GET/request
@@ -0,0 +1,7 @@
+> GET /api/v1/dependencies?gems=bundler
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: api.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies/HEAD/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies/HEAD/request
new file mode 100644
index 0000000000..13b3c98dd2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies/HEAD/request
@@ -0,0 +1,6 @@
+> HEAD /api/v1/dependencies
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: api.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies/HEAD/response b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies/HEAD/response
new file mode 100644
index 0000000000..fa6cc543da
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/api/v1/dependencies/HEAD/response
@@ -0,0 +1,24 @@
+HTTP/1.1 200 OK
+content-type: text/plain; charset=utf-8
+x-frame-options: SAMEORIGIN
+x-xss-protection: 1; mode=block
+x-content-type-options: nosniff
+content-security-policy: default-src 'self'; script-src 'self' https://secure.gaug.es; style-src 'self' https://fonts.googleapis.com; img-src 'self' https://secure.gaug.es https://gravatar.com https://secure.gravatar.com; font-src 'self' https://fonts.gstatic.com; connect-src https://s3-us-west-2.amazonaws.com/rubygems-dumps/; frame-src https://ghbtns.com
+cache-control: no-cache
+x-request-id: a7d87e66-6bb3-4b7a-9d3a-89ee68784d0e
+x-runtime: 0.003648
+x-ua-compatible: IE=Edge,chrome=1
+x-backend: F_Rails 54.186.104.15:443
+content-length: 0
+accept-ranges: bytes
+date: Fri, 28 Apr 2017 09:22:23 GMT
+via: 1.1 varnish
+age: 3103
+connection: keep-alive
+x-served-by: cache-fra1243-FRA
+x-cache: HIT
+x-cache-hits: 107
+x-timer: S1493371344.878545,VS0,VE0
+vary: Accept-Encoding,Fastly-SSL
+server: RubyGems.org
+
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/gems/bundler-1.12.3.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/gems/bundler-1.12.3.gem/GET/request
new file mode 100644
index 0000000000..230892854c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/gems/bundler-1.12.3.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/bundler-1.12.3.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: api.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/quick/Marshal.4.8/bundler-1.12.3.gemspec.rz/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/quick/Marshal.4.8/bundler-1.12.3.gemspec.rz/GET/request
new file mode 100644
index 0000000000..c9337e9a6b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/quick/Marshal.4.8/bundler-1.12.3.gemspec.rz/GET/request
@@ -0,0 +1,7 @@
+> GET /quick/Marshal.4.8/bundler-1.12.3.gemspec.rz
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: api.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/specs.4.8.gz/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/specs.4.8.gz/GET/request
new file mode 100644
index 0000000000..94f2b68688
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/api.rubygems.org/specs.4.8.gz/GET/request
@@ -0,0 +1,7 @@
+> GET /specs.4.8.gz
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: api.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
new file mode 100644
index 0000000000..a92b8bb55f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/CFPropertyList/GET/request
@@ -0,0 +1,7 @@
+> GET /info/CFPropertyList
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
new file mode 100644
index 0000000000..278c2103b3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ParseTree/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ParseTree
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RedCloth/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RedCloth/GET/request
new file mode 100644
index 0000000000..895b7dfd54
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RedCloth/GET/request
@@ -0,0 +1,7 @@
+> GET /info/RedCloth
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
new file mode 100644
index 0000000000..6bf0e6d3af
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/RubyInline/GET/request
@@ -0,0 +1,7 @@
+> GET /info/RubyInline
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
new file mode 100644
index 0000000000..30a9943801
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/SexpProcessor/GET/request
@@ -0,0 +1,7 @@
+> GET /info/SexpProcessor
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
new file mode 100644
index 0000000000..927fce847b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ZenTest/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ZenTest
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
new file mode 100644
index 0000000000..33ba985fc9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/abstract/GET/request
@@ -0,0 +1,7 @@
+> GET /info/abstract
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
new file mode 100644
index 0000000000..67f6acff19
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actioncable/GET/request
@@ -0,0 +1,7 @@
+> GET /info/actioncable
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
new file mode 100644
index 0000000000..ff277f113a
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionmailer/GET/request
@@ -0,0 +1,7 @@
+> GET /info/actionmailer
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
new file mode 100644
index 0000000000..85c039b7eb
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionpack/GET/request
@@ -0,0 +1,7 @@
+> GET /info/actionpack
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
new file mode 100644
index 0000000000..8b11f69250
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionview/GET/request
@@ -0,0 +1,7 @@
+> GET /info/actionview
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
new file mode 100644
index 0000000000..2ec89cf1b2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/actionwebservice/GET/request
@@ -0,0 +1,7 @@
+> GET /info/actionwebservice
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
new file mode 100644
index 0000000000..a215f0abf9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activejob/GET/request
@@ -0,0 +1,7 @@
+> GET /info/activejob
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
new file mode 100644
index 0000000000..ceae0f0950
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel-globalid/GET/request
@@ -0,0 +1,7 @@
+> GET /info/activemodel-globalid
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
new file mode 100644
index 0000000000..e4b113569e
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activemodel/GET/request
@@ -0,0 +1,7 @@
+> GET /info/activemodel
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
new file mode 100644
index 0000000000..095307216d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord-deprecated_finders/GET/request
@@ -0,0 +1,7 @@
+> GET /info/activerecord-deprecated_finders
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
new file mode 100644
index 0000000000..72983afdb3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activerecord/GET/request
@@ -0,0 +1,7 @@
+> GET /info/activerecord
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
new file mode 100644
index 0000000000..6da07e07c1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activeresource/GET/request
@@ -0,0 +1,7 @@
+> GET /info/activeresource
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
new file mode 100644
index 0000000000..e850b19891
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/activesupport/GET/request
@@ -0,0 +1,7 @@
+> GET /info/activesupport
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
new file mode 100644
index 0000000000..b85f0d4abc
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/adamantium/GET/request
@@ -0,0 +1,7 @@
+> GET /info/adamantium
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
new file mode 100644
index 0000000000..332d846d65
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/addressable/GET/request
@@ -0,0 +1,7 @@
+> GET /info/addressable
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/allison/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/allison/GET/request
new file mode 100644
index 0000000000..8b0e9de941
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/allison/GET/request
@@ -0,0 +1,7 @@
+> GET /info/allison
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ansi/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ansi/GET/request
new file mode 100644
index 0000000000..35a798ad01
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ansi/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ansi
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/archive-tar-minitar/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/archive-tar-minitar/GET/request
new file mode 100644
index 0000000000..15113c6b35
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/archive-tar-minitar/GET/request
@@ -0,0 +1,7 @@
+> GET /info/archive-tar-minitar
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
new file mode 100644
index 0000000000..c8e60e5198
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/arel/GET/request
@@ -0,0 +1,7 @@
+> GET /info/arel
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
new file mode 100644
index 0000000000..83c157b83d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ast/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ast
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
new file mode 100644
index 0000000000..eb816b9425
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/astrolabe/GET/request
@@ -0,0 +1,7 @@
+> GET /info/astrolabe
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
new file mode 100644
index 0000000000..95416a8856
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/atomic/GET/request
@@ -0,0 +1,7 @@
+> GET /info/atomic
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
new file mode 100644
index 0000000000..9a7e6768c9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/autoparse/GET/request
@@ -0,0 +1,7 @@
+> GET /info/autoparse
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
new file mode 100644
index 0000000000..56d2f3f4be
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/axiom-types/GET/request
@@ -0,0 +1,7 @@
+> GET /info/axiom-types
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
new file mode 100644
index 0000000000..d39a8eefed
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/backports/GET/request
@@ -0,0 +1,7 @@
+> GET /info/backports
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bacon/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bacon/GET/request
new file mode 100644
index 0000000000..99b3624b68
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bacon/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bacon
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
new file mode 100644
index 0000000000..da2d06bb39
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt-ruby/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bcrypt-ruby
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
new file mode 100644
index 0000000000..6516d4f90d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bcrypt
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
new file mode 100644
index 0000000000..cba3167ca3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bcrypt_pbkdf/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bcrypt_pbkdf
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-extras/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-extras/GET/request
new file mode 100644
index 0000000000..a42bb5fc39
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-extras/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bones-extras
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-git/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-git/GET/request
new file mode 100644
index 0000000000..f887a9d8b9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-git/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bones-git
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rcov/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rcov/GET/request
new file mode 100644
index 0000000000..123d71230b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rcov/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bones-rcov
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rspec/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rspec/GET/request
new file mode 100644
index 0000000000..11d8de5fd1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rspec/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bones-rspec
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rubyforge/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rubyforge/GET/request
new file mode 100644
index 0000000000..759c5dc5a7
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-rubyforge/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bones-rubyforge
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-zentest/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-zentest/GET/request
new file mode 100644
index 0000000000..ae5a9b068f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones-zentest/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bones-zentest
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones/GET/request
new file mode 100644
index 0000000000..3e6d44e52f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bones/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bones
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
new file mode 100644
index 0000000000..5fe5431bfa
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/builder/GET/request
@@ -0,0 +1,7 @@
+> GET /info/builder
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
new file mode 100644
index 0000000000..d0af347d8a
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/bundler/GET/request
@@ -0,0 +1,7 @@
+> GET /info/bundler
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/camping/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/camping/GET/request
new file mode 100644
index 0000000000..d40975748c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/camping/GET/request
@@ -0,0 +1,7 @@
+> GET /info/camping
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
new file mode 100644
index 0000000000..4e852e84e4
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/capybara/GET/request
@@ -0,0 +1,7 @@
+> GET /info/capybara
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
new file mode 100644
index 0000000000..8ae268566f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celerity/GET/request
@@ -0,0 +1,7 @@
+> GET /info/celerity
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
new file mode 100644
index 0000000000..caa2b64772
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-essentials/GET/request
@@ -0,0 +1,7 @@
+> GET /info/celluloid-essentials
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
new file mode 100644
index 0000000000..3680210c06
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-extras/GET/request
@@ -0,0 +1,7 @@
+> GET /info/celluloid-extras
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
new file mode 100644
index 0000000000..9ab4a9f9a5
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-fsm/GET/request
@@ -0,0 +1,7 @@
+> GET /info/celluloid-fsm
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
new file mode 100644
index 0000000000..48deb49201
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-io/GET/request
@@ -0,0 +1,7 @@
+> GET /info/celluloid-io
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
new file mode 100644
index 0000000000..375710b600
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-pool/GET/request
@@ -0,0 +1,7 @@
+> GET /info/celluloid-pool
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
new file mode 100644
index 0000000000..62acec8401
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid-supervision/GET/request
@@ -0,0 +1,7 @@
+> GET /info/celluloid-supervision
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
new file mode 100644
index 0000000000..ea4c6081c6
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/celluloid/GET/request
@@ -0,0 +1,7 @@
+> GET /info/celluloid
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
new file mode 100644
index 0000000000..2087abff17
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cgi_multipart_eof_fix/GET/request
@@ -0,0 +1,7 @@
+> GET /info/cgi_multipart_eof_fix
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
new file mode 100644
index 0000000000..c9c4c8e1c3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/childprocess/GET/request
@@ -0,0 +1,7 @@
+> GET /info/childprocess
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
new file mode 100644
index 0000000000..627dfc2236
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/climate_control/GET/request
@@ -0,0 +1,7 @@
+> GET /info/climate_control
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 6d5397ce7f8b26e0
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
new file mode 100644
index 0000000000..0b58b496a2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cocaine/GET/request
@@ -0,0 +1,7 @@
+> GET /info/cocaine
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 6d5397ce7f8b26e0
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
new file mode 100644
index 0000000000..26fb80abd9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coercible/GET/request
@@ -0,0 +1,7 @@
+> GET /info/coercible
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
new file mode 100644
index 0000000000..35f65b1c96
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-rails/GET/request
@@ -0,0 +1,7 @@
+> GET /info/coffee-rails
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
new file mode 100644
index 0000000000..c1e276d2f2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script-source/GET/request
@@ -0,0 +1,7 @@
+> GET /info/coffee-script-source
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
new file mode 100644
index 0000000000..6fb40106a3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coffee-script/GET/request
@@ -0,0 +1,7 @@
+> GET /info/coffee-script
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
new file mode 100644
index 0000000000..86a09b2b35
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/colorize/GET/request
@@ -0,0 +1,7 @@
+> GET /info/colorize
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
new file mode 100644
index 0000000000..0069d6d724
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/concurrent-ruby/GET/request
@@ -0,0 +1,7 @@
+> GET /info/concurrent-ruby
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
new file mode 100644
index 0000000000..70e600db3d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/configuration/GET/request
@@ -0,0 +1,7 @@
+> GET /info/configuration
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
new file mode 100644
index 0000000000..5692539d94
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/coveralls/GET/request
@@ -0,0 +1,7 @@
+> GET /info/coveralls
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
new file mode 100644
index 0000000000..8b9407ffc0
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/crass/GET/request
@@ -0,0 +1,7 @@
+> GET /info/crass
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
new file mode 100644
index 0000000000..2675af7847
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-core/GET/request
@@ -0,0 +1,7 @@
+> GET /info/cucumber-core
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
new file mode 100644
index 0000000000..f37e871807
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber-wire/GET/request
@@ -0,0 +1,7 @@
+> GET /info/cucumber-wire
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
new file mode 100644
index 0000000000..db80e44ac1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/cucumber/GET/request
@@ -0,0 +1,7 @@
+> GET /info/cucumber
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
new file mode 100644
index 0000000000..f92a2f7bc9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/culerity/GET/request
@@ -0,0 +1,7 @@
+> GET /info/culerity
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
new file mode 100644
index 0000000000..1119c8e4fa
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/curses/GET/request
@@ -0,0 +1,7 @@
+> GET /info/curses
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
new file mode 100644
index 0000000000..5080c98e6d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/daemons/GET/request
@@ -0,0 +1,7 @@
+> GET /info/daemons
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
new file mode 100644
index 0000000000..d80f93551a
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/database_cleaner/GET/request
@@ -0,0 +1,7 @@
+> GET /info/database_cleaner
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
new file mode 100644
index 0000000000..f2463924fc
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative-option/GET/request
@@ -0,0 +1,7 @@
+> GET /info/declarative-option
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
new file mode 100644
index 0000000000..de13d40070
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/declarative/GET/request
@@ -0,0 +1,7 @@
+> GET /info/declarative
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
new file mode 100644
index 0000000000..563a4f60c2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/descendants_tracker/GET/request
@@ -0,0 +1,7 @@
+> GET /info/descendants_tracker
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
new file mode 100644
index 0000000000..3c60080b38
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/diff-lcs/GET/request
@@ -0,0 +1,7 @@
+> GET /info/diff-lcs
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
new file mode 100644
index 0000000000..de1e687189
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/docile/GET/request
@@ -0,0 +1,7 @@
+> GET /info/docile
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
new file mode 100644
index 0000000000..6157da6082
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/domain_name/GET/request
@@ -0,0 +1,7 @@
+> GET /info/domain_name
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
new file mode 100644
index 0000000000..fe63740e5b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv-deployment/GET/request
@@ -0,0 +1,7 @@
+> GET /info/dotenv-deployment
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
new file mode 100644
index 0000000000..234c682a25
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/dotenv/GET/request
@@ -0,0 +1,7 @@
+> GET /info/dotenv
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/echoe/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/echoe/GET/request
new file mode 100644
index 0000000000..98ea72af26
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/echoe/GET/request
@@ -0,0 +1,7 @@
+> GET /info/echoe
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
new file mode 100644
index 0000000000..12962c23bb
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/em-hiredis/GET/request
@@ -0,0 +1,7 @@
+> GET /info/em-hiredis
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
new file mode 100644
index 0000000000..e558748718
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/english/GET/request
@@ -0,0 +1,7 @@
+> GET /info/english
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
new file mode 100644
index 0000000000..830c2ba57f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/equalizer/GET/request
@@ -0,0 +1,7 @@
+> GET /info/equalizer
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
new file mode 100644
index 0000000000..60045f3490
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubi/GET/request
@@ -0,0 +1,7 @@
+> GET /info/erubi
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
new file mode 100644
index 0000000000..1f4a5d9599
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/erubis/GET/request
@@ -0,0 +1,7 @@
+> GET /info/erubis
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
new file mode 100644
index 0000000000..27c56dc41b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/escape_utils/GET/request
@@ -0,0 +1,7 @@
+> GET /info/escape_utils
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
new file mode 100644
index 0000000000..83753cb9de
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/et-orbi/GET/request
@@ -0,0 +1,7 @@
+> GET /info/et-orbi
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
new file mode 100644
index 0000000000..aad129c8f2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/event-bus/GET/request
@@ -0,0 +1,7 @@
+> GET /info/event-bus
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine-le/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine-le/GET/request
new file mode 100644
index 0000000000..1d83bd53b7
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine-le/GET/request
@@ -0,0 +1,7 @@
+> GET /info/eventmachine-le
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
new file mode 100644
index 0000000000..614a5bf2a8
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/eventmachine/GET/request
@@ -0,0 +1,7 @@
+> GET /info/eventmachine
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
new file mode 100644
index 0000000000..531f5c9b31
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/execjs/GET/request
@@ -0,0 +1,7 @@
+> GET /info/execjs
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
new file mode 100644
index 0000000000..3fa0dea3a5
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/extlib/GET/request
@@ -0,0 +1,7 @@
+> GET /info/extlib
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
new file mode 100644
index 0000000000..6aae67744b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facets/GET/request
@@ -0,0 +1,7 @@
+> GET /info/facets
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
new file mode 100644
index 0000000000..18bdd6e328
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/facter/GET/request
@@ -0,0 +1,7 @@
+> GET /info/facter
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
new file mode 100644
index 0000000000..7bf2fb9f5d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faker/GET/request
@@ -0,0 +1,7 @@
+> GET /info/faker
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/jobs,spec_run,trampoline_disable,plugins ab677b2f2db78951
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
new file mode 100644
index 0000000000..b7ee2f317a
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faraday/GET/request
@@ -0,0 +1,7 @@
+> GET /info/faraday
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
new file mode 100644
index 0000000000..7ea5079231
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fastthread/GET/request
@@ -0,0 +1,7 @@
+> GET /info/fastthread
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
new file mode 100644
index 0000000000..e865b6d68f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/faye-websocket/GET/request
@@ -0,0 +1,7 @@
+> GET /info/faye-websocket
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fcgi/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fcgi/GET/request
new file mode 100644
index 0000000000..4389c71b0c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/fcgi/GET/request
@@ -0,0 +1,7 @@
+> GET /info/fcgi
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
new file mode 100644
index 0000000000..c2ea7ed71b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi-win32-extensions/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ffi-win32-extensions
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
new file mode 100644
index 0000000000..337ccf260d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ffi/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ffi
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
new file mode 100644
index 0000000000..85e3d59016
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/flexmock/GET/request
@@ -0,0 +1,7 @@
+> GET /info/flexmock
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
new file mode 100644
index 0000000000..2946076a0c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/functional-ruby/GET/request
@@ -0,0 +1,7 @@
+> GET /info/functional-ruby
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
new file mode 100644
index 0000000000..037082143d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gem_plugin/GET/request
@@ -0,0 +1,7 @@
+> GET /info/gem_plugin
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
new file mode 100644
index 0000000000..48a190b596
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gemcutter/GET/request
@@ -0,0 +1,7 @@
+> GET /info/gemcutter
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
new file mode 100644
index 0000000000..e16c323f9f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin/GET/request
@@ -0,0 +1,7 @@
+> GET /info/gherkin
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
new file mode 100644
index 0000000000..51c1b8dbd1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gherkin3/GET/request
@@ -0,0 +1,7 @@
+> GET /info/gherkin3
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/git/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/git/GET/request
new file mode 100644
index 0000000000..a17b1c9fd9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/git/GET/request
@@ -0,0 +1,7 @@
+> GET /info/git
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
new file mode 100644
index 0000000000..0b5cc95383
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/globalid/GET/request
@@ -0,0 +1,7 @@
+> GET /info/globalid
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
new file mode 100644
index 0000000000..6997e2f1b1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/google-api-client/GET/request
@@ -0,0 +1,7 @@
+> GET /info/google-api-client
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
new file mode 100644
index 0000000000..a5af0919c9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/googleauth/GET/request
@@ -0,0 +1,7 @@
+> GET /info/googleauth
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
new file mode 100644
index 0000000000..35f5608ebe
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/gxapi_rails/GET/request
@@ -0,0 +1,7 @@
+> GET /info/gxapi_rails
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
new file mode 100644
index 0000000000..385ec2b595
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hashie/GET/request
@@ -0,0 +1,7 @@
+> GET /info/hashie
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/highline/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/highline/GET/request
new file mode 100644
index 0000000000..4b571d66c1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/highline/GET/request
@@ -0,0 +1,7 @@
+> GET /info/highline
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
new file mode 100644
index 0000000000..f6c38deaaa
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hike/GET/request
@@ -0,0 +1,7 @@
+> GET /info/hike
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
new file mode 100644
index 0000000000..6113e85b8d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hiredis/GET/request
@@ -0,0 +1,7 @@
+> GET /info/hiredis
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
new file mode 100644
index 0000000000..4a3c1bbdab
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hitimes/GET/request
@@ -0,0 +1,7 @@
+> GET /info/hitimes
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
new file mode 100644
index 0000000000..e86bcea319
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hoe/GET/request
@@ -0,0 +1,7 @@
+> GET /info/hoe
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
new file mode 100644
index 0000000000..40972439c4
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hooks/GET/request
@@ -0,0 +1,7 @@
+> GET /info/hooks
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
new file mode 100644
index 0000000000..dc272ed3b8
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http-cookie/GET/request
@@ -0,0 +1,7 @@
+> GET /info/http-cookie
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http_parser.rb/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http_parser.rb/GET/request
new file mode 100644
index 0000000000..e67979aa78
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/http_parser.rb/GET/request
@@ -0,0 +1,7 @@
+> GET /info/http_parser.rb
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
new file mode 100644
index 0000000000..c9d3ce3546
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpadapter/GET/request
@@ -0,0 +1,7 @@
+> GET /info/httpadapter
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
new file mode 100644
index 0000000000..499c716b32
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/httpclient/GET/request
@@ -0,0 +1,7 @@
+> GET /info/httpclient
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
new file mode 100644
index 0000000000..218736d428
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/hurley/GET/request
@@ -0,0 +1,7 @@
+> GET /info/hurley
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
new file mode 100644
index 0000000000..ad35d73f0b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/i18n/GET/request
@@ -0,0 +1,7 @@
+> GET /info/i18n
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
new file mode 100644
index 0000000000..0c7e990bf7
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ice_nine/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ice_nine
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
new file mode 100644
index 0000000000..8f9b0132c2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/journey/GET/request
@@ -0,0 +1,7 @@
+> GET /info/journey
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
new file mode 100644
index 0000000000..6dd6f3f43b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jruby-pageant/GET/request
@@ -0,0 +1,7 @@
+> GET /info/jruby-pageant
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
new file mode 100644
index 0000000000..7ca746d23e
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json/GET/request
@@ -0,0 +1,7 @@
+> GET /info/json
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
new file mode 100644
index 0000000000..aa2b18bd1d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/json_pure/GET/request
@@ -0,0 +1,7 @@
+> GET /info/json_pure
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
new file mode 100644
index 0000000000..c8669eac08
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/jwt/GET/request
@@ -0,0 +1,7 @@
+> GET /info/jwt
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/language/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/language/GET/request
new file mode 100644
index 0000000000..4aeefbc7aa
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/language/GET/request
@@ -0,0 +1,7 @@
+> GET /info/language
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
new file mode 100644
index 0000000000..60da8f9a26
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/launchy/GET/request
@@ -0,0 +1,7 @@
+> GET /info/launchy
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
new file mode 100644
index 0000000000..3764e1e3b1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libwebsocket/GET/request
@@ -0,0 +1,7 @@
+> GET /info/libwebsocket
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
new file mode 100644
index 0000000000..fad58483fe
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/libxml-ruby/GET/request
@@ -0,0 +1,7 @@
+> GET /info/libxml-ruby
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
new file mode 100644
index 0000000000..9ac0105694
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/liquid/GET/request
@@ -0,0 +1,7 @@
+> GET /info/liquid
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
new file mode 100644
index 0000000000..d8ac06f4ea
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/listen/GET/request
@@ -0,0 +1,7 @@
+> GET /info/listen
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
new file mode 100644
index 0000000000..34f643d772
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/little-plugger/GET/request
@@ -0,0 +1,7 @@
+> GET /info/little-plugger
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
new file mode 100644
index 0000000000..21ccaf6be5
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/lockfile/GET/request
@@ -0,0 +1,7 @@
+> GET /info/lockfile
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
new file mode 100644
index 0000000000..8a7bd45f35
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/logging/GET/request
@@ -0,0 +1,7 @@
+> GET /info/logging
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
new file mode 100644
index 0000000000..2cb6d57679
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loofah/GET/request
@@ -0,0 +1,7 @@
+> GET /info/loofah
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loquacious/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loquacious/GET/request
new file mode 100644
index 0000000000..bda60b4dc3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/loquacious/GET/request
@@ -0,0 +1,7 @@
+> GET /info/loquacious
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mab/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mab/GET/request
new file mode 100644
index 0000000000..115c6e6448
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mab/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mab
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
new file mode 100644
index 0000000000..ea11e0bd0c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mail/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mail
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/markaby/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/markaby/GET/request
new file mode 100644
index 0000000000..27db5d09cf
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/markaby/GET/request
@@ -0,0 +1,7 @@
+> GET /info/markaby
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
new file mode 100644
index 0000000000..4b0c275c6e
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memcache-client/GET/request
@@ -0,0 +1,7 @@
+> GET /info/memcache-client
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
new file mode 100644
index 0000000000..fcda1676de
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoist/GET/request
@@ -0,0 +1,7 @@
+> GET /info/memoist
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
new file mode 100644
index 0000000000..ae7e5e0371
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/memoizable/GET/request
@@ -0,0 +1,7 @@
+> GET /info/memoizable
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
new file mode 100644
index 0000000000..9f502c21cc
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaclass/GET/request
@@ -0,0 +1,7 @@
+> GET /info/metaclass
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaid/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaid/GET/request
new file mode 100644
index 0000000000..872aea81e0
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/metaid/GET/request
@@ -0,0 +1,7 @@
+> GET /info/metaid
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
new file mode 100644
index 0000000000..4d02bc1e42
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/method_source/GET/request
@@ -0,0 +1,7 @@
+> GET /info/method_source
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
new file mode 100644
index 0000000000..8081aaa002
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types-data/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mime-types-data
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
new file mode 100644
index 0000000000..eb07326eda
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mime-types/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mime-types
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
new file mode 100644
index 0000000000..cbb2e4f493
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mimemagic/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mimemagic
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 6d5397ce7f8b26e0
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
new file mode 100644
index 0000000000..f536dc4d3e
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mini_portile
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
new file mode 100644
index 0000000000..ab28a8089a
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mini_portile2/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mini_portile2
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitar-cli/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitar-cli/GET/request
new file mode 100644
index 0000000000..d9d5968a5b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitar-cli/GET/request
@@ -0,0 +1,7 @@
+> GET /info/minitar-cli
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitar/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitar/GET/request
new file mode 100644
index 0000000000..c8b5697b43
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitar/GET/request
@@ -0,0 +1,7 @@
+> GET /info/minitar
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
new file mode 100644
index 0000000000..4d88e89a59
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/minitest/GET/request
@@ -0,0 +1,7 @@
+> GET /info/minitest
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
new file mode 100644
index 0000000000..3333257711
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mkrf/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mkrf
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
new file mode 100644
index 0000000000..f215f1f8d6
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mocha/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mocha
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
new file mode 100644
index 0000000000..0181a370bc
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mongrel/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mongrel
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
new file mode 100644
index 0000000000..088d7a648d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mono_logger/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mono_logger
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
new file mode 100644
index 0000000000..56942172dc
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_json/GET/request
@@ -0,0 +1,7 @@
+> GET /info/multi_json
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
new file mode 100644
index 0000000000..8f4d50696c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multi_test/GET/request
@@ -0,0 +1,7 @@
+> GET /info/multi_test
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
new file mode 100644
index 0000000000..1a64e6f47e
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multimap/GET/request
@@ -0,0 +1,7 @@
+> GET /info/multimap
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
new file mode 100644
index 0000000000..63eec25ffd
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/multipart-post/GET/request
@@ -0,0 +1,7 @@
+> GET /info/multipart-post
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
new file mode 100644
index 0000000000..710afe0829
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/mustermann/GET/request
@@ -0,0 +1,7 @@
+> GET /info/mustermann
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
new file mode 100644
index 0000000000..45d8ea2c39
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/needle/GET/request
@@ -0,0 +1,7 @@
+> GET /info/needle
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
new file mode 100644
index 0000000000..4ab184fc81
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nenv/GET/request
@@ -0,0 +1,7 @@
+> GET /info/nenv
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
new file mode 100644
index 0000000000..8910edf8e6
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-scp/GET/request
@@ -0,0 +1,7 @@
+> GET /info/net-scp
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
new file mode 100644
index 0000000000..043751da3b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/net-ssh/GET/request
@@ -0,0 +1,7 @@
+> GET /info/net-ssh
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
new file mode 100644
index 0000000000..912c799120
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/netrc/GET/request
@@ -0,0 +1,7 @@
+> GET /info/netrc
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/newgem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/newgem/GET/request
new file mode 100644
index 0000000000..ef62899a0c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/newgem/GET/request
@@ -0,0 +1,7 @@
+> GET /info/newgem
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
new file mode 100644
index 0000000000..44337df0b7
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nio4r/GET/request
@@ -0,0 +1,7 @@
+> GET /info/nio4r
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
new file mode 100644
index 0000000000..09c5dd725f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/nokogiri/GET/request
@@ -0,0 +1,7 @@
+> GET /info/nokogiri
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
new file mode 100644
index 0000000000..1d82322d8b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/os/GET/request
@@ -0,0 +1,7 @@
+> GET /info/os
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
new file mode 100644
index 0000000000..c977514627
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/paperclip/GET/request
@@ -0,0 +1,7 @@
+> GET /info/paperclip
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 6d5397ce7f8b26e0
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
new file mode 100644
index 0000000000..5fc1b9ff8b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/parser/GET/request
@@ -0,0 +1,7 @@
+> GET /info/parser
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
new file mode 100644
index 0000000000..1636fe07b4
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pattern-match/GET/request
@@ -0,0 +1,7 @@
+> GET /info/pattern-match
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
new file mode 100644
index 0000000000..8f2f5ae398
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/pkg-config/GET/request
@@ -0,0 +1,7 @@
+> GET /info/pkg-config
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
new file mode 100644
index 0000000000..8841563b63
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/polyglot/GET/request
@@ -0,0 +1,7 @@
+> GET /info/polyglot
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
new file mode 100644
index 0000000000..73ef4c0d07
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/power_assert/GET/request
@@ -0,0 +1,7 @@
+> GET /info/power_assert
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerbar/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerbar/GET/request
new file mode 100644
index 0000000000..0a69936bc6
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerbar/GET/request
@@ -0,0 +1,7 @@
+> GET /info/powerbar
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
new file mode 100644
index 0000000000..20bf19b1af
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/powerpack/GET/request
@@ -0,0 +1,7 @@
+> GET /info/powerpack
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/preforker/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/preforker/GET/request
new file mode 100644
index 0000000000..2ba448e12d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/preforker/GET/request
@@ -0,0 +1,7 @@
+> GET /info/preforker
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
new file mode 100644
index 0000000000..9f13c8a79e
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/public_suffix/GET/request
@@ -0,0 +1,7 @@
+> GET /info/public_suffix
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
new file mode 100644
index 0000000000..dfc9adfa9d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/racc/GET/request
@@ -0,0 +1,7 @@
+> GET /info/racc
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
new file mode 100644
index 0000000000..27a90522f4
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-cache/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rack-cache
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
new file mode 100644
index 0000000000..59a7bd8b8d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-mount/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rack-mount
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
new file mode 100644
index 0000000000..4eb6557436
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-protection/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rack-protection
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
new file mode 100644
index 0000000000..6871bc0308
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-ssl/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rack-ssl
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
new file mode 100644
index 0000000000..624dd07678
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack-test/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rack-test
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
new file mode 100644
index 0000000000..16a0feac8c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rack/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rack
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
new file mode 100644
index 0000000000..67514b94c8
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-deprecated_sanitizer/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rails-deprecated_sanitizer
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
new file mode 100644
index 0000000000..2caa6d814e
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-dom-testing/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rails-dom-testing
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
new file mode 100644
index 0000000000..54db344173
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-html-sanitizer/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rails-html-sanitizer
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
new file mode 100644
index 0000000000..3fb8b11adf
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails-observers/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rails-observers
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
new file mode 100644
index 0000000000..b156e7be25
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rails/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rails
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
new file mode 100644
index 0000000000..40f66c8a6f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/railties/GET/request
@@ -0,0 +1,7 @@
+> GET /info/railties
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
new file mode 100644
index 0000000000..ae1ee0cee9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rainbow/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rainbow
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
new file mode 100644
index 0000000000..2f777a05bf
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake-compiler/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rake-compiler
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
new file mode 100644
index 0000000000..c161c917e1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rake/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rake
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
new file mode 100644
index 0000000000..101066914a
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fchange/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rb-fchange
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
new file mode 100644
index 0000000000..192253cb0c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-fsevent/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rb-fsevent
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
new file mode 100644
index 0000000000..cf7287e37b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-inotify/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rb-inotify
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
new file mode 100644
index 0000000000..6d256b9e11
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rb-kqueue/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rb-kqueue
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
new file mode 100644
index 0000000000..0a269f784f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl-libsodium/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rbnacl-libsodium
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
new file mode 100644
index 0000000000..d28de6c266
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rbnacl/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rbnacl
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rcov/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rcov/GET/request
new file mode 100644
index 0000000000..611a286a4c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rcov/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rcov
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
new file mode 100644
index 0000000000..8402303257
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rdoc/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rdoc
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
new file mode 100644
index 0000000000..9b78830065
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis-namespace/GET/request
@@ -0,0 +1,7 @@
+> GET /info/redis-namespace
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
new file mode 100644
index 0000000000..9bfcfc80fd
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/redis/GET/request
@@ -0,0 +1,7 @@
+> GET /info/redis
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
new file mode 100644
index 0000000000..42ee151f68
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ref/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ref
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
new file mode 100644
index 0000000000..73a5ca084e
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/representable/GET/request
@@ -0,0 +1,7 @@
+> GET /info/representable
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
new file mode 100644
index 0000000000..aa9132aedb
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque-scheduler/GET/request
@@ -0,0 +1,7 @@
+> GET /info/resque-scheduler
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
new file mode 100644
index 0000000000..616a9e5b9f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/resque/GET/request
@@ -0,0 +1,7 @@
+> GET /info/resque
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
new file mode 100644
index 0000000000..5935658273
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rest-client/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rest-client
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
new file mode 100644
index 0000000000..a76f439668
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/retriable/GET/request
@@ -0,0 +1,7 @@
+> GET /info/retriable
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexical/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexical/GET/request
new file mode 100644
index 0000000000..0390a17867
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rexical/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rexical
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
new file mode 100644
index 0000000000..46fa433f4a
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_aws/GET/request
@@ -0,0 +1,7 @@
+> GET /info/right_aws
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 6d5397ce7f8b26e0
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
new file mode 100644
index 0000000000..9afacd1726
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/right_http_connection/GET/request
@@ -0,0 +1,7 @@
+> GET /info/right_http_connection
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 6d5397ce7f8b26e0
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
new file mode 100644
index 0000000000..c9398deae9
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-core/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rspec-core
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
new file mode 100644
index 0000000000..660b24d100
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-expectations/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rspec-expectations
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
new file mode 100644
index 0000000000..5ff447f187
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-logsplit/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rspec-logsplit
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
new file mode 100644
index 0000000000..0a961c6ffd
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-mocks/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rspec-mocks
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
new file mode 100644
index 0000000000..ab2d3175ec
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec-support/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rspec-support
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
new file mode 100644
index 0000000000..3f327837cc
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rspec/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rspec
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubigen/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubigen/GET/request
new file mode 100644
index 0000000000..19c742edd3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubigen/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rubigen
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
new file mode 100644
index 0000000000..49899f0274
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubocop/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rubocop
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-openid/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-openid/GET/request
new file mode 100644
index 0000000000..83ae0d71e7
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-openid/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ruby-openid
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
new file mode 100644
index 0000000000..85bcdde042
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-progressbar/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ruby-progressbar
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-yadis/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-yadis/GET/request
new file mode 100644
index 0000000000..e12117e5dc
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby-yadis/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ruby-yadis
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
new file mode 100644
index 0000000000..43d58ead74
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_dep/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ruby_dep
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
new file mode 100644
index 0000000000..460ff43276
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/ruby_parser/GET/request
@@ -0,0 +1,7 @@
+> GET /info/ruby_parser
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
new file mode 100644
index 0000000000..92e612095d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyforge/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rubyforge
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
new file mode 100644
index 0000000000..56ff5252ce
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rubyzip/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rubyzip
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
new file mode 100644
index 0000000000..9c499d7320
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/rufus-scheduler/GET/request
@@ -0,0 +1,7 @@
+> GET /info/rufus-scheduler
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
new file mode 100644
index 0000000000..98051f0722
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-listen/GET/request
@@ -0,0 +1,7 @@
+> GET /info/sass-listen
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
new file mode 100644
index 0000000000..ab996f5ead
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass-rails/GET/request
@@ -0,0 +1,7 @@
+> GET /info/sass-rails
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
new file mode 100644
index 0000000000..16f4a2d834
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sass/GET/request
@@ -0,0 +1,7 @@
+> GET /info/sass
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
new file mode 100644
index 0000000000..3128daa2f7
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/selenium-webdriver/GET/request
@@ -0,0 +1,7 @@
+> GET /info/selenium-webdriver
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
new file mode 100644
index 0000000000..e92bfe4e50
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sexp_processor/GET/request
@@ -0,0 +1,7 @@
+> GET /info/sexp_processor
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/shotgun/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/shotgun/GET/request
new file mode 100644
index 0000000000..9d6b958cc5
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/shotgun/GET/request
@@ -0,0 +1,7 @@
+> GET /info/shotgun
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
new file mode 100644
index 0000000000..bd4257ed35
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/signet/GET/request
@@ -0,0 +1,7 @@
+> GET /info/signet
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
new file mode 100644
index 0000000000..daf148a4f8
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov-html/GET/request
@@ -0,0 +1,7 @@
+> GET /info/simplecov-html
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
new file mode 100644
index 0000000000..be9dec39be
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/simplecov/GET/request
@@ -0,0 +1,7 @@
+> GET /info/simplecov
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
new file mode 100644
index 0000000000..f442fa89f6
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sinatra/GET/request
@@ -0,0 +1,7 @@
+> GET /info/sinatra
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
new file mode 100644
index 0000000000..f4a1685ba2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/slop/GET/request
@@ -0,0 +1,7 @@
+> GET /info/slop
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spicycode-rcov/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spicycode-rcov/GET/request
new file mode 100644
index 0000000000..c9686cae49
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spicycode-rcov/GET/request
@@ -0,0 +1,7 @@
+> GET /info/spicycode-rcov
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
new file mode 100644
index 0000000000..d1f41c369a
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spoon/GET/request
@@ -0,0 +1,7 @@
+> GET /info/spoon
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
new file mode 100644
index 0000000000..7b7ba15490
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets-rails/GET/request
@@ -0,0 +1,7 @@
+> GET /info/sprockets-rails
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
new file mode 100644
index 0000000000..06767f04a6
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sprockets/GET/request
@@ -0,0 +1,7 @@
+> GET /info/sprockets
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
new file mode 100644
index 0000000000..a9995940f5
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/spruz/GET/request
@@ -0,0 +1,7 @@
+> GET /info/spruz
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
new file mode 100644
index 0000000000..39542c09e8
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sqlite3/GET/request
@@ -0,0 +1,7 @@
+> GET /info/sqlite3
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/syntax/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/syntax/GET/request
new file mode 100644
index 0000000000..462e2bd29b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/syntax/GET/request
@@ -0,0 +1,7 @@
+> GET /info/syntax
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
new file mode 100644
index 0000000000..4ce082dcaa
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/sys-admin/GET/request
@@ -0,0 +1,7 @@
+> GET /info/sys-admin
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tenderlove-frex/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tenderlove-frex/GET/request
new file mode 100644
index 0000000000..39f37ebdf3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tenderlove-frex/GET/request
@@ -0,0 +1,7 @@
+> GET /info/tenderlove-frex
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
new file mode 100644
index 0000000000..23c9c82fcf
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/term-ansicolor/GET/request
@@ -0,0 +1,7 @@
+> GET /info/term-ansicolor
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/termios/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/termios/GET/request
new file mode 100644
index 0000000000..17087a36e1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/termios/GET/request
@@ -0,0 +1,7 @@
+> GET /info/termios
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-spec/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-spec/GET/request
new file mode 100644
index 0000000000..1d2d581583
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-spec/GET/request
@@ -0,0 +1,7 @@
+> GET /info/test-spec
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
new file mode 100644
index 0000000000..92b85c1751
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/test-unit/GET/request
@@ -0,0 +1,7 @@
+> GET /info/test-unit
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
new file mode 100644
index 0000000000..3a8cbe23e1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-format/GET/request
@@ -0,0 +1,7 @@
+> GET /info/text-format
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
new file mode 100644
index 0000000000..5aa85a177c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/text-hyphen/GET/request
@@ -0,0 +1,7 @@
+> GET /info/text-hyphen
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
new file mode 100644
index 0000000000..ce6a7c8e8e
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thin/GET/request
@@ -0,0 +1,7 @@
+> GET /info/thin
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
new file mode 100644
index 0000000000..413597fd9d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thor/GET/request
@@ -0,0 +1,7 @@
+> GET /info/thor
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
new file mode 100644
index 0000000000..b420081022
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thoughtbot-shoulda/GET/request
@@ -0,0 +1,7 @@
+> GET /info/thoughtbot-shoulda
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 6d5397ce7f8b26e0
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
new file mode 100644
index 0000000000..f5f0fffab3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/thread_safe/GET/request
@@ -0,0 +1,7 @@
+> GET /info/thread_safe
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
new file mode 100644
index 0000000000..039aa0341f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tilt/GET/request
@@ -0,0 +1,7 @@
+> GET /info/tilt
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
new file mode 100644
index 0000000000..2fc37f3a88
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/timers/GET/request
@@ -0,0 +1,7 @@
+> GET /info/timers
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
new file mode 100644
index 0000000000..be1d206beb
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tins/GET/request
@@ -0,0 +1,7 @@
+> GET /info/tins
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
new file mode 100644
index 0000000000..56ebce0f3f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tlsmail/GET/request
@@ -0,0 +1,7 @@
+> GET /info/tlsmail
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
new file mode 100644
index 0000000000..a8492ade82
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tool/GET/request
@@ -0,0 +1,7 @@
+> GET /info/tool
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
new file mode 100644
index 0000000000..52f2db382f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/treetop/GET/request
@@ -0,0 +1,7 @@
+> GET /info/treetop
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
new file mode 100644
index 0000000000..224cab6988
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/trollop/GET/request
@@ -0,0 +1,7 @@
+> GET /info/trollop
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
new file mode 100644
index 0000000000..0175216dd1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/tzinfo/GET/request
@@ -0,0 +1,7 @@
+> GET /info/tzinfo
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
new file mode 100644
index 0000000000..9fe5b0c30a
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uber/GET/request
@@ -0,0 +1,7 @@
+> GET /info/uber
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
new file mode 100644
index 0000000000..f67c99558f
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf/GET/request
@@ -0,0 +1,7 @@
+> GET /info/unf
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
new file mode 100644
index 0000000000..77598fe741
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unf_ext/GET/request
@@ -0,0 +1,7 @@
+> GET /info/unf_ext
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
new file mode 100644
index 0000000000..9d4f27056b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/unicode-display_width/GET/request
@@ -0,0 +1,7 @@
+> GET /info/unicode-display_width
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
new file mode 100644
index 0000000000..5daed37793
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/uuidtools/GET/request
@@ -0,0 +1,7 @@
+> GET /info/uuidtools
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
new file mode 100644
index 0000000000..f22bf57945
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/vegas/GET/request
@@ -0,0 +1,7 @@
+> GET /info/vegas
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/install options/retry,path,disable_shared_gems,spec_run,plugins,trampoline_disable d59b382d069fc94f
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
new file mode 100644
index 0000000000..858ceea38d
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/virtus/GET/request
@@ -0,0 +1,7 @@
+> GET /info/virtus
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 53c83f3e5793993c
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
new file mode 100644
index 0000000000..f6a7eb9098
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/weakling/GET/request
@@ -0,0 +1,7 @@
+> GET /info/weakling
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
new file mode 100644
index 0000000000..06463439e0
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-driver/GET/request
@@ -0,0 +1,7 @@
+> GET /info/websocket-driver
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
new file mode 100644
index 0000000000..4757f24824
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket-extensions/GET/request
@@ -0,0 +1,7 @@
+> GET /info/websocket-extensions
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
new file mode 100644
index 0000000000..45eaa04e55
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/websocket/GET/request
@@ -0,0 +1,7 @@
+> GET /info/websocket
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
new file mode 100644
index 0000000000..9d47c2709b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-api/GET/request
@@ -0,0 +1,7 @@
+> GET /info/win32-api
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
new file mode 100644
index 0000000000..6a11013349
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-dir/GET/request
@@ -0,0 +1,7 @@
+> GET /info/win32-dir
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
new file mode 100644
index 0000000000..d89f956691
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32-security/GET/request
@@ -0,0 +1,7 @@
+> GET /info/win32-security
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
new file mode 100644
index 0000000000..37acaa6088
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/win32console/GET/request
@@ -0,0 +1,7 @@
+> GET /info/win32console
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
new file mode 100644
index 0000000000..30402b17e0
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-api/GET/request
@@ -0,0 +1,7 @@
+> GET /info/windows-api
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
new file mode 100644
index 0000000000..31c1180ac7
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/windows-pr/GET/request
@@ -0,0 +1,7 @@
+> GET /info/windows-pr
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
new file mode 100644
index 0000000000..5cc409d8db
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/info/xpath/GET/request
@@ -0,0 +1,7 @@
+> GET /info/xpath
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
new file mode 100644
index 0000000000..ec73805dff
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/index.rubygems.org/versions/GET/request
@@ -0,0 +1,7 @@
+> GET /versions
+> accept-encoding: gzip
+> accept: */*
+> user-agent: bundler/1.15.0.pre.2 rubygems/2.6.11 ruby/2.4.1 (x86_64-apple-darwin16.5.0) command/lock options/spec_run,plugins,trampoline_disable 7edfb7e0938d0c35
+> connection: keep-alive
+> keep-alive: 30
+> host: index.rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
new file mode 100644
index 0000000000..c078ac79e1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.12.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/activesupport-3.2.12.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
new file mode 100644
index 0000000000..b87c281d96
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/activesupport-3.2.22.5.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/activesupport-3.2.22.5.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-1.12.3.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-1.12.3.gem/GET/request
new file mode 100644
index 0000000000..af03dfd189
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/bundler-1.12.3.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/bundler-1.12.3.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.3.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.3.gem/GET/request
new file mode 100644
index 0000000000..6b0ccfc8c1
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/diff-lcs-1.3.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/diff-lcs-1.3.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
new file mode 100644
index 0000000000..5226c62cd2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/faker-1.1.2.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/faker-1.1.2.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
new file mode 100644
index 0000000000..6e27eac32c
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.6.11.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/i18n-0.6.11.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.8.1.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.8.1.gem/GET/request
new file mode 100644
index 0000000000..04e800510b
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/i18n-0.8.1.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/i18n-0.8.1.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.0.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.0.gem/GET/request
new file mode 100644
index 0000000000..8e82694d05
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/mono_logger-1.1.0.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/mono_logger-1.1.0.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.12.1.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.12.1.gem/GET/request
new file mode 100644
index 0000000000..fa8e059e91
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/multi_json-1.12.1.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/multi_json-1.12.1.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-1.0.1.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-1.0.1.gem/GET/request
new file mode 100644
index 0000000000..9d96c013bc
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-1.0.1.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/rack-1.0.1.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-1.6.5.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-1.6.5.gem/GET/request
new file mode 100644
index 0000000000..4ae9745db3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-1.6.5.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/rack-1.6.5.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.1.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.1.gem/GET/request
new file mode 100644
index 0000000000..ed63e334fa
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-2.0.1.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/rack-2.0.1.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-1.5.3.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-1.5.3.gem/GET/request
new file mode 100644
index 0000000000..03a85b2308
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rack-protection-1.5.3.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/rack-protection-1.5.3.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-3.3.3.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-3.3.3.gem/GET/request
new file mode 100644
index 0000000000..7d5ad5ad23
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-3.3.3.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/redis-3.3.3.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.5.3.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.5.3.gem/GET/request
new file mode 100644
index 0000000000..f928e29dd3
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/redis-namespace-1.5.3.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/redis-namespace-1.5.3.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
new file mode 100644
index 0000000000..91c5562047
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-1.24.1.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/resque-1.24.1.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
new file mode 100644
index 0000000000..0237de2859
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/resque-scheduler-2.2.0.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/resque-scheduler-2.2.0.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
new file mode 100644
index 0000000000..a8428bc9f6
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/rufus-scheduler-2.0.24.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/rufus-scheduler-2.0.24.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-1.4.8.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-1.4.8.gem/GET/request
new file mode 100644
index 0000000000..ee93e5d100
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/sinatra-1.4.8.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/sinatra-1.4.8.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/thread_safe-0.3.6.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/thread_safe-0.3.6.gem/GET/request
new file mode 100644
index 0000000000..51ddc098ae
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/thread_safe-0.3.6.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/thread_safe-0.3.6.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.7.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.7.gem/GET/request
new file mode 100644
index 0000000000..c329c7b4c2
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tilt-2.0.7.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/tilt-2.0.7.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-1.2.3.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-1.2.3.gem/GET/request
new file mode 100644
index 0000000000..df7b05b346
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/tzinfo-1.2.3.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/tzinfo-1.2.3.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
new file mode 100644
index 0000000000..562c01e339
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/gems/vegas-0.1.11.gem/GET/request
@@ -0,0 +1,7 @@
+> GET /gems/vegas-0.1.11.gem
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/quick/Marshal.4.8/bundler-1.12.3.gemspec.rz/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/quick/Marshal.4.8/bundler-1.12.3.gemspec.rz/GET/request
new file mode 100644
index 0000000000..ef6eab21ce
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/quick/Marshal.4.8/bundler-1.12.3.gemspec.rz/GET/request
@@ -0,0 +1,7 @@
+> GET /quick/Marshal.4.8/bundler-1.12.3.gemspec.rz
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/specs.4.8.gz/GET/request b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/specs.4.8.gz/GET/request
new file mode 100644
index 0000000000..ca0f179471
--- /dev/null
+++ b/spec/bundler/support/artifice/vcr_cassettes/realworld/rubygems.org/specs.4.8.gz/GET/request
@@ -0,0 +1,7 @@
+> GET /specs.4.8.gz
+> accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+> accept: */*
+> user-agent: Ruby
+> connection: keep-alive
+> keep-alive: 30
+> host: rubygems.org \ No newline at end of file
diff --git a/spec/bundler/support/artifice/windows.rb b/spec/bundler/support/artifice/windows.rb
new file mode 100644
index 0000000000..f39b2c6d53
--- /dev/null
+++ b/spec/bundler/support/artifice/windows.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require File.expand_path("../../path.rb", __FILE__)
+include Spec::Path
+
+$LOAD_PATH.unshift Dir[base_system_gems.join("gems/artifice*/lib")].first.to_s
+$LOAD_PATH.unshift(*Dir[base_system_gems.join("gems/rack-*/lib")])
+$LOAD_PATH.unshift Dir[base_system_gems.join("gems/tilt*/lib")].first.to_s
+$LOAD_PATH.unshift Dir[base_system_gems.join("gems/sinatra*/lib")].first.to_s
+require "artifice"
+require "sinatra/base"
+
+Artifice.deactivate
+
+class Windows < Sinatra::Base
+ set :raise_errors, true
+ set :show_exceptions, false
+
+ helpers do
+ def gem_repo
+ Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"] || Spec::Path.gem_repo1)
+ end
+ end
+
+ files = ["specs.4.8.gz",
+ "prerelease_specs.4.8.gz",
+ "quick/Marshal.4.8/rcov-1.0-mswin32.gemspec.rz",
+ "gems/rcov-1.0-mswin32.gem"]
+
+ files.each do |file|
+ get "/#{file}" do
+ File.read gem_repo.join(file)
+ end
+ end
+
+ get "/gems/rcov-1.0-x86-mswin32.gem" do
+ halt 404
+ end
+
+ get "/api/v1/dependencies" do
+ halt 404
+ end
+
+ get "/versions" do
+ halt 500
+ end
+end
+
+Artifice.activate_with(Windows)
diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb
new file mode 100644
index 0000000000..97134a045a
--- /dev/null
+++ b/spec/bundler/support/builders.rb
@@ -0,0 +1,819 @@
+# frozen_string_literal: true
+
+require "bundler/shared_helpers"
+require "shellwords"
+
+module Spec
+ module Builders
+ def self.constantize(name)
+ name.delete("-").upcase
+ end
+
+ def v(version)
+ Gem::Version.new(version)
+ end
+
+ def pl(platform)
+ Gem::Platform.new(platform)
+ end
+
+ def build_repo1
+ build_repo gem_repo1 do
+ build_gem "rack", %w[0.9.1 1.0.0] do |s|
+ s.executables = "rackup"
+ s.post_install_message = "Rack's post install message"
+ end
+
+ build_gem "thin" do |s|
+ s.add_dependency "rack"
+ s.post_install_message = "Thin's post install message"
+ end
+
+ build_gem "rack-obama" do |s|
+ s.add_dependency "rack"
+ s.post_install_message = "Rack-obama's post install message"
+ end
+
+ build_gem "rack_middleware", "1.0" do |s|
+ s.add_dependency "rack", "0.9.1"
+ end
+
+ build_gem "rails", "2.3.2" do |s|
+ s.executables = "rails"
+ s.add_dependency "rake", "10.0.2"
+ s.add_dependency "actionpack", "2.3.2"
+ s.add_dependency "activerecord", "2.3.2"
+ s.add_dependency "actionmailer", "2.3.2"
+ s.add_dependency "activeresource", "2.3.2"
+ end
+ build_gem "actionpack", "2.3.2" do |s|
+ s.add_dependency "activesupport", "2.3.2"
+ end
+ build_gem "activerecord", ["2.3.1", "2.3.2"] do |s|
+ s.add_dependency "activesupport", "2.3.2"
+ end
+ build_gem "actionmailer", "2.3.2" do |s|
+ s.add_dependency "activesupport", "2.3.2"
+ end
+ build_gem "activeresource", "2.3.2" do |s|
+ s.add_dependency "activesupport", "2.3.2"
+ end
+ build_gem "activesupport", %w[1.2.3 2.3.2 2.3.5]
+
+ build_gem "activemerchant" do |s|
+ s.add_dependency "activesupport", ">= 2.0.0"
+ end
+
+ build_gem "rails_fail" do |s|
+ s.add_dependency "activesupport", "= 1.2.3"
+ end
+
+ build_gem "missing_dep" do |s|
+ s.add_dependency "not_here"
+ end
+
+ build_gem "rspec", "1.2.7", :no_default => true do |s|
+ s.write "lib/spec.rb", "SPEC = '1.2.7'"
+ end
+
+ build_gem "rack-test", :no_default => true do |s|
+ s.write "lib/rack/test.rb", "RACK_TEST = '1.0'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = Bundler.local_platform
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "java"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 JAVA'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "ruby"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x86-mswin32"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 MSWIN'"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x86-mingw32"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x64-mingw32"
+ end
+
+ build_gem "platform_specific" do |s|
+ s.platform = "x86-darwin-100"
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 x86-darwin-100'"
+ end
+
+ build_gem "only_java", "1.0" do |s|
+ s.platform = "java"
+ s.write "lib/only_java.rb", "ONLY_JAVA = '1.0.0 JAVA'"
+ end
+
+ build_gem "only_java", "1.1" do |s|
+ s.platform = "java"
+ s.write "lib/only_java.rb", "ONLY_JAVA = '1.1.0 JAVA'"
+ end
+
+ build_gem "nokogiri", "1.4.2"
+ build_gem "nokogiri", "1.4.2" do |s|
+ s.platform = "java"
+ s.write "lib/nokogiri.rb", "NOKOGIRI = '1.4.2 JAVA'"
+ s.add_dependency "weakling", ">= 0.0.3"
+ end
+
+ build_gem "laduradura", "5.15.2"
+ build_gem "laduradura", "5.15.2" do |s|
+ s.platform = "java"
+ s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'"
+ end
+ build_gem "laduradura", "5.15.3" do |s|
+ s.platform = "java"
+ s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'"
+ end
+
+ build_gem "weakling", "0.0.3"
+
+ build_gem "terranova", "8"
+
+ build_gem "duradura", "7.0"
+
+ build_gem "multiple_versioned_deps" do |s|
+ s.add_dependency "weakling", ">= 0.0.1", "< 0.1"
+ end
+
+ build_gem "not_released", "1.0.pre"
+
+ build_gem "has_prerelease", "1.0"
+ build_gem "has_prerelease", "1.1.pre"
+
+ build_gem "with_development_dependency" do |s|
+ s.add_development_dependency "activesupport", "= 2.3.5"
+ end
+
+ build_gem "with_license" do |s|
+ s.license = "MIT"
+ end
+
+ build_gem "with_implicit_rake_dep" do |s|
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("../lib", __FILE__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/implicit_rake_dep.rb", "w") do |f|
+ f.puts "IMPLICIT_RAKE_DEP = 'YES'"
+ end
+ end
+ RUBY
+ end
+
+ build_gem "another_implicit_rake_dep" do |s|
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("../lib", __FILE__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f|
+ f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'"
+ end
+ end
+ RUBY
+ end
+
+ build_gem "very_simple_binary", &:add_c_extension
+ build_gem "simple_binary", &:add_c_extension
+
+ build_gem "bundler", "0.9" do |s|
+ s.executables = "bundle"
+ s.write "bin/bundle", "puts 'FAIL'"
+ end
+
+ # The bundler 0.8 gem has a rubygems plugin that always loads :(
+ build_gem "bundler", "0.8.1" do |s|
+ s.write "lib/bundler/omg.rb", ""
+ s.write "lib/rubygems_plugin.rb", "require 'bundler/omg' ; puts 'FAIL'"
+ end
+
+ build_gem "bundler_dep" do |s|
+ s.add_dependency "bundler"
+ end
+
+ # The yard gem iterates over Gem.source_index looking for plugins
+ build_gem "yard" do |s|
+ s.write "lib/yard.rb", <<-Y
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.10")
+ specs = Gem::Specification
+ else
+ specs = Gem.source_index.find_name('')
+ end
+ specs.sort_by(&:name).each do |gem|
+ puts gem.full_name
+ end
+ Y
+ end
+
+ # The rcov gem is platform mswin32, but has no arch
+ build_gem "rcov" do |s|
+ s.platform = Gem::Platform.new([nil, "mswin32", nil])
+ s.write "lib/rcov.rb", "RCOV = '1.0.0'"
+ end
+
+ build_gem "net-ssh"
+ build_gem "net-sftp", "1.1.1" do |s|
+ s.add_dependency "net-ssh", ">= 1.0.0", "< 1.99.0"
+ end
+
+ # Test complicated gem dependencies for install
+ build_gem "net_a" do |s|
+ s.add_dependency "net_b"
+ s.add_dependency "net_build_extensions"
+ end
+
+ build_gem "net_b"
+
+ build_gem "net_build_extensions" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("../lib", __FILE__)
+ FileUtils.mkdir_p(path)
+ File.open("\#{path}/net_build_extensions.rb", "w") do |f|
+ f.puts "NET_BUILD_EXTENSIONS = 'YES'"
+ end
+ end
+ RUBY
+ end
+
+ build_gem "net_c" do |s|
+ s.add_dependency "net_a"
+ s.add_dependency "net_d"
+ end
+
+ build_gem "net_d"
+
+ build_gem "net_e" do |s|
+ s.add_dependency "net_d"
+ end
+
+ # Capistrano did this (at least until version 2.5.10)
+ # RubyGems 2.2 doesn't allow the specifying of a dependency twice
+ # See https://github.com/rubygems/rubygems/commit/03dbac93a3396a80db258d9bc63500333c25bd2f
+ build_gem "double_deps", "1.0", :skip_validation => true do |s|
+ s.add_dependency "net-ssh", ">= 1.0.0"
+ s.add_dependency "net-ssh"
+ end
+
+ build_gem "foo"
+
+ # A minimal fake pry console
+ build_gem "pry" do |s|
+ s.write "lib/pry.rb", <<-RUBY
+ class Pry
+ class << self
+ def toplevel_binding
+ unless defined?(@toplevel_binding) && @toplevel_binding
+ TOPLEVEL_BINDING.eval %{
+ def self.__pry__; binding; end
+ Pry.instance_variable_set(:@toplevel_binding, __pry__)
+ class << self; undef __pry__; end
+ }
+ end
+ @toplevel_binding.eval('private')
+ @toplevel_binding
+ end
+
+ def __pry__
+ while line = gets
+ begin
+ puts eval(line, toplevel_binding).inspect.sub(/^"(.*)"$/, '=> \\1')
+ rescue Exception => e
+ puts "\#{e.class}: \#{e.message}"
+ puts e.backtrace.first
+ end
+ end
+ end
+ alias start __pry__
+ end
+ end
+ RUBY
+ end
+ end
+ end
+
+ def build_repo2(&blk)
+ FileUtils.rm_rf gem_repo2
+ FileUtils.cp_r gem_repo1, gem_repo2
+ update_repo2(&blk) if block_given?
+ end
+
+ def build_repo3
+ build_repo gem_repo3 do
+ build_gem "rack"
+ end
+ FileUtils.rm_rf Dir[gem_repo3("prerelease*")]
+ end
+
+ # A repo that has no pre-installed gems included. (The caller completely
+ # determines the contents with the block.)
+ def build_repo4(&blk)
+ FileUtils.rm_rf gem_repo4
+ build_repo(gem_repo4, &blk)
+ end
+
+ def update_repo4(&blk)
+ update_repo(gem_repo4, &blk)
+ end
+
+ def update_repo2
+ update_repo gem_repo2 do
+ build_gem "rack", "1.2" do |s|
+ s.executables = "rackup"
+ end
+ yield if block_given?
+ end
+ end
+
+ def build_security_repo
+ build_repo security_repo do
+ build_gem "rack"
+
+ build_gem "signed_gem" do |s|
+ cert = "signing-cert.pem"
+ pkey = "signing-pkey.pem"
+ s.write cert, TEST_CERT
+ s.write pkey, TEST_PKEY
+ s.signing_key = pkey
+ s.cert_chain = [cert]
+ end
+ end
+ end
+
+ def build_repo(path, &blk)
+ return if File.directory?(path)
+ rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
+
+ if rake_path.nil?
+ Spec::Path.base_system_gems.rmtree
+ Spec::Rubygems.setup
+ rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
+ end
+
+ if rake_path
+ FileUtils.mkdir_p("#{path}/gems")
+ FileUtils.cp rake_path, "#{path}/gems/"
+ else
+ abort "Your test gems are missing! Run `rm -rf #{tmp}` and try again."
+ end
+
+ update_repo(path, &blk)
+ end
+
+ def update_repo(path)
+ if path == gem_repo1 && caller.first.split(" ").last == "`build_repo`"
+ raise "Updating gem_repo1 is unsupported -- use gem_repo2 instead"
+ end
+ return unless block_given?
+ @_build_path = "#{path}/gems"
+ @_build_repo = File.basename(path)
+ yield
+ with_gem_path_as Path.base_system_gems do
+ Dir.chdir(path) { gem_command! :generate_index }
+ end
+ ensure
+ @_build_path = nil
+ @_build_repo = nil
+ end
+
+ def build_index(&block)
+ index = Bundler::Index.new
+ IndexBuilder.run(index, &block) if block_given?
+ index
+ end
+
+ def build_spec(name, version = "0.0.1", platform = nil, &block)
+ Array(version).map do |v|
+ Gem::Specification.new do |s|
+ s.name = name
+ s.version = Gem::Version.new(v)
+ s.platform = platform
+ s.authors = ["no one in particular"]
+ s.summary = "a gemspec used only for testing"
+ DepBuilder.run(s, &block) if block_given?
+ end
+ end
+ end
+
+ def build_dep(name, requirements = Gem::Requirement.default, type = :runtime)
+ Bundler::Dependency.new(name, :version => requirements)
+ end
+
+ def build_lib(name, *args, &blk)
+ build_with(LibBuilder, name, args, &blk)
+ end
+
+ def build_gem(name, *args, &blk)
+ build_with(GemBuilder, name, args, &blk)
+ end
+
+ def build_git(name, *args, &block)
+ opts = args.last.is_a?(Hash) ? args.last : {}
+ builder = opts[:bare] ? GitBareBuilder : GitBuilder
+ spec = build_with(builder, name, args, &block)
+ GitReader.new(opts[:path] || lib_path(spec.full_name))
+ end
+
+ def update_git(name, *args, &block)
+ opts = args.last.is_a?(Hash) ? args.last : {}
+ spec = build_with(GitUpdater, name, args, &block)
+ GitReader.new(opts[:path] || lib_path(spec.full_name))
+ end
+
+ def build_plugin(name, *args, &blk)
+ build_with(PluginBuilder, name, args, &blk)
+ end
+
+ private
+
+ def build_with(builder, name, args, &blk)
+ @_build_path ||= nil
+ @_build_repo ||= nil
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ versions = args.last || "1.0"
+ spec = nil
+
+ options[:path] ||= @_build_path
+ options[:source] ||= @_build_repo
+
+ Array(versions).each do |version|
+ spec = builder.new(self, name, version)
+ spec.authors = ["no one"] if !spec.authors || spec.authors.empty?
+ yield spec if block_given?
+ spec._build(options)
+ end
+
+ spec
+ end
+
+ class IndexBuilder
+ include Builders
+
+ def self.run(index, &block)
+ new(index).run(&block)
+ end
+
+ def initialize(index)
+ @index = index
+ end
+
+ def run(&block)
+ instance_eval(&block)
+ end
+
+ def gem(*args, &block)
+ build_spec(*args, &block).each do |s|
+ @index << s
+ end
+ end
+
+ def platforms(platforms)
+ platforms.split(/\s+/).each do |platform|
+ platform.gsub!(/^(mswin32)$/, 'x86-\1')
+ yield Gem::Platform.new(platform)
+ end
+ end
+
+ def versions(versions)
+ versions.split(/\s+/).each {|version| yield v(version) }
+ end
+ end
+
+ class DepBuilder
+ include Builders
+
+ def self.run(spec, &block)
+ new(spec).run(&block)
+ end
+
+ def initialize(spec)
+ @spec = spec
+ end
+
+ def run(&block)
+ instance_eval(&block)
+ end
+
+ def runtime(name, requirements)
+ @spec.add_runtime_dependency(name, requirements)
+ end
+
+ def development(name, requirements)
+ @spec.add_development_dependency(name, requirements)
+ end
+
+ def required_ruby_version=(*reqs)
+ @spec.required_ruby_version = *reqs
+ end
+
+ alias_method :dep, :runtime
+ end
+
+ class LibBuilder
+ def initialize(context, name, version)
+ @context = context
+ @name = name
+ @spec = Gem::Specification.new do |s|
+ s.name = name
+ s.version = version
+ s.summary = "This is just a fake gem for testing"
+ s.description = "This is a completely fake gem, for testing purposes."
+ s.author = "no one"
+ s.email = "foo@bar.baz"
+ s.homepage = "http://example.com"
+ s.license = "MIT"
+ end
+ @files = {}
+ end
+
+ def method_missing(*args, &blk)
+ @spec.send(*args, &blk)
+ end
+
+ def write(file, source = "")
+ @files[file] = source
+ end
+
+ def executables=(val)
+ @spec.executables = Array(val)
+ @spec.executables.each do |file|
+ executable = "#{@spec.bindir}/#{file}"
+ shebang = if Bundler.current_ruby.jruby?
+ "#!/usr/bin/env jruby\n"
+ else
+ "#!/usr/bin/env ruby\n"
+ end
+ @spec.files << executable
+ write executable, "#{shebang}require '#{@name}' ; puts #{Builders.constantize(@name)}"
+ end
+ end
+
+ def add_c_extension
+ require_paths << "ext"
+ extensions << "ext/extconf.rb"
+ write "ext/extconf.rb", <<-RUBY
+ require "mkmf"
+
+
+ # exit 1 unless with_config("simple")
+
+ extension_name = "#{name}_c"
+ if extra_lib_dir = with_config("ext-lib")
+ # add extra libpath if --with-ext-lib is
+ # passed in as a build_arg
+ dir_config extension_name, nil, extra_lib_dir
+ else
+ dir_config extension_name
+ end
+ create_makefile extension_name
+ RUBY
+ write "ext/#{name}.c", <<-C
+ #include "ruby.h"
+
+ void Init_#{name}_c() {
+ rb_define_module("#{Builders.constantize(name)}_IN_C");
+ }
+ C
+ end
+
+ def _build(options)
+ path = options[:path] || _default_path
+
+ if options[:rubygems_version]
+ @spec.rubygems_version = options[:rubygems_version]
+ def @spec.mark_version; end
+
+ def @spec.validate(*); end
+ end
+
+ case options[:gemspec]
+ when false
+ # do nothing
+ when :yaml
+ @files["#{name}.gemspec"] = @spec.to_yaml
+ else
+ @files["#{name}.gemspec"] = @spec.to_ruby
+ end
+
+ unless options[:no_default]
+ gem_source = options[:source] || "path@#{path}"
+ @files = _default_files.
+ merge("lib/#{name}/source.rb" => "#{Builders.constantize(name)}_SOURCE = #{gem_source.to_s.dump}").
+ merge(@files)
+ end
+
+ @spec.authors = ["no one"]
+
+ @files.each do |file, source|
+ file = Pathname.new(path).join(file)
+ FileUtils.mkdir_p(file.dirname)
+ File.open(file, "w") {|f| f.puts source }
+ end
+ @spec.files = @files.keys
+ path
+ end
+
+ def _default_files
+ @_default_files ||= begin
+ platform_string = " #{@spec.platform}" unless @spec.platform == Gem::Platform::RUBY
+ { "lib/#{name}.rb" => "#{Builders.constantize(name)} = '#{version}#{platform_string}'" }
+ end
+ end
+
+ def _default_path
+ @context.tmp("libs", @spec.full_name)
+ end
+ end
+
+ class GitBuilder < LibBuilder
+ def _build(options)
+ path = options[:path] || _default_path
+ source = options[:source] || "git@#{path}"
+ super(options.merge(:path => path, :source => source))
+ Dir.chdir(path) do
+ `git init`
+ `git add *`
+ `git config user.email "lol@wut.com"`
+ `git config user.name "lolwut"`
+ `git commit -m 'OMG INITIAL COMMIT'`
+ end
+ end
+ end
+
+ class GitBareBuilder < LibBuilder
+ def _build(options)
+ path = options[:path] || _default_path
+ super(options.merge(:path => path))
+ Dir.chdir(path) do
+ `git init --bare`
+ end
+ end
+ end
+
+ class GitUpdater < LibBuilder
+ def silently(str)
+ `#{str} 2>#{Bundler::NULL}`
+ end
+
+ def _build(options)
+ libpath = options[:path] || _default_path
+ update_gemspec = options[:gemspec] || false
+ source = options[:source] || "git@#{libpath}"
+
+ Dir.chdir(libpath) do
+ silently "git checkout master"
+
+ if branch = options[:branch]
+ raise "You can't specify `master` as the branch" if branch == "master"
+ escaped_branch = Shellwords.shellescape(branch)
+
+ if `git branch | grep #{escaped_branch}`.empty?
+ silently("git branch #{escaped_branch}")
+ end
+
+ silently("git checkout #{escaped_branch}")
+ elsif tag = options[:tag]
+ `git tag #{Shellwords.shellescape(tag)}`
+ elsif options[:remote]
+ silently("git remote add origin file://#{options[:remote]}")
+ elsif options[:push]
+ silently("git push origin #{options[:push]}")
+ end
+
+ current_ref = `git rev-parse HEAD`.strip
+ _default_files.keys.each do |path|
+ _default_files[path] += "\n#{Builders.constantize(name)}_PREV_REF = '#{current_ref}'"
+ end
+ super(options.merge(:path => libpath, :gemspec => update_gemspec, :source => source))
+ `git add *`
+ `git commit -m "BUMP"`
+ end
+ end
+ end
+
+ class GitReader
+ attr_reader :path
+
+ def initialize(path)
+ @path = path
+ end
+
+ def ref_for(ref, len = nil)
+ ref = git "rev-parse #{ref}"
+ ref = ref[0..len] if len
+ ref
+ end
+
+ private
+
+ def git(cmd)
+ Bundler::SharedHelpers.with_clean_git_env do
+ Dir.chdir(@path) { `git #{cmd}`.strip }
+ end
+ end
+ end
+
+ class GemBuilder < LibBuilder
+ def _build(opts)
+ lib_path = super(opts.merge(:path => @context.tmp(".tmp/#{@spec.full_name}"), :no_default => opts[:no_default]))
+ destination = opts[:path] || _default_path
+ Dir.chdir(lib_path) do
+ FileUtils.mkdir_p(destination)
+
+ @spec.authors = ["that guy"] if !@spec.authors || @spec.authors.empty?
+
+ Bundler.rubygems.build(@spec, opts[:skip_validation])
+ end
+ gem_path = File.expand_path("#{@spec.full_name}.gem", lib_path)
+ if opts[:to_system]
+ @context.system_gems gem_path, :keep_path => true
+ elsif opts[:to_bundle]
+ @context.system_gems gem_path, :path => :bundle_path, :keep_path => true
+ else
+ FileUtils.mv(gem_path, destination)
+ end
+ end
+
+ def _default_path
+ @context.gem_repo1("gems")
+ end
+ end
+
+ class PluginBuilder < GemBuilder
+ def _default_files
+ @_default_files ||= super.merge("plugins.rb" => "")
+ end
+ end
+
+ TEST_CERT = <<-CERT.gsub(/^\s*/, "")
+ -----BEGIN CERTIFICATE-----
+ MIIDMjCCAhqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAnMQwwCgYDVQQDDAN5b3Ux
+ FzAVBgoJkiaJk/IsZAEZFgdleGFtcGxlMB4XDTE1MDIwODAwMTIyM1oXDTQyMDYy
+ NTAwMTIyM1owJzEMMAoGA1UEAwwDeW91MRcwFQYKCZImiZPyLGQBGRYHZXhhbXBs
+ ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANlvFdpN43c4DMS9Jo06
+ m0a7k3bQ3HWQ1yrYhZMi77F1F73NpBknYHIzDktQpGn6hs/4QFJT4m4zNEBF47UL
+ jHU5nTK5rjkS3niGYUjvh3ZEzVeo9zHUlD/UwflDo4ALl3TSo2KY/KdPS/UTdLXL
+ ajkQvaVJtEDgBPE3DPhlj5whp+Ik3mDHej7qpV6F502leAwYaFyOtlEG/ZGNG+nZ
+ L0clH0j77HpP42AylHDi+vakEM3xcjo9BeWQ6Vkboic93c9RTt6CWBWxMQP7Nol1
+ MOebz9XOSQclxpxWteXNfPRtMdAhmRl76SMI8ywzThNPpa4EH/yz34ftebVOgKyM
+ nd0CAwEAAaNpMGcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFA7D
+ n9qo0np23qi3aOYuAAPn/5IdMBYGA1UdEQQPMA2BC3lvdUBleGFtcGxlMBYGA1Ud
+ EgQPMA2BC3lvdUBleGFtcGxlMA0GCSqGSIb3DQEBBQUAA4IBAQA7Gyk62sWOUX/N
+ vk4tJrgKESph6Ns8+E36A7n3jt8zCep8ldzMvwTWquf9iqhsC68FilEoaDnUlWw7
+ d6oNuaFkv7zfrWGLlvqQJC+cu2X5EpcCksg5oRp8VNbwJysJ6JgwosxzROII8eXc
+ R+j1j6mDvQYqig2QOnzf480pjaqbP+tspfDFZbhKPrgM3Blrb3ZYuFpv4zkqI7aB
+ 6fuk2DUhNO1CuwrJA84TqC+jGo73bDKaT5hrIDiaJRrN5+zcWja2uEWrj5jSbep4
+ oXdEdyH73hOHMBP40uds3PqnUsxEJhzjB2sCCe1geV24kw9J4m7EQXPVkUKDgKrt
+ LlpDmOoo
+ -----END CERTIFICATE-----
+ CERT
+
+ TEST_PKEY = <<-PKEY.gsub(/^\s*/, "")
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEowIBAAKCAQEA2W8V2k3jdzgMxL0mjTqbRruTdtDcdZDXKtiFkyLvsXUXvc2k
+ GSdgcjMOS1CkafqGz/hAUlPibjM0QEXjtQuMdTmdMrmuORLeeIZhSO+HdkTNV6j3
+ MdSUP9TB+UOjgAuXdNKjYpj8p09L9RN0tctqORC9pUm0QOAE8TcM+GWPnCGn4iTe
+ YMd6PuqlXoXnTaV4DBhoXI62UQb9kY0b6dkvRyUfSPvsek/jYDKUcOL69qQQzfFy
+ Oj0F5ZDpWRuiJz3dz1FO3oJYFbExA/s2iXUw55vP1c5JByXGnFa15c189G0x0CGZ
+ GXvpIwjzLDNOE0+lrgQf/LPfh+15tU6ArIyd3QIDAQABAoIBACbDqz20TS1gDMa2
+ gj0DidNedbflHKjJHdNBru7Ad8NHgOgR1YO2hXdWquG6itVqGMbTF4SV9/R1pIcg
+ 7qvEV1I+50u31tvOBWOvcYCzU48+TO2n7gowQA3xPHPYHzog1uu48fAOHl0lwgD7
+ av9OOK3b0jO5pC08wyTOD73pPWU0NrkTh2+N364leIi1pNuI1z4V+nEuIIm7XpVd
+ 5V4sXidMTiEMJwE6baEDfTjHKaoRndXrrPo3ryIXmcX7Ag1SwAQwF5fBCRToCgIx
+ dszEZB1bJD5gA6r+eGnJLB/F60nK607az5o3EdguoB2LKa6q6krpaRCmZU5svvoF
+ J7xgBPECgYEA8RIzHAQ3zbaibKdnllBLIgsqGdSzebTLKheFuigRotEV3Or/z5Lg
+ k/nVnThWVkTOSRqXTNpJAME6a4KTdcVSxYP+SdZVO1esazHrGb7xPVb7MWSE1cqp
+ WEk3Yy8OUOPoPQMc4dyGzd30Mi8IBB6gnFIYOTrpUo0XtkBv8rGGhfsCgYEA5uYn
+ 6QgL4NqNT84IXylmMb5ia3iBt6lhxI/A28CDtQvfScl4eYK0IjBwdfG6E1vJgyzg
+ nJzv3xEVo9bz+Kq7CcThWpK5JQaPnsV0Q74Wjk0ShHet15txOdJuKImnh5F6lylC
+ GTLR9gnptytfMH/uuw4ws0Q2kcg4l5NHKOWOnAcCgYEAvAwIVkhsB0n59Wu4gCZu
+ FUZENxYWUk/XUyQ6KnZrG2ih90xQ8+iMyqFOIm/52R2fFKNrdoWoALC6E3ct8+ZS
+ pMRLrelFXx8K3it4SwMJR2H8XBEfFW4bH0UtsW7Zafv+AunUs9LETP5gKG1LgXsq
+ qgXX43yy2LQ61O365YPZfdUCgYBVbTvA3MhARbvYldrFEnUL3GtfZbNgdxuD9Mee
+ xig0eJMBIrgfBLuOlqtVB70XYnM4xAbKCso4loKSHnofO1N99siFkRlM2JOUY2tz
+ kMWZmmxKdFjuF0WZ5f/5oYxI/QsFGC+rUQEbbWl56mMKd5qkvEhKWudxoklF0yiV
+ ufC8SwKBgDWb8iWqWN5a/kfvKoxFcDM74UHk/SeKMGAL+ujKLf58F+CbweM5pX9C
+ EUsxeoUEraVWTiyFVNqD81rCdceus9TdBj0ZIK1vUttaRZyrMAwF0uQSfjtxsOpd
+ l69BkyvzjgDPkmOHVGiSZDLi3YDvypbUpo6LOy4v5rVg5U2F/A0v
+ -----END RSA PRIVATE KEY-----
+ PKEY
+ end
+end
diff --git a/spec/bundler/support/code_climate.rb b/spec/bundler/support/code_climate.rb
new file mode 100644
index 0000000000..a15442cabe
--- /dev/null
+++ b/spec/bundler/support/code_climate.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Spec
+ module CodeClimate
+ def self.setup
+ require "codeclimate-test-reporter"
+ ::CodeClimate::TestReporter.start
+ configure_exclusions
+ rescue LoadError
+ # it's fine if CodeClimate isn't set up
+ nil
+ end
+
+ def self.configure_exclusions
+ SimpleCov.start do
+ add_filter "/bin/"
+ add_filter "/lib/bundler/man/"
+ add_filter "/lib/bundler/vendor/"
+ add_filter "/man/"
+ add_filter "/pkg/"
+ add_filter "/spec/"
+ add_filter "/tmp/"
+ end
+ end
+ end
+end
diff --git a/spec/bundler/support/command_execution.rb b/spec/bundler/support/command_execution.rb
new file mode 100644
index 0000000000..556285ac52
--- /dev/null
+++ b/spec/bundler/support/command_execution.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require "support/helpers"
+require "support/path"
+
+module Spec
+ CommandExecution = Struct.new(:command, :working_directory, :exitstatus, :stdout, :stderr) do
+ include RSpec::Matchers::Composable
+
+ def to_s
+ c = Shellwords.shellsplit(command.strip).map {|s| s.include?("\n") ? " \\\n <<EOS\n#{s.gsub(/^/, " ").chomp}\nEOS" : Shellwords.shellescape(s) }
+ c = c.reduce("") do |acc, elem|
+ concat = acc + " " + elem
+
+ last_line = concat.match(/.*\z/)[0]
+ if last_line.size >= 100
+ acc + " \\\n " + elem
+ else
+ concat
+ end
+ end
+ "$ #{c.strip}"
+ end
+ alias_method :inspect, :to_s
+
+ def stdboth
+ @stdboth ||= [stderr, stdout].join("\n").strip
+ end
+
+ def bundler_err
+ if Bundler::VERSION.start_with?("1.")
+ stdout
+ else
+ stderr
+ end
+ end
+
+ def to_s_verbose
+ [
+ to_s,
+ stdout,
+ stderr,
+ exitstatus ? "# $? => #{exitstatus}" : "",
+ ].reject(&:empty?).join("\n")
+ end
+
+ def success?
+ return true unless exitstatus
+ exitstatus == 0
+ end
+
+ def failure?
+ return true unless exitstatus
+ exitstatus > 0
+ end
+ end
+end
diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb
new file mode 100644
index 0000000000..b14e4a5943
--- /dev/null
+++ b/spec/bundler/support/hax.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require "rubygems"
+
+module Gem
+ if version = ENV["BUNDLER_SPEC_RUBYGEMS_VERSION"]
+ remove_const(:VERSION) if const_defined?(:VERSION)
+ VERSION = version
+ end
+
+ class Platform
+ @local = new(ENV["BUNDLER_SPEC_PLATFORM"]) if ENV["BUNDLER_SPEC_PLATFORM"]
+ end
+ @platforms = [Gem::Platform::RUBY, Gem::Platform.local]
+
+ if defined?(@path_to_default_spec_map) && !ENV["BUNDLER_SPEC_KEEP_DEFAULT_BUNDLER_GEM"]
+ @path_to_default_spec_map.delete_if do |_path, spec|
+ spec.name == "bundler"
+ end
+ end
+end
+
+if ENV["BUNDLER_SPEC_VERSION"]
+ module Bundler
+ remove_const(:VERSION) if const_defined?(:VERSION)
+ VERSION = ENV["BUNDLER_SPEC_VERSION"].dup
+ end
+end
+
+if ENV["BUNDLER_SPEC_WINDOWS"] == "true"
+ require "bundler/constants"
+
+ module Bundler
+ remove_const :WINDOWS if defined?(WINDOWS)
+ WINDOWS = true
+ end
+end
+
+class Object
+ if ENV["BUNDLER_SPEC_RUBY_ENGINE"]
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE != "jruby" && ENV["BUNDLER_SPEC_RUBY_ENGINE"] == "jruby"
+ begin
+ # this has to be done up front because psych will try to load a .jar
+ # if it thinks its on jruby
+ require "psych"
+ rescue LoadError
+ nil
+ end
+ end
+
+ remove_const :RUBY_ENGINE if defined?(RUBY_ENGINE)
+ RUBY_ENGINE = ENV["BUNDLER_SPEC_RUBY_ENGINE"]
+
+ if RUBY_ENGINE == "jruby"
+ remove_const :JRUBY_VERSION if defined?(JRUBY_VERSION)
+ JRUBY_VERSION = ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"]
+ end
+ end
+end
+
+if ENV["BUNDLER_SPEC_IGNORE_COMPATIBILITY_GUARD"]
+ $LOADED_FEATURES << File.expand_path("../../../bundler/compatibility_guard.rb", __FILE__)
+ $LOADED_FEATURES << File.expand_path("../../../bundler/compatibility_guard", __FILE__)
+ $LOADED_FEATURES << "bundler/compatibility_guard.rb"
+ $LOADED_FEATURES << "bundler/compatibility_guard"
+ require "bundler/compatibility_guard"
+end
diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb
new file mode 100644
index 0000000000..b027e7a922
--- /dev/null
+++ b/spec/bundler/support/helpers.rb
@@ -0,0 +1,600 @@
+# frozen_string_literal: true
+
+require "open3"
+
+module Spec
+ module Helpers
+ def reset!
+ Dir.glob("#{tmp}/{gems/*,*}", File::FNM_DOTMATCH).each do |dir|
+ next if %w[base remote1 gems rubygems . ..].include?(File.basename(dir))
+ if ENV["BUNDLER_SUDO_TESTS"]
+ `sudo rm -rf "#{dir}"`
+ else
+ FileUtils.rm_rf(dir)
+ end
+ end
+ FileUtils.mkdir_p(home)
+ FileUtils.mkdir_p(tmpdir)
+ Bundler.reset!
+ Bundler.ui = nil
+ Bundler.ui # force it to initialize
+ end
+
+ def self.bang(method)
+ define_method("#{method}!") do |*args, &blk|
+ send(method, *args, &blk).tap do
+ unless last_command.success?
+ raise RuntimeError,
+ "Invoking #{method}!(#{args.map(&:inspect).join(", ")}) failed:\n#{last_command.stdboth}",
+ caller.drop_while {|bt| bt.start_with?(__FILE__) }
+ end
+ end
+ end
+ end
+
+ def the_bundle(*args)
+ TheBundle.new(*args)
+ end
+
+ def last_command
+ @command_executions.last || raise("There is no last command")
+ end
+
+ def out
+ last_command.stdboth
+ end
+
+ def err
+ last_command.stderr
+ end
+
+ def exitstatus
+ last_command.exitstatus
+ end
+
+ def bundle_update_requires_all?
+ Bundler::VERSION.start_with?("1.") ? nil : true
+ end
+
+ def in_app_root(&blk)
+ Dir.chdir(bundled_app, &blk)
+ end
+
+ def in_app_root2(&blk)
+ Dir.chdir(bundled_app2, &blk)
+ end
+
+ def in_app_root_custom(root, &blk)
+ Dir.chdir(root, &blk)
+ end
+
+ def run(cmd, *args)
+ opts = args.last.is_a?(Hash) ? args.pop : {}
+ groups = args.map(&:inspect).join(", ")
+ setup = "require 'rubygems' ; require 'bundler' ; Bundler.setup(#{groups})\n"
+ ruby(setup + cmd, opts)
+ end
+ bang :run
+
+ def load_error_run(ruby, name, *args)
+ cmd = <<-RUBY
+ begin
+ #{ruby}
+ rescue LoadError => e
+ $stderr.puts "ZOMG LOAD ERROR" if e.message.include?("-- #{name}")
+ end
+ RUBY
+ opts = args.last.is_a?(Hash) ? args.pop : {}
+ args += [opts]
+ run(cmd, *args)
+ end
+
+ def lib
+ root.join("lib")
+ end
+
+ def spec
+ spec_dir.to_s
+ end
+
+ def bundle(cmd, options = {})
+ with_sudo = options.delete(:sudo)
+ sudo = with_sudo == :preserve_env ? "sudo -E" : "sudo" if with_sudo
+
+ bundle_bin = options.delete("bundle_bin") || bindir.join("bundle")
+
+ if system_bundler = options.delete(:system_bundler)
+ bundle_bin = "-S bundle"
+ end
+
+ env = options.delete(:env) || {}
+ env["PATH"].gsub!("#{Path.root}/exe", "") if env["PATH"] && system_bundler
+
+ requires = options.delete(:requires) || []
+ requires << "support/hax"
+
+ artifice = options.delete(:artifice) do
+ if RSpec.current_example.metadata[:realworld]
+ "vcr"
+ else
+ "fail"
+ end
+ end
+ if artifice
+ requires << File.expand_path("../artifice/#{artifice}", __FILE__)
+ end
+
+ requires_str = requires.map {|r| "-r#{r}" }.join(" ")
+
+ load_path = []
+ load_path << lib unless system_bundler
+ load_path << spec
+ load_path_str = "-I#{load_path.join(File::PATH_SEPARATOR)}"
+
+ env = env.map {|k, v| "#{k}='#{v}'" }.join(" ")
+
+ args = options.map do |k, v|
+ case v
+ when nil
+ next
+ when true
+ " --#{k}"
+ when false
+ " --no-#{k}"
+ else
+ " --#{k} #{v}"
+ end
+ end.join
+
+ cmd = "#{env} #{sudo} #{Gem.ruby} #{load_path_str} #{requires_str} #{bundle_bin} #{cmd}#{args}"
+ sys_exec(cmd) {|i, o, thr| yield i, o, thr if block_given? }
+ end
+ bang :bundle
+
+ def forgotten_command_line_options(options)
+ remembered = Bundler::VERSION.split(".", 2).first == "1"
+ options = options.map do |k, v|
+ k = Array(k)[remembered ? 0 : -1]
+ v = '""' if v && v.to_s.empty?
+ [k, v]
+ end
+ return Hash[options] if remembered
+ options.each do |k, v|
+ if v.nil?
+ bundle! "config --delete #{k}"
+ else
+ bundle! "config --local #{k} #{v}"
+ end
+ end
+ {}
+ end
+
+ def bundler(cmd, options = {})
+ options["bundle_bin"] = bindir.join("bundler")
+ bundle(cmd, options)
+ end
+
+ def bundle_ruby(options = {})
+ options["bundle_bin"] = bindir.join("bundle_ruby")
+ bundle("", options)
+ end
+
+ def ruby(ruby, options = {})
+ env = (options.delete(:env) || {}).map {|k, v| "#{k}='#{v}' " }.join
+ ruby = ruby.gsub(/["`\$]/) {|m| "\\#{m}" }
+ lib_option = options[:no_lib] ? "" : " -I#{lib}"
+ sys_exec(%(#{env}#{Gem.ruby}#{lib_option} -e "#{ruby}"))
+ end
+ bang :ruby
+
+ def load_error_ruby(ruby, name, opts = {})
+ ruby(<<-R)
+ begin
+ #{ruby}
+ rescue LoadError => e
+ $stderr.puts "ZOMG LOAD ERROR"# if e.message.include?("-- #{name}")
+ end
+ R
+ end
+
+ def gembin(cmd)
+ lib = File.expand_path("../../../lib", __FILE__)
+ old = ENV["RUBYOPT"]
+ ENV["RUBYOPT"] = "#{ENV["RUBYOPT"]} -I#{lib}"
+ cmd = bundled_app("bin/#{cmd}") unless cmd.to_s.include?("/")
+ sys_exec(cmd.to_s)
+ ensure
+ ENV["RUBYOPT"] = old
+ end
+
+ def gem_command(command, args = "", options = {})
+ if command == :exec && !options[:no_quote]
+ args = args.gsub(/(?=")/, "\\")
+ args = %("#{args}")
+ end
+ gem = ENV["BUNDLE_GEM"] || "#{Gem.ruby} -rrubygems -S gem --backtrace"
+ sys_exec("#{gem} #{command} #{args}")
+ end
+ bang :gem_command
+
+ def rake
+ "#{Gem.ruby} -S #{ENV["GEM_PATH"]}/bin/rake"
+ end
+
+ def sys_exec(cmd)
+ command_execution = CommandExecution.new(cmd.to_s, Dir.pwd)
+
+ Open3.popen3(cmd.to_s) do |stdin, stdout, stderr, wait_thr|
+ yield stdin, stdout, wait_thr if block_given?
+ stdin.close
+
+ command_execution.exitstatus = wait_thr && wait_thr.value.exitstatus
+ command_execution.stdout = Thread.new { stdout.read }.value.strip
+ command_execution.stderr = Thread.new { stderr.read }.value.strip
+ end
+
+ (@command_executions ||= []) << command_execution
+
+ command_execution.stdout
+ end
+ bang :sys_exec
+
+ def config(config = nil, path = bundled_app(".bundle/config"))
+ return YAML.load_file(path) unless config
+ FileUtils.mkdir_p(File.dirname(path))
+ File.open(path, "w") do |f|
+ f.puts config.to_yaml
+ end
+ config
+ end
+
+ def global_config(config = nil)
+ config(config, home(".bundle/config"))
+ end
+
+ def create_file(*args)
+ path = bundled_app(args.shift)
+ path = args.shift if args.first.is_a?(Pathname)
+ str = args.shift || ""
+ path.dirname.mkpath
+ File.open(path.to_s, "w") do |f|
+ f.puts strip_whitespace(str)
+ end
+ end
+
+ def gemfile(*args)
+ if args.empty?
+ File.open("Gemfile", "r", &:read)
+ else
+ create_file("Gemfile", *args)
+ end
+ end
+
+ def lockfile(*args)
+ if args.empty?
+ File.open("Gemfile.lock", "r", &:read)
+ else
+ create_file("Gemfile.lock", *args)
+ end
+ end
+
+ def strip_whitespace(str)
+ # Trim the leading spaces
+ spaces = str[/\A\s+/, 0] || ""
+ str.gsub(/^#{spaces}/, "")
+ end
+
+ def normalize_uri_file(str)
+ # URI::File of Ruby 2.6 normalize localhost variable with file protocol.
+ if defined?(URI::File)
+ str.gsub(%r{file:\/\/localhost}, "file://")
+ else
+ str
+ end
+ end
+
+ def install_gemfile(*args)
+ gemfile(*args)
+ opts = args.last.is_a?(Hash) ? args.last : {}
+ opts[:retry] ||= 0
+ bundle :install, opts
+ end
+ bang :install_gemfile
+
+ def lock_gemfile(*args)
+ gemfile(*args)
+ opts = args.last.is_a?(Hash) ? args.last : {}
+ opts[:retry] ||= 0
+ bundle :lock, opts
+ end
+
+ def install_gems(*gems)
+ options = gems.last.is_a?(Hash) ? gems.pop : {}
+ gem_repo = options.fetch(:gem_repo) { gem_repo1 }
+ gems.each do |g|
+ path = if g == :bundler
+ Dir.chdir(root) { gem_command! :build, gemspec.to_s }
+ bundler_path = if ruby_core?
+ root + "lib/bundler-#{Bundler::VERSION}.gem"
+ else
+ root + "bundler-#{Bundler::VERSION}.gem"
+ end
+ elsif g.to_s =~ %r{\A/.*\.gem\z}
+ g
+ else
+ "#{gem_repo}/gems/#{g}.gem"
+ end
+
+ raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+
+ if Gem::VERSION < "2.0.0"
+ gem_command! :install, "--no-rdoc --no-ri --ignore-dependencies '#{path}'"
+ else
+ gem_command! :install, "--no-document --ignore-dependencies '#{path}'"
+ end
+ bundler_path && bundler_path.rmtree
+ end
+ end
+
+ alias_method :install_gem, :install_gems
+
+ def with_gem_path_as(path)
+ backup = ENV.to_hash
+ ENV["GEM_HOME"] = path.to_s
+ ENV["GEM_PATH"] = path.to_s
+ ENV["BUNDLER_ORIG_GEM_PATH"] = nil
+ yield
+ ensure
+ ENV.replace(backup)
+ end
+
+ def with_path_as(path)
+ backup = ENV.to_hash
+ ENV["PATH"] = path.to_s
+ ENV["BUNDLER_ORIG_PATH"] = nil
+ yield
+ ensure
+ ENV.replace(backup)
+ end
+
+ def with_path_added(path)
+ with_path_as(path.to_s + ":" + ENV["PATH"]) do
+ yield
+ end
+ end
+
+ def break_git!
+ FileUtils.mkdir_p(tmp("broken_path"))
+ File.open(tmp("broken_path/git"), "w", 0o755) do |f|
+ f.puts "#!/usr/bin/env ruby\nSTDERR.puts 'This is not the git you are looking for'\nexit 1"
+ end
+
+ ENV["PATH"] = "#{tmp("broken_path")}:#{ENV["PATH"]}"
+ end
+
+ def with_fake_man
+ FileUtils.mkdir_p(tmp("fake_man"))
+ File.open(tmp("fake_man/man"), "w", 0o755) do |f|
+ f.puts "#!/usr/bin/env ruby\nputs ARGV.inspect\n"
+ end
+ with_path_added(tmp("fake_man")) { yield }
+ end
+
+ def system_gems(*gems)
+ opts = gems.last.is_a?(Hash) ? gems.last : {}
+ path = opts.fetch(:path, system_gem_path)
+ if path == :bundle_path
+ path = ruby!(<<-RUBY)
+ require "bundler"
+ begin
+ puts Bundler.bundle_path
+ rescue Bundler::GemfileNotFound
+ ENV["BUNDLE_GEMFILE"] = "Gemfile"
+ retry
+ end
+
+ RUBY
+ end
+ gems = gems.flatten
+
+ unless opts[:keep_path]
+ FileUtils.rm_rf(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ Gem.clear_paths
+
+ env_backup = ENV.to_hash
+ ENV["GEM_HOME"] = path.to_s
+ ENV["GEM_PATH"] = path.to_s
+ ENV["BUNDLER_ORIG_GEM_PATH"] = nil
+
+ install_gems(*gems)
+ return unless block_given?
+ begin
+ yield
+ ensure
+ ENV.replace(env_backup)
+ end
+ end
+
+ def realworld_system_gems(*gems)
+ gems = gems.flatten
+
+ FileUtils.rm_rf(system_gem_path)
+ FileUtils.mkdir_p(system_gem_path)
+
+ Gem.clear_paths
+
+ gem_home = ENV["GEM_HOME"]
+ gem_path = ENV["GEM_PATH"]
+ path = ENV["PATH"]
+ ENV["GEM_HOME"] = system_gem_path.to_s
+ ENV["GEM_PATH"] = system_gem_path.to_s
+
+ gems.each do |gem|
+ gem_command :install, "--no-rdoc --no-ri #{gem}"
+ end
+ return unless block_given?
+ begin
+ yield
+ ensure
+ ENV["GEM_HOME"] = gem_home
+ ENV["GEM_PATH"] = gem_path
+ ENV["PATH"] = path
+ end
+ end
+
+ def cache_gems(*gems)
+ gems = gems.flatten
+
+ FileUtils.rm_rf("#{bundled_app}/vendor/cache")
+ FileUtils.mkdir_p("#{bundled_app}/vendor/cache")
+
+ gems.each do |g|
+ path = "#{gem_repo1}/gems/#{g}.gem"
+ raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+ FileUtils.cp(path, "#{bundled_app}/vendor/cache")
+ end
+ end
+
+ def simulate_new_machine
+ system_gems []
+ FileUtils.rm_rf system_gem_path
+ FileUtils.rm_rf bundled_app(".bundle")
+ end
+
+ def simulate_platform(platform)
+ old = ENV["BUNDLER_SPEC_PLATFORM"]
+ ENV["BUNDLER_SPEC_PLATFORM"] = platform.to_s
+ yield if block_given?
+ ensure
+ ENV["BUNDLER_SPEC_PLATFORM"] = old if block_given?
+ end
+
+ def simulate_ruby_version(version)
+ return if version == RUBY_VERSION
+ old = ENV["BUNDLER_SPEC_RUBY_VERSION"]
+ ENV["BUNDLER_SPEC_RUBY_VERSION"] = version
+ yield if block_given?
+ ensure
+ ENV["BUNDLER_SPEC_RUBY_VERSION"] = old if block_given?
+ end
+
+ def simulate_ruby_engine(engine, version = "1.6.0")
+ return if engine == local_ruby_engine
+
+ old = ENV["BUNDLER_SPEC_RUBY_ENGINE"]
+ ENV["BUNDLER_SPEC_RUBY_ENGINE"] = engine
+ old_version = ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"]
+ ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"] = version
+ yield if block_given?
+ ensure
+ ENV["BUNDLER_SPEC_RUBY_ENGINE"] = old if block_given?
+ ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"] = old_version if block_given?
+ end
+
+ def simulate_bundler_version(version)
+ old = ENV["BUNDLER_SPEC_VERSION"]
+ ENV["BUNDLER_SPEC_VERSION"] = version.to_s
+ yield if block_given?
+ ensure
+ ENV["BUNDLER_SPEC_VERSION"] = old if block_given?
+ end
+
+ def simulate_rubygems_version(version)
+ old = ENV["BUNDLER_SPEC_RUBYGEMS_VERSION"]
+ ENV["BUNDLER_SPEC_RUBYGEMS_VERSION"] = version.to_s
+ yield if block_given?
+ ensure
+ ENV["BUNDLER_SPEC_RUBYGEMS_VERSION"] = old if block_given?
+ end
+
+ def simulate_windows(platform = mswin)
+ old = ENV["BUNDLER_SPEC_WINDOWS"]
+ ENV["BUNDLER_SPEC_WINDOWS"] = "true"
+ simulate_platform platform do
+ yield
+ end
+ ensure
+ ENV["BUNDLER_SPEC_WINDOWS"] = old
+ end
+
+ def revision_for(path)
+ Dir.chdir(path) { `git rev-parse HEAD`.strip }
+ end
+
+ def capture_output
+ capture(:stdout)
+ end
+
+ def with_read_only(pattern)
+ chmod = lambda do |dirmode, filemode|
+ lambda do |f|
+ mode = File.directory?(f) ? dirmode : filemode
+ File.chmod(mode, f)
+ end
+ end
+
+ Dir[pattern].each(&chmod[0o555, 0o444])
+ yield
+ ensure
+ Dir[pattern].each(&chmod[0o755, 0o644])
+ end
+
+ def process_file(pathname)
+ changed_lines = pathname.readlines.map do |line|
+ yield line
+ end
+ File.open(pathname, "w") {|file| file.puts(changed_lines.join) }
+ end
+
+ def with_env_vars(env_hash, &block)
+ current_values = {}
+ env_hash.each do |k, v|
+ current_values[k] = ENV[k]
+ ENV[k] = v
+ end
+ block.call if block_given?
+ env_hash.each do |k, _|
+ ENV[k] = current_values[k]
+ end
+ end
+
+ def require_rack
+ # need to hack, so we can require rack
+ old_gem_home = ENV["GEM_HOME"]
+ ENV["GEM_HOME"] = Spec::Path.base_system_gems.to_s
+ require "rack"
+ ENV["GEM_HOME"] = old_gem_home
+ end
+
+ def wait_for_server(host, port, seconds = 15)
+ tries = 0
+ sleep 0.5
+ TCPSocket.new(host, port)
+ rescue => e
+ raise(e) if tries > (seconds * 2)
+ tries += 1
+ retry
+ end
+
+ def find_unused_port
+ port = 21_453
+ begin
+ port += 1 while TCPSocket.new("127.0.0.1", port)
+ rescue
+ false
+ end
+ port
+ end
+
+ def bundler_fileutils
+ if RUBY_VERSION >= "2.4"
+ ::Bundler::FileUtils
+ else
+ ::FileUtils
+ end
+ end
+ end
+end
diff --git a/spec/bundler/support/indexes.rb b/spec/bundler/support/indexes.rb
new file mode 100644
index 0000000000..69f8d9f679
--- /dev/null
+++ b/spec/bundler/support/indexes.rb
@@ -0,0 +1,421 @@
+# frozen_string_literal: true
+
+module Spec
+ module Indexes
+ def dep(name, reqs = nil)
+ @deps ||= []
+ @deps << Bundler::Dependency.new(name, reqs)
+ end
+
+ def platform(*args)
+ @platforms ||= []
+ @platforms.concat args.map {|p| Gem::Platform.new(p) }
+ end
+
+ alias_method :platforms, :platform
+
+ def resolve(args = [])
+ @platforms ||= ["ruby"]
+ deps = []
+ default_source = instance_double("Bundler::Source::Rubygems", :specs => @index)
+ source_requirements = { :default => default_source }
+ @deps.each do |d|
+ @platforms.each do |p|
+ source_requirements[d.name] = d.source = default_source
+ deps << Bundler::DepProxy.new(d, p)
+ end
+ end
+ source_requirements ||= {}
+ Bundler::Resolver.resolve(deps, @index, source_requirements, *args)
+ end
+
+ def should_resolve_as(specs)
+ got = resolve
+ got = got.map(&:full_name).sort
+ expect(got).to eq(specs.sort)
+ end
+
+ def should_resolve_and_include(specs, args = [])
+ got = resolve(args)
+ got = got.map(&:full_name).sort
+ specs.each do |s|
+ expect(got).to include(s)
+ end
+ end
+
+ def should_conflict_on(names)
+ got = resolve
+ flunk "The resolve succeeded with: #{got.map(&:full_name).sort.inspect}"
+ rescue Bundler::VersionConflict => e
+ expect(Array(names).sort).to eq(e.conflicts.sort)
+ end
+
+ def gem(*args, &blk)
+ build_spec(*args, &blk).first
+ end
+
+ def locked(*args)
+ Bundler::SpecSet.new(args.map do |name, version|
+ gem(name, version)
+ end)
+ end
+
+ def should_conservative_resolve_and_include(opts, unlock, specs)
+ # empty unlock means unlock all
+ opts = Array(opts)
+ search = Bundler::GemVersionPromoter.new(@locked, unlock).tap do |s|
+ s.level = opts.first
+ s.strict = opts.include?(:strict)
+ s.prerelease_specified = Hash[@deps.map {|d| [d.name, d.requirement.prerelease?] }]
+ end
+ should_resolve_and_include specs, [@base, search]
+ end
+
+ def an_awesome_index
+ build_index do
+ gem "rack", %w[0.8 0.9 0.9.1 0.9.2 1.0 1.1]
+ gem "rack-mount", %w[0.4 0.5 0.5.1 0.5.2 0.6]
+
+ # --- Pre-release support
+ gem "rubygems\0", ["1.3.2"]
+
+ # --- Rails
+ versions "1.2.3 2.2.3 2.3.5 3.0.0.beta 3.0.0.beta1" do |version|
+ gem "activesupport", version
+ gem "actionpack", version do
+ dep "activesupport", version
+ if version >= v("3.0.0.beta")
+ dep "rack", "~> 1.1"
+ dep "rack-mount", ">= 0.5"
+ elsif version > v("2.3") then dep "rack", "~> 1.0.0"
+ elsif version > v("2.0.0") then dep "rack", "~> 0.9.0"
+ end
+ end
+ gem "activerecord", version do
+ dep "activesupport", version
+ dep "arel", ">= 0.2" if version >= v("3.0.0.beta")
+ end
+ gem "actionmailer", version do
+ dep "activesupport", version
+ dep "actionmailer", version
+ end
+ if version < v("3.0.0.beta")
+ gem "railties", version do
+ dep "activerecord", version
+ dep "actionpack", version
+ dep "actionmailer", version
+ dep "activesupport", version
+ end
+ else
+ gem "railties", version
+ gem "rails", version do
+ dep "activerecord", version
+ dep "actionpack", version
+ dep "actionmailer", version
+ dep "activesupport", version
+ dep "railties", version
+ end
+ end
+ end
+
+ versions "1.0 1.2 1.2.1 1.2.2 1.3 1.3.0.1 1.3.5 1.4.0 1.4.2 1.4.2.1" do |version|
+ platforms "ruby java mswin32 mingw32 x64-mingw32" do |platform|
+ next if version == v("1.4.2.1") && platform != pl("x86-mswin32")
+ next if version == v("1.4.2") && platform == pl("x86-mswin32")
+ gem "nokogiri", version, platform do
+ dep "weakling", ">= 0.0.3" if platform =~ pl("java")
+ end
+ end
+ end
+
+ versions "0.0.1 0.0.2 0.0.3" do |version|
+ gem "weakling", version
+ end
+
+ # --- Rails related
+ versions "1.2.3 2.2.3 2.3.5" do |version|
+ gem "activemerchant", version do
+ dep "activesupport", ">= #{version}"
+ end
+ end
+
+ gem "reform", ["1.0.0"] do
+ dep "activesupport", ">= 1.0.0.beta1"
+ end
+
+ gem "need-pre", ["1.0.0"] do
+ dep "activesupport", "~> 3.0.0.beta1"
+ end
+ end
+ end
+
+ # Builder 3.1.4 will activate first, but if all
+ # goes well, it should resolve to 3.0.4
+ def a_conflict_index
+ build_index do
+ gem "builder", %w[3.0.4 3.1.4]
+ gem("grape", "0.2.6") do
+ dep "builder", ">= 0"
+ end
+
+ versions "3.2.8 3.2.9 3.2.10 3.2.11" do |version|
+ gem("activemodel", version) do
+ dep "builder", "~> 3.0.0"
+ end
+ end
+
+ gem("my_app", "1.0.0") do
+ dep "activemodel", ">= 0"
+ dep "grape", ">= 0"
+ end
+ end
+ end
+
+ def a_complex_conflict_index
+ build_index do
+ gem("a", %w[1.0.2 1.1.4 1.2.0 1.4.0]) do
+ dep "d", ">= 0"
+ end
+
+ gem("d", %w[1.3.0 1.4.1]) do
+ dep "x", ">= 0"
+ end
+
+ gem "d", "0.9.8"
+
+ gem("b", "0.3.4") do
+ dep "a", ">= 1.5.0"
+ end
+
+ gem("b", "0.3.5") do
+ dep "a", ">= 1.2"
+ end
+
+ gem("b", "0.3.3") do
+ dep "a", "> 1.0"
+ end
+
+ versions "3.2 3.3" do |version|
+ gem("c", version) do
+ dep "a", "~> 1.0"
+ end
+ end
+
+ gem("my_app", "1.3.0") do
+ dep "c", ">= 4.0"
+ dep "b", ">= 0"
+ end
+
+ gem("my_app", "1.2.0") do
+ dep "c", "~> 3.3.0"
+ dep "b", "0.3.4"
+ end
+
+ gem("my_app", "1.1.0") do
+ dep "c", "~> 3.2.0"
+ dep "b", "0.3.5"
+ end
+ end
+ end
+
+ def index_with_conflict_on_child
+ build_index do
+ gem "json", %w[1.6.5 1.7.7 1.8.0]
+
+ gem("chef", "10.26") do
+ dep "json", [">= 1.4.4", "<= 1.7.7"]
+ end
+
+ gem("berkshelf", "2.0.7") do
+ dep "json", ">= 1.7.7"
+ end
+
+ gem("chef_app", "1.0.0") do
+ dep "berkshelf", "~> 2.0"
+ dep "chef", "~> 10.26"
+ end
+ end
+ end
+
+ # Issue #3459
+ def a_complicated_index
+ build_index do
+ gem "foo", %w[3.0.0 3.0.5] do
+ dep "qux", ["~> 3.1"]
+ dep "baz", ["< 9.0", ">= 5.0"]
+ dep "bar", ["~> 1.0"]
+ dep "grault", ["~> 3.1"]
+ end
+
+ gem "foo", "1.2.1" do
+ dep "baz", ["~> 4.2"]
+ dep "bar", ["~> 1.0"]
+ dep "qux", ["~> 3.1"]
+ dep "grault", ["~> 2.0"]
+ end
+
+ gem "bar", "1.0.5" do
+ dep "grault", ["~> 3.1"]
+ dep "baz", ["< 9", ">= 4.2"]
+ end
+
+ gem "bar", "1.0.3" do
+ dep "baz", ["< 9", ">= 4.2"]
+ dep "grault", ["~> 2.0"]
+ end
+
+ gem "baz", "8.2.10" do
+ dep "grault", ["~> 3.0"]
+ dep "garply", [">= 0.5.1", "~> 0.5"]
+ end
+
+ gem "baz", "5.0.2" do
+ dep "grault", ["~> 2.0"]
+ dep "garply", [">= 0.3.1"]
+ end
+
+ gem "baz", "4.2.0" do
+ dep "grault", ["~> 2.0"]
+ dep "garply", [">= 0.3.1"]
+ end
+
+ gem "grault", %w[2.6.3 3.1.1]
+
+ gem "garply", "0.5.1" do
+ dep "waldo", ["~> 0.1.3"]
+ end
+
+ gem "waldo", "0.1.5" do
+ dep "plugh", ["~> 0.6.0"]
+ end
+
+ gem "plugh", %w[0.6.3 0.6.11 0.7.0]
+
+ gem "qux", "3.2.21" do
+ dep "plugh", [">= 0.6.4", "~> 0.6"]
+ dep "corge", ["~> 1.0"]
+ end
+
+ gem "corge", "1.10.1"
+ end
+ end
+
+ def a_unresovable_child_index
+ build_index do
+ gem "json", %w[1.8.0]
+
+ gem("chef", "10.26") do
+ dep "json", [">= 1.4.4", "<= 1.7.7"]
+ end
+
+ gem("berkshelf", "2.0.7") do
+ dep "json", ">= 1.7.7"
+ end
+
+ gem("chef_app_error", "1.0.0") do
+ dep "berkshelf", "~> 2.0"
+ dep "chef", "~> 10.26"
+ end
+ end
+ end
+
+ def a_index_with_root_conflict_on_child
+ build_index do
+ gem "builder", %w[2.1.2 3.0.1 3.1.3]
+ gem "i18n", %w[0.4.1 0.4.2]
+
+ gem "activesupport", %w[3.0.0 3.0.1 3.0.5 3.1.7]
+
+ gem("activemodel", "3.0.5") do
+ dep "activesupport", "= 3.0.5"
+ dep "builder", "~> 2.1.2"
+ dep "i18n", "~> 0.4"
+ end
+
+ gem("activemodel", "3.0.0") do
+ dep "activesupport", "= 3.0.0"
+ dep "builder", "~> 2.1.2"
+ dep "i18n", "~> 0.4.1"
+ end
+
+ gem("activemodel", "3.1.3") do
+ dep "activesupport", "= 3.1.3"
+ dep "builder", "~> 2.1.2"
+ dep "i18n", "~> 0.5"
+ end
+
+ gem("activerecord", "3.0.0") do
+ dep "activesupport", "= 3.0.0"
+ dep "activemodel", "= 3.0.0"
+ end
+
+ gem("activerecord", "3.0.5") do
+ dep "activesupport", "= 3.0.5"
+ dep "activemodel", "= 3.0.5"
+ end
+
+ gem("activerecord", "3.0.9") do
+ dep "activesupport", "= 3.1.5"
+ dep "activemodel", "= 3.1.5"
+ end
+ end
+ end
+
+ def a_circular_index
+ build_index do
+ gem "rack", "1.0.1"
+ gem("foo", "0.2.6") do
+ dep "bar", ">= 0"
+ end
+
+ gem("bar", "1.0.0") do
+ dep "foo", ">= 0"
+ end
+
+ gem("circular_app", "1.0.0") do
+ dep "foo", ">= 0"
+ dep "bar", ">= 0"
+ end
+ end
+ end
+
+ def an_ambiguous_index
+ build_index do
+ gem("a", "1.0.0") do
+ dep "c", ">= 0"
+ end
+
+ gem("b", %w[0.5.0 1.0.0])
+
+ gem("b", "2.0.0") do
+ dep "c", "< 2.0.0"
+ end
+
+ gem("c", "1.0.0") do
+ dep "d", "1.0.0"
+ end
+
+ gem("c", "2.0.0") do
+ dep "d", "2.0.0"
+ end
+
+ gem("d", %w[1.0.0 2.0.0])
+ end
+ end
+
+ def optional_prereleases_index
+ build_index do
+ gem("a", %w[1.0.0])
+
+ gem("a", "2.0.0") do
+ dep "b", ">= 2.0.0.pre"
+ end
+
+ gem("b", %w[0.9.0 1.5.0 2.0.0.pre])
+
+ # --- Pre-release support
+ gem "rubygems\0", ["1.3.2"]
+ end
+ end
+ end
+end
diff --git a/spec/bundler/support/less_than_proc.rb b/spec/bundler/support/less_than_proc.rb
new file mode 100644
index 0000000000..ddac5458b7
--- /dev/null
+++ b/spec/bundler/support/less_than_proc.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class LessThanProc < Proc
+ attr_accessor :present
+
+ def self.with(present)
+ provided = Gem::Version.new(present.dup)
+ new do |required|
+ if required =~ /[=><~]/
+ !Gem::Requirement.new(required).satisfied_by?(provided)
+ else
+ provided < Gem::Version.new(required)
+ end
+ end.tap {|l| l.present = present }
+ end
+
+ def inspect
+ "\"=< #{present}\""
+ end
+end
diff --git a/spec/bundler/support/manpages.rb b/spec/bundler/support/manpages.rb
new file mode 100644
index 0000000000..ce1f72cc49
--- /dev/null
+++ b/spec/bundler/support/manpages.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Spec
+ module Manpages
+ def self.setup
+ man_path = Spec::Path.root.join("man")
+ return if man_path.children(false).select {|file| file.extname == ".ronn" }.all? do |man|
+ Dir[man_path.join("#{man.to_s[0..-6]}*.txt").to_s].any?
+ end
+
+ system(Spec::Path.root.join("bin", "rake").to_s, "man:build") || raise("Failed building man pages")
+ end
+ end
+end
diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb
new file mode 100644
index 0000000000..8e17be3a02
--- /dev/null
+++ b/spec/bundler/support/matchers.rb
@@ -0,0 +1,246 @@
+# frozen_string_literal: true
+
+require "forwardable"
+require "support/the_bundle"
+module Spec
+ module Matchers
+ extend RSpec::Matchers
+
+ class Precondition
+ include RSpec::Matchers::Composable
+ extend Forwardable
+ def_delegators :failing_matcher,
+ :failure_message,
+ :actual,
+ :description,
+ :diffable?,
+ :expected,
+ :failure_message_when_negated
+
+ def initialize(matcher, preconditions)
+ @matcher = with_matchers_cloned(matcher)
+ @preconditions = with_matchers_cloned(preconditions)
+ @failure_index = nil
+ end
+
+ def matches?(target, &blk)
+ return false if @failure_index = @preconditions.index {|pc| !pc.matches?(target, &blk) }
+ @matcher.matches?(target, &blk)
+ end
+
+ def does_not_match?(target, &blk)
+ return false if @failure_index = @preconditions.index {|pc| !pc.matches?(target, &blk) }
+ if @matcher.respond_to?(:does_not_match?)
+ @matcher.does_not_match?(target, &blk)
+ else
+ !@matcher.matches?(target, &blk)
+ end
+ end
+
+ def expects_call_stack_jump?
+ @matcher.expects_call_stack_jump? || @preconditions.any?(&:expects_call_stack_jump)
+ end
+
+ def supports_block_expectations?
+ @matcher.supports_block_expectations? || @preconditions.any?(&:supports_block_expectations)
+ end
+
+ def failing_matcher
+ @failure_index ? @preconditions[@failure_index] : @matcher
+ end
+ end
+
+ def self.define_compound_matcher(matcher, preconditions, &declarations)
+ raise "Must have preconditions to define a compound matcher" if preconditions.empty?
+ define_method(matcher) do |*expected, &block_arg|
+ Precondition.new(
+ RSpec::Matchers::DSL::Matcher.new(matcher, declarations, self, *expected, &block_arg),
+ preconditions
+ )
+ end
+ end
+
+ MAJOR_DEPRECATION = /^\[DEPRECATED FOR 2\.0\]\s*/
+
+ RSpec::Matchers.define :lack_errors do
+ diffable
+ match do |actual|
+ actual.gsub(/#{MAJOR_DEPRECATION}.+[\n]?/, "") == ""
+ end
+ end
+
+ RSpec::Matchers.define :eq_err do |expected|
+ diffable
+ match do |actual|
+ actual.gsub(/#{MAJOR_DEPRECATION}.+[\n]?/, "") == expected
+ end
+ end
+
+ RSpec::Matchers.define :have_major_deprecation do |expected|
+ diffable
+ match do |actual|
+ deprecations = actual.split(MAJOR_DEPRECATION)
+
+ return !expected.nil? if deprecations.size <= 1
+ return true if expected.nil?
+
+ deprecations.any? do |d|
+ !d.empty? && values_match?(expected, d.strip)
+ end
+ end
+ end
+
+ RSpec::Matchers.define :have_dep do |*args|
+ dep = Bundler::Dependency.new(*args)
+
+ match do |actual|
+ actual.length == 1 && actual.all? {|d| d == dep }
+ end
+ end
+
+ RSpec::Matchers.define :have_gem do |*args|
+ match do |actual|
+ actual.length == args.length && actual.all? {|a| args.include?(a.full_name) }
+ end
+ end
+
+ RSpec::Matchers.define :have_rubyopts do |*args|
+ args = args.flatten
+ args = args.first.split(/\s+/) if args.size == 1
+
+ match do |actual|
+ actual = actual.split(/\s+/) if actual.is_a?(String)
+ args.all? {|arg| actual.include?(arg) } && actual.uniq.size == actual.size
+ end
+ end
+
+ RSpec::Matchers.define :be_sorted do
+ diffable
+ attr_reader :expected
+ match do |actual|
+ expected = block_arg ? actual.sort_by(&block_arg) : actual.sort
+ actual.==(expected).tap do
+ # HACK: since rspec won't show a diff when everything is a string
+ differ = RSpec::Support::Differ.new
+ @actual = differ.send(:object_to_string, actual)
+ @expected = differ.send(:object_to_string, expected)
+ end
+ end
+ end
+
+ define_compound_matcher :read_as, [exist] do |file_contents|
+ diffable
+
+ match do |actual|
+ @actual = Bundler.read_file(actual)
+ values_match?(file_contents, @actual)
+ end
+ end
+
+ def indent(string, padding = 4, indent_character = " ")
+ string.to_s.gsub(/^/, indent_character * padding).gsub("\t", " ")
+ end
+
+ define_compound_matcher :include_gems, [be_an_instance_of(Spec::TheBundle)] do |*names|
+ match do
+ opts = names.last.is_a?(Hash) ? names.pop : {}
+ source = opts.delete(:source)
+ groups = Array(opts[:groups])
+ groups << opts
+ @errors = names.map do |name|
+ name, version, platform = name.split(/\s+/)
+ version_const = name == "bundler" ? "Bundler::VERSION" : Spec::Builders.constantize(name)
+ begin
+ run! "require '#{name}.rb'; puts #{version_const}", *groups
+ rescue => e
+ next "#{name} is not installed:\n#{indent(e)}"
+ end
+ last_command.stdout.gsub!(/#{MAJOR_DEPRECATION}.*$/, "")
+ actual_version, actual_platform = last_command.stdout.strip.split(/\s+/, 2)
+ unless Gem::Version.new(actual_version) == Gem::Version.new(version)
+ next "#{name} was expected to be at version #{version} but was #{actual_version}"
+ end
+ unless actual_platform == platform
+ next "#{name} was expected to be of platform #{platform} but was #{actual_platform}"
+ end
+ next unless source
+ begin
+ source_const = "#{Spec::Builders.constantize(name)}_SOURCE"
+ run! "require '#{name}/source'; puts #{source_const}", *groups
+ rescue
+ next "#{name} does not have a source defined:\n#{indent(e)}"
+ end
+ last_command.stdout.gsub!(/#{MAJOR_DEPRECATION}.*$/, "")
+ unless last_command.stdout.strip == source
+ next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{out}`"
+ end
+ end.compact
+
+ @errors.empty?
+ end
+
+ match_when_negated do
+ opts = names.last.is_a?(Hash) ? names.pop : {}
+ groups = Array(opts[:groups]) || []
+ @errors = names.map do |name|
+ name, version = name.split(/\s+/, 2)
+ begin
+ run <<-R, *(groups + [opts])
+ begin
+ require '#{name}'
+ puts #{Spec::Builders.constantize(name)}
+ rescue LoadError, NameError
+ puts "WIN"
+ end
+ R
+ rescue => e
+ next "checking for #{name} failed:\n#{e}"
+ end
+ next if last_command.stdout == "WIN"
+ next "expected #{name} to not be installed, but it was" if version.nil?
+ if Gem::Version.new(last_command.stdout) == Gem::Version.new(version)
+ next "expected #{name} (#{version}) not to be installed, but it was"
+ end
+ end.compact
+
+ @errors.empty?
+ end
+
+ failure_message do
+ super() + " but:\n" + @errors.map {|e| indent(e) }.join("\n")
+ end
+
+ failure_message_when_negated do
+ super() + " but:\n" + @errors.map {|e| indent(e) }.join("\n")
+ end
+ end
+ RSpec::Matchers.define_negated_matcher :not_include_gems, :include_gems
+ RSpec::Matchers.alias_matcher :include_gem, :include_gems
+
+ def have_lockfile(expected)
+ read_as(strip_whitespace(expected))
+ end
+
+ def plugin_should_be_installed(*names)
+ names.each do |name|
+ expect(Bundler::Plugin).to be_installed(name)
+ path = Pathname.new(Bundler::Plugin.installed?(name))
+ expect(path + "plugins.rb").to exist
+ end
+ end
+
+ def plugin_should_not_be_installed(*names)
+ names.each do |name|
+ expect(Bundler::Plugin).not_to be_installed(name)
+ end
+ end
+
+ def lockfile_should_be(expected)
+ expect(bundled_app("Gemfile.lock")).to read_as(normalize_uri_file(strip_whitespace(expected)))
+ end
+
+ def gemfile_should_be(expected)
+ expect(bundled_app("Gemfile")).to read_as(strip_whitespace(expected))
+ end
+ end
+end
diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb
new file mode 100644
index 0000000000..2eea088eea
--- /dev/null
+++ b/spec/bundler/support/path.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require "pathname"
+
+module Spec
+ module Path
+ def root
+ @root ||= Pathname.new(ruby_core? ? "../../../.." : "../../..").expand_path(__FILE__)
+ end
+
+ def gemspec
+ @gemspec ||= root.join(ruby_core? ? "lib/bundler.gemspec" : "bundler.gemspec")
+ end
+
+ def bindir
+ @bindir ||= root.join(ruby_core? ? "bin" : "exe")
+ end
+
+ def spec_dir
+ @spec_dir ||= root.join(ruby_core? ? "spec/bundler" : "spec")
+ end
+
+ def tmp(*path)
+ root.join("tmp", *path)
+ end
+
+ def home(*path)
+ tmp.join("home", *path)
+ end
+
+ def default_bundle_path(*path)
+ if Bundler::VERSION.split(".").first.to_i < 2
+ system_gem_path(*path)
+ else
+ bundled_app(*[".bundle", ENV.fetch("BUNDLER_SPEC_RUBY_ENGINE", Gem.ruby_engine), Gem::ConfigMap[:ruby_version], *path].compact)
+ end
+ end
+
+ def bundled_app(*path)
+ root = tmp.join("bundled_app")
+ FileUtils.mkdir_p(root)
+ root.join(*path)
+ end
+
+ alias_method :bundled_app1, :bundled_app
+
+ def bundled_app2(*path)
+ root = tmp.join("bundled_app2")
+ FileUtils.mkdir_p(root)
+ root.join(*path)
+ end
+
+ def vendored_gems(path = nil)
+ bundled_app(*["vendor/bundle", Gem.ruby_engine, Gem::ConfigMap[:ruby_version], path].compact)
+ end
+
+ def cached_gem(path)
+ bundled_app("vendor/cache/#{path}.gem")
+ end
+
+ def base_system_gems
+ tmp.join("gems/base")
+ end
+
+ def gem_repo1(*args)
+ tmp("gems/remote1", *args)
+ end
+
+ def gem_repo_missing(*args)
+ tmp("gems/missing", *args)
+ end
+
+ def gem_repo2(*args)
+ tmp("gems/remote2", *args)
+ end
+
+ def gem_repo3(*args)
+ tmp("gems/remote3", *args)
+ end
+
+ def gem_repo4(*args)
+ tmp("gems/remote4", *args)
+ end
+
+ def security_repo(*args)
+ tmp("gems/security_repo", *args)
+ end
+
+ def system_gem_path(*path)
+ tmp("gems/system", *path)
+ end
+
+ def lib_path(*args)
+ tmp("libs", *args)
+ end
+
+ def bundler_path
+ Pathname.new(File.expand_path(root.join("lib"), __FILE__))
+ end
+
+ def global_plugin_gem(*args)
+ home ".bundle", "plugin", "gems", *args
+ end
+
+ def local_plugin_gem(*args)
+ bundled_app ".bundle", "plugin", "gems", *args
+ end
+
+ def tmpdir(*args)
+ tmp "tmpdir", *args
+ end
+
+ def ruby_core?
+ # avoid to wornings
+ @ruby_core ||= nil
+
+ if @ruby_core.nil?
+ @ruby_core = true & (ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"])
+ else
+ @ruby_core
+ end
+ end
+
+ extend self
+ end
+end
diff --git a/spec/bundler/support/permissions.rb b/spec/bundler/support/permissions.rb
new file mode 100644
index 0000000000..b21ce3848d
--- /dev/null
+++ b/spec/bundler/support/permissions.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Spec
+ module Permissions
+ def with_umask(new_umask)
+ old_umask = File.umask(new_umask)
+ yield if block_given?
+ ensure
+ File.umask(old_umask)
+ end
+ end
+end
diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb
new file mode 100644
index 0000000000..39040a61bd
--- /dev/null
+++ b/spec/bundler/support/platforms.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+module Spec
+ module Platforms
+ include Bundler::GemHelpers
+
+ def rb
+ Gem::Platform::RUBY
+ end
+
+ def mac
+ Gem::Platform.new("x86-darwin-10")
+ end
+
+ def x64_mac
+ Gem::Platform.new("x86_64-darwin-15")
+ end
+
+ def java
+ Gem::Platform.new([nil, "java", nil])
+ end
+
+ def linux
+ Gem::Platform.new(["x86", "linux", nil])
+ end
+
+ def mswin
+ Gem::Platform.new(["x86", "mswin32", nil])
+ end
+
+ def mingw
+ Gem::Platform.new(["x86", "mingw32", nil])
+ end
+
+ def x64_mingw
+ Gem::Platform.new(["x64", "mingw32", nil])
+ end
+
+ def all_platforms
+ [rb, java, linux, mswin, mingw, x64_mingw]
+ end
+
+ def local
+ generic_local_platform
+ end
+
+ def specific_local_platform
+ Bundler.local_platform
+ end
+
+ def not_local
+ all_platforms.find {|p| p != generic_local_platform }
+ end
+
+ def local_tag
+ if RUBY_PLATFORM == "java"
+ :jruby
+ else
+ :ruby
+ end
+ end
+
+ def not_local_tag
+ [:ruby, :jruby].find {|tag| tag != local_tag }
+ end
+
+ def local_ruby_engine
+ ENV["BUNDLER_SPEC_RUBY_ENGINE"] || (defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby")
+ end
+
+ def local_engine_version
+ return ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"] if ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"]
+
+ case local_ruby_engine
+ when "ruby"
+ RUBY_VERSION
+ when "rbx"
+ Rubinius::VERSION
+ when "jruby"
+ JRUBY_VERSION
+ else
+ RUBY_ENGINE_VERSION
+ end
+ end
+
+ def not_local_engine_version
+ case not_local_tag
+ when :ruby
+ not_local_ruby_version
+ when :jruby
+ "1.6.1"
+ end
+ end
+
+ def not_local_ruby_version
+ "1.12"
+ end
+
+ def not_local_patchlevel
+ 9999
+ end
+
+ def lockfile_platforms(*platforms)
+ platforms = local_platforms if platforms.empty?
+ platforms.map(&:to_s).sort.join("\n ")
+ end
+
+ def local_platforms
+ if Bundler::VERSION.split(".").first.to_i > 1
+ [local, specific_local_platform]
+ else
+ [local]
+ end
+ end
+ end
+end
diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb
new file mode 100644
index 0000000000..806933fe2f
--- /dev/null
+++ b/spec/bundler/support/rubygems_ext.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require "rubygems/user_interaction"
+require "support/path" unless defined?(Spec::Path)
+
+module Spec
+ module Rubygems
+ DEPS = begin
+ deps = {
+ # rack 2.x requires Ruby version >= 2.2.2.
+ # artifice doesn't support rack 2.x now.
+ # TODO: revert to `< 2` once https://github.com/rack/rack/issues/1168 is
+ # addressed
+ "rack" => "1.6.6",
+ # rack-test 0.7.0 dropped 1.8.7 support
+ # https://github.com/rack-test/rack-test/issues/193#issuecomment-314230318
+ "rack-test" => "< 0.7.0",
+ "artifice" => "~> 0.6.0",
+ "compact_index" => "~> 0.11.0",
+ "sinatra" => "~> 1.4.7",
+ # Rake version has to be consistent for tests to pass
+ "rake" => "10.0.2",
+ # 3.0.0 breaks 1.9.2 specs
+ "builder" => "2.1.2",
+ }
+ # ruby-graphviz is used by the viz tests
+ deps["ruby-graphviz"] = nil if RUBY_VERSION >= "1.9.3"
+ deps
+ end
+
+ def self.setup
+ Gem.clear_paths
+
+ ENV["BUNDLE_PATH"] = nil
+ ENV["GEM_HOME"] = ENV["GEM_PATH"] = Path.base_system_gems.to_s
+ ENV["PATH"] = ["#{Path.root}/exe", "#{Path.system_gem_path}/bin", ENV["PATH"]].join(File::PATH_SEPARATOR)
+
+ manifest = DEPS.to_a.sort_by(&:first).map {|k, v| "#{k} => #{v}\n" }
+ manifest_path = "#{Path.base_system_gems}/manifest.txt"
+ # it's OK if there are extra gems
+ if !File.exist?(manifest_path) || !(manifest - File.readlines(manifest_path)).empty?
+ FileUtils.rm_rf(Path.base_system_gems)
+ FileUtils.mkdir_p(Path.base_system_gems)
+ puts "installing gems for the tests to use..."
+ install_gems(DEPS)
+ File.open(manifest_path, "w") {|f| f << manifest.join }
+ end
+
+ ENV["HOME"] = Path.home.to_s
+ ENV["TMPDIR"] = Path.tmpdir.to_s
+
+ Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
+ end
+
+ def self.install_gems(gems)
+ reqs, no_reqs = gems.partition {|_, req| !req.nil? && !req.split(" ").empty? }
+ # TODO: remove when we drop ruby 1.8.7-2.2.2 support
+ reqs = reqs.sort_by {|name, _| name == "rack" ? 0 : 1 }.sort_by {|name, _| name =~ /rack/ ? 0 : 1 }
+ no_reqs.map!(&:first)
+ reqs.map! {|name, req| "'#{name}:#{req}'" }
+ deps = reqs.concat(no_reqs).join(" ")
+ cmd = if Gem::VERSION < "2.0.0"
+ "gem install #{deps} --no-rdoc --no-ri --conservative"
+ else
+ "gem install #{deps} --no-document --conservative"
+ end
+ puts cmd
+ system(cmd) || raise("Installing gems #{deps} for the tests to use failed!")
+ end
+ end
+end
diff --git a/spec/bundler/support/silent_logger.rb b/spec/bundler/support/silent_logger.rb
new file mode 100644
index 0000000000..8665beb2c9
--- /dev/null
+++ b/spec/bundler/support/silent_logger.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require "logger"
+module Spec
+ class SilentLogger
+ (::Logger.instance_methods - Object.instance_methods).each do |logger_instance_method|
+ define_method(logger_instance_method) {|*args, &blk| }
+ end
+ end
+end
diff --git a/spec/bundler/support/sometimes.rb b/spec/bundler/support/sometimes.rb
new file mode 100644
index 0000000000..65a95ed59c
--- /dev/null
+++ b/spec/bundler/support/sometimes.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Sometimes
+ def run_with_retries(example_to_run, retries)
+ example = RSpec.current_example
+ example.metadata[:retries] ||= retries
+
+ retries.times do |t|
+ example.metadata[:retried] = t + 1
+ example.instance_variable_set(:@exception, nil)
+ example_to_run.run
+ break unless example.exception
+ end
+
+ if e = example.exception
+ new_exception = e.exception(e.message + "[Retried #{retries} times]")
+ new_exception.set_backtrace e.backtrace
+ example.instance_variable_set(:@exception, new_exception)
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.include Sometimes
+ config.alias_example_to :sometimes, :sometimes => true
+ config.add_setting :sometimes_retry_count, :default => 5
+
+ config.around(:each, :sometimes => true) do |example|
+ retries = example.metadata[:retries] || RSpec.configuration.sometimes_retry_count
+ run_with_retries(example, retries)
+ end
+
+ config.after(:suite) do
+ message = proc do |color, text|
+ colored = RSpec::Core::Formatters::ConsoleCodes.wrap(text, color)
+ notification = RSpec::Core::Notifications::MessageNotification.new(colored)
+ formatter = RSpec.configuration.formatters.first
+ formatter.message(notification) if formatter.respond_to?(:message)
+ end
+
+ retried_examples = RSpec.world.example_groups.map do |g|
+ g.descendants.map do |d|
+ d.filtered_examples.select do |e|
+ e.metadata[:sometimes] && e.metadata.fetch(:retried, 1) > 1
+ end
+ end
+ end.flatten
+
+ message.call(retried_examples.empty? ? :green : :yellow, "\n\nRetried examples: #{retried_examples.count}")
+
+ retried_examples.each do |e|
+ message.call(:cyan, " #{e.full_description}")
+ path = RSpec::Core::Metadata.relative_path(e.location)
+ message.call(:cyan, " [#{e.metadata[:retried]}/#{e.metadata[:retries]}] " + path)
+ end
+ end
+end
diff --git a/spec/bundler/support/streams.rb b/spec/bundler/support/streams.rb
new file mode 100644
index 0000000000..a947eebf6f
--- /dev/null
+++ b/spec/bundler/support/streams.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "stringio"
+
+def capture(*args)
+ opts = args.pop if args.last.is_a?(Hash)
+ opts ||= {}
+
+ args.map!(&:to_s)
+ begin
+ result = StringIO.new
+ result.close if opts[:closed]
+ args.each {|stream| eval "$#{stream} = result" }
+ yield
+ ensure
+ args.each {|stream| eval("$#{stream} = #{stream.upcase}") }
+ end
+ result.string
+end
diff --git a/spec/bundler/support/sudo.rb b/spec/bundler/support/sudo.rb
new file mode 100644
index 0000000000..04e9443945
--- /dev/null
+++ b/spec/bundler/support/sudo.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Spec
+ module Sudo
+ def self.present?
+ @which_sudo ||= Bundler.which("sudo")
+ end
+
+ def sudo(cmd)
+ raise "sudo not present" unless Sudo.present?
+ sys_exec("sudo #{cmd}")
+ end
+
+ def chown_system_gems_to_root
+ sudo "chown -R root #{system_gem_path}"
+ end
+ end
+end
diff --git a/spec/bundler/support/the_bundle.rb b/spec/bundler/support/the_bundle.rb
new file mode 100644
index 0000000000..c994eaae78
--- /dev/null
+++ b/spec/bundler/support/the_bundle.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "support/helpers"
+require "support/path"
+
+module Spec
+ class TheBundle
+ include Spec::Helpers
+ include Spec::Path
+
+ attr_accessor :bundle_dir
+
+ def initialize(opts = {})
+ opts = opts.dup
+ @bundle_dir = Pathname.new(opts.delete(:bundle_dir) { bundled_app })
+ raise "Too many options! #{opts}" unless opts.empty?
+ end
+
+ def to_s
+ "the bundle"
+ end
+ alias_method :inspect, :to_s
+
+ def locked?
+ lockfile.file?
+ end
+
+ def lockfile
+ bundle_dir.join("Gemfile.lock")
+ end
+
+ def locked_gems
+ raise "Cannot read lockfile if it doesn't exist" unless locked?
+ Bundler::LockfileParser.new(lockfile.read)
+ end
+ end
+end
diff --git a/spec/bundler/update/gemfile_spec.rb b/spec/bundler/update/gemfile_spec.rb
new file mode 100644
index 0000000000..f59f3a2d32
--- /dev/null
+++ b/spec/bundler/update/gemfile_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ context "with --gemfile" do
+ it "finds the gemfile" do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ bundle! :install, :gemfile => bundled_app("NotGemfile")
+ bundle! :update, :gemfile => bundled_app("NotGemfile"), :all => bundle_update_requires_all?
+
+ # Specify BUNDLE_GEMFILE for `the_bundle`
+ # to retrieve the proper Gemfile
+ ENV["BUNDLE_GEMFILE"] = "NotGemfile"
+ expect(the_bundle).to include_gems "rack 1.0.0"
+ end
+ end
+
+ context "with gemfile set via config" do
+ before do
+ gemfile bundled_app("NotGemfile"), <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ G
+
+ bundle "config --local gemfile #{bundled_app("NotGemfile")}"
+ bundle! :install
+ end
+
+ it "uses the gemfile to update" do
+ bundle! "update", :all => bundle_update_requires_all?
+ bundle "list"
+
+ expect(out).to include("rack (1.0.0)")
+ end
+
+ it "uses the gemfile while in a subdirectory" do
+ bundled_app("subdir").mkpath
+ Dir.chdir(bundled_app("subdir")) do
+ bundle! "update", :all => bundle_update_requires_all?
+ bundle "list"
+
+ expect(out).to include("rack (1.0.0)")
+ end
+ end
+ end
+
+ context "with prefer_gems_rb set" do
+ before { bundle! "config prefer_gems_rb true" }
+
+ it "prefers gems.rb to Gemfile" do
+ create_file("gems.rb", "gem 'bundler'")
+ create_file("Gemfile", "raise 'wrong Gemfile!'")
+
+ bundle! :install
+ bundle! :update, :all => bundle_update_requires_all?
+
+ expect(bundled_app("gems.rb")).to be_file
+ expect(bundled_app("Gemfile.lock")).not_to be_file
+
+ expect(the_bundle).to include_gem "bundler #{Bundler::VERSION}"
+ end
+ end
+end
diff --git a/spec/bundler/update/gems/post_install_spec.rb b/spec/bundler/update/gems/post_install_spec.rb
new file mode 100644
index 0000000000..2fb3547806
--- /dev/null
+++ b/spec/bundler/update/gems/post_install_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ let(:config) {}
+
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack', "< 1.0"
+ gem 'thin'
+ G
+
+ bundle! "config #{config}" if config
+
+ bundle! :install
+ end
+
+ shared_examples "a config observer" do
+ context "when ignore post-install messages for gem is set" do
+ let(:config) { "ignore_messages.rack true" }
+
+ it "doesn't display gem's post-install message" do
+ expect(out).not_to include("Rack's post install message")
+ end
+ end
+
+ context "when ignore post-install messages for all gems" do
+ let(:config) { "ignore_messages true" }
+
+ it "doesn't display any post-install messages" do
+ expect(out).not_to include("Post-install message")
+ end
+ end
+ end
+
+ shared_examples "a post-install message outputter" do
+ it "should display post-install messages for updated gems" do
+ expect(out).to include("Post-install message from rack:")
+ expect(out).to include("Rack's post install message")
+ end
+
+ it "should not display the post-install message for non-updated gems" do
+ expect(out).not_to include("Thin's post install message")
+ end
+ end
+
+ context "when listed gem is updated" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ gem 'thin'
+ G
+
+ bundle! :update, :all => bundle_update_requires_all?
+ end
+
+ it_behaves_like "a post-install message outputter"
+ it_behaves_like "a config observer"
+ end
+
+ context "when dependency triggers update" do
+ before do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem 'rack-obama'
+ gem 'thin'
+ G
+
+ bundle! :update, :all => bundle_update_requires_all?
+ end
+
+ it_behaves_like "a post-install message outputter"
+ it_behaves_like "a config observer"
+ end
+end
diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb
new file mode 100644
index 0000000000..b4cbb79434
--- /dev/null
+++ b/spec/bundler/update/git_spec.rb
@@ -0,0 +1,374 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update" do
+ describe "git sources" do
+ it "floats on a branch when :branch is used" do
+ build_git "foo", "1.0"
+ update_git "foo", :branch => "omg"
+
+ install_gemfile <<-G
+ git "#{lib_path("foo-1.0")}", :branch => "omg" do
+ gem 'foo'
+ end
+ G
+
+ update_git "foo", :branch => "omg" do |s|
+ s.write "lib/foo.rb", "FOO = '1.1'"
+ end
+
+ bundle "update", :all => bundle_update_requires_all?
+
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "updates correctly when you have like craziness" do
+ build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport")
+ build_git "rails", "3.0", :path => lib_path("rails") do |s|
+ s.add_dependency "activesupport", "= 3.0"
+ end
+
+ install_gemfile! <<-G
+ gem "rails", :git => "#{lib_path("rails")}"
+ G
+
+ bundle! "update rails"
+ expect(the_bundle).to include_gems "rails 3.0", "activesupport 3.0"
+ end
+
+ it "floats on a branch when :branch is used and the source is specified in the update" do
+ build_git "foo", "1.0", :path => lib_path("foo")
+ update_git "foo", :branch => "omg", :path => lib_path("foo")
+
+ install_gemfile <<-G
+ git "#{lib_path("foo")}", :branch => "omg" do
+ gem 'foo'
+ end
+ G
+
+ update_git "foo", :branch => "omg", :path => lib_path("foo") do |s|
+ s.write "lib/foo.rb", "FOO = '1.1'"
+ end
+
+ bundle "update --source foo"
+
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "floats on master when updating all gems that are pinned to the source even if you have child dependencies" do
+ build_git "foo", :path => lib_path("foo")
+ build_gem "bar", :to_bundle => true do |s|
+ s.add_dependency "foo"
+ end
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo")}"
+ gem "bar"
+ G
+
+ update_git "foo", :path => lib_path("foo") do |s|
+ s.write "lib/foo.rb", "FOO = '1.1'"
+ end
+
+ bundle "update foo"
+
+ expect(the_bundle).to include_gems "foo 1.1"
+ end
+
+ it "notices when you change the repo url in the Gemfile" do
+ build_git "foo", :path => lib_path("foo_one")
+ build_git "foo", :path => lib_path("foo_two")
+
+ install_gemfile <<-G
+ gem "foo", "1.0", :git => "#{lib_path("foo_one")}"
+ G
+
+ FileUtils.rm_rf lib_path("foo_one")
+
+ install_gemfile <<-G
+ gem "foo", "1.0", :git => "#{lib_path("foo_two")}"
+ G
+
+ expect(err).to lack_errors
+ expect(out).to include("Fetching #{lib_path}/foo_two")
+ expect(out).to include("Bundle complete!")
+ end
+
+ it "fetches tags from the remote" do
+ build_git "foo"
+ @remote = build_git("bar", :bare => true)
+ update_git "foo", :remote => @remote.path
+ update_git "foo", :push => "master"
+
+ install_gemfile <<-G
+ gem 'foo', :git => "#{@remote.path}"
+ G
+
+ # Create a new tag on the remote that needs fetching
+ update_git "foo", :tag => "fubar"
+ update_git "foo", :push => "fubar"
+
+ gemfile <<-G
+ gem 'foo', :git => "#{@remote.path}", :tag => "fubar"
+ G
+
+ bundle "update", :all => bundle_update_requires_all?
+ expect(exitstatus).to eq(0) if exitstatus
+ end
+
+ describe "with submodules" do
+ before :each do
+ build_repo4 do
+ build_gem "submodule" do |s|
+ s.write "lib/submodule.rb", "puts 'GEM'"
+ end
+ end
+
+ build_git "submodule", "1.0" do |s|
+ s.write "lib/submodule.rb", "puts 'GIT'"
+ end
+
+ build_git "has_submodule", "1.0" do |s|
+ s.add_dependency "submodule"
+ end
+
+ Dir.chdir(lib_path("has_submodule-1.0")) do
+ sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0"
+ `git commit -m "submodulator"`
+ end
+ end
+
+ it "it unlocks the source when submodules are added to a git source" do
+ install_gemfile <<-G
+ source "file:#{gem_repo4}"
+ git "#{lib_path("has_submodule-1.0")}" do
+ gem "has_submodule"
+ end
+ G
+
+ run "require 'submodule'"
+ expect(out).to eq("GEM")
+
+ install_gemfile <<-G
+ source "file:#{gem_repo4}"
+ git "#{lib_path("has_submodule-1.0")}", :submodules => true do
+ gem "has_submodule"
+ end
+ G
+
+ run "require 'submodule'"
+ expect(out).to eq("GIT")
+ end
+
+ it "unlocks the source when submodules are removed from git source", :git => ">= 2.9.0" do
+ install_gemfile! <<-G
+ source "file:#{gem_repo4}"
+ git "#{lib_path("has_submodule-1.0")}", :submodules => true do
+ gem "has_submodule"
+ end
+ G
+
+ run! "require 'submodule'"
+ expect(out).to eq("GIT")
+
+ install_gemfile! <<-G
+ source "file:#{gem_repo4}"
+ git "#{lib_path("has_submodule-1.0")}" do
+ gem "has_submodule"
+ end
+ G
+
+ run! "require 'submodule'"
+ expect(out).to eq("GEM")
+ end
+ end
+
+ it "errors with a message when the .git repo is gone" do
+ build_git "foo", "1.0"
+
+ install_gemfile <<-G
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ lib_path("foo-1.0").join(".git").rmtree
+
+ bundle :update, :all => bundle_update_requires_all?
+ expect(last_command.bundler_err).to include(lib_path("foo-1.0").to_s).
+ and match(/Git error: command `git fetch.+has failed/)
+ end
+
+ it "should not explode on invalid revision on update of gem by name" do
+ build_git "rack", "0.8"
+
+ build_git "rack", "0.8", :path => lib_path("local-rack") do |s|
+ s.write "lib/rack.rb", "puts :LOCAL"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master"
+ G
+
+ bundle %(config local.rack #{lib_path("local-rack")})
+ bundle "update rack"
+ expect(out).to include("Bundle updated!")
+ end
+
+ it "shows the previous version of the gem" do
+ build_git "rails", "3.0", :path => lib_path("rails")
+
+ install_gemfile <<-G
+ gem "rails", :git => "#{lib_path("rails")}"
+ G
+
+ lockfile <<-G
+ GIT
+ remote: #{lib_path("rails")}
+ specs:
+ rails (2.3.2)
+
+ PLATFORMS
+ #{generic_local_platform}
+
+ DEPENDENCIES
+ rails!
+ G
+
+ bundle "update", :all => bundle_update_requires_all?
+ expect(out).to include("Using rails 3.0 (was 2.3.2) from #{lib_path("rails")} (at master@#{revision_for(lib_path("rails"))[0..6]})")
+ end
+ end
+
+ describe "with --source flag" do
+ before :each do
+ build_repo2
+ @git = build_git "foo", :path => lib_path("foo") do |s|
+ s.executables = "foobar"
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+ git "#{lib_path("foo")}" do
+ gem 'foo'
+ end
+ gem 'rack'
+ G
+ end
+
+ it "updates the source" do
+ update_git "foo", :path => @git.path
+
+ bundle "update --source foo"
+
+ in_app_root do
+ run <<-RUBY
+ require 'foo'
+ puts "WIN" if defined?(FOO_PREV_REF)
+ RUBY
+
+ expect(out).to eq("WIN")
+ end
+ end
+
+ it "unlocks gems that were originally pulled in by the source" do
+ update_git "foo", "2.0", :path => @git.path
+
+ bundle "update --source foo"
+ expect(the_bundle).to include_gems "foo 2.0"
+ end
+
+ it "leaves all other gems frozen" do
+ update_repo2
+ update_git "foo", :path => @git.path
+
+ bundle "update --source foo"
+ expect(the_bundle).to include_gems "rack 1.0"
+ end
+ end
+
+ context "when the gem and the repository have different names" do
+ before :each do
+ build_repo2
+ @git = build_git "foo", :path => lib_path("bar")
+
+ install_gemfile <<-G
+ source "file://localhost#{gem_repo2}"
+ git "#{lib_path("bar")}" do
+ gem 'foo'
+ end
+ gem 'rack'
+ G
+ end
+
+ it "the --source flag updates version of gems that were originally pulled in by the source", :bundler => "< 2" do
+ spec_lines = lib_path("bar/foo.gemspec").read.split("\n")
+ spec_lines[5] = "s.version = '2.0'"
+
+ update_git "foo", "2.0", :path => @git.path do |s|
+ s.write "foo.gemspec", spec_lines.join("\n")
+ end
+
+ ref = @git.ref_for "master"
+
+ bundle "update --source bar"
+
+ lockfile_should_be <<-G
+ GIT
+ remote: #{@git.path}
+ revision: #{ref}
+ specs:
+ foo (2.0)
+
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ rack (1.0.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ foo!
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ it "the --source flag updates version of gems that were originally pulled in by the source", :bundler => "2" do
+ spec_lines = lib_path("bar/foo.gemspec").read.split("\n")
+ spec_lines[5] = "s.version = '2.0'"
+
+ update_git "foo", "2.0", :path => @git.path do |s|
+ s.write "foo.gemspec", spec_lines.join("\n")
+ end
+
+ ref = @git.ref_for "master"
+
+ bundle "update --source bar"
+
+ lockfile_should_be <<-G
+ GEM
+ remote: file://localhost#{gem_repo2}/
+ specs:
+ rack (1.0.0)
+
+ GIT
+ remote: #{@git.path}
+ revision: #{ref}
+ specs:
+ foo (2.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ foo!
+ rack
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+ end
+end
diff --git a/spec/bundler/update/path_spec.rb b/spec/bundler/update/path_spec.rb
new file mode 100644
index 0000000000..38c125e04b
--- /dev/null
+++ b/spec/bundler/update/path_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+RSpec.describe "path sources" do
+ describe "bundle update --source" do
+ it "shows the previous version of the gem when updated from path source" do
+ build_lib "activesupport", "2.3.5", :path => lib_path("rails/activesupport")
+
+ install_gemfile <<-G
+ gem "activesupport", :path => "#{lib_path("rails/activesupport")}"
+ G
+
+ build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport")
+
+ bundle "update --source activesupport"
+ expect(out).to include("Using activesupport 3.0 (was 2.3.5) from source at `#{lib_path("rails/activesupport")}`")
+ end
+ end
+end
diff --git a/spec/bundler/update/redownload_spec.rb b/spec/bundler/update/redownload_spec.rb
new file mode 100644
index 0000000000..018d3ed2e9
--- /dev/null
+++ b/spec/bundler/update/redownload_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+RSpec.describe "bundle update", :bundler => "< 2", :ruby => ">= 2.0" do
+ before :each do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+ end
+
+ before { bundle "config major_deprecations yes" }
+
+ describe "with --force" do
+ it "shows a deprecation when single flag passed" do
+ bundle! "update rack --force"
+ expect(out).to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`"
+ end
+
+ it "shows a deprecation when multiple flags passed" do
+ bundle! "update rack --no-color --force"
+ expect(out).to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`"
+ end
+ end
+
+ describe "with --redownload" do
+ it "does not show a deprecation when single flag passed" do
+ bundle! "update rack --redownload"
+ expect(out).not_to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`"
+ end
+
+ it "does not show a deprecation when single multiple flags passed" do
+ bundle! "update rack --no-color --redownload"
+ expect(out).not_to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`"
+ end
+ end
+end
diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb
index 72cb3b16f9..d42ad70658 100644
--- a/tool/sync_default_gems.rb
+++ b/tool/sync_default_gems.rb
@@ -1,6 +1,7 @@
# sync following repositories to ruby repository
#
# * https://github.com/rubygems/rubygems
+# * https://github.com/bundler/bundler
# * https://github.com/ruby/rdoc
# * https://github.com/flori/json
# * https://github.com/ruby/psych
@@ -39,6 +40,7 @@
$repositories = {
rubygems: 'rubygems/rubygems',
+ bundler: 'bundler/bundler',
rdoc: 'ruby/rdoc',
json: 'flori/json',
psych: 'ruby/psych',
@@ -83,6 +85,13 @@ def sync_default_gems(gem)
`rm -rf lib/rubygems* test/rubygems`
`cp -r ../../rubygems/rubygems/lib/rubygems* ./lib`
`cp -r ../../rubygems/rubygems/test/rubygems ./test`
+ when "bundler"
+ `rm -rf lib/bundler* bin/bundler bin/bundle bin/bundle_ruby spec/bundler man/bundle* man/gemfile*`
+ `cp -r ../../bundler/bundler/lib/bundler* ./lib`
+ `cp -r ../../bundler/bundler/exe/bundle* ./bin`
+ `cp ../../bundler/bundler/bundler.gemspec ./lib`
+ `cp -r ../../bundler/bundler/spec spec/bundler`
+ `cp -r ../../bundler/bundler/man/*.{1,5,1\.txt,5\.txt,ronn} ./man`
when "rdoc"
`rm -rf lib/rdoc* test/rdoc`
`cp -rf ../rdoc/lib/rdoc* ./lib`