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