rubygems-update 2.6.14 → 2.7.0

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

Potentially problematic release.


This version of rubygems-update might be problematic. Click here for more details.

Files changed (347) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -7
  3. data/CONTRIBUTING.rdoc +53 -54
  4. data/History.txt +173 -9
  5. data/Manifest.txt +39 -11
  6. data/POLICIES.rdoc +3 -3
  7. data/README.md +72 -0
  8. data/Rakefile +30 -5
  9. data/appveyor.yml +29 -1
  10. data/bin/gem +1 -1
  11. data/bin/update_rubygems +2 -2
  12. data/bundler/CHANGELOG.md +269 -9
  13. data/bundler/CODE_OF_CONDUCT.md +1 -1
  14. data/bundler/CONTRIBUTING.md +10 -29
  15. data/bundler/README.md +22 -10
  16. data/bundler/exe/bundle +5 -7
  17. data/bundler/exe/bundle_ruby +4 -3
  18. data/bundler/lib/bundler.rb +94 -74
  19. data/bundler/lib/bundler/build_metadata.rb +36 -0
  20. data/bundler/lib/bundler/capistrano.rb +5 -0
  21. data/bundler/lib/bundler/cli.rb +229 -66
  22. data/bundler/lib/bundler/cli/add.rb +25 -0
  23. data/bundler/lib/bundler/cli/binstubs.rb +9 -7
  24. data/bundler/lib/bundler/cli/cache.rb +5 -4
  25. data/bundler/lib/bundler/cli/check.rb +3 -5
  26. data/bundler/lib/bundler/cli/clean.rb +5 -6
  27. data/bundler/lib/bundler/cli/common.rb +18 -2
  28. data/bundler/lib/bundler/cli/config.rb +26 -7
  29. data/bundler/lib/bundler/cli/console.rb +2 -1
  30. data/bundler/lib/bundler/cli/doctor.rb +1 -0
  31. data/bundler/lib/bundler/cli/exec.rb +3 -2
  32. data/bundler/lib/bundler/cli/gem.rb +36 -15
  33. data/bundler/lib/bundler/cli/info.rb +50 -0
  34. data/bundler/lib/bundler/cli/init.rb +20 -7
  35. data/bundler/lib/bundler/cli/inject.rb +13 -4
  36. data/bundler/lib/bundler/cli/install.rb +61 -77
  37. data/bundler/lib/bundler/cli/issue.rb +40 -0
  38. data/bundler/lib/bundler/cli/list.rb +22 -0
  39. data/bundler/lib/bundler/cli/lock.rb +4 -2
  40. data/bundler/lib/bundler/cli/open.rb +2 -2
  41. data/bundler/lib/bundler/cli/outdated.rb +30 -28
  42. data/bundler/lib/bundler/cli/package.rb +9 -6
  43. data/bundler/lib/bundler/cli/platform.rb +1 -0
  44. data/bundler/lib/bundler/cli/plugin.rb +1 -0
  45. data/bundler/lib/bundler/cli/pristine.rb +43 -0
  46. data/bundler/lib/bundler/cli/show.rb +1 -1
  47. data/bundler/lib/bundler/cli/update.rb +32 -11
  48. data/bundler/lib/bundler/cli/viz.rb +5 -1
  49. data/bundler/lib/bundler/compact_index_client.rb +1 -0
  50. data/bundler/lib/bundler/compact_index_client/cache.rb +1 -2
  51. data/bundler/lib/bundler/compact_index_client/updater.rb +26 -7
  52. data/bundler/lib/bundler/compatibility_guard.rb +14 -0
  53. data/bundler/lib/bundler/constants.rb +1 -0
  54. data/bundler/lib/bundler/current_ruby.rb +8 -7
  55. data/bundler/lib/bundler/definition.rb +231 -159
  56. data/bundler/lib/bundler/dep_proxy.rb +2 -0
  57. data/bundler/lib/bundler/dependency.rb +6 -7
  58. data/bundler/lib/bundler/deployment.rb +1 -1
  59. data/bundler/lib/bundler/deprecate.rb +14 -3
  60. data/bundler/lib/bundler/dsl.rb +103 -62
  61. data/bundler/lib/bundler/endpoint_specification.rb +12 -2
  62. data/bundler/lib/bundler/env.rb +97 -36
  63. data/bundler/lib/bundler/environment_preserver.rb +27 -6
  64. data/bundler/lib/bundler/errors.rb +3 -1
  65. data/bundler/lib/bundler/feature_flag.rb +39 -4
  66. data/bundler/lib/bundler/fetcher.rb +15 -8
  67. data/bundler/lib/bundler/fetcher/base.rb +1 -0
  68. data/bundler/lib/bundler/fetcher/compact_index.rb +2 -12
  69. data/bundler/lib/bundler/fetcher/dependency.rb +2 -1
  70. data/bundler/lib/bundler/fetcher/downloader.rb +4 -2
  71. data/bundler/lib/bundler/fetcher/index.rb +1 -0
  72. data/bundler/lib/bundler/friendly_errors.rb +5 -2
  73. data/bundler/lib/bundler/gem_helper.rb +23 -9
  74. data/bundler/lib/bundler/gem_helpers.rb +1 -0
  75. data/bundler/lib/bundler/gem_remote_fetcher.rb +1 -0
  76. data/bundler/lib/bundler/gem_tasks.rb +1 -0
  77. data/bundler/lib/bundler/gem_version_promoter.rb +1 -0
  78. data/bundler/lib/bundler/gemdeps.rb +1 -0
  79. data/bundler/lib/bundler/graph.rb +1 -0
  80. data/bundler/lib/bundler/index.rb +19 -11
  81. data/bundler/lib/bundler/injector.rb +51 -27
  82. data/bundler/lib/bundler/inline.rb +10 -10
  83. data/bundler/lib/bundler/installer.rb +104 -50
  84. data/bundler/lib/bundler/installer/gem_installer.rb +5 -2
  85. data/bundler/lib/bundler/installer/parallel_installer.rb +91 -42
  86. data/bundler/lib/bundler/installer/standalone.rb +1 -0
  87. data/bundler/lib/bundler/lazy_specification.rb +17 -4
  88. data/bundler/lib/bundler/lockfile_generator.rb +95 -0
  89. data/bundler/lib/bundler/lockfile_parser.rb +49 -35
  90. data/bundler/lib/bundler/match_platform.rb +1 -0
  91. data/bundler/lib/bundler/mirror.rb +8 -3
  92. data/bundler/lib/bundler/plugin.rb +6 -1
  93. data/bundler/lib/bundler/plugin/api/source.rb +16 -3
  94. data/bundler/lib/bundler/plugin/index.rb +2 -0
  95. data/bundler/lib/bundler/plugin/installer.rb +7 -6
  96. data/bundler/lib/bundler/plugin/source_list.rb +7 -8
  97. data/bundler/lib/bundler/process_lock.rb +24 -0
  98. data/bundler/lib/bundler/psyched_yaml.rb +10 -0
  99. data/bundler/lib/bundler/remote_specification.rb +25 -1
  100. data/bundler/lib/bundler/resolver.rb +171 -192
  101. data/bundler/lib/bundler/resolver/spec_group.rb +111 -0
  102. data/bundler/lib/bundler/retry.rb +1 -0
  103. data/bundler/lib/bundler/ruby_dsl.rb +1 -0
  104. data/bundler/lib/bundler/ruby_version.rb +6 -1
  105. data/bundler/lib/bundler/rubygems_ext.rb +18 -8
  106. data/bundler/lib/bundler/rubygems_gem_installer.rb +25 -2
  107. data/bundler/lib/bundler/rubygems_integration.rb +157 -66
  108. data/bundler/lib/bundler/runtime.rb +28 -18
  109. data/bundler/lib/bundler/settings.rb +202 -87
  110. data/bundler/lib/bundler/settings/validator.rb +79 -0
  111. data/bundler/lib/bundler/setup.rb +4 -7
  112. data/bundler/lib/bundler/shared_helpers.rb +129 -25
  113. data/bundler/lib/bundler/similarity_detector.rb +1 -0
  114. data/bundler/lib/bundler/source.rb +53 -1
  115. data/bundler/lib/bundler/source/gemspec.rb +1 -0
  116. data/bundler/lib/bundler/source/git.rb +49 -21
  117. data/bundler/lib/bundler/source/git/git_proxy.rb +17 -12
  118. data/bundler/lib/bundler/source/metadata.rb +63 -0
  119. data/bundler/lib/bundler/source/path.rb +38 -17
  120. data/bundler/lib/bundler/source/path/installer.rb +4 -2
  121. data/bundler/lib/bundler/source/rubygems.rb +154 -82
  122. data/bundler/lib/bundler/source/rubygems/remote.rb +8 -1
  123. data/bundler/lib/bundler/source_list.rb +75 -15
  124. data/bundler/lib/bundler/spec_set.rb +34 -21
  125. data/bundler/lib/bundler/ssl_certs/certificate_manager.rb +2 -1
  126. data/bundler/lib/bundler/stub_specification.rb +86 -2
  127. data/bundler/lib/bundler/templates/Executable +5 -1
  128. data/bundler/lib/bundler/templates/Executable.bundler +105 -0
  129. data/bundler/lib/bundler/templates/Executable.standalone +5 -5
  130. data/bundler/lib/bundler/templates/Gemfile +3 -0
  131. data/bundler/lib/bundler/templates/gems.rb +8 -0
  132. data/bundler/lib/bundler/templates/newgem/Gemfile.tt +4 -2
  133. data/bundler/lib/bundler/templates/newgem/LICENSE.txt.tt +1 -1
  134. data/bundler/lib/bundler/templates/newgem/README.md.tt +14 -8
  135. data/bundler/lib/bundler/templates/newgem/Rakefile.tt +5 -5
  136. data/bundler/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt +4 -4
  137. data/bundler/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt +3 -3
  138. data/bundler/lib/bundler/templates/newgem/gitignore.tt +0 -1
  139. data/bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt +6 -6
  140. data/bundler/lib/bundler/templates/newgem/lib/newgem/version.rb.tt +4 -4
  141. data/bundler/lib/bundler/templates/newgem/newgem.gemspec.tt +13 -10
  142. data/bundler/lib/bundler/templates/newgem/rspec.tt +1 -0
  143. data/bundler/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +0 -2
  144. data/bundler/lib/bundler/templates/newgem/spec/spec_helper.rb.tt +3 -0
  145. data/bundler/lib/bundler/templates/newgem/test/newgem_test.rb.tt +1 -1
  146. data/bundler/lib/bundler/templates/newgem/test/test_helper.rb.tt +3 -3
  147. data/bundler/lib/bundler/ui.rb +1 -0
  148. data/bundler/lib/bundler/ui/rg_proxy.rb +1 -0
  149. data/bundler/lib/bundler/ui/shell.rb +24 -10
  150. data/bundler/lib/bundler/ui/silent.rb +12 -1
  151. data/bundler/lib/bundler/uri_credentials_filter.rb +1 -0
  152. data/bundler/lib/bundler/vendor/fileutils/lib/fileutils.rb +1638 -0
  153. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo.rb +2 -0
  154. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb +26 -0
  155. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb +7 -0
  156. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb +1 -0
  157. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +16 -5
  158. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb +1 -0
  159. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +10 -2
  160. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +1 -0
  161. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +1 -0
  162. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +1 -0
  163. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb +1 -0
  164. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb +1 -0
  165. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb +1 -0
  166. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb +5 -4
  167. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb +69 -6
  168. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +2 -1
  169. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +1 -0
  170. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb +3 -1
  171. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +501 -138
  172. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb +1 -0
  173. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/state.rb +8 -4
  174. data/bundler/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +3 -1
  175. data/bundler/lib/bundler/vendor/thor/lib/thor.rb +46 -21
  176. data/bundler/lib/bundler/vendor/thor/lib/thor/actions.rb +24 -22
  177. data/bundler/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb +2 -1
  178. data/bundler/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb +2 -1
  179. data/bundler/lib/bundler/vendor/thor/lib/thor/actions/directory.rb +2 -2
  180. data/bundler/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb +16 -8
  181. data/bundler/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +66 -18
  182. data/bundler/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +17 -15
  183. data/bundler/lib/bundler/vendor/thor/lib/thor/base.rb +55 -32
  184. data/bundler/lib/bundler/vendor/thor/lib/thor/command.rb +13 -11
  185. data/bundler/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +21 -1
  186. data/bundler/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb +7 -5
  187. data/bundler/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb +94 -63
  188. data/bundler/lib/bundler/vendor/thor/lib/thor/error.rb +3 -3
  189. data/bundler/lib/bundler/vendor/thor/lib/thor/group.rb +13 -13
  190. data/bundler/lib/bundler/vendor/thor/lib/thor/invocation.rb +4 -5
  191. data/bundler/lib/bundler/vendor/thor/lib/thor/line_editor/basic.rb +2 -0
  192. data/bundler/lib/bundler/vendor/thor/lib/thor/parser/argument.rb +4 -7
  193. data/bundler/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb +16 -16
  194. data/bundler/lib/bundler/vendor/thor/lib/thor/parser/option.rb +42 -21
  195. data/bundler/lib/bundler/vendor/thor/lib/thor/parser/options.rb +13 -10
  196. data/bundler/lib/bundler/vendor/thor/lib/thor/runner.rb +31 -29
  197. data/bundler/lib/bundler/vendor/thor/lib/thor/shell.rb +1 -1
  198. data/bundler/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +49 -33
  199. data/bundler/lib/bundler/vendor/thor/lib/thor/shell/color.rb +1 -1
  200. data/bundler/lib/bundler/vendor/thor/lib/thor/shell/html.rb +4 -4
  201. data/bundler/lib/bundler/vendor/thor/lib/thor/util.rb +8 -7
  202. data/bundler/lib/bundler/vendor/thor/lib/thor/version.rb +1 -1
  203. data/bundler/lib/bundler/vendored_fileutils.rb +9 -0
  204. data/bundler/lib/bundler/vendored_molinillo.rb +1 -0
  205. data/bundler/lib/bundler/vendored_persistent.rb +35 -0
  206. data/bundler/lib/bundler/vendored_thor.rb +6 -2
  207. data/bundler/lib/bundler/version.rb +19 -2
  208. data/bundler/lib/bundler/version_ranges.rb +76 -0
  209. data/bundler/lib/bundler/vlad.rb +5 -0
  210. data/bundler/lib/bundler/worker.rb +3 -1
  211. data/bundler/lib/bundler/yaml_serializer.rb +3 -3
  212. data/bundler/man/bundle-add.ronn +29 -0
  213. data/bundler/man/bundle-binstubs.ronn +14 -0
  214. data/bundler/man/bundle-check.ronn +26 -0
  215. data/bundler/man/bundle-clean.ronn +18 -0
  216. data/bundler/man/bundle-config.ronn +180 -60
  217. data/bundler/man/bundle-exec.ronn +7 -0
  218. data/bundler/man/bundle-gem.ronn +3 -2
  219. data/bundler/man/bundle-info.ronn +17 -0
  220. data/bundler/man/bundle-init.ronn +18 -0
  221. data/bundler/man/bundle-inject.ronn +22 -0
  222. data/bundler/man/bundle-install.ronn +32 -32
  223. data/bundler/man/bundle-list.ronn +15 -0
  224. data/bundler/man/bundle-open.ronn +19 -0
  225. data/bundler/man/bundle-outdated.ronn +1 -1
  226. data/bundler/man/bundle-package.ronn +5 -0
  227. data/bundler/man/bundle-pristine.ronn +34 -0
  228. data/bundler/man/bundle-show.ronn +20 -0
  229. data/bundler/man/bundle-update.ronn +6 -3
  230. data/bundler/man/bundle-viz.ronn +30 -0
  231. data/bundler/man/bundle.ronn +11 -20
  232. data/bundler/man/gemfile.5.ronn +55 -64
  233. data/lib/rubygems.rb +79 -36
  234. data/lib/rubygems/basic_specification.rb +8 -4
  235. data/lib/rubygems/bundler_version_finder.rb +112 -0
  236. data/lib/rubygems/command.rb +1 -1
  237. data/lib/rubygems/command_manager.rb +3 -1
  238. data/lib/rubygems/commands/cert_command.rb +31 -6
  239. data/lib/rubygems/commands/cleanup_command.rb +1 -1
  240. data/lib/rubygems/commands/help_command.rb +1 -1
  241. data/lib/rubygems/commands/owner_command.rb +3 -1
  242. data/lib/rubygems/commands/pristine_command.rb +11 -8
  243. data/lib/rubygems/commands/push_command.rb +2 -1
  244. data/lib/rubygems/commands/query_command.rb +13 -14
  245. data/lib/rubygems/commands/setup_command.rb +124 -69
  246. data/lib/rubygems/commands/signin_command.rb +33 -0
  247. data/lib/rubygems/commands/signout_command.rb +33 -0
  248. data/lib/rubygems/commands/uninstall_command.rb +4 -3
  249. data/lib/rubygems/commands/unpack_command.rb +16 -4
  250. data/lib/rubygems/commands/update_command.rb +1 -1
  251. data/lib/rubygems/commands/which_command.rb +1 -1
  252. data/lib/rubygems/commands/yank_command.rb +4 -11
  253. data/lib/rubygems/config_file.rb +13 -24
  254. data/lib/rubygems/core_ext/kernel_require.rb +10 -9
  255. data/lib/rubygems/dependency.rb +2 -0
  256. data/lib/rubygems/dependency_installer.rb +4 -0
  257. data/lib/rubygems/errors.rb +3 -0
  258. data/lib/rubygems/exceptions.rb +6 -0
  259. data/lib/rubygems/ext/builder.rb +1 -1
  260. data/lib/rubygems/ext/ext_conf_builder.rb +2 -4
  261. data/lib/rubygems/ext/rake_builder.rb +1 -1
  262. data/lib/rubygems/gem_runner.rb +5 -1
  263. data/lib/rubygems/install_update_options.rb +5 -28
  264. data/lib/rubygems/installer.rb +12 -7
  265. data/lib/rubygems/installer_test_case.rb +6 -3
  266. data/lib/rubygems/package/old.rb +1 -1
  267. data/lib/rubygems/request.rb +1 -1
  268. data/lib/rubygems/request_set.rb +20 -3
  269. data/lib/rubygems/request_set/gem_dependency_api.rb +3 -3
  270. data/lib/rubygems/requirement.rb +5 -1
  271. data/lib/rubygems/resolver.rb +24 -3
  272. data/lib/rubygems/resolver/installer_set.rb +4 -6
  273. data/lib/rubygems/safe_yaml.rb +4 -1
  274. data/lib/rubygems/security.rb +10 -3
  275. data/lib/rubygems/security_option.rb +43 -0
  276. data/lib/rubygems/server.rb +4 -12
  277. data/lib/rubygems/source.rb +7 -4
  278. data/lib/rubygems/source/git.rb +2 -1
  279. data/lib/rubygems/source/local.rb +38 -35
  280. data/lib/rubygems/source/lock.rb +4 -1
  281. data/lib/rubygems/source_local.rb +3 -1
  282. data/lib/rubygems/source_specific_file.rb +3 -2
  283. data/lib/rubygems/spec_fetcher.rb +7 -3
  284. data/lib/rubygems/specification.rb +281 -231
  285. data/lib/rubygems/stub_specification.rb +2 -3
  286. data/lib/rubygems/test_case.rb +14 -1
  287. data/lib/rubygems/user_interaction.rb +15 -13
  288. data/lib/rubygems/util.rb +6 -17
  289. data/lib/rubygems/version.rb +17 -3
  290. data/lib/rubygems/version_option.rb +6 -1
  291. data/setup.rb +1 -1
  292. data/test/rubygems/private3072_key.pem +40 -0
  293. data/test/rubygems/public3072_cert.pem +25 -0
  294. data/test/rubygems/test_config.rb +1 -1
  295. data/test/rubygems/test_gem.rb +72 -14
  296. data/test/rubygems/test_gem_bundler_version_finder.rb +125 -0
  297. data/test/rubygems/test_gem_command.rb +1 -1
  298. data/test/rubygems/test_gem_commands_build_command.rb +27 -1
  299. data/test/rubygems/test_gem_commands_cert_command.rb +64 -0
  300. data/test/rubygems/test_gem_commands_install_command.rb +35 -2
  301. data/test/rubygems/test_gem_commands_pristine_command.rb +1 -1
  302. data/test/rubygems/test_gem_commands_query_command.rb +19 -0
  303. data/test/rubygems/test_gem_commands_setup_command.rb +17 -0
  304. data/test/rubygems/test_gem_commands_signin_command.rb +95 -0
  305. data/test/rubygems/test_gem_commands_signout_command.rb +37 -0
  306. data/test/rubygems/test_gem_commands_sources_command.rb +1 -1
  307. data/test/rubygems/test_gem_commands_uninstall_command.rb +12 -0
  308. data/test/rubygems/test_gem_commands_update_command.rb +1 -1
  309. data/test/rubygems/test_gem_commands_which_command.rb +3 -3
  310. data/test/rubygems/test_gem_dependency.rb +28 -0
  311. data/test/rubygems/test_gem_ext_builder.rb +2 -2
  312. data/test/rubygems/test_gem_ext_rake_builder.rb +2 -2
  313. data/test/rubygems/test_gem_install_update_options.rb +2 -1
  314. data/test/rubygems/test_gem_installer.rb +29 -27
  315. data/test/rubygems/test_gem_package.rb +5 -5
  316. data/test/rubygems/test_gem_remote_fetcher.rb +2 -2
  317. data/test/rubygems/test_gem_request_set_gem_dependency_api.rb +3 -3
  318. data/test/rubygems/test_gem_requirement.rb +6 -0
  319. data/test/rubygems/test_gem_resolver.rb +26 -0
  320. data/test/rubygems/test_gem_resolver_conflict.rb +1 -1
  321. data/test/rubygems/test_gem_security.rb +5 -0
  322. data/test/rubygems/test_gem_security_policy.rb +24 -24
  323. data/test/rubygems/test_gem_security_signer.rb +6 -6
  324. data/test/rubygems/test_gem_security_trust_dir.rb +2 -2
  325. data/test/rubygems/test_gem_server.rb +18 -1
  326. data/test/rubygems/test_gem_source.rb +9 -0
  327. data/test/rubygems/test_gem_spec_fetcher.rb +20 -0
  328. data/test/rubygems/test_gem_specification.rb +85 -10
  329. data/test/rubygems/test_gem_stream_ui.rb +6 -6
  330. data/test/rubygems/test_gem_stub_specification.rb +19 -1
  331. data/test/rubygems/test_gem_util.rb +1 -0
  332. data/test/rubygems/test_gem_version.rb +28 -7
  333. data/test/rubygems/test_gem_version_option.rb +15 -0
  334. data/test/rubygems/test_kernel.rb +30 -0
  335. data/test/rubygems/test_require.rb +44 -0
  336. metadata +47 -46
  337. data/README.rdoc +0 -54
  338. data/bundler/DEVELOPMENT.md +0 -150
  339. data/bundler/ISSUES.md +0 -117
  340. data/bundler/lib/bundler/postit_trampoline.rb +0 -73
  341. data/bundler/lib/bundler/vendor/postit/lib/postit.rb +0 -15
  342. data/bundler/lib/bundler/vendor/postit/lib/postit/environment.rb +0 -44
  343. data/bundler/lib/bundler/vendor/postit/lib/postit/installer.rb +0 -28
  344. data/bundler/lib/bundler/vendor/postit/lib/postit/parser.rb +0 -21
  345. data/bundler/lib/bundler/vendor/postit/lib/postit/setup.rb +0 -12
  346. data/bundler/lib/bundler/vendor/postit/lib/postit/version.rb +0 -3
  347. data/bundler/man/index.txt +0 -8
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'bundler/vendor/molinillo/lib/molinillo/compatibility'
2
4
  require 'bundler/vendor/molinillo/lib/molinillo/gem_metadata'
3
5
  require 'bundler/vendor/molinillo/lib/molinillo/errors'
4
6
  require 'bundler/vendor/molinillo/lib/molinillo/resolver'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler::Molinillo
4
+ # Hacks needed for old Ruby versions.
5
+ module Compatibility
6
+ module_function
7
+
8
+ if [].respond_to?(:flat_map)
9
+ # Flat map
10
+ # @param [Enumerable] enum an enumerable object
11
+ # @block the block to flat-map with
12
+ # @return The enum, flat-mapped
13
+ def flat_map(enum, &blk)
14
+ enum.flat_map(&blk)
15
+ end
16
+ else
17
+ # Flat map
18
+ # @param [Enumerable] enum an enumerable object
19
+ # @block the block to flat-map with
20
+ # @return The enum, flat-mapped
21
+ def flat_map(enum, &blk)
22
+ enum.map(&blk).flatten(1)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # @!visibility private
4
5
  module Delegates
@@ -45,6 +46,12 @@ module Bundler::Molinillo
45
46
  current_state = state || Bundler::Molinillo::ResolutionState.empty
46
47
  current_state.conflicts
47
48
  end
49
+
50
+ # (see Bundler::Molinillo::ResolutionState#unused_unwind_options)
51
+ def unused_unwind_options
52
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
53
+ current_state.unused_unwind_options
54
+ end
48
55
  end
49
56
  end
50
57
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  module Delegates
4
5
  # Delegates all {Bundler::Molinillo::SpecificationProvider} methods to a
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'set'
3
4
  require 'tsort'
4
5
 
@@ -98,18 +99,27 @@ module Bundler::Molinillo
98
99
  "#{self.class}:#{vertices.values.inspect}"
99
100
  end
100
101
 
102
+ # @param [Hash] options options for dot output.
101
103
  # @return [String] Returns a dot format representation of the graph
102
- def to_dot
104
+ def to_dot(options = {})
105
+ edge_label = options.delete(:edge_label)
106
+ raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty?
107
+
103
108
  dot_vertices = []
104
109
  dot_edges = []
105
110
  vertices.each do |n, v|
106
111
  dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]"
107
112
  v.outgoing_edges.each do |e|
108
- dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=\"#{e.requirement}\"]"
113
+ label = edge_label ? edge_label.call(e) : e.requirement
114
+ dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]"
109
115
  end
110
116
  end
117
+
118
+ dot_vertices.uniq!
111
119
  dot_vertices.sort!
120
+ dot_edges.uniq!
112
121
  dot_edges.sort!
122
+
113
123
  dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}')
114
124
  dot.join("\n")
115
125
  end
@@ -123,7 +133,8 @@ module Bundler::Molinillo
123
133
  vertices.each do |name, vertex|
124
134
  other_vertex = other.vertex_named(name)
125
135
  return false unless other_vertex
126
- return false unless other_vertex.successors.map(&:name).to_set == vertex.successors.map(&:name).to_set
136
+ return false unless vertex.payload == other_vertex.payload
137
+ return false unless other_vertex.successors.to_set == vertex.successors.to_set
127
138
  end
128
139
  end
129
140
 
@@ -137,8 +148,8 @@ module Bundler::Molinillo
137
148
  vertex = add_vertex(name, payload, root)
138
149
  vertex.explicit_requirements << requirement if root
139
150
  parent_names.each do |parent_name|
140
- parent_node = vertex_named(parent_name)
141
- add_edge(parent_node, vertex, requirement)
151
+ parent_vertex = vertex_named(parent_name)
152
+ add_edge(parent_vertex, vertex, requirement)
142
153
  end
143
154
  vertex
144
155
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  class DependencyGraph
4
5
  # An action that modifies a {DependencyGraph} that is reversible.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -23,8 +24,8 @@ module Bundler::Molinillo
23
24
  # (see Action#down)
24
25
  def down(graph)
25
26
  edge = make_edge(graph)
26
- edge.origin.outgoing_edges.delete(edge)
27
- edge.destination.incoming_edges.delete(edge)
27
+ delete_first(edge.origin.outgoing_edges, edge)
28
+ delete_first(edge.destination.incoming_edges, edge)
28
29
  end
29
30
 
30
31
  # @!group AddEdgeNoCircular
@@ -53,6 +54,13 @@ module Bundler::Molinillo
53
54
  @destination = destination
54
55
  @requirement = requirement
55
56
  end
57
+
58
+ private
59
+
60
+ def delete_first(array, item)
61
+ return unless index = array.index(item)
62
+ array.delete_at(index)
63
+ end
56
64
  end
57
65
  end
58
66
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular'
3
4
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex'
4
5
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  class DependencyGraph
4
5
  # A vertex in a {DependencyGraph} that encapsulates a {#name} and a
@@ -10,7 +11,7 @@ module Bundler::Molinillo
10
11
  # @return [Object] the payload the vertex holds
11
12
  attr_accessor :payload
12
13
 
13
- # @return [Arrary<Object>] the explicit requirements that required
14
+ # @return [Array<Object>] the explicit requirements that required
14
15
  # this vertex
15
16
  attr_reader :explicit_requirements
16
17
 
@@ -32,7 +33,7 @@ module Bundler::Molinillo
32
33
  # @return [Array<Object>] all of the requirements that required
33
34
  # this vertex
34
35
  def requirements
35
- incoming_edges.map(&:requirement) + explicit_requirements
36
+ (incoming_edges.map(&:requirement) + explicit_requirements).uniq
36
37
  end
37
38
 
38
39
  # @return [Array<Edge>] the edges of {#graph} that have `self` as their
@@ -53,7 +54,7 @@ module Bundler::Molinillo
53
54
  # {#descendent?}
54
55
  def recursive_predecessors
55
56
  vertices = predecessors
56
- vertices += vertices.map(&:recursive_predecessors).flatten(1)
57
+ vertices += Compatibility.flat_map(vertices, &:recursive_predecessors)
57
58
  vertices.uniq!
58
59
  vertices
59
60
  end
@@ -68,7 +69,7 @@ module Bundler::Molinillo
68
69
  # {#ancestor?}
69
70
  def recursive_successors
70
71
  vertices = successors
71
- vertices += vertices.map(&:recursive_successors).flatten(1)
72
+ vertices += Compatibility.flat_map(vertices, &:recursive_successors)
72
73
  vertices.uniq!
73
74
  vertices
74
75
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # An error that occurred during the resolution process
4
5
  class ResolverError < StandardError; end
@@ -41,11 +42,11 @@ module Bundler::Molinillo
41
42
  attr_reader :dependencies
42
43
 
43
44
  # Initializes a new error with the given circular vertices.
44
- # @param [Array<DependencyGraph::Vertex>] nodes the nodes in the dependency
45
+ # @param [Array<DependencyGraph::Vertex>] vertices the vertices in the dependency
45
46
  # that caused the error
46
- def initialize(nodes)
47
- super "There is a circular dependency between #{nodes.map(&:name).join(' and ')}"
48
- @dependencies = nodes.map(&:payload).to_set
47
+ def initialize(vertices)
48
+ super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}"
49
+ @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set
49
50
  end
50
51
  end
51
52
 
@@ -55,11 +56,16 @@ module Bundler::Molinillo
55
56
  # resolution to fail
56
57
  attr_reader :conflicts
57
58
 
59
+ # @return [SpecificationProvider] the specification provider used during
60
+ # resolution
61
+ attr_reader :specification_provider
62
+
58
63
  # Initializes a new error with the given version conflicts.
59
64
  # @param [{String => Resolution::Conflict}] conflicts see {#conflicts}
60
- def initialize(conflicts)
65
+ # @param [SpecificationProvider] specification_provider see {#specification_provider}
66
+ def initialize(conflicts, specification_provider)
61
67
  pairs = []
62
- conflicts.values.flatten.map(&:requirements).flatten.each do |conflicting|
68
+ Compatibility.flat_map(conflicts.values.flatten, &:requirements).each do |conflicting|
63
69
  conflicting.each do |source, conflict_requirements|
64
70
  conflict_requirements.each do |c|
65
71
  pairs << [c, source]
@@ -69,7 +75,64 @@ module Bundler::Molinillo
69
75
 
70
76
  super "Unable to satisfy the following requirements:\n\n" \
71
77
  "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}"
78
+
72
79
  @conflicts = conflicts
80
+ @specification_provider = specification_provider
81
+ end
82
+
83
+ require 'bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider'
84
+ include Delegates::SpecificationProvider
85
+
86
+ # @return [String] An error message that includes requirement trees,
87
+ # which is much more detailed & customizable than the default message
88
+ # @param [Hash] opts the options to create a message with.
89
+ # @option opts [String] :solver_name The user-facing name of the solver
90
+ # @option opts [String] :possibility_type The generic name of a possibility
91
+ # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees
92
+ # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements
93
+ # @option opts [Proc] :additional_message_for_conflict A proc that appends additional
94
+ # messages for each conflict
95
+ # @option opts [Proc] :version_for_spec A proc that returns the version number for a
96
+ # possibility
97
+ def message_with_trees(opts = {})
98
+ solver_name = opts.delete(:solver_name) { self.class.name.split('::').first }
99
+ possibility_type = opts.delete(:possibility_type) { 'possibility named' }
100
+ reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } }
101
+ printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } }
102
+ additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} }
103
+ version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) }
104
+
105
+ conflicts.sort.reduce(''.dup) do |o, (name, conflict)|
106
+ o << %(\n#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":\n)
107
+ if conflict.locked_requirement
108
+ o << %( In snapshot (#{name_for_locking_dependency_source}):\n)
109
+ o << %( #{printable_requirement.call(conflict.locked_requirement)}\n)
110
+ o << %(\n)
111
+ end
112
+ o << %( In #{name_for_explicit_dependency_source}:\n)
113
+ trees = reduce_trees.call(conflict.requirement_trees)
114
+
115
+ o << trees.map do |tree|
116
+ t = ''.dup
117
+ depth = 2
118
+ tree.each do |req|
119
+ t << ' ' * depth << req.to_s
120
+ unless tree.last == req
121
+ if spec = conflict.activated_by_name[name_for(req)]
122
+ t << %( was resolved to #{version_for_spec.call(spec)}, which)
123
+ end
124
+ t << %( depends on)
125
+ end
126
+ t << %(\n)
127
+ depth += 1
128
+ end
129
+ t
130
+ end.join("\n")
131
+
132
+ additional_message_for_conflict.call(o, name, conflict)
133
+
134
+ o
135
+ end.strip
73
136
  end
74
137
  end
75
138
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # The version of Bundler::Molinillo.
4
- VERSION = '0.5.5'.freeze
5
+ VERSION = '0.6.4'.freeze
5
6
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # Provides information about specifcations and dependencies to the resolver,
4
5
  # allowing the {Resolver} class to remain generic while still providing power
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # Conveys information about the resolution process to a user.
4
5
  module UI
@@ -48,7 +49,8 @@ module Bundler::Molinillo
48
49
  if debug?
49
50
  debug_info = yield
50
51
  debug_info = debug_info.inspect unless debug_info.is_a?(String)
51
- output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
52
+ debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" }
53
+ output.puts debug_info
52
54
  end
53
55
  end
54
56
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  class Resolver
4
5
  # A specific resolution from a given {Resolver}
@@ -8,22 +9,125 @@ module Bundler::Molinillo
8
9
  # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict
9
10
  # @attr [Object, nil] existing the existing spec that was in conflict with
10
11
  # the {#possibility}
11
- # @attr [Object] possibility the spec that was unable to be activated due
12
- # to a conflict
12
+ # @attr [Object] possibility_set the set of specs that was unable to be
13
+ # activated due to a conflict.
13
14
  # @attr [Object] locked_requirement the relevant locking requirement.
14
15
  # @attr [Array<Array<Object>>] requirement_trees the different requirement
15
16
  # trees that led to every requirement for the conflicting name.
16
17
  # @attr [{String=>Object}] activated_by_name the already-activated specs.
18
+ # @attr [Object] underlying_error an error that has occurred during resolution, and
19
+ # will be raised at the end of it if no resolution is found.
17
20
  Conflict = Struct.new(
18
21
  :requirement,
19
22
  :requirements,
20
23
  :existing,
21
- :possibility,
24
+ :possibility_set,
22
25
  :locked_requirement,
23
26
  :requirement_trees,
24
- :activated_by_name
27
+ :activated_by_name,
28
+ :underlying_error
29
+ )
30
+
31
+ class Conflict
32
+ # @return [Object] a spec that was unable to be activated due to a conflict
33
+ def possibility
34
+ possibility_set && possibility_set.latest_version
35
+ end
36
+ end
37
+
38
+ # A collection of possibility states that share the same dependencies
39
+ # @attr [Array] dependencies the dependencies for this set of possibilities
40
+ # @attr [Array] possibilities the possibilities
41
+ PossibilitySet = Struct.new(:dependencies, :possibilities)
42
+
43
+ class PossibilitySet
44
+ # String representation of the possibility set, for debugging
45
+ def to_s
46
+ "[#{possibilities.join(', ')}]"
47
+ end
48
+
49
+ # @return [Object] most up-to-date dependency in the possibility set
50
+ def latest_version
51
+ possibilities.last
52
+ end
53
+ end
54
+
55
+ # Details of the state to unwind to when a conflict occurs, and the cause of the unwind
56
+ # @attr [Integer] state_index the index of the state to unwind to
57
+ # @attr [Object] state_requirement the requirement of the state we're unwinding to
58
+ # @attr [Array] requirement_tree for the requirement we're relaxing
59
+ # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict
60
+ # @attr [Array] requirement_trees for the conflict
61
+ # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind
62
+ UnwindDetails = Struct.new(
63
+ :state_index,
64
+ :state_requirement,
65
+ :requirement_tree,
66
+ :conflicting_requirements,
67
+ :requirement_trees,
68
+ :requirements_unwound_to_instead
25
69
  )
26
70
 
71
+ class UnwindDetails
72
+ include Comparable
73
+
74
+ # We compare UnwindDetails when choosing which state to unwind to. If
75
+ # two options have the same state_index we prefer the one most
76
+ # removed from a requirement that caused the conflict. Both options
77
+ # would unwind to the same state, but a `grandparent` option will
78
+ # filter out fewer of its possibilities after doing so - where a state
79
+ # is both a `parent` and a `grandparent` to requirements that have
80
+ # caused a conflict this is the correct behaviour.
81
+ # @param [UnwindDetail] other UnwindDetail to be compared
82
+ # @return [Integer] integer specifying ordering
83
+ def <=>(other)
84
+ if state_index > other.state_index
85
+ 1
86
+ elsif state_index == other.state_index
87
+ reversed_requirement_tree_index <=> other.reversed_requirement_tree_index
88
+ else
89
+ -1
90
+ end
91
+ end
92
+
93
+ # @return [Integer] index of state requirement in reversed requirement tree
94
+ # (the conflicting requirement itself will be at position 0)
95
+ def reversed_requirement_tree_index
96
+ @reversed_requirement_tree_index ||=
97
+ if state_requirement
98
+ requirement_tree.reverse.index(state_requirement)
99
+ else
100
+ 999_999
101
+ end
102
+ end
103
+
104
+ # @return [Boolean] where the requirement of the state we're unwinding
105
+ # to directly caused the conflict. Note: in this case, it is
106
+ # impossible for the state we're unwinding to to be a parent of
107
+ # any of the other conflicting requirements (or we would have
108
+ # circularity)
109
+ def unwinding_to_primary_requirement?
110
+ requirement_tree.last == state_requirement
111
+ end
112
+
113
+ # @return [Array] array of sub-dependencies to avoid when choosing a
114
+ # new possibility for the state we've unwound to. Only relevant for
115
+ # non-primary unwinds
116
+ def sub_dependencies_to_avoid
117
+ @requirements_to_avoid ||=
118
+ requirement_trees.map do |tree|
119
+ index = tree.index(state_requirement)
120
+ tree[index + 1] if index
121
+ end.compact
122
+ end
123
+
124
+ # @return [Array] array of all the requirements that led to the need for
125
+ # this unwind
126
+ def all_requirements
127
+ @all_requirements ||= requirement_trees.flatten(1)
128
+ end
129
+ end
130
+
27
131
  # @return [SpecificationProvider] the provider that knows about
28
132
  # dependencies, requirements, specifications, versions, etc.
29
133
  attr_reader :specification_provider
@@ -52,7 +156,7 @@ module Bundler::Molinillo
52
156
  @base = base
53
157
  @states = []
54
158
  @iteration_counter = 0
55
- @parent_of = {}
159
+ @parents_of = Hash.new { |h, k| h[k] = [] }
56
160
  end
57
161
 
58
162
  # Resolves the {#original_requested} dependencies into a full dependency
@@ -64,7 +168,7 @@ module Bundler::Molinillo
64
168
  start_resolution
65
169
 
66
170
  while state
67
- break unless state.requirements.any? || state.requirement
171
+ break if !state.requirement && state.requirements.empty?
68
172
  indicate_progress
69
173
  if state.respond_to?(:pop_possibility_state) # DependencyState
70
174
  debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
@@ -78,7 +182,7 @@ module Bundler::Molinillo
78
182
  process_topmost_state
79
183
  end
80
184
 
81
- activated.freeze
185
+ resolve_activated_specs
82
186
  ensure
83
187
  end_resolution
84
188
  end
@@ -105,10 +209,23 @@ module Bundler::Molinillo
105
209
 
106
210
  handle_missing_or_push_dependency_state(initial_state)
107
211
 
108
- debug { "Starting resolution (#{@started_at})" }
212
+ debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" }
109
213
  resolver_ui.before_resolution
110
214
  end
111
215
 
216
+ def resolve_activated_specs
217
+ activated.vertices.each do |_, vertex|
218
+ next unless vertex.payload
219
+
220
+ latest_version = vertex.payload.possibilities.reverse_each.find do |possibility|
221
+ vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) }
222
+ end
223
+
224
+ activated.set_payload(vertex.name, latest_version)
225
+ end
226
+ activated.freeze
227
+ end
228
+
112
229
  # Ends the resolution process
113
230
  # @return [void]
114
231
  def end_resolution
@@ -136,9 +253,12 @@ module Bundler::Molinillo
136
253
  if possibility
137
254
  attempt_to_activate
138
255
  else
139
- create_conflict if state.is_a? PossibilityState
140
- unwind_for_conflict until possibility && state.is_a?(DependencyState)
256
+ create_conflict
257
+ unwind_for_conflict
141
258
  end
259
+ rescue CircularDependencyError => underlying_error
260
+ create_conflict(underlying_error)
261
+ unwind_for_conflict
142
262
  end
143
263
 
144
264
  # @return [Object] the current possibility that the resolution is trying
@@ -158,7 +278,10 @@ module Bundler::Molinillo
158
278
  # @return [DependencyState] the initial state for the resolution
159
279
  def initial_state
160
280
  graph = DependencyGraph.new.tap do |dg|
161
- original_requested.each { |r| dg.add_vertex(name_for(r), nil, true).tap { |v| v.explicit_requirements << r } }
281
+ original_requested.each do |requested|
282
+ vertex = dg.add_vertex(name_for(requested), nil, true)
283
+ vertex.explicit_requirements << requested
284
+ end
162
285
  dg.tag(:initial_state)
163
286
  end
164
287
 
@@ -169,52 +292,287 @@ module Bundler::Molinillo
169
292
  requirements,
170
293
  graph,
171
294
  initial_requirement,
172
- initial_requirement && search_for(initial_requirement),
295
+ possibilities_for_requirement(initial_requirement, graph),
173
296
  0,
174
- {}
297
+ {},
298
+ []
175
299
  )
176
300
  end
177
301
 
178
302
  # Unwinds the states stack because a conflict has been encountered
179
303
  # @return [void]
180
304
  def unwind_for_conflict
181
- debug(depth) { "Unwinding for conflict: #{requirement}" }
305
+ details_for_unwind = build_details_for_unwind
306
+ unwind_options = unused_unwind_options
307
+ debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" }
182
308
  conflicts.tap do |c|
183
- sliced_states = states.slice!((state_index_for_unwind + 1)..-1)
184
- raise VersionConflict.new(c) unless state
309
+ sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1)
310
+ raise_error_unless_state(c)
185
311
  activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
186
312
  state.conflicts = c
313
+ state.unused_unwind_options = unwind_options
314
+ filter_possibilities_after_unwind(details_for_unwind)
187
315
  index = states.size - 1
188
- @parent_of.reject! { |_, i| i >= index }
189
- end
190
- end
191
-
192
- # @return [Integer] The index to which the resolution should unwind in the
193
- # case of conflict.
194
- def state_index_for_unwind
195
- current_requirement = requirement
196
- existing_requirement = requirement_for_existing_name(name)
197
- index = -1
198
- [current_requirement, existing_requirement].each do |r|
199
- until r.nil?
200
- current_state = find_state_for(r)
201
- if state_any?(current_state)
202
- current_index = states.index(current_state)
203
- index = current_index if current_index > index
204
- break
316
+ @parents_of.each { |_, a| a.reject! { |i| i >= index } }
317
+ state.unused_unwind_options.reject! { |uw| uw.state_index >= index }
318
+ end
319
+ end
320
+
321
+ # Raises a VersionConflict error, or any underlying error, if there is no
322
+ # current state
323
+ # @return [void]
324
+ def raise_error_unless_state(conflicts)
325
+ return if state
326
+
327
+ error = conflicts.values.map(&:underlying_error).compact.first
328
+ raise error || VersionConflict.new(conflicts, specification_provider)
329
+ end
330
+
331
+ # @return [UnwindDetails] Details of the nearest index to which we could unwind
332
+ def build_details_for_unwind
333
+ # Get the possible unwinds for the current conflict
334
+ current_conflict = conflicts[name]
335
+ binding_requirements = binding_requirements_for_conflict(current_conflict)
336
+ unwind_details = unwind_options_for_requirements(binding_requirements)
337
+
338
+ last_detail_for_current_unwind = unwind_details.sort.last
339
+ current_detail = last_detail_for_current_unwind
340
+
341
+ # Look for past conflicts that could be unwound to affect the
342
+ # requirement tree for the current conflict
343
+ relevant_unused_unwinds = unused_unwind_options.select do |alternative|
344
+ intersecting_requirements =
345
+ last_detail_for_current_unwind.all_requirements &
346
+ alternative.requirements_unwound_to_instead
347
+ next if intersecting_requirements.empty?
348
+ # Find the highest index unwind whilst looping through
349
+ current_detail = alternative if alternative > current_detail
350
+ alternative
351
+ end
352
+
353
+ # Add the current unwind options to the `unused_unwind_options` array.
354
+ # The "used" option will be filtered out during `unwind_for_conflict`.
355
+ state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 }
356
+
357
+ # Update the requirements_unwound_to_instead on any relevant unused unwinds
358
+ relevant_unused_unwinds.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
359
+ unwind_details.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
360
+
361
+ current_detail
362
+ end
363
+
364
+ # @param [Array<Object>] array of requirements that combine to create a conflict
365
+ # @return [Array<UnwindDetails>] array of UnwindDetails that have a chance
366
+ # of resolving the passed requirements
367
+ def unwind_options_for_requirements(binding_requirements)
368
+ unwind_details = []
369
+
370
+ trees = []
371
+ binding_requirements.reverse_each do |r|
372
+ partial_tree = [r]
373
+ trees << partial_tree
374
+ unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, [])
375
+
376
+ # If this requirement has alternative possibilities, check if any would
377
+ # satisfy the other requirements that created this conflict
378
+ requirement_state = find_state_for(r)
379
+ if conflict_fixing_possibilities?(requirement_state, binding_requirements)
380
+ unwind_details << UnwindDetails.new(
381
+ states.index(requirement_state),
382
+ r,
383
+ partial_tree,
384
+ binding_requirements,
385
+ trees,
386
+ []
387
+ )
388
+ end
389
+
390
+ # Next, look at the parent of this requirement, and check if the requirement
391
+ # could have been avoided if an alternative PossibilitySet had been chosen
392
+ parent_r = parent_of(r)
393
+ next if parent_r.nil?
394
+ partial_tree.unshift(parent_r)
395
+ requirement_state = find_state_for(parent_r)
396
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) }
397
+ unwind_details << UnwindDetails.new(
398
+ states.index(requirement_state),
399
+ parent_r,
400
+ partial_tree,
401
+ binding_requirements,
402
+ trees,
403
+ []
404
+ )
405
+ end
406
+
407
+ # Finally, look at the grandparent and up of this requirement, looking
408
+ # for any possibilities that wouldn't create their parent requirement
409
+ grandparent_r = parent_of(parent_r)
410
+ until grandparent_r.nil?
411
+ partial_tree.unshift(grandparent_r)
412
+ requirement_state = find_state_for(grandparent_r)
413
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) }
414
+ unwind_details << UnwindDetails.new(
415
+ states.index(requirement_state),
416
+ grandparent_r,
417
+ partial_tree,
418
+ binding_requirements,
419
+ trees,
420
+ []
421
+ )
422
+ end
423
+ parent_r = grandparent_r
424
+ grandparent_r = parent_of(parent_r)
425
+ end
426
+ end
427
+
428
+ unwind_details
429
+ end
430
+
431
+ # @param [DependencyState] state
432
+ # @param [Array] array of requirements
433
+ # @return [Boolean] whether or not the given state has any possibilities
434
+ # that could satisfy the given requirements
435
+ def conflict_fixing_possibilities?(state, binding_requirements)
436
+ return false unless state
437
+
438
+ state.possibilities.any? do |possibility_set|
439
+ possibility_set.possibilities.any? do |poss|
440
+ possibility_satisfies_requirements?(poss, binding_requirements)
441
+ end
442
+ end
443
+ end
444
+
445
+ # Filter's a state's possibilities to remove any that would not fix the
446
+ # conflict we've just rewound from
447
+ # @param [UnwindDetails] details of the conflict just unwound from
448
+ # @return [void]
449
+ def filter_possibilities_after_unwind(unwind_details)
450
+ return unless state && !state.possibilities.empty?
451
+
452
+ if unwind_details.unwinding_to_primary_requirement?
453
+ filter_possibilities_for_primary_unwind(unwind_details)
454
+ else
455
+ filter_possibilities_for_parent_unwind(unwind_details)
456
+ end
457
+ end
458
+
459
+ # Filter's a state's possibilities to remove any that would not satisfy
460
+ # the requirements in the conflict we've just rewound from
461
+ # @param [UnwindDetails] details of the conflict just unwound from
462
+ # @return [void]
463
+ def filter_possibilities_for_primary_unwind(unwind_details)
464
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
465
+ unwinds_to_state << unwind_details
466
+ unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements)
467
+
468
+ state.possibilities.reject! do |possibility_set|
469
+ possibility_set.possibilities.none? do |poss|
470
+ unwind_requirement_sets.any? do |requirements|
471
+ possibility_satisfies_requirements?(poss, requirements)
472
+ end
473
+ end
474
+ end
475
+ end
476
+
477
+ # @param [Object] possibility a single possibility
478
+ # @param [Array] requirements an array of requirements
479
+ # @return [Boolean] whether the possibility satisfies all of the
480
+ # given requirements
481
+ def possibility_satisfies_requirements?(possibility, requirements)
482
+ name = name_for(possibility)
483
+
484
+ activated.tag(:swap)
485
+ activated.set_payload(name, possibility) if activated.vertex_named(name)
486
+ satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) }
487
+ activated.rewind_to(:swap)
488
+
489
+ satisfied
490
+ end
491
+
492
+ # Filter's a state's possibilities to remove any that would (eventually)
493
+ # create a requirement in the conflict we've just rewound from
494
+ # @param [UnwindDetails] details of the conflict just unwound from
495
+ # @return [void]
496
+ def filter_possibilities_for_parent_unwind(unwind_details)
497
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
498
+ unwinds_to_state << unwind_details
499
+
500
+ primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq
501
+ parent_unwinds = unwinds_to_state.uniq - primary_unwinds
502
+
503
+ allowed_possibility_sets = Compatibility.flat_map(primary_unwinds) do |unwind|
504
+ states[unwind.state_index].possibilities.select do |possibility_set|
505
+ possibility_set.possibilities.any? do |poss|
506
+ possibility_satisfies_requirements?(poss, unwind.conflicting_requirements)
205
507
  end
206
- r = parent_of(r)
207
508
  end
208
509
  end
209
510
 
210
- index
511
+ requirements_to_avoid = Compatibility.flat_map(parent_unwinds, &:sub_dependencies_to_avoid)
512
+
513
+ state.possibilities.reject! do |possibility_set|
514
+ !allowed_possibility_sets.include?(possibility_set) &&
515
+ (requirements_to_avoid - possibility_set.dependencies).empty?
516
+ end
517
+ end
518
+
519
+ # @param [Conflict] conflict
520
+ # @return [Array] minimal array of requirements that would cause the passed
521
+ # conflict to occur.
522
+ def binding_requirements_for_conflict(conflict)
523
+ return [conflict.requirement] if conflict.possibility.nil?
524
+
525
+ possible_binding_requirements = conflict.requirements.values.flatten(1).uniq
526
+
527
+ # When there’s a `CircularDependency` error the conflicting requirement
528
+ # (the one causing the circular) won’t be `conflict.requirement`
529
+ # (which won’t be for the right state, because we won’t have created it,
530
+ # because it’s circular).
531
+ # We need to make sure we have that requirement in the conflict’s list,
532
+ # otherwise we won’t be able to unwind properly, so we just return all
533
+ # the requirements for the conflict.
534
+ return possible_binding_requirements if conflict.underlying_error
535
+
536
+ possibilities = search_for(conflict.requirement)
537
+
538
+ # If all the requirements together don't filter out all possibilities,
539
+ # then the only two requirements we need to consider are the initial one
540
+ # (where the dependency's version was first chosen) and the last
541
+ if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities)
542
+ return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact
543
+ end
544
+
545
+ # Loop through the possible binding requirements, removing each one
546
+ # that doesn't bind. Use a `reverse_each` as we want the earliest set of
547
+ # binding requirements, and don't use `reject!` as we wish to refine the
548
+ # array *on each iteration*.
549
+ binding_requirements = possible_binding_requirements.dup
550
+ possible_binding_requirements.reverse_each do |req|
551
+ next if req == conflict.requirement
552
+ unless binding_requirement_in_set?(req, binding_requirements, possibilities)
553
+ binding_requirements -= [req]
554
+ end
555
+ end
556
+
557
+ binding_requirements
558
+ end
559
+
560
+ # @param [Object] requirement we wish to check
561
+ # @param [Array] array of requirements
562
+ # @param [Array] array of possibilities the requirements will be used to filter
563
+ # @return [Boolean] whether or not the given requirement is required to filter
564
+ # out all elements of the array of possibilities.
565
+ def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities)
566
+ possibilities.any? do |poss|
567
+ possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement])
568
+ end
211
569
  end
212
570
 
213
571
  # @return [Object] the requirement that led to `requirement` being added
214
572
  # to the list of requirements.
215
573
  def parent_of(requirement)
216
574
  return unless requirement
217
- return unless index = @parent_of[requirement]
575
+ return unless index = @parents_of[requirement].last
218
576
  return unless parent_state = @states[index]
219
577
  parent_state.requirement
220
578
  end
@@ -222,7 +580,8 @@ module Bundler::Molinillo
222
580
  # @return [Object] the requirement that led to a version of a possibility
223
581
  # with the given name being activated.
224
582
  def requirement_for_existing_name(name)
225
- return nil unless activated.vertex_named(name).payload
583
+ return nil unless vertex = activated.vertex_named(name)
584
+ return nil unless vertex.payload
226
585
  states.find { |s| s.name == name }.requirement
227
586
  end
228
587
 
@@ -230,18 +589,12 @@ module Bundler::Molinillo
230
589
  # `requirement`.
231
590
  def find_state_for(requirement)
232
591
  return nil unless requirement
233
- states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) }
234
- end
235
-
236
- # @return [Boolean] whether or not the given state has any possibilities
237
- # left.
238
- def state_any?(state)
239
- state && state.possibilities.any?
592
+ states.find { |i| requirement == i.requirement }
240
593
  end
241
594
 
242
595
  # @return [Conflict] a {Conflict} that reflects the failure to activate
243
596
  # the {#possibility} in conjunction with the current {#state}
244
- def create_conflict
597
+ def create_conflict(underlying_error = nil)
245
598
  vertex = activated.vertex_named(name)
246
599
  locked_requirement = locked_requirement_named(name)
247
600
 
@@ -250,18 +603,21 @@ module Bundler::Molinillo
250
603
  requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
251
604
  end
252
605
  requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
253
- vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) }
606
+ vertex.incoming_edges.each do |edge|
607
+ (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement)
608
+ end
254
609
 
255
610
  activated_by_name = {}
256
- activated.each { |v| activated_by_name[v.name] = v.payload if v.payload }
611
+ activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload }
257
612
  conflicts[name] = Conflict.new(
258
613
  requirement,
259
614
  requirements,
260
- vertex.payload,
615
+ vertex.payload && vertex.payload.latest_version,
261
616
  possibility,
262
617
  locked_requirement,
263
618
  requirement_trees,
264
- activated_by_name
619
+ activated_by_name,
620
+ underlying_error
265
621
  )
266
622
  end
267
623
 
@@ -311,97 +667,48 @@ module Bundler::Molinillo
311
667
  # @return [void]
312
668
  def attempt_to_activate
313
669
  debug(depth) { 'Attempting to activate ' + possibility.to_s }
314
- existing_node = activated.vertex_named(name)
315
- if existing_node.payload
316
- debug(depth) { "Found existing spec (#{existing_node.payload})" }
317
- attempt_to_activate_existing_spec(existing_node)
670
+ existing_vertex = activated.vertex_named(name)
671
+ if existing_vertex.payload
672
+ debug(depth) { "Found existing spec (#{existing_vertex.payload})" }
673
+ attempt_to_filter_existing_spec(existing_vertex)
318
674
  else
319
- attempt_to_activate_new_spec
675
+ latest = possibility.latest_version
676
+ # use reject!(!satisfied) for 1.8.7 compatibility
677
+ possibility.possibilities.reject! do |possibility|
678
+ !requirement_satisfied_by?(requirement, activated, possibility)
679
+ end
680
+ if possibility.latest_version.nil?
681
+ # ensure there's a possibility for better error messages
682
+ possibility.possibilities << latest if latest
683
+ create_conflict
684
+ unwind_for_conflict
685
+ else
686
+ activate_new_spec
687
+ end
320
688
  end
321
689
  end
322
690
 
323
- # Attempts to activate the current {#possibility} (given that it has
324
- # already been activated)
691
+ # Attempts to update the existing vertex's `PossibilitySet` with a filtered version
325
692
  # @return [void]
326
- def attempt_to_activate_existing_spec(existing_node)
327
- existing_spec = existing_node.payload
328
- if requirement_satisfied_by?(requirement, activated, existing_spec)
693
+ def attempt_to_filter_existing_spec(vertex)
694
+ filtered_set = filtered_possibility_set(vertex)
695
+ if !filtered_set.possibilities.empty?
696
+ activated.set_payload(name, filtered_set)
329
697
  new_requirements = requirements.dup
330
698
  push_state_for_requirements(new_requirements, false)
331
699
  else
332
- return if attempt_to_swap_possibility
333
700
  create_conflict
334
- debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" }
701
+ debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" }
335
702
  unwind_for_conflict
336
703
  end
337
704
  end
338
705
 
339
- # Attempts to swp the current {#possibility} with the already-activated
340
- # spec with the given name
341
- # @return [Boolean] Whether the possibility was swapped into {#activated}
342
- def attempt_to_swap_possibility
343
- activated.tag(:swap)
344
- vertex = activated.vertex_named(name)
345
- activated.set_payload(name, possibility)
346
- if !vertex.requirements.
347
- all? { |r| requirement_satisfied_by?(r, activated, possibility) } ||
348
- !new_spec_satisfied?
349
- activated.rewind_to(:swap)
350
- return
351
- end
352
- fixup_swapped_children(vertex)
353
- activate_spec
354
- end
355
-
356
- # Ensures there are no orphaned successors to the given {vertex}.
357
- # @param [DependencyGraph::Vertex] vertex the vertex to fix up.
358
- # @return [void]
359
- def fixup_swapped_children(vertex)
360
- payload = vertex.payload
361
- deps = dependencies_for(payload).group_by(&method(:name_for))
362
- vertex.outgoing_edges.each do |outgoing_edge|
363
- @parent_of[outgoing_edge.requirement] = states.size - 1
364
- succ = outgoing_edge.destination
365
- matching_deps = Array(deps[succ.name])
366
- if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex]
367
- debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" }
368
- succ.requirements.each { |r| @parent_of.delete(r) }
369
-
370
- removed_names = activated.detach_vertex_named(succ.name).map(&:name)
371
- requirements.delete_if do |r|
372
- # the only removed vertices are those with no other requirements,
373
- # so it's safe to delete only based upon name here
374
- removed_names.include?(name_for(r))
375
- end
376
- elsif !matching_deps.include?(outgoing_edge.requirement)
377
- activated.delete_edge(outgoing_edge)
378
- requirements.delete(outgoing_edge.requirement)
379
- end
380
- end
381
- end
382
-
383
- # Attempts to activate the current {#possibility} (given that it hasn't
384
- # already been activated)
385
- # @return [void]
386
- def attempt_to_activate_new_spec
387
- if new_spec_satisfied?
388
- activate_spec
389
- else
390
- create_conflict
391
- unwind_for_conflict
392
- end
393
- end
394
-
395
- # @return [Boolean] whether the current spec is satisfied as a new
396
- # possibility.
397
- def new_spec_satisfied?
398
- locked_requirement = locked_requirement_named(name)
399
- requested_spec_satisfied = requirement_satisfied_by?(requirement, activated, possibility)
400
- locked_spec_satisfied = !locked_requirement ||
401
- requirement_satisfied_by?(locked_requirement, activated, possibility)
402
- debug(depth) { 'Unsatisfied by requested spec' } unless requested_spec_satisfied
403
- debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
404
- requested_spec_satisfied && locked_spec_satisfied
706
+ # Generates a filtered version of the existing vertex's `PossibilitySet` using the
707
+ # current state's `requirement`
708
+ # @param [Object] existing vertex
709
+ # @return [PossibilitySet] filtered possibility set
710
+ def filtered_possibility_set(vertex)
711
+ PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities)
405
712
  end
406
713
 
407
714
  # @param [String] requirement_name the spec name to search for
@@ -415,24 +722,25 @@ module Bundler::Molinillo
415
722
  # Add the current {#possibility} to the dependency graph of the current
416
723
  # {#state}
417
724
  # @return [void]
418
- def activate_spec
725
+ def activate_new_spec
419
726
  conflicts.delete(name)
420
- debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s }
727
+ debug(depth) { "Activated #{name} at #{possibility}" }
421
728
  activated.set_payload(name, possibility)
422
729
  require_nested_dependencies_for(possibility)
423
730
  end
424
731
 
425
732
  # Requires the dependencies that the recently activated spec has
426
- # @param [Object] activated_spec the specification that has just been
733
+ # @param [Object] activated_possibility the PossibilitySet that has just been
427
734
  # activated
428
735
  # @return [void]
429
- def require_nested_dependencies_for(activated_spec)
430
- nested_dependencies = dependencies_for(activated_spec)
736
+ def require_nested_dependencies_for(possibility_set)
737
+ nested_dependencies = dependencies_for(possibility_set.latest_version)
431
738
  debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
432
739
  nested_dependencies.each do |d|
433
- activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d)
740
+ activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d)
434
741
  parent_index = states.size - 1
435
- @parent_of[d] ||= parent_index
742
+ parents = @parents_of[d]
743
+ parents << parent_index if parents.empty?
436
744
  end
437
745
 
438
746
  push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?)
@@ -444,20 +752,75 @@ module Bundler::Molinillo
444
752
  # @return [void]
445
753
  def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
446
754
  new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
447
- new_requirement = new_requirements.shift
755
+ new_requirement = nil
756
+ loop do
757
+ new_requirement = new_requirements.shift
758
+ break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement }
759
+ end
448
760
  new_name = new_requirement ? name_for(new_requirement) : ''.freeze
449
- possibilities = new_requirement ? search_for(new_requirement) : []
761
+ possibilities = possibilities_for_requirement(new_requirement)
450
762
  handle_missing_or_push_dependency_state DependencyState.new(
451
763
  new_name, new_requirements, new_activated,
452
- new_requirement, possibilities, depth, conflicts.dup
764
+ new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup
453
765
  )
454
766
  end
455
767
 
768
+ # Checks a proposed requirement with any existing locked requirement
769
+ # before generating an array of possibilities for it.
770
+ # @param [Object] the proposed requirement
771
+ # @return [Array] possibilities
772
+ def possibilities_for_requirement(requirement, activated = self.activated)
773
+ return [] unless requirement
774
+ if locked_requirement_named(name_for(requirement))
775
+ return locked_requirement_possibility_set(requirement, activated)
776
+ end
777
+
778
+ group_possibilities(search_for(requirement))
779
+ end
780
+
781
+ # @param [Object] the proposed requirement
782
+ # @return [Array] possibility set containing only the locked requirement, if any
783
+ def locked_requirement_possibility_set(requirement, activated = self.activated)
784
+ all_possibilities = search_for(requirement)
785
+ locked_requirement = locked_requirement_named(name_for(requirement))
786
+
787
+ # Longwinded way to build a possibilities array with either the locked
788
+ # requirement or nothing in it. Required, since the API for
789
+ # locked_requirement isn't guaranteed.
790
+ locked_possibilities = all_possibilities.select do |possibility|
791
+ requirement_satisfied_by?(locked_requirement, activated, possibility)
792
+ end
793
+
794
+ group_possibilities(locked_possibilities)
795
+ end
796
+
797
+ # Build an array of PossibilitySets, with each element representing a group of
798
+ # dependency versions that all have the same sub-dependency version constraints
799
+ # and are contiguous.
800
+ # @param [Array] an array of possibilities
801
+ # @return [Array] an array of possibility sets
802
+ def group_possibilities(possibilities)
803
+ possibility_sets = []
804
+ current_possibility_set = nil
805
+
806
+ possibilities.reverse_each do |possibility|
807
+ dependencies = dependencies_for(possibility)
808
+ if current_possibility_set && current_possibility_set.dependencies == dependencies
809
+ current_possibility_set.possibilities.unshift(possibility)
810
+ else
811
+ possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility]))
812
+ current_possibility_set = possibility_sets.first
813
+ end
814
+ end
815
+
816
+ possibility_sets
817
+ end
818
+
456
819
  # Pushes a new {DependencyState}.
457
820
  # If the {#specification_provider} says to
458
821
  # {SpecificationProvider#allow_missing?} that particular requirement, and
459
822
  # there are no possibilities for that requirement, then `state` is not
460
- # pushed, and the node in {#activated} is removed, and we continue
823
+ # pushed, and the vertex in {#activated} is removed, and we continue
461
824
  # resolving the remaining requirements.
462
825
  # @param [DependencyState] state
463
826
  # @return [void]