rubygems-update 3.5.6 → 3.5.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +204 -1
  3. data/CODE_OF_CONDUCT.md +79 -28
  4. data/CONTRIBUTING.md +2 -2
  5. data/Manifest.txt +9 -4
  6. data/POLICIES.md +75 -6
  7. data/bundler/CHANGELOG.md +156 -0
  8. data/bundler/lib/bundler/build_metadata.rb +2 -2
  9. data/bundler/lib/bundler/cli/binstubs.rb +1 -1
  10. data/bundler/lib/bundler/cli/fund.rb +1 -1
  11. data/bundler/lib/bundler/cli/gem.rb +7 -14
  12. data/bundler/lib/bundler/cli/install.rb +1 -1
  13. data/bundler/lib/bundler/cli/plugin.rb +3 -2
  14. data/bundler/lib/bundler/cli.rb +14 -31
  15. data/bundler/lib/bundler/compact_index_client/cache.rb +47 -72
  16. data/bundler/lib/bundler/compact_index_client/parser.rb +84 -0
  17. data/bundler/lib/bundler/compact_index_client.rb +51 -80
  18. data/bundler/lib/bundler/constants.rb +8 -1
  19. data/bundler/lib/bundler/definition.rb +114 -71
  20. data/bundler/lib/bundler/dependency.rb +2 -1
  21. data/bundler/lib/bundler/dsl.rb +16 -1
  22. data/bundler/lib/bundler/endpoint_specification.rb +11 -0
  23. data/bundler/lib/bundler/env.rb +1 -1
  24. data/bundler/lib/bundler/environment_preserver.rb +2 -20
  25. data/bundler/lib/bundler/errors.rb +14 -0
  26. data/bundler/lib/bundler/fetcher/compact_index.rb +15 -24
  27. data/bundler/lib/bundler/gem_helper.rb +1 -1
  28. data/bundler/lib/bundler/gem_helpers.rb +14 -7
  29. data/bundler/lib/bundler/gem_version_promoter.rb +42 -38
  30. data/bundler/lib/bundler/injector.rb +3 -5
  31. data/bundler/lib/bundler/installer/gem_installer.rb +0 -1
  32. data/bundler/lib/bundler/installer/standalone.rb +0 -3
  33. data/bundler/lib/bundler/installer.rb +9 -11
  34. data/bundler/lib/bundler/lazy_specification.rb +1 -0
  35. data/bundler/lib/bundler/man/bundle-add.1 +1 -1
  36. data/bundler/lib/bundler/man/bundle-binstubs.1 +1 -1
  37. data/bundler/lib/bundler/man/bundle-cache.1 +1 -1
  38. data/bundler/lib/bundler/man/bundle-check.1 +3 -1
  39. data/bundler/lib/bundler/man/bundle-check.1.ronn +3 -0
  40. data/bundler/lib/bundler/man/bundle-clean.1 +1 -1
  41. data/bundler/lib/bundler/man/bundle-config.1 +2 -4
  42. data/bundler/lib/bundler/man/bundle-config.1.ronn +1 -4
  43. data/bundler/lib/bundler/man/bundle-console.1 +1 -1
  44. data/bundler/lib/bundler/man/bundle-doctor.1 +1 -1
  45. data/bundler/lib/bundler/man/bundle-exec.1 +1 -1
  46. data/bundler/lib/bundler/man/bundle-gem.1 +7 -1
  47. data/bundler/lib/bundler/man/bundle-gem.1.ronn +11 -0
  48. data/bundler/lib/bundler/man/bundle-help.1 +1 -1
  49. data/bundler/lib/bundler/man/bundle-info.1 +1 -1
  50. data/bundler/lib/bundler/man/bundle-init.1 +1 -1
  51. data/bundler/lib/bundler/man/bundle-inject.1 +1 -1
  52. data/bundler/lib/bundler/man/bundle-install.1 +3 -3
  53. data/bundler/lib/bundler/man/bundle-install.1.ronn +2 -2
  54. data/bundler/lib/bundler/man/bundle-list.1 +1 -1
  55. data/bundler/lib/bundler/man/bundle-lock.1 +1 -1
  56. data/bundler/lib/bundler/man/bundle-open.1 +1 -1
  57. data/bundler/lib/bundler/man/bundle-outdated.1 +1 -1
  58. data/bundler/lib/bundler/man/bundle-platform.1 +1 -1
  59. data/bundler/lib/bundler/man/bundle-plugin.1 +7 -4
  60. data/bundler/lib/bundler/man/bundle-plugin.1.ronn +7 -3
  61. data/bundler/lib/bundler/man/bundle-pristine.1 +1 -1
  62. data/bundler/lib/bundler/man/bundle-remove.1 +1 -1
  63. data/bundler/lib/bundler/man/bundle-show.1 +1 -1
  64. data/bundler/lib/bundler/man/bundle-update.1 +1 -1
  65. data/bundler/lib/bundler/man/bundle-version.1 +1 -1
  66. data/bundler/lib/bundler/man/bundle-viz.1 +1 -1
  67. data/bundler/lib/bundler/man/bundle.1 +1 -1
  68. data/bundler/lib/bundler/man/gemfile.5 +3 -3
  69. data/bundler/lib/bundler/man/gemfile.5.ronn +2 -2
  70. data/bundler/lib/bundler/plugin/installer/path.rb +18 -0
  71. data/bundler/lib/bundler/plugin/installer.rb +36 -16
  72. data/bundler/lib/bundler/plugin/source_list.rb +4 -4
  73. data/bundler/lib/bundler/resolver/base.rb +4 -0
  74. data/bundler/lib/bundler/resolver/candidate.rb +5 -17
  75. data/bundler/lib/bundler/resolver/package.rb +4 -0
  76. data/bundler/lib/bundler/resolver/spec_group.rb +20 -2
  77. data/bundler/lib/bundler/resolver.rb +72 -33
  78. data/bundler/lib/bundler/rubygems_ext.rb +98 -10
  79. data/bundler/lib/bundler/rubygems_gem_installer.rb +35 -2
  80. data/bundler/lib/bundler/rubygems_integration.rb +16 -2
  81. data/bundler/lib/bundler/runtime.rb +1 -1
  82. data/bundler/lib/bundler/self_manager.rb +22 -2
  83. data/bundler/lib/bundler/settings.rb +22 -16
  84. data/bundler/lib/bundler/setup.rb +6 -0
  85. data/bundler/lib/bundler/shared_helpers.rb +6 -4
  86. data/bundler/lib/bundler/source/git/git_proxy.rb +8 -0
  87. data/bundler/lib/bundler/source/git.rb +14 -0
  88. data/bundler/lib/bundler/source/metadata.rb +2 -0
  89. data/bundler/lib/bundler/source/path.rb +0 -13
  90. data/bundler/lib/bundler/source/rubygems.rb +31 -30
  91. data/bundler/lib/bundler/source_list.rb +26 -2
  92. data/bundler/lib/bundler/spec_set.rb +15 -13
  93. data/bundler/lib/bundler/stub_specification.rb +8 -0
  94. data/bundler/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt +77 -29
  95. data/bundler/lib/bundler/templates/newgem/newgem.gemspec.tt +4 -3
  96. data/bundler/lib/bundler/templates/newgem/rubocop.yml.tt +0 -5
  97. data/bundler/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb +1 -0
  98. data/bundler/lib/bundler/vendored_net_http.rb +20 -5
  99. data/bundler/lib/bundler/vendored_timeout.rb +7 -3
  100. data/bundler/lib/bundler/version.rb +1 -1
  101. data/bundler/lib/bundler/yaml_serializer.rb +1 -8
  102. data/bundler/lib/bundler.rb +26 -1
  103. data/exe/update_rubygems +1 -1
  104. data/lib/rubygems/basic_specification.rb +27 -0
  105. data/lib/rubygems/bundler_version_finder.rb +1 -1
  106. data/lib/rubygems/command.rb +1 -1
  107. data/lib/rubygems/command_manager.rb +2 -1
  108. data/lib/rubygems/commands/build_command.rb +2 -11
  109. data/lib/rubygems/commands/help_command.rb +2 -2
  110. data/lib/rubygems/commands/pristine_command.rb +12 -9
  111. data/lib/rubygems/commands/rdoc_command.rb +1 -8
  112. data/lib/rubygems/commands/rebuild_command.rb +264 -0
  113. data/lib/rubygems/commands/setup_command.rb +2 -0
  114. data/lib/rubygems/commands/uninstall_command.rb +1 -1
  115. data/lib/rubygems/commands/update_command.rb +8 -9
  116. data/lib/rubygems/config_file.rb +33 -16
  117. data/lib/rubygems/defaults.rb +4 -4
  118. data/lib/rubygems/dependency.rb +3 -15
  119. data/lib/rubygems/dependency_list.rb +1 -1
  120. data/lib/rubygems/deprecate.rb +79 -77
  121. data/lib/rubygems/ext/cargo_builder.rb +2 -17
  122. data/lib/rubygems/gemcutter_utilities/webauthn_poller.rb +3 -1
  123. data/lib/rubygems/gemcutter_utilities.rb +1 -1
  124. data/lib/rubygems/gemspec_helpers.rb +19 -0
  125. data/lib/rubygems/installer.rb +9 -8
  126. data/lib/rubygems/package/tar_header.rb +20 -4
  127. data/lib/rubygems/package.rb +13 -8
  128. data/lib/rubygems/platform.rb +3 -2
  129. data/lib/rubygems/remote_fetcher.rb +1 -1
  130. data/lib/rubygems/request.rb +1 -1
  131. data/lib/rubygems/request_set.rb +1 -1
  132. data/lib/rubygems/requirement.rb +2 -2
  133. data/lib/rubygems/resolver/spec_specification.rb +7 -0
  134. data/lib/rubygems/s3_uri_signer.rb +1 -1
  135. data/lib/rubygems/safe_yaml.rb +10 -1
  136. data/lib/rubygems/security.rb +1 -1
  137. data/lib/rubygems/specification.rb +55 -124
  138. data/lib/rubygems/specification_policy.rb +26 -6
  139. data/lib/rubygems/specification_record.rb +212 -0
  140. data/lib/rubygems/stub_specification.rb +21 -0
  141. data/lib/rubygems/uninstaller.rb +27 -20
  142. data/lib/rubygems/util/licenses.rb +68 -0
  143. data/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rb +1 -1
  144. data/lib/rubygems/vendored_net_http.rb +5 -0
  145. data/lib/rubygems/vendored_timeout.rb +5 -0
  146. data/lib/rubygems/yaml_serializer.rb +1 -8
  147. data/lib/rubygems.rb +28 -15
  148. data/rubygems-update.gemspec +1 -1
  149. metadata +12 -7
  150. data/lib/rubygems/net/http.rb +0 -3
  151. data/lib/rubygems/timeout.rb +0 -3
  152. /data/lib/rubygems/{optparse.rb → vendored_optparse.rb} +0 -0
  153. /data/lib/rubygems/{tsort.rb → vendored_tsort.rb} +0 -0
@@ -4,6 +4,29 @@ require "pathname"
4
4
  require "set"
5
5
 
6
6
  module Bundler
7
+ # The CompactIndexClient is responsible for fetching and parsing the compact index.
8
+ #
9
+ # The compact index is a set of caching optimized files that are used to fetch gem information.
10
+ # The files are:
11
+ # - names: a list of all gem names
12
+ # - versions: a list of all gem versions
13
+ # - info/[gem]: a list of all versions of a gem
14
+ #
15
+ # The client is instantiated with:
16
+ # - `directory`: the root directory where the cache files are stored.
17
+ # - `fetcher`: (optional) an object that responds to #call(uri_path, headers) and returns an http response.
18
+ # If the `fetcher` is not provided, the client will only read cached files from disk.
19
+ #
20
+ # The client is organized into:
21
+ # - `Updater`: updates the cached files on disk using the fetcher.
22
+ # - `Cache`: calls the updater, caches files, read and return them from disk
23
+ # - `Parser`: parses the compact index file data
24
+ # - `CacheFile`: a concurrency safe file reader/writer that verifies checksums
25
+ #
26
+ # The client is intended to optimize memory usage and performance.
27
+ # It is called 100s or 1000s of times, parsing files with hundreds of thousands of lines.
28
+ # It may be called concurrently without global interpreter lock in some Rubies.
29
+ # As a result, some methods may look more complex than necessary to save memory or time.
7
30
  class CompactIndexClient
8
31
  # NOTE: MD5 is here not because we expect a server to respond with it, but
9
32
  # because we use it to generate the etag on first request during the upgrade
@@ -12,6 +35,13 @@ module Bundler
12
35
  SUPPORTED_DIGESTS = { "sha-256" => :SHA256, "md5" => :MD5 }.freeze
13
36
  DEBUG_MUTEX = Thread::Mutex.new
14
37
 
38
+ # info returns an Array of INFO Arrays. Each INFO Array has the following indices:
39
+ INFO_NAME = 0
40
+ INFO_VERSION = 1
41
+ INFO_PLATFORM = 2
42
+ INFO_DEPS = 3
43
+ INFO_REQS = 4
44
+
15
45
  def self.debug
16
46
  return unless ENV["DEBUG_COMPACT_INDEX"]
17
47
  DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
@@ -21,106 +51,47 @@ module Bundler
21
51
 
22
52
  require_relative "compact_index_client/cache"
23
53
  require_relative "compact_index_client/cache_file"
54
+ require_relative "compact_index_client/parser"
24
55
  require_relative "compact_index_client/updater"
25
56
 
26
- attr_reader :directory
27
-
28
- def initialize(directory, fetcher)
29
- @directory = Pathname.new(directory)
30
- @updater = Updater.new(fetcher)
31
- @cache = Cache.new(@directory)
32
- @endpoints = Set.new
33
- @info_checksums_by_name = {}
34
- @parsed_checksums = false
35
- @mutex = Thread::Mutex.new
36
- end
37
-
38
- def execution_mode=(block)
39
- Bundler::CompactIndexClient.debug { "execution_mode=" }
40
- @endpoints = Set.new
41
-
42
- @execution_mode = block
43
- end
44
-
45
- # @return [Lambda] A lambda that takes an array of inputs and a block, and
46
- # maps the inputs with the block in parallel.
47
- #
48
- def execution_mode
49
- @execution_mode || sequentially
50
- end
51
-
52
- def sequential_execution_mode!
53
- self.execution_mode = sequentially
54
- end
55
-
56
- def sequentially
57
- @sequentially ||= lambda do |inputs, &blk|
58
- inputs.map(&blk)
59
- end
57
+ def initialize(directory, fetcher = nil)
58
+ @cache = Cache.new(directory, fetcher)
59
+ @parser = Parser.new(@cache)
60
60
  end
61
61
 
62
62
  def names
63
- Bundler::CompactIndexClient.debug { "/names" }
64
- update("names", @cache.names_path, @cache.names_etag_path)
65
- @cache.names
63
+ Bundler::CompactIndexClient.debug { "names" }
64
+ @parser.names
66
65
  end
67
66
 
68
67
  def versions
69
- Bundler::CompactIndexClient.debug { "/versions" }
70
- update("versions", @cache.versions_path, @cache.versions_etag_path)
71
- versions, @info_checksums_by_name = @cache.versions
72
- versions
68
+ Bundler::CompactIndexClient.debug { "versions" }
69
+ @parser.versions
73
70
  end
74
71
 
75
72
  def dependencies(names)
76
73
  Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
77
- execution_mode.call(names) do |name|
78
- update_info(name)
79
- @cache.dependencies(name).map {|d| d.unshift(name) }
80
- end.flatten(1)
74
+ names.map {|name| info(name) }
81
75
  end
82
76
 
83
- def update_and_parse_checksums!
84
- Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" }
85
- return @info_checksums_by_name if @parsed_checksums
86
- update("versions", @cache.versions_path, @cache.versions_etag_path)
87
- @info_checksums_by_name = @cache.checksums
88
- @parsed_checksums = true
89
- end
90
-
91
- private
92
-
93
- def update(remote_path, local_path, local_etag_path)
94
- Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" }
95
- unless synchronize { @endpoints.add?(remote_path) }
96
- Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
97
- return
98
- end
99
- @updater.update(url(remote_path), local_path, local_etag_path)
77
+ def info(name)
78
+ Bundler::CompactIndexClient.debug { "info(#{name})" }
79
+ @parser.info(name)
100
80
  end
101
81
 
102
- def update_info(name)
103
- Bundler::CompactIndexClient.debug { "update_info(#{name})" }
104
- path = @cache.info_path(name)
105
- unless existing = @info_checksums_by_name[name]
106
- Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" }
107
- return
108
- end
109
- checksum = SharedHelpers.checksum_for_file(path, :MD5)
110
- if checksum == existing
111
- Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" }
112
- return
113
- end
114
- Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" }
115
- update("info/#{name}", path, @cache.info_etag_path(name))
82
+ def latest_version(name)
83
+ Bundler::CompactIndexClient.debug { "latest_version(#{name})" }
84
+ @parser.info(name).map {|d| Gem::Version.new(d[INFO_VERSION]) }.max
116
85
  end
117
86
 
118
- def url(path)
119
- path
87
+ def available?
88
+ Bundler::CompactIndexClient.debug { "available?" }
89
+ @parser.available?
120
90
  end
121
91
 
122
- def synchronize
123
- @mutex.synchronize { yield }
92
+ def reset!
93
+ Bundler::CompactIndexClient.debug { "reset!" }
94
+ @cache.reset!
124
95
  end
125
96
  end
126
97
  end
@@ -1,7 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rbconfig"
4
+
3
5
  module Bundler
4
6
  WINDOWS = RbConfig::CONFIG["host_os"] =~ /(msdos|mswin|djgpp|mingw)/
7
+ deprecate_constant :WINDOWS
8
+
5
9
  FREEBSD = RbConfig::CONFIG["host_os"].to_s.include?("bsd")
6
- NULL = File::NULL
10
+ deprecate_constant :FREEBSD
11
+
12
+ NULL = File::NULL
13
+ deprecate_constant :NULL
7
14
  end
@@ -69,7 +69,6 @@ module Bundler
69
69
  @sources = sources
70
70
  @unlock = unlock
71
71
  @optional_groups = optional_groups
72
- @remote = false
73
72
  @prefer_local = false
74
73
  @specs = nil
75
74
  @ruby_version = ruby_version
@@ -82,7 +81,7 @@ module Bundler
82
81
  @resolved_bundler_version = nil
83
82
 
84
83
  @locked_ruby_version = nil
85
- @new_platform = nil
84
+ @new_platforms = []
86
85
  @removed_platform = nil
87
86
 
88
87
  if lockfile_exists?
@@ -92,11 +91,12 @@ module Bundler
92
91
  @platforms = @locked_platforms.dup
93
92
  @locked_bundler_version = @locked_gems.bundler_version
94
93
  @locked_ruby_version = @locked_gems.ruby_version
94
+ @originally_locked_deps = @locked_gems.dependencies
95
95
  @originally_locked_specs = SpecSet.new(@locked_gems.specs)
96
96
  @locked_checksums = @locked_gems.checksums
97
97
 
98
98
  if unlock != true
99
- @locked_deps = @locked_gems.dependencies
99
+ @locked_deps = @originally_locked_deps
100
100
  @locked_specs = @originally_locked_specs
101
101
  @locked_sources = @locked_gems.sources
102
102
  else
@@ -111,6 +111,7 @@ module Bundler
111
111
  @locked_gems = nil
112
112
  @locked_deps = {}
113
113
  @locked_specs = SpecSet.new([])
114
+ @originally_locked_deps = {}
114
115
  @originally_locked_specs = @locked_specs
115
116
  @locked_sources = []
116
117
  @locked_platforms = []
@@ -130,7 +131,7 @@ module Bundler
130
131
  @sources.merged_gem_lockfile_sections!(locked_gem_sources.first)
131
132
  end
132
133
 
133
- @unlock[:sources] ||= []
134
+ @sources_to_unlock = @unlock.delete(:sources) || []
134
135
  @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
135
136
  @ruby_version.diff(locked_ruby_version_object)
136
137
  end
@@ -142,11 +143,13 @@ module Bundler
142
143
  @path_changes = converge_paths
143
144
  @source_changes = converge_sources
144
145
 
146
+ @explicit_unlocks = @unlock.delete(:gems) || []
147
+
145
148
  if @unlock[:conservative]
146
- @unlock[:gems] ||= @dependencies.map(&:name)
149
+ @gems_to_unlock = @explicit_unlocks.any? ? @explicit_unlocks : @dependencies.map(&:name)
147
150
  else
148
- eager_unlock = (@unlock[:gems] || []).map {|name| Dependency.new(name, ">= 0") }
149
- @unlock[:gems] = @locked_specs.for(eager_unlock, false, platforms).map(&:name).uniq
151
+ eager_unlock = @explicit_unlocks.map {|name| Dependency.new(name, ">= 0") }
152
+ @gems_to_unlock = @locked_specs.for(eager_unlock, false, platforms).map(&:name).uniq
150
153
  end
151
154
 
152
155
  @dependency_changes = converge_dependencies
@@ -160,37 +163,24 @@ module Bundler
160
163
  end
161
164
 
162
165
  def resolve_only_locally!
163
- @remote = false
164
166
  sources.local_only!
165
167
  resolve
166
168
  end
167
169
 
168
170
  def resolve_with_cache!
171
+ sources.local!
169
172
  sources.cached!
170
173
  resolve
171
174
  end
172
175
 
173
176
  def resolve_remotely!
174
- @remote = true
177
+ sources.cached!
175
178
  sources.remote!
176
179
  resolve
177
180
  end
178
181
 
179
- def resolution_mode=(options)
180
- if options["local"]
181
- @remote = false
182
- else
183
- @remote = true
184
- @prefer_local = options["prefer-local"]
185
- end
186
- end
187
-
188
- def setup_sources_for_resolve
189
- if @remote == false
190
- sources.cached!
191
- else
192
- sources.remote!
193
- end
182
+ def prefer_local!
183
+ @prefer_local = true
194
184
  end
195
185
 
196
186
  # For given dependency list returns a SpecSet with Gemspec of all the required
@@ -225,7 +215,6 @@ module Bundler
225
215
  @resolver = nil
226
216
  @resolution_packages = nil
227
217
  @specs = nil
228
- @gem_version_promoter = nil
229
218
 
230
219
  Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})"
231
220
  true
@@ -307,7 +296,12 @@ module Bundler
307
296
  end
308
297
  end
309
298
  else
310
- Bundler.ui.debug "Found changes from the lockfile, re-resolving dependencies because #{change_reason}"
299
+ if lockfile_exists?
300
+ Bundler.ui.debug "Found changes from the lockfile, re-resolving dependencies because #{change_reason}"
301
+ else
302
+ Bundler.ui.debug "Resolving dependencies because there's no lockfile"
303
+ end
304
+
311
305
  start_resolution
312
306
  end
313
307
  end
@@ -373,6 +367,10 @@ module Bundler
373
367
  end
374
368
 
375
369
  def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
370
+ return unless Bundler.frozen_bundle?
371
+
372
+ raise ProductionError, "Frozen mode is set, but there's no lockfile" unless lockfile_exists?
373
+
376
374
  added = []
377
375
  deleted = []
378
376
  changed = []
@@ -401,7 +399,7 @@ module Bundler
401
399
  changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`"
402
400
  end
403
401
 
404
- reason = change_reason
402
+ reason = nothing_changed? ? "some dependencies were deleted from your gemfile" : change_reason
405
403
  msg = String.new
406
404
  msg << "#{reason.capitalize.strip}, but the lockfile can't be updated because frozen mode is set"
407
405
  msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
@@ -459,8 +457,10 @@ module Bundler
459
457
  end
460
458
 
461
459
  def add_platform(platform)
462
- @new_platform ||= !@platforms.include?(platform)
463
- @platforms |= [platform]
460
+ return if @platforms.include?(platform)
461
+
462
+ @new_platforms << platform
463
+ @platforms << platform
464
464
  end
465
465
 
466
466
  def remove_platform(platform)
@@ -480,9 +480,11 @@ module Bundler
480
480
  private :sources
481
481
 
482
482
  def nothing_changed?
483
+ return false unless lockfile_exists?
484
+
483
485
  !@source_changes &&
484
486
  !@dependency_changes &&
485
- !@new_platform &&
487
+ @new_platforms.empty? &&
486
488
  !@path_changes &&
487
489
  !@local_changes &&
488
490
  !@missing_lockfile_dep &&
@@ -565,9 +567,11 @@ module Bundler
565
567
  def resolution_packages
566
568
  @resolution_packages ||= begin
567
569
  last_resolve = converge_locked_specs
568
- remove_invalid_platforms!(current_dependencies)
569
- packages = Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: @unlock[:gems], prerelease: gem_version_promoter.pre?)
570
- additional_base_requirements_for_resolve(packages, last_resolve)
570
+ remove_invalid_platforms!
571
+ packages = Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: @gems_to_unlock, prerelease: gem_version_promoter.pre?)
572
+ packages = additional_base_requirements_to_prevent_downgrades(packages, last_resolve)
573
+ packages = additional_base_requirements_to_force_updates(packages)
574
+ packages
571
575
  end
572
576
  end
573
577
 
@@ -582,7 +586,7 @@ module Bundler
582
586
  if missing_specs.any?
583
587
  missing_specs.each do |s|
584
588
  locked_gem = @locked_specs[s.name].last
585
- next if locked_gem.nil? || locked_gem.version != s.version || !@remote
589
+ next if locked_gem.nil? || locked_gem.version != s.version || sources.local_mode?
586
590
  raise GemNotFound, "Your bundle is locked to #{locked_gem} from #{locked_gem.source}, but that version can " \
587
591
  "no longer be found in that source. That means the author of #{locked_gem} has removed it. " \
588
592
  "You'll need to update your bundle to a version other than #{locked_gem} that hasn't been " \
@@ -601,7 +605,7 @@ module Bundler
601
605
  break if incomplete_specs.empty?
602
606
 
603
607
  Bundler.ui.debug("The lockfile does not have all gems needed for the current platform though, Bundler will still re-resolve dependencies")
604
- setup_sources_for_resolve
608
+ sources.remote!
605
609
  resolution_packages.delete(incomplete_specs)
606
610
  @resolve = start_resolution
607
611
  specs = resolve.materialize(dependencies)
@@ -623,12 +627,22 @@ module Bundler
623
627
  end
624
628
 
625
629
  def start_resolution
630
+ local_platform_needed_for_resolvability = @most_specific_non_local_locked_ruby_platform && !@platforms.include?(local_platform)
631
+ @platforms << local_platform if local_platform_needed_for_resolvability
632
+
626
633
  result = SpecSet.new(resolver.start)
627
634
 
628
635
  @resolved_bundler_version = result.find {|spec| spec.name == "bundler" }&.version
629
- @platforms = result.add_extra_platforms!(platforms) if should_add_extra_platforms?
630
636
 
631
- result.complete_platforms!(platforms)
637
+ if @most_specific_non_local_locked_ruby_platform
638
+ if spec_set_incomplete_for_platform?(result, @most_specific_non_local_locked_ruby_platform)
639
+ @platforms.delete(@most_specific_non_local_locked_ruby_platform)
640
+ elsif local_platform_needed_for_resolvability
641
+ @platforms.delete(local_platform)
642
+ end
643
+ end
644
+
645
+ @platforms = result.add_extra_platforms!(platforms) if should_add_extra_platforms?
632
646
 
633
647
  SpecSet.new(result.for(dependencies, false, @platforms))
634
648
  end
@@ -650,13 +664,6 @@ module Bundler
650
664
  end
651
665
  end
652
666
 
653
- def current_ruby_platform_locked?
654
- return false unless generic_local_platform_is_ruby?
655
- return false if Bundler.settings[:force_ruby_platform] && !@platforms.include?(Gem::Platform::RUBY)
656
-
657
- current_platform_locked?
658
- end
659
-
660
667
  def current_platform_locked?
661
668
  @platforms.any? do |bundle_platform|
662
669
  MatchPlatform.platforms_match?(bundle_platform, local_platform)
@@ -664,27 +671,41 @@ module Bundler
664
671
  end
665
672
 
666
673
  def add_current_platform
667
- return if current_ruby_platform_locked?
674
+ @most_specific_non_local_locked_ruby_platform = find_most_specific_non_local_locked_ruby_platform
675
+ return if @most_specific_non_local_locked_ruby_platform
668
676
 
669
677
  add_platform(local_platform)
670
678
  end
671
679
 
680
+ def find_most_specific_non_local_locked_ruby_platform
681
+ return unless generic_local_platform_is_ruby? && current_platform_locked?
682
+
683
+ most_specific_locked_ruby_platform = most_specific_locked_platform
684
+ return unless most_specific_locked_ruby_platform != local_platform
685
+
686
+ most_specific_locked_ruby_platform
687
+ end
688
+
672
689
  def change_reason
673
690
  if unlocking?
674
- unlock_reason = @unlock.reject {|_k, v| Array(v).empty? }.map do |k, v|
675
- if v == true
676
- k.to_s
677
- else
678
- v = Array(v)
679
- "#{k}: (#{v.join(", ")})"
680
- end
681
- end.join(", ")
691
+ unlock_targets = if @gems_to_unlock.any?
692
+ ["gems", @gems_to_unlock]
693
+ elsif @sources_to_unlock.any?
694
+ ["sources", @sources_to_unlock]
695
+ end
696
+
697
+ unlock_reason = if unlock_targets
698
+ "#{unlock_targets.first}: (#{unlock_targets.last.join(", ")})"
699
+ else
700
+ @unlock[:ruby] ? "ruby" : ""
701
+ end
702
+
682
703
  return "bundler is unlocking #{unlock_reason}"
683
704
  end
684
705
  [
685
706
  [@source_changes, "the list of sources changed"],
686
707
  [@dependency_changes, "the dependencies in your gemfile changed"],
687
- [@new_platform, "you added a new platform to your gemfile"],
708
+ [@new_platforms.any?, "you added a new platform to your gemfile"],
688
709
  [@path_changes, "the gemspecs for path gems changed"],
689
710
  [@local_changes, "the gemspecs for git local gems changed"],
690
711
  [@missing_lockfile_dep, "your lock file is missing \"#{@missing_lockfile_dep}\""],
@@ -733,7 +754,7 @@ module Bundler
733
754
  spec = @dependencies.find {|s| s.name == k }
734
755
  source = spec&.source
735
756
  if source&.respond_to?(:local_override!)
736
- source.unlock! if @unlock[:gems].include?(spec.name)
757
+ source.unlock! if @gems_to_unlock.include?(spec.name)
737
758
  locals << [source, source.local_override!(v)]
738
759
  end
739
760
  end
@@ -741,7 +762,7 @@ module Bundler
741
762
  sources_with_changes = locals.select do |source, changed|
742
763
  changed || specs_changed?(source)
743
764
  end.map(&:first)
744
- !sources_with_changes.each {|source| @unlock[:sources] << source.name }.empty?
765
+ !sources_with_changes.each {|source| @sources_to_unlock << source.name }.empty?
745
766
  end
746
767
 
747
768
  def check_lockfile
@@ -818,7 +839,7 @@ module Bundler
818
839
  # gem), unlock it. For git sources, this means to unlock the revision, which
819
840
  # will cause the `ref` used to be the most recent for the branch (or master) if
820
841
  # an explicit `ref` is not used.
821
- if source.respond_to?(:unlock!) && @unlock[:sources].include?(source.name)
842
+ if source.respond_to?(:unlock!) && @sources_to_unlock.include?(source.name)
822
843
  source.unlock!
823
844
  changes = true
824
845
  end
@@ -835,9 +856,7 @@ module Bundler
835
856
  dep.source = sources.get(dep.source)
836
857
  end
837
858
 
838
- next if unlocking?
839
-
840
- unless locked_dep = @locked_deps[dep.name]
859
+ unless locked_dep = @originally_locked_deps[dep.name]
841
860
  changes = true
842
861
  next
843
862
  end
@@ -864,7 +883,7 @@ module Bundler
864
883
  def converge_locked_specs
865
884
  converged = converge_specs(@locked_specs)
866
885
 
867
- resolve = SpecSet.new(converged.reject {|s| @unlock[:gems].include?(s.name) })
886
+ resolve = SpecSet.new(converged.reject {|s| @gems_to_unlock.include?(s.name) })
868
887
 
869
888
  diff = nil
870
889
 
@@ -897,7 +916,7 @@ module Bundler
897
916
 
898
917
  @specs_that_changed_sources << s if gemfile_source != lockfile_source
899
918
  deps << dep if !dep.source || lockfile_source.include?(dep.source)
900
- @unlock[:gems] << name if lockfile_source.include?(dep.source) && lockfile_source != gemfile_source
919
+ @gems_to_unlock << name if lockfile_source.include?(dep.source) && lockfile_source != gemfile_source
901
920
 
902
921
  # Replace the locked dependency's source with the equivalent source from the Gemfile
903
922
  s.source = gemfile_source
@@ -906,7 +925,7 @@ module Bundler
906
925
  s.source = default_source unless sources.get(lockfile_source)
907
926
  end
908
927
 
909
- next if @unlock[:sources].include?(s.source.name)
928
+ next if @sources_to_unlock.include?(s.source.name)
910
929
 
911
930
  # Path sources have special logic
912
931
  if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec)
@@ -928,12 +947,12 @@ module Bundler
928
947
  else
929
948
  # If the spec is no longer in the path source, unlock it. This
930
949
  # commonly happens if the version changed in the gemspec
931
- @unlock[:gems] << name
950
+ @gems_to_unlock << name
932
951
  end
933
952
  end
934
953
 
935
954
  if dep.nil? && requested_dependencies.find {|d| name == d.name }
936
- @unlock[:gems] << s.name
955
+ @gems_to_unlock << s.name
937
956
  else
938
957
  converged << s
939
958
  end
@@ -960,7 +979,7 @@ module Bundler
960
979
  else
961
980
  { default: Source::RubygemsAggregate.new(sources, source_map) }.merge(source_map.direct_requirements)
962
981
  end
963
- source_requirements.merge!(source_map.locked_requirements) unless @remote
982
+ source_requirements.merge!(source_map.locked_requirements) if nothing_changed?
964
983
  metadata_dependencies.each do |dep|
965
984
  source_requirements[dep.name] = sources.metadata_source
966
985
  end
@@ -1010,7 +1029,7 @@ module Bundler
1010
1029
  current == proposed
1011
1030
  end
1012
1031
 
1013
- def additional_base_requirements_for_resolve(resolution_packages, last_resolve)
1032
+ def additional_base_requirements_to_prevent_downgrades(resolution_packages, last_resolve)
1014
1033
  return resolution_packages unless @locked_gems && !sources.expired_sources?(@locked_gems.sources)
1015
1034
  converge_specs(@originally_locked_specs - last_resolve).each do |locked_spec|
1016
1035
  next if locked_spec.source.is_a?(Source::Path)
@@ -1019,21 +1038,45 @@ module Bundler
1019
1038
  resolution_packages
1020
1039
  end
1021
1040
 
1022
- def remove_invalid_platforms!(dependencies)
1041
+ def additional_base_requirements_to_force_updates(resolution_packages)
1042
+ return resolution_packages if @explicit_unlocks.empty?
1043
+ full_update = dup_for_full_unlock.resolve
1044
+ @explicit_unlocks.each do |name|
1045
+ version = full_update[name].first&.version
1046
+ resolution_packages.base_requirements[name] = Gem::Requirement.new("= #{version}") if version
1047
+ end
1048
+ resolution_packages
1049
+ end
1050
+
1051
+ def dup_for_full_unlock
1052
+ unlocked_definition = self.class.new(@lockfile, @dependencies, @sources, true, @ruby_version, @optional_groups, @gemfiles)
1053
+ unlocked_definition.gem_version_promoter.tap do |gvp|
1054
+ gvp.level = gem_version_promoter.level
1055
+ gvp.strict = gem_version_promoter.strict
1056
+ gvp.pre = gem_version_promoter.pre
1057
+ end
1058
+ unlocked_definition
1059
+ end
1060
+
1061
+ def remove_invalid_platforms!
1023
1062
  return if Bundler.frozen_bundle?
1024
1063
 
1025
1064
  platforms.reverse_each do |platform|
1026
1065
  next if local_platform == platform ||
1027
- (@new_platform && platforms.last == platform) ||
1066
+ @new_platforms.include?(platform) ||
1028
1067
  @path_changes ||
1029
1068
  @dependency_changes ||
1030
- !@originally_locked_specs.incomplete_for_platform?(dependencies, platform)
1069
+ @locked_spec_with_invalid_deps ||
1070
+ !spec_set_incomplete_for_platform?(@originally_locked_specs, platform)
1031
1071
 
1032
1072
  remove_platform(platform)
1033
- add_current_platform if platform == Gem::Platform::RUBY
1034
1073
  end
1035
1074
  end
1036
1075
 
1076
+ def spec_set_incomplete_for_platform?(spec_set, platform)
1077
+ spec_set.incomplete_for_platform?(current_dependencies, platform)
1078
+ end
1079
+
1037
1080
  def source_map
1038
1081
  @source_map ||= SourceMap.new(sources, dependencies, @locked_specs)
1039
1082
  end
@@ -7,7 +7,7 @@ require_relative "rubygems_ext"
7
7
  module Bundler
8
8
  class Dependency < Gem::Dependency
9
9
  attr_reader :autorequire
10
- attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref
10
+ attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref, :glob
11
11
 
12
12
  ALL_RUBY_VERSIONS = (18..27).to_a.concat((30..34).to_a).freeze
13
13
  PLATFORM_MAP = {
@@ -39,6 +39,7 @@ module Bundler
39
39
  @github = options["github"]
40
40
  @branch = options["branch"]
41
41
  @ref = options["ref"]
42
+ @glob = options["glob"]
42
43
  @platforms = Array(options["platforms"])
43
44
  @env = options["env"]
44
45
  @should_include = options.fetch("should_include", true)
@@ -19,6 +19,7 @@ module Bundler
19
19
  platform platforms type source install_if gemfile force_ruby_platform].freeze
20
20
 
21
21
  GITHUB_PULL_REQUEST_URL = %r{\Ahttps://github\.com/([A-Za-z0-9_\-\.]+/[A-Za-z0-9_\-\.]+)/pull/(\d+)\z}
22
+ GITLAB_MERGE_REQUEST_URL = %r{\Ahttps://gitlab\.com/([A-Za-z0-9_\-\./]+)/-/merge_requests/(\d+)\z}
22
23
 
23
24
  attr_reader :gemspecs, :gemfile
24
25
  attr_accessor :dependencies
@@ -46,7 +47,7 @@ module Bundler
46
47
  @gemfile = expanded_gemfile_path
47
48
  @gemfiles << expanded_gemfile_path
48
49
  contents ||= Bundler.read_file(@gemfile.to_s)
49
- instance_eval(contents, gemfile.to_s, 1)
50
+ instance_eval(contents, @gemfile.to_s, 1)
50
51
  rescue Exception => e # rubocop:disable Lint/RescueException
51
52
  message = "There was an error " \
52
53
  "#{e.is_a?(GemfileEvalError) ? "evaluating" : "parsing"} " \
@@ -308,6 +309,20 @@ module Bundler
308
309
  repo_name ||= user_name
309
310
  "https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
310
311
  end
312
+
313
+ git_source(:gitlab) do |repo_name|
314
+ if repo_name =~ GITLAB_MERGE_REQUEST_URL
315
+ {
316
+ "git" => "https://gitlab.com/#{$1}.git",
317
+ "branch" => nil,
318
+ "ref" => "refs/merge-requests/#{$2}/head",
319
+ "tag" => nil,
320
+ }
321
+ else
322
+ repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
323
+ "https://gitlab.com/#{repo_name}.git"
324
+ end
325
+ end
311
326
  end
312
327
 
313
328
  def with_source(source)