lib/bundler/checksum.rb in bundler-2.5.0 vs lib/bundler/checksum.rb in bundler-2.5.1
- old
+ new
@@ -28,10 +28,11 @@
Checksum.new(algo, digest.hexdigest!, Source.new(:gem, pathname))
end
def from_api(digest, source_uri, algo = DEFAULT_ALGORITHM)
return if Bundler.settings[:disable_checksum_validation]
+
Checksum.new(algo, to_hexdigest(digest, algo), Source.new(:api, source_uri))
end
def from_lock(lock_checksum, lockfile_location)
algo, digest = lock_checksum.strip.split(ALGO_SEPARATOR, 2)
@@ -39,15 +40,17 @@
end
def to_hexdigest(digest, algo = DEFAULT_ALGORITHM)
return digest unless algo == DEFAULT_ALGORITHM
return digest if digest.match?(/\A[0-9a-f]{64}\z/i)
+
if digest.match?(%r{\A[-0-9a-z_+/]{43}={0,2}\z}i)
digest = digest.tr("-_", "+/") # fix urlsafe base64
- return digest.unpack1("m0").unpack1("H*")
+ digest.unpack1("m0").unpack1("H*")
+ else
+ raise ArgumentError, "#{digest.inspect} is not a valid SHA256 hex or base64 digest"
end
- raise ArgumentError, "#{digest.inspect} is not a valid SHA256 hex or base64 digest"
end
end
attr_reader :algo, :digest, :sources
@@ -61,10 +64,14 @@
match?(other) && other.sources == sources
end
alias_method :eql?, :==
+ def same_source?(other)
+ sources.include?(other.sources.first)
+ end
+
def match?(other)
other.is_a?(self.class) && other.digest == digest && other.algo == algo
end
def hash
@@ -79,10 +86,11 @@
"#{algo}#{ALGO_SEPARATOR}#{digest}"
end
def merge!(other)
return nil unless match?(other)
+
@sources.concat(other.sources).uniq!
self
end
def formatted_sources
@@ -159,30 +167,21 @@
attr_reader :store
protected :store
def initialize
@store = {}
+ @store_mutex = Mutex.new
end
- def initialize_copy(other)
- @store = {}
- other.store.each do |lock_name, checksums|
- store[lock_name] = checksums.dup
- end
- end
-
def inspect
"#<#{self.class}:#{object_id} size=#{store.size}>"
end
- def fetch(spec, algo = DEFAULT_ALGORITHM)
- store[spec.name_tuple.lock_name]&.fetch(algo, nil)
- end
-
# Replace when the new checksum is from the same source.
- # The primary purpose of this registering checksums from gems where there are
+ # The primary purpose is registering checksums from gems where there are
# duplicates of the same gem (according to full_name) in the index.
+ #
# In particular, this is when 2 gems have two similar platforms, e.g.
# "darwin20" and "darwin-20", both of which resolve to darwin-20.
# In the Index, the later gem replaces the former, so we do that here.
#
# However, if the new checksum is from a different source, we register like normal.
@@ -190,23 +189,23 @@
# that contain the same gem with different checksums.
def replace(spec, checksum)
return unless checksum
lock_name = spec.name_tuple.lock_name
- checksums = (store[lock_name] ||= {})
- existing = checksums[checksum.algo]
-
- # we assume only one source because this is used while building the index
- if !existing || existing.sources.first == checksum.sources.first
- checksums[checksum.algo] = checksum
- else
- register_checksum(lock_name, checksum)
+ @store_mutex.synchronize do
+ existing = fetch_checksum(lock_name, checksum.algo)
+ if !existing || existing.same_source?(checksum)
+ store_checksum(lock_name, checksum)
+ else
+ merge_checksum(lock_name, checksum, existing)
+ end
end
end
def register(spec, checksum)
return unless checksum
+
register_checksum(spec.name_tuple.lock_name, checksum)
end
def merge!(other)
other.store.each do |lock_name, checksums|
@@ -216,30 +215,40 @@
end
end
def to_lock(spec)
lock_name = spec.name_tuple.lock_name
- if checksums = store[lock_name]
+ checksums = @store[lock_name]
+ if checksums
"#{lock_name} #{checksums.values.map(&:to_lock).sort.join(",")}"
else
lock_name
end
end
private
def register_checksum(lock_name, checksum)
- return unless checksum
- checksums = (store[lock_name] ||= {})
- existing = checksums[checksum.algo]
-
- if !existing
- checksums[checksum.algo] = checksum
- elsif existing.merge!(checksum)
- checksum
- else
- raise ChecksumMismatchError.new(lock_name, existing, checksum)
+ @store_mutex.synchronize do
+ existing = fetch_checksum(lock_name, checksum.algo)
+ if existing
+ merge_checksum(lock_name, checksum, existing)
+ else
+ store_checksum(lock_name, checksum)
+ end
end
+ end
+
+ def merge_checksum(lock_name, checksum, existing)
+ existing.merge!(checksum) || raise(ChecksumMismatchError.new(lock_name, existing, checksum))
+ end
+
+ def store_checksum(lock_name, checksum)
+ (@store[lock_name] ||= {})[checksum.algo] = checksum
+ end
+
+ def fetch_checksum(lock_name, algo)
+ @store[lock_name]&.fetch(algo, nil)
end
end
end
end